feat: 积分商城-供应商

This commit is contained in:
2026-09-01 18:03:37 +08:00
parent 20d059443c
commit 750c36116d
39 changed files with 3692 additions and 9 deletions
+31
View File
@@ -159,6 +159,37 @@ model SupplierOrderSub {
@@map("supplier_order_sub")
}
model SupplierReconciliationOrder {
id Int @id @default(autoincrement())
supplierId String
orderId String
mainOrderId String?
userId String?
reconciliationStatus Int @default(0)
diffType Int?
diffReason String? @db.Text
platformTotalPrice Decimal? @db.Decimal(12, 2)
platformCashAmount Decimal? @db.Decimal(12, 2)
platformPointsPrice Decimal? @db.Decimal(12, 2)
platformRefundPrice Decimal? @db.Decimal(12, 2)
supplierTotalPrice Decimal? @db.Decimal(12, 2)
supplierCashAmount Decimal? @db.Decimal(12, 2)
supplierPointsPrice Decimal? @db.Decimal(12, 2)
supplierRefundPrice Decimal? @db.Decimal(12, 2)
reconciliationBatchNo String?
reconciliationTime DateTime?
resultPayload Json?
createAt DateTime @default(now())
updateAt DateTime @updatedAt
deleted Int @default(0)
@@unique([supplierId, orderId])
@@index([supplierId, reconciliationStatus])
@@index([reconciliationBatchNo])
@@index([mainOrderId])
@@map("supplier_reconciliation_order")
}
model SupplierOrderProduct {
id Int @id @default(autoincrement())
subOrderId String
@@ -113,3 +113,33 @@ CREATE TABLE IF NOT EXISTS `supplier_order_product` (
FOREIGN KEY (`subOrderId`) REFERENCES `supplier_order_sub` (`orderId`)
ON DELETE CASCADE ON UPDATE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='供应商子订单商品表';
CREATE TABLE IF NOT EXISTS `supplier_reconciliation_order` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`supplierId` VARCHAR(191) NOT NULL COMMENT '供应商ID',
`orderId` VARCHAR(191) NOT NULL COMMENT '子订单ID',
`mainOrderId` VARCHAR(191) NULL COMMENT '主订单ID',
`userId` VARCHAR(191) NULL COMMENT '平台用户ID',
`reconciliationStatus` TINYINT NOT NULL DEFAULT 0 COMMENT '对账状态:0未对账,1对账成功,2对账异常',
`diffType` INT NULL COMMENT '差异类型',
`diffReason` TEXT NULL COMMENT '差异说明',
`platformTotalPrice` DECIMAL(12, 2) NULL COMMENT '平台订单总金额',
`platformCashAmount` DECIMAL(12, 2) NULL COMMENT '平台现金支付金额',
`platformPointsPrice` DECIMAL(12, 2) NULL COMMENT '平台积分支付金额',
`platformRefundPrice` DECIMAL(12, 2) NULL COMMENT '平台退款金额',
`supplierTotalPrice` DECIMAL(12, 2) NULL COMMENT '供应商订单总金额',
`supplierCashAmount` DECIMAL(12, 2) NULL COMMENT '供应商现金支付金额',
`supplierPointsPrice` DECIMAL(12, 2) NULL COMMENT '供应商积分支付金额',
`supplierRefundPrice` DECIMAL(12, 2) NULL COMMENT '供应商退款金额',
`reconciliationBatchNo` VARCHAR(191) NULL COMMENT '对账批次号',
`reconciliationTime` DATETIME(3) NULL COMMENT '对账完成时间',
`resultPayload` JSON NULL COMMENT '对账回调原始结果',
`createAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录创建时间',
`updateAt` DATETIME(3) NOT NULL COMMENT '记录更新时间',
`deleted` INT NOT NULL DEFAULT 0 COMMENT '删除状态:0未删除,1已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `supplier_reconciliation_order_supplierId_orderId_key` (`supplierId`, `orderId`),
KEY `supplier_reconciliation_order_supplierId_reconciliationStatus_idx` (`supplierId`, `reconciliationStatus`),
KEY `supplier_reconciliation_order_reconciliationBatchNo_idx` (`reconciliationBatchNo`),
KEY `supplier_reconciliation_order_mainOrderId_idx` (`mainOrderId`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='供应商子订单对账结果表';
@@ -0,0 +1,39 @@
CREATE TABLE IF NOT EXISTS `supplier_reconciliation_order`
(
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`supplierId` VARCHAR(191) NOT NULL COMMENT '供应商ID',
`orderId` VARCHAR(191) NOT NULL COMMENT '子订单ID',
`mainOrderId` VARCHAR(191) DEFAULT NULL COMMENT '主订单ID',
`userId` VARCHAR(191) DEFAULT NULL COMMENT '用户ID',
`reconciliationStatus` TINYINT NOT NULL DEFAULT 0 COMMENT '对账状态:0未对账,1对账成功,2对账异常',
`diffType` VARCHAR(64) DEFAULT NULL COMMENT '差异类型',
`diffReason` VARCHAR(1000) DEFAULT NULL COMMENT '差异说明',
`platformTotalPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '平台订单总金额',
`platformCashAmount` DECIMAL(12, 2) DEFAULT NULL COMMENT '平台现金支付金额',
`platformPointsPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '平台积分支付金额',
`platformRefundPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '平台退款金额',
`supplierTotalPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '供应商订单总金额',
`supplierCashAmount` DECIMAL(12, 2) DEFAULT NULL COMMENT '供应商现金支付金额',
`supplierPointsPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '供应商积分支付金额',
`supplierRefundPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '供应商退款金额',
`reconciliationBatchNo` VARCHAR(64) DEFAULT NULL COMMENT '对账批次号',
`reconciliationTime` DATETIME(3) DEFAULT NULL COMMENT '对账完成时间',
`resultPayload` JSON DEFAULT NULL COMMENT '对账回调原始结果',
`createAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updateAt` DATETIME(3) NOT NULL,
`deleted` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_supplier_order` (`supplierId`, `orderId`),
KEY `idx_supplier_status` (`supplierId`, `reconciliationStatus`),
KEY `idx_batch_no` (`reconciliationBatchNo`),
KEY `idx_main_order` (`mainOrderId`)
) ENGINE = InnoDB
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci
COMMENT ='供应商子订单对账结果表';
@@ -23,6 +23,13 @@ export const merchantOrderListController = withErrorBoundary(async (request: Req
return toHttpResponse(await merchantService.listOrders(await readJson<MerchantOrderListInput>(request)))
})
export const merchantSupplierListController = withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
return toHttpResponse(await merchantService.listSuppliers())
})
export const merchantOrderShipController = withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
@@ -14,7 +14,11 @@ import { orderActionService } from '../services/order-action.service.js'
import { orderCreateService, type CheckoutGoods } from '../services/order-create.service.js'
import { orderListService } from '../services/order-list.service.js'
import { orderPushEventService, type OrderPushEventInput } from '../services/order-push-event.service.js'
import { supplierOpenOrderService, type SupplierOrderQueryRequest } from '../services/supplier-open-order.service.js'
import {
supplierOpenOrderService,
type SupplierOrderQueryRequest,
type SupplierReconciliationQueryRequest
} from '../services/supplier-open-order.service.js'
import { toHttpResponse } from './controller-response.js'
type OrderListRequest = {
@@ -239,3 +243,21 @@ export const supplierOrderInfoController = withErrorBoundary(async (request: Req
return Response.json(await supplierOpenOrderService.queryOrderInfo(body))
})
export const supplierReconciliationInfoController = withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
let body: SupplierReconciliationQueryRequest
try {
body = supplierOpenOrderService.parseBody(await readJson<SupplierReconciliationQueryRequest>(request))
} catch (error) {
return Response.json({
resultCode: 400,
message: error instanceof Error ? error.message : '请求参数解密失败',
orderInfos: []
})
}
return Response.json(await supplierOpenOrderService.queryReconciliationInfo(body))
})
@@ -1,5 +1,6 @@
import type { Context } from '../utils/handler.js'
import { guardMethod } from '../utils/method.js'
import { readJson } from '../utils/request.js'
import { withErrorBoundary } from '../utils/handler.js'
import { productService } from '../services/product.service.js'
import { toHttpResponse } from './controller-response.js'
@@ -25,3 +26,28 @@ export const productDetailController = withErrorBoundary(async (request: Request
return toHttpResponse(await productService.getProductDetail(id, sku))
})
export const merchantProductListController = withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
const body = await readJson<{ keyword?: string; categoryId?: string }>(request)
return toHttpResponse(await productService.listManageProducts(String(body.keyword || '').trim(), String(body.categoryId || '').trim()))
})
export const merchantProductSaveController = withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
return toHttpResponse(await productService.saveManageProduct(
await readJson<Parameters<typeof productService.saveManageProduct>[0]>(request)
))
})
export const merchantProductDeleteController = withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
const body = await readJson<{ id?: number }>(request)
return toHttpResponse(await productService.deleteManageProduct(Number(body.id || 0)))
})
@@ -0,0 +1,75 @@
import type { Context } from '../utils/handler.js'
import { withErrorBoundary } from '../utils/handler.js'
import { guardMethod } from '../utils/method.js'
import { readJson } from '../utils/request.js'
import { toHttpResponse } from './controller-response.js'
import { supplierOpenOrderService } from '../services/supplier-open-order.service.js'
import {
reconciliationResultService,
type ReconciliationResultCallbackInput,
type ReconciliationResultListInput,
type ReconciliationResultUpdateInput
} from '../services/reconciliation-result.service.js'
type UnReconciledMsgInput = {
supplierId?: string
token?: string
timestamp?: number
orderIds?: string
unReconciledType?: number
unReconciledMsg?: string
uniqueSeqNo?: string
AplParmObjct?: string
}
export const supplierReconciliationResultController = withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
let body: ReconciliationResultCallbackInput
try {
body = supplierOpenOrderService.parseBody(await readJson<ReconciliationResultCallbackInput>(request)) as ReconciliationResultCallbackInput
} catch (error) {
return Response.json({ resultCode: 400, message: error instanceof Error ? error.message : '请求参数解密失败' })
}
return Response.json(await reconciliationResultService.receiveCallback(body))
})
export const supplierUnReconciledMsgController = withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
let body: UnReconciledMsgInput
try {
body = supplierOpenOrderService.parseBody(await readJson<UnReconciledMsgInput>(request)) as UnReconciledMsgInput
} catch (error) {
return Response.json({ resultCode: 400, message: error instanceof Error ? error.message : '请求参数解密失败' })
}
const reconciliationResults = String(body.orderIds || '').split(',').map(orderId => orderId.trim()).filter(Boolean).map(orderId => ({
orderId,
reconciliationStatus: 2,
diffType: body.unReconciledType === undefined ? undefined : Number(body.unReconciledType),
diffReason: body.unReconciledMsg
}))
return Response.json(await reconciliationResultService.receiveCallback({
supplierId: body.supplierId,
token: body.token,
timestamp: body.timestamp,
reconciliationBatchNo: body.uniqueSeqNo,
reconciliationResults
}))
})
export const merchantReconciliationListController = withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
return toHttpResponse(await reconciliationResultService.list(await readJson<ReconciliationResultListInput>(request)))
})
export const merchantReconciliationUpdateController = withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
return toHttpResponse(await reconciliationResultService.update(await readJson<ReconciliationResultUpdateInput>(request)))
})
+28 -2
View File
@@ -137,12 +137,38 @@ export const orderDao = {
})
},
findMerchantSubOrders(supplierId: string, orderState?: number) {
findOpenApiReconciliationSubOrders(supplierId: string, orderIds: string[], startDate?: Date, endDate?: Date, orderState?: number) {
return prisma.supplierOrderSub.findMany({
where: {
supplierId,
deleted: 0,
...(Number.isInteger(orderState) ? { orderState } : {})
...(Number.isInteger(orderState) ? { orderState } : {}),
...(orderIds.length ? { orderId: { in: orderIds } } : {}),
...(startDate || endDate ? {
completeTime: {
...(startDate ? { gte: startDate } : {}),
...(endDate ? { lte: endDate } : {})
}
} : {})
},
include: {
products: {
where: { deleted: 0 },
orderBy: { createAt: 'asc' }
}
},
orderBy: { createAt: 'asc' }
})
},
findMerchantSubOrders(supplierId: string, orderState?: number, orderId?: string, userId?: string) {
return prisma.supplierOrderSub.findMany({
where: {
supplierId,
deleted: 0,
...(Number.isInteger(orderState) ? { orderState } : {}),
...(orderId ? { OR: [{ orderId: { contains: orderId } }, { orderNumberId: { contains: orderId } }] } : {}),
...(userId ? { userId: { contains: userId } } : {})
},
include: {
products: {
+26
View File
@@ -33,5 +33,31 @@ export const productDao = {
]
}
})
},
findManageProducts(keyword: string, categoryId: string) {
return prisma.supplierProduct.findMany({
where: {
deleted: 0,
...(keyword ? { OR: [{ name: { contains: keyword } }, { sku: { contains: keyword } }] } : {}),
...(categoryId ? { categoryId } : {})
},
orderBy: [
{ sortOrder: 'asc' },
{ createdAt: 'desc' }
]
})
},
createProduct(data: Parameters<typeof prisma.supplierProduct.create>[0]['data']) {
return prisma.supplierProduct.create({ data })
},
updateProduct(id: number, data: Parameters<typeof prisma.supplierProduct.update>[0]['data']) {
return prisma.supplierProduct.update({ where: { id }, data })
},
findManageProduct(id: number) {
return prisma.supplierProduct.findFirst({ where: { id, deleted: 0 } })
}
}
+8
View File
@@ -11,5 +11,13 @@ export const supplierDao = {
return prisma.supplierInfo.findFirst({
where: { supplierId, deleted: 0 }
})
},
findActiveSuppliers() {
return prisma.supplierInfo.findMany({
where: { deleted: 0 },
select: { supplierId: true, supplierName: true },
orderBy: { supplierName: 'asc' }
})
}
}
+30 -3
View File
@@ -33,8 +33,15 @@ import {
orderPayController,
orderPushEventController,
orderShipController,
supplierOrderInfoController
supplierOrderInfoController,
supplierReconciliationInfoController
} from './controllers/order.controller.js'
import {
merchantReconciliationListController,
merchantReconciliationUpdateController,
supplierReconciliationResultController,
supplierUnReconciledMsgController
} from './controllers/reconciliation.controller.js'
import {
merchantAfterSaleAuditController,
merchantAfterSaleDetailController,
@@ -42,9 +49,16 @@ import {
merchantAfterSaleReceiveController,
merchantAfterSaleShipController,
merchantOrderListController,
merchantOrderShipController
merchantOrderShipController,
merchantSupplierListController
} from './controllers/merchant.controller.js'
import { productDetailController, productListController } from './controllers/product.controller.js'
import {
merchantProductDeleteController,
merchantProductListController,
merchantProductSaveController,
productDetailController,
productListController
} from './controllers/product.controller.js'
import {
supplierDebugTokensController,
supplierInfoController,
@@ -75,6 +89,7 @@ const handlers: Record<string, FunctionHandler> = {
'merchant-after-sale-ship': merchantAfterSaleShipController,
'merchant-order-list': merchantOrderListController,
'merchant-order-ship': merchantOrderShipController,
'merchant-supplier-list': merchantSupplierListController,
'order-cancel-main': orderCancelMainController,
'order-cancel-paid-main': orderCancelPaidMainController,
'order-cancel-sub': orderCancelSubController,
@@ -87,9 +102,17 @@ const handlers: Record<string, FunctionHandler> = {
'order-ship': orderShipController,
'product-detail': productDetailController,
'product-list': productListController,
'merchant-product-list': merchantProductListController,
'merchant-product-save': merchantProductSaveController,
'merchant-product-delete': merchantProductDeleteController,
'supplier-debug-tokens': supplierDebugTokensController,
'supplier-info': supplierInfoController,
'supplier-order-info': supplierOrderInfoController,
'supplier-reconciliation-info': supplierReconciliationInfoController,
'supplier-reconciliation-result': supplierReconciliationResultController,
'supplier-unreconciled-msg': supplierUnReconciledMsgController,
'merchant-reconciliation-list': merchantReconciliationListController,
'merchant-reconciliation-update': merchantReconciliationUpdateController,
'supplier-points': supplierPointsController,
'supplier-token': supplierTokenController,
upload: uploadController,
@@ -174,7 +197,11 @@ router.delete('/api/supplier-logs', ctx => {
}
})
router.all('/api/:name', async ctx => dispatch(ctx.params.name)(ctx))
router.all('/api/admin/:name', async ctx => dispatch(ctx.params.name)(ctx))
router.post('/supplierOpenApi/pointOrder/queryOrderInfo', dispatch('supplier-order-info'))
router.post('/supplierOpenApi/pointOrder/queryReconciliationInfo', dispatch('supplier-reconciliation-info'))
router.post('/supplierOpenApi/pointOrder/reconciliationResult', dispatch('supplier-reconciliation-result'))
router.post('/supplierOpenApi/pointOrder/unReconciledMsg', dispatch('supplier-unreconciled-msg'))
router.post('/supplierOpenApi/debug/tokens', dispatch('supplier-debug-tokens'))
app.use(router.routes())
@@ -5,6 +5,7 @@ import {
ORDER_STATE
} from '../constants/order.js'
import { orderDao } from '../dao/order.dao.js'
import { supplierDao } from '../dao/supplier.dao.js'
import { afterSaleService, type AfterSaleAuditInput, type AfterSaleLogisticsInput } from './after-sale.service.js'
import { orderActionService } from './order-action.service.js'
import { logger } from '../utils/logger.js'
@@ -13,6 +14,8 @@ import { serviceFail, serviceOk } from '../types/service-result.js'
export type MerchantOrderListInput = {
supplierId?: string
orderState?: number
orderId?: string
userId?: string
}
export type MerchantShipInput = {
@@ -105,14 +108,16 @@ const isOrderSigned = (orderState?: number | null) => signedOrderStates.has(Numb
export const merchantService = {
async listOrders(input: MerchantOrderListInput) {
const supplierId = String(input.supplierId || '').trim()
const orderState = Number(input.orderState ?? ORDER_STATE.FINISH_PAY)
const orderState = input.orderState === undefined ? undefined : Number(input.orderState)
const orderId = String(input.orderId || '').trim()
const userId = String(input.userId || '').trim()
logger.info('merchant order list query', { supplierId, orderState })
if (!supplierId) {
return serviceFail('供应商不能为空')
}
const orders = await orderDao.findMerchantSubOrders(supplierId, orderState)
const orders = await orderDao.findMerchantSubOrders(supplierId, orderState, orderId, userId)
const afterSales = await orderDao.findMerchantAfterSales(supplierId, pendingShipExcludeAfterSaleStates)
const afterSaleSubOrderIds = new Set(afterSales.map(item => item.subOrderId).filter(Boolean))
const orderItems = orders.filter(order => !afterSaleSubOrderIds.has(order.orderId)).map(order => ({
@@ -139,6 +144,11 @@ export const merchantService = {
return serviceOk({ orderItems })
},
async listSuppliers() {
const suppliers = await supplierDao.findActiveSuppliers()
return serviceOk({ suppliers })
},
shipOrder(input: MerchantShipInput) {
return orderActionService.shipOrder({
supplierId: String(input.supplierId || '').trim(),
@@ -35,7 +35,9 @@ const toProductItem = (product: ProductRecord) => {
category: product.category || '',
tag: product.tag || '',
sales: product.sales || '',
stock: product.stock
stock: product.stock,
sortOrder: product.sortOrder,
enabled: product.enabled
}
}
@@ -64,5 +66,67 @@ export const productService = {
}
return serviceOk(toProductItem(product))
},
async listManageProducts(keyword: string, categoryId: string) {
const products = await productDao.findManageProducts(keyword, categoryId)
return serviceOk({ categories, products: products.map(toProductItem) })
},
async saveManageProduct(input: {
id?: number
sku?: string
name?: string
price?: number
number?: number
stock?: number
taxRate?: number
categoryId?: string
category?: string
imageUrl?: string
description?: string
detail?: string
originPrice?: number
tag?: string
sales?: string
sortOrder?: number
enabled?: boolean
}) {
const sku = String(input.sku || '').trim()
const name = String(input.name || '').trim()
const price = Number(input.price)
if (!sku || !name || Number.isNaN(price) || price < 0) {
return serviceFail('商品SKU、名称和价格不能为空')
}
const data = {
sku,
name,
price,
number: Math.max(Number(input.number || 0), 0),
stock: Math.max(Number(input.stock || 0), 0),
taxRate: Number(input.taxRate ?? 0.13),
categoryId: String(input.categoryId || '').trim() || null,
category: String(input.category || '').trim() || null,
imageUrl: String(input.imageUrl || '').trim() || null,
description: String(input.description || '').trim() || null,
detail: String(input.detail || '').trim() || null,
originPrice: input.originPrice === undefined || input.originPrice === null ? null : Number(input.originPrice),
tag: String(input.tag || '').trim() || null,
sales: String(input.sales || '').trim() || null,
sortOrder: Math.max(Number(input.sortOrder || 0), 0),
enabled: input.enabled !== false
}
const id = Number(input.id || 0)
const product = id > 0 ? await productDao.updateProduct(id, data) : await productDao.createProduct(data)
return serviceOk(toProductItem(product))
},
async deleteManageProduct(id: number) {
if (!Number.isInteger(id) || id <= 0) return serviceFail('商品ID不正确')
const product = await productDao.findManageProduct(id)
if (!product) return serviceFail('商品不存在')
await productDao.updateProduct(id, { deleted: 1 })
return serviceOk({})
}
}
@@ -0,0 +1,176 @@
import type { Prisma } from '@prisma/client'
import { prisma } from '../utils/prisma.js'
import { supplierDao } from '../dao/supplier.dao.js'
import { verifySupplierOpenSignature } from '../utils/supplier-open-crypto.js'
import { serviceFail, serviceOk } from '../types/service-result.js'
export type ReconciliationResultItem = {
orderId?: string
mainOrderId?: string
userId?: string
reconciliationStatus?: number
diffType?: number
diffReason?: string
platformTotalPrice?: number
platformCashAmount?: number
platformPointsPrice?: number
platformRefundPrice?: number
supplierTotalPrice?: number
supplierCashAmount?: number
supplierPointsPrice?: number
supplierRefundPrice?: number
}
export type ReconciliationResultCallbackInput = {
supplierId?: string
token?: string
timestamp?: number
reconciliationBatchNo?: string
reconciliationTime?: string
reconciliationResults?: ReconciliationResultItem[]
AplParmObjct?: string
}
export type ReconciliationResultListInput = {
supplierId?: string
reconciliationStatus?: number
orderId?: string
}
export type ReconciliationResultUpdateInput = {
supplierId?: string
orderId?: string
reconciliationStatus?: number
diffType?: number
diffReason?: string
}
const toNumber = (value?: number) => value === undefined || value === null ? null : Number(value)
const toTime = (value?: string) => {
if (!value) return new Date()
const time = new Date(value)
return Number.isNaN(time.getTime()) ? new Date() : time
}
const resultItem = (record: Awaited<ReturnType<typeof prisma.supplierReconciliationOrder.findMany>>[number]) => ({
supplierId: record.supplierId,
orderId: record.orderId,
mainOrderId: record.mainOrderId || '',
userId: record.userId || '',
reconciliationStatus: record.reconciliationStatus,
diffType: record.diffType,
diffReason: record.diffReason || '',
platformTotalPrice: record.platformTotalPrice?.toFixed(2) || '',
platformCashAmount: record.platformCashAmount?.toFixed(2) || '',
platformPointsPrice: record.platformPointsPrice?.toFixed(2) || '',
platformRefundPrice: record.platformRefundPrice?.toFixed(2) || '',
supplierTotalPrice: record.supplierTotalPrice?.toFixed(2) || '',
supplierCashAmount: record.supplierCashAmount?.toFixed(2) || '',
supplierPointsPrice: record.supplierPointsPrice?.toFixed(2) || '',
supplierRefundPrice: record.supplierRefundPrice?.toFixed(2) || '',
reconciliationBatchNo: record.reconciliationBatchNo || '',
reconciliationTime: record.reconciliationTime,
updateAt: record.updateAt
})
export const reconciliationResultService = {
async receiveCallback(input: ReconciliationResultCallbackInput) {
const supplierId = String(input.supplierId || '').trim()
const token = String(input.token || '').trim()
const timestamp = Number(input.timestamp || 0)
const records = Array.isArray(input.reconciliationResults) ? input.reconciliationResults : []
if (!supplierId || !token || !timestamp || !records.length) {
return { resultCode: 400, message: '供应商、签名或对账结果不能为空' }
}
const supplier = await supplierDao.findActiveBySupplierId(supplierId)
if (!supplier) return { resultCode: 404, message: '供应商信息不存在' }
if (!verifySupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp, token)) {
return { resultCode: 401, message: '签名无效或已过期' }
}
const reconciliationTime = toTime(input.reconciliationTime)
for (const record of records) {
const orderId = String(record.orderId || '').trim()
const status = Number(record.reconciliationStatus)
if (!orderId || ![0, 1, 2].includes(status)) {
return { resultCode: 400, message: '子订单ID或对账状态不正确' }
}
}
await prisma.$transaction(records.map(record => {
const orderId = String(record.orderId || '').trim()
const data = {
mainOrderId: String(record.mainOrderId || '').trim() || null,
userId: String(record.userId || '').trim() || null,
reconciliationStatus: Number(record.reconciliationStatus),
diffType: record.diffType === undefined || record.diffType === null ? null : Number(record.diffType),
diffReason: String(record.diffReason || '').trim() || null,
platformTotalPrice: toNumber(record.platformTotalPrice),
platformCashAmount: toNumber(record.platformCashAmount),
platformPointsPrice: toNumber(record.platformPointsPrice),
platformRefundPrice: toNumber(record.platformRefundPrice),
supplierTotalPrice: toNumber(record.supplierTotalPrice),
supplierCashAmount: toNumber(record.supplierCashAmount),
supplierPointsPrice: toNumber(record.supplierPointsPrice),
supplierRefundPrice: toNumber(record.supplierRefundPrice),
reconciliationBatchNo: String(input.reconciliationBatchNo || '').trim() || null,
reconciliationTime,
resultPayload: record as Prisma.InputJsonValue,
deleted: 0
}
return prisma.supplierReconciliationOrder.upsert({
where: { supplierId_orderId: { supplierId, orderId } },
update: data,
create: { supplierId, orderId, ...data }
})
}))
return { resultCode: 0, message: '成功' }
},
async list(input: ReconciliationResultListInput) {
const supplierId = String(input.supplierId || '').trim()
if (!supplierId) return serviceFail('供应商不能为空')
const reconciliationStatus = input.reconciliationStatus === undefined ? undefined : Number(input.reconciliationStatus)
if (reconciliationStatus !== undefined && ![0, 1, 2].includes(reconciliationStatus)) {
return serviceFail('对账状态不正确')
}
const records = await prisma.supplierReconciliationOrder.findMany({
where: {
supplierId,
deleted: 0,
...(reconciliationStatus === undefined ? {} : { reconciliationStatus }),
...(String(input.orderId || '').trim() ? { orderId: { contains: String(input.orderId).trim() } } : {})
},
orderBy: { updateAt: 'desc' }
})
return serviceOk({ reconciliationItems: records.map(resultItem) })
},
async update(input: ReconciliationResultUpdateInput) {
const supplierId = String(input.supplierId || '').trim()
const orderId = String(input.orderId || '').trim()
const reconciliationStatus = Number(input.reconciliationStatus)
if (!supplierId || !orderId || ![0, 1, 2].includes(reconciliationStatus)) {
return serviceFail('供应商、子订单或对账状态不正确')
}
const record = await prisma.supplierReconciliationOrder.findUnique({
where: { supplierId_orderId: { supplierId, orderId } }
})
if (!record || record.deleted !== 0) return serviceFail('对账结果不存在')
await prisma.supplierReconciliationOrder.update({
where: { supplierId_orderId: { supplierId, orderId } },
data: {
reconciliationStatus,
diffType: input.diffType === undefined || input.diffType === null ? null : Number(input.diffType),
diffReason: String(input.diffReason || '').trim() || null
}
})
return serviceOk({})
}
}
@@ -13,6 +13,19 @@ export type SupplierOrderQueryRequest = {
AplParmObjct?: string
}
export type SupplierReconciliationQueryRequest = {
supplierId?: string
token?: string
reconciliationType?: number
orderId?: string
startDate?: string
endDate?: string
page?: number
pageSize?: number
timestamp?: number
AplParmObjct?: string
}
const parseBody = (body: SupplierOrderQueryRequest) => {
if (!body.AplParmObjct) return body
@@ -149,5 +162,66 @@ export const supplierOpenOrderService = {
}))
return { resultCode: 0, message: '成功', orderInfo }
},
async queryReconciliationInfo(body: SupplierReconciliationQueryRequest) {
const supplierId = String(body.supplierId || '').trim()
const token = String(body.token || '').trim()
const reconciliationType = Number(body.reconciliationType || 0)
const orderIds = String(body.orderId || '')
.split(',')
.map(item => item.trim())
.filter(Boolean)
const timestamp = Number(body.timestamp || 0)
if (!supplierId || !token || !timestamp) {
return { resultCode: 400, message: '供应商或签名信息不能为空', orderInfos: [] }
}
if (reconciliationType === 1 && !body.startDate) {
return { resultCode: 400, message: '请选择开始日期', orderInfos: [] }
}
if (reconciliationType === 2 && !orderIds.length) {
return { resultCode: 400, message: '订单号不能为空', orderInfos: [] }
}
if (![1, 2].includes(reconciliationType)) {
return { resultCode: 400, message: '对账类型不正确', orderInfos: [] }
}
const supplier = await supplierDao.findActiveBySupplierId(supplierId)
if (!supplier) {
return { resultCode: 404, message: '供应商信息不存在', orderInfos: [] }
}
if (!verifySupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp, token)) {
return { resultCode: 401, message: '签名无效或已过期', orderInfos: [] }
}
const startDate = body.startDate ? new Date(`${body.startDate}T00:00:00`) : undefined
const endDate = body.endDate ? new Date(`${body.endDate}T23:59:59`) : undefined
if ((startDate && Number.isNaN(startDate.getTime())) || (endDate && Number.isNaN(endDate.getTime()))) {
return { resultCode: 400, message: '对账日期格式不正确', orderInfos: [] }
}
const orders = await orderDao.findOpenApiReconciliationSubOrders(
supplierId,
orderIds,
startDate,
endDate,
reconciliationType === 1 ? ORDER_STATE.COMPLETED : undefined
)
const page = Math.max(Number(body.page || 1), 1)
const pageSize = Math.max(Number(body.pageSize || 100), 1)
const totalPage = Math.max(Math.ceil(orders.length / pageSize), 1)
const pagedOrders = orders.slice((page - 1) * pageSize, page * pageSize)
const orderInfos = pagedOrders.map(order => ({
orderId: order.orderId,
supplierId: order.supplierId,
totalPrice: toMoney(order.totalPrice),
pointsPrice: toMoney(order.pointsPrice),
wPayOrAPay: toMoney(order.wPayOrAPay),
invoiceNakedPrice: toMoney(order.totalPrice),
orderProInfos: toOrderProductInfo(order)
}))
return { resultCode: 0, message: '成功', totalPage, currentPage: page, orderInfos }
}
}