feat: 去掉商品取货码,管理员可直接发货完成
已付款订单不再生成或核验取货码,管理员确认后即可完成订单。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
-- Paid product orders are fulfilled directly by an administrator.
|
||||
-- The pickup code is no longer generated, shown, or required.
|
||||
ALTER TABLE `product_orders` DROP COLUMN `pickup_code`;
|
||||
@@ -663,7 +663,6 @@ model ProductOrder {
|
||||
contactName String @map("contact_name") @db.VarChar(40)
|
||||
contactPhone String @map("contact_phone") @db.VarChar(20)
|
||||
status ProductOrderStatus @default(PENDING)
|
||||
pickupCode String @map("pickup_code") @db.VarChar(12)
|
||||
wxTransactionId String? @unique @map("wx_transaction_id")
|
||||
expiresAt DateTime @map("expires_at")
|
||||
paidAt DateTime? @map("paid_at")
|
||||
|
||||
@@ -15,7 +15,6 @@ const order = () => ({
|
||||
contactName: '顾客',
|
||||
contactPhone: '13800138000',
|
||||
status: 'PENDING',
|
||||
pickupCode: 'ABCDEF123456',
|
||||
expiresAt: new Date(Date.now() + 900000),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
@@ -78,7 +77,6 @@ describe('ShopService inventory and payment', () => {
|
||||
create: jest.fn(async ({ data }) => ({ ...current, ...data })),
|
||||
updateMany: jest.fn(async ({ where, data }) => {
|
||||
if (where.status && where.status !== current.status) return { count: 0 }
|
||||
if (where.pickupCode && where.pickupCode !== current.pickupCode) return { count: 0 }
|
||||
Object.assign(current, data)
|
||||
return { count: 1 }
|
||||
}),
|
||||
@@ -246,14 +244,13 @@ describe('ShopService inventory and payment', () => {
|
||||
data: { productId: 'p1', delta: 2, reason: '到货', operatorId: 'admin' },
|
||||
})
|
||||
})
|
||||
it('requires correct pickup code and paid state', async () => {
|
||||
await expect(service.complete('o1', current.pickupCode, 'admin')).rejects.toThrow('不可核销')
|
||||
it('completes a paid order directly and only once', async () => {
|
||||
await expect(service.complete('o1', 'admin')).rejects.toThrow('仅已付款')
|
||||
current.status = 'PAID'
|
||||
await expect(service.complete('o1', 'WRONG', 'admin')).rejects.toThrow('不可核销')
|
||||
await service.complete('o1', current.pickupCode, 'admin')
|
||||
await service.complete('o1', 'admin')
|
||||
expect(current.status).toBe('COMPLETED')
|
||||
expect(current.completedBy).toBe('admin')
|
||||
await expect(service.complete('o1', current.pickupCode, 'admin')).rejects.toThrow('不可核销')
|
||||
await expect(service.complete('o1', 'admin')).rejects.toThrow('仅已付款')
|
||||
})
|
||||
it('scopes user lists and hides inactive products publicly', async () => {
|
||||
await service.orders(new ShopQueryDto(), 'u1')
|
||||
|
||||
@@ -62,6 +62,3 @@ export class ShopQueryDto {
|
||||
@IsOptional() @IsString() @MaxLength(80) search?: string
|
||||
@IsOptional() @IsString() @MaxLength(50) productId?: string
|
||||
}
|
||||
export class PickupDto {
|
||||
@IsString() @Matches(/^[A-F0-9]{12}$/) pickupCode!: string
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { RolesGuard } from '../auth/roles.guard'
|
||||
import { Roles } from '../auth/roles.decorator'
|
||||
import { CurrentUser } from '../common/decorators/current-user.decorator'
|
||||
import { ShopService } from './shop.service'
|
||||
import { BuyProductDto, PickupDto, ProductDto, ShopQueryDto, StockDto } from './dto/shop.dto'
|
||||
import { BuyProductDto, ProductDto, ShopQueryDto, StockDto } from './dto/shop.dto'
|
||||
|
||||
@Controller('shop')
|
||||
export class ShopController {
|
||||
@@ -101,9 +101,8 @@ export class ShopAdminController {
|
||||
}
|
||||
@Post('orders/:id/complete') complete(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: PickupDto,
|
||||
@CurrentUser('sub') userId: string,
|
||||
) {
|
||||
return this.shop.complete(id, dto.pickupCode, userId)
|
||||
return this.shop.complete(id, userId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@nestjs/common'
|
||||
import { Cron } from '@nestjs/schedule'
|
||||
import { Prisma, ProductOrder } from '@prisma/client'
|
||||
import { randomBytes, randomUUID } from 'crypto'
|
||||
import { randomUUID } from 'crypto'
|
||||
import { PrismaService } from '../prisma/prisma.service'
|
||||
import { WechatPayService } from '../payment/wechat-pay.service'
|
||||
import { BuyProductDto, ProductDto, ShopQueryDto, StockDto } from './dto/shop.dto'
|
||||
@@ -139,7 +139,6 @@ export class ShopService {
|
||||
coverUrl: product.coverUrl,
|
||||
unitPrice: product.price,
|
||||
amount: product.price * dto.quantity,
|
||||
pickupCode: randomBytes(6).toString('hex').toUpperCase(),
|
||||
expiresAt: new Date(Date.now() + 15 * 60 * 1000),
|
||||
},
|
||||
})
|
||||
@@ -273,12 +272,12 @@ export class ShopService {
|
||||
return { items, total, page: query.page, limit: query.limit }
|
||||
}
|
||||
|
||||
async complete(id: string, pickupCode: string, operatorId: string) {
|
||||
async complete(id: string, operatorId: string) {
|
||||
const result = await this.prisma.productOrder.updateMany({
|
||||
where: { id, pickupCode, status: 'PAID' },
|
||||
where: { id, status: 'PAID' },
|
||||
data: { status: 'COMPLETED', completedAt: new Date(), completedBy: operatorId },
|
||||
})
|
||||
if (!result.count) throw new BadRequestException('提货码不正确,或订单不可核销')
|
||||
if (!result.count) throw new BadRequestException('仅已付款订单可以发货完成')
|
||||
return this.prisma.productOrder.findUniqueOrThrow({ where: { id } })
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user