feat: 查积分、支付、拆弹、取消订单

This commit is contained in:
2026-07-28 18:39:34 +08:00
parent 53305757f8
commit c443109869
40 changed files with 1814 additions and 163 deletions
@@ -0,0 +1,132 @@
import type { Context } from '@netlify/functions'
import { guardMethod } from '../utils/method.js'
import { fail, ok } from '../utils/http.js'
import { readJson } from '../utils/request.js'
import { prisma } from '../utils/prisma.js'
import { withErrorBoundary } from '../utils/handler.js'
type OrderCancelMainRequest = {
supplierId?: string
token?: string
userId?: string
orderId?: string
mainOrderId?: string
}
type BridgeOrderResponse = {
resultCode?: number
message?: string
}
const normalizeBase = (base: string) => base.replace(/\/$/, '')
export default withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
const body = await readJson<OrderCancelMainRequest>(request)
const supplierId = String(body.supplierId || '').trim()
const token = String(body.token || '').trim()
const userId = String(body.userId || '').trim()
const mainOrderId = String(body.mainOrderId || body.orderId || '').trim()
const orderId = String(body.orderId || mainOrderId).trim()
if (!supplierId || !token || !userId || !mainOrderId) {
return fail('供应商、用户、授权或订单信息不能为空')
}
const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
if (!apiBase) {
return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING')
}
const order = await prisma.supplierOrderMain.findFirst({
where: {
orderId: mainOrderId,
supplierId,
userId,
deleted: 0
}
})
if (!order) {
return fail('主单不存在或已取消', 404, 'MAIN_ORDER_NOT_FOUND')
}
const bridgeResponse = await fetch(`${apiBase}/pointOrder/pushOrderInfoMsg`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
token
},
body: JSON.stringify({
supplierId,
orderType: 1,
orderId,
mainOrderId,
userId,
time: Date.now(),
token
})
})
const bridgePayload = (await bridgeResponse.json().catch(() => ({}))) as BridgeOrderResponse
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
return fail(bridgePayload.message || '取消主单推送失败', 502, 'BRIDGE_CANCEL_MAIN_FAILED')
}
const subOrders = await prisma.supplierOrderSub.findMany({
where: {
mainOrderId,
supplierId,
userId,
deleted: 0
},
select: {
orderId: true
}
})
const subOrderIds = subOrders.map(item => item.orderId)
await prisma.$transaction(async tx => {
await tx.supplierOrderMain.update({
where: { orderId: mainOrderId },
data: {
deleted: 1,
updateTime: new Date()
}
})
await tx.supplierOrderSub.updateMany({
where: {
mainOrderId,
supplierId,
userId,
deleted: 0
},
data: {
deleted: 1,
updateTime: new Date()
}
})
await tx.supplierOrderProduct.updateMany({
where: {
subOrderId: { in: subOrderIds },
deleted: 0
},
data: {
deleted: 1
}
})
})
return ok({
supplierId,
orderType: 1,
orderId,
mainOrderId,
userId
})
})
@@ -0,0 +1,107 @@
import type { Context } from '@netlify/functions'
import { guardMethod } from '../utils/method.js'
import { fail, ok } from '../utils/http.js'
import { readJson } from '../utils/request.js'
import { prisma } from '../utils/prisma.js'
import { withErrorBoundary } from '../utils/handler.js'
type OrderCancelSubRequest = {
supplierId?: string
token?: string
userId?: string
orderId?: string
mainOrderId?: string
}
type BridgeOrderResponse = {
resultCode?: number
message?: string
}
const normalizeBase = (base: string) => base.replace(/\/$/, '')
export default withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
const body = await readJson<OrderCancelSubRequest>(request)
const supplierId = String(body.supplierId || '').trim()
const token = String(body.token || '').trim()
const userId = String(body.userId || '').trim()
const orderId = String(body.orderId || '').trim()
if (!supplierId || !token || !userId || !orderId) {
return fail('供应商、用户、授权或子单信息不能为空')
}
const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
if (!apiBase) {
return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING')
}
const subOrder = await prisma.supplierOrderSub.findFirst({
where: {
orderId,
supplierId,
userId,
deleted: 0
}
})
if (!subOrder) {
return fail('子单不存在或已取消', 404, 'SUB_ORDER_NOT_FOUND')
}
const mainOrderId = String(body.mainOrderId || subOrder.mainOrderId).trim()
const bridgeResponse = await fetch(`${apiBase}/pointOrder/pushOrderInfoMsg`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
token
},
body: JSON.stringify({
supplierId,
orderType: 3,
orderId,
mainOrderId,
userId,
time: Date.now(),
token
})
})
const bridgePayload = (await bridgeResponse.json().catch(() => ({}))) as BridgeOrderResponse
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
return fail(bridgePayload.message || '取消子单推送失败', 502, 'BRIDGE_CANCEL_SUB_FAILED')
}
await prisma.$transaction(async tx => {
await tx.supplierOrderSub.update({
where: { orderId },
data: {
deleted: 1,
updateTime: new Date()
}
})
await tx.supplierOrderProduct.updateMany({
where: {
subOrderId: orderId,
deleted: 0
},
data: {
deleted: 1
}
})
})
return ok({
supplierId,
orderType: 3,
orderId,
mainOrderId,
userId
})
})
@@ -1,11 +1,259 @@
import type { Context } from '@netlify/functions'
import { guardMethod } from '../utils/method.js'
import { ok } from '../utils/http.js'
import { fail, ok } from '../utils/http.js'
import { readJson } from '../utils/request.js'
import { prisma } from '../utils/prisma.js'
import { withErrorBoundary } from '../utils/handler.js'
type CheckoutGoods = {
id?: string | number
sku?: string
count?: number
number?: number
price?: string | number
name?: string
image?: string
imageUrl?: string
picInfo?: string[]
taxRate?: number
}
type OrderCreateRequest = {
supplierId?: string
token?: string
userId?: string
orderNo?: string
orderNumberId?: string
amount?: number
goods?: CheckoutGoods[]
}
type BridgeOrderResponse = {
resultCode?: number
message?: string
}
const normalizeBase = (base: string) => base.replace(/\/$/, '')
const nowText = () => {
const now = new Date()
const pad = (value: number) => String(value).padStart(2, '0')
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
}
const createOrderNumberId = () => {
const now = new Date()
const pad = (value: number, size = 2) => String(value).padStart(size, '0')
return `TR${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}${pad(now.getMilliseconds(), 3)}`
}
const toYuan = (centAmount?: number) => Number(((Number(centAmount || 0)) / 100).toFixed(2))
const toOrderProducts = (goods: CheckoutGoods[]) => goods.map((item, index) => {
const image = item.image || item.imageUrl || item.picInfo?.[0] || ''
return {
sku: String(item.sku || item.id || `SKU_${index + 1}`),
number: Number(item.count || item.number || 1),
price: Number(item.price || 0),
name: String(item.name || ''),
taxRate: Number(item.taxRate || 0.13),
picInfo: item.picInfo?.length ? item.picInfo : image ? [image] : [],
fileId: image || null,
filePath: image || null
}
})
export default withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
return ok({ message: '创建订单接口待实现' })
const body = await readJson<OrderCreateRequest>(request)
const supplierId = String(body.supplierId || '').trim()
const token = String(body.token || '').trim()
const userId = String(body.userId || '').trim()
const orderId = String(body.orderNo || '').trim()
const amount = toYuan(body.amount)
const goods = Array.isArray(body.goods) ? body.goods : []
if (!supplierId || !token || !userId || !orderId) {
return fail('供应商、用户、授权或订单信息不能为空')
}
if (amount <= 0) {
return fail('订单金额必须大于0')
}
const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
if (!apiBase) {
return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING')
}
const orderNumberId = String(body.orderNumberId || createOrderNumberId())
const subOrderId = `${orderId}_001`
const subOrderNumberId = `${orderNumberId}_001`
const orderTime = nowText()
const orderProInfos = toOrderProducts(goods)
const orderInfo = {
orderId,
orderNumberId,
supplierId,
aplName: '积分商城',
createTime: orderTime,
updateTime: orderTime,
deliveryTime: null,
orderFinishTime: null,
completeTime: null,
deliveryStatus: '0',
freightCost: 0,
wPayOrAPayFreightCost: 0,
refund: 0,
wPayOrAPayRefund: 0,
logisticsOrderId: null,
crrgBsnNm: null,
orderState: 1,
totalPrice: amount,
wPayOrAPay: 0,
pointsPrice: amount,
invoiceCode: null,
orderProInfos
}
await prisma.$transaction(async tx => {
await tx.supplierOrderMain.upsert({
where: { orderId },
update: {
orderNumberId,
supplierId,
userId,
aplName: orderInfo.aplName,
updateTime: new Date(),
deliveryStatus: orderInfo.deliveryStatus,
freightCost: orderInfo.freightCost,
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
refund: orderInfo.refund,
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
orderState: orderInfo.orderState,
totalPrice: orderInfo.totalPrice,
wPayOrAPay: orderInfo.wPayOrAPay,
pointsPrice: orderInfo.pointsPrice,
invoiceCode: orderInfo.invoiceCode,
deleted: 0
},
create: {
orderId,
orderNumberId,
supplierId,
userId,
aplName: orderInfo.aplName,
createTime: new Date(),
updateTime: new Date(),
deliveryStatus: orderInfo.deliveryStatus,
freightCost: orderInfo.freightCost,
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
refund: orderInfo.refund,
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
orderState: orderInfo.orderState,
totalPrice: orderInfo.totalPrice,
wPayOrAPay: orderInfo.wPayOrAPay,
pointsPrice: orderInfo.pointsPrice,
invoiceCode: orderInfo.invoiceCode
}
})
await tx.supplierOrderSub.upsert({
where: { orderId: subOrderId },
update: {
orderNumberId: subOrderNumberId,
mainOrderId: orderId,
supplierId,
token,
userId,
aplName: orderInfo.aplName,
updateTime: new Date(),
deliveryStatus: orderInfo.deliveryStatus,
freightCost: orderInfo.freightCost,
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
refund: orderInfo.refund,
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
orderState: orderInfo.orderState,
totalPrice: orderInfo.totalPrice,
wPayOrAPay: orderInfo.wPayOrAPay,
pointsPrice: orderInfo.pointsPrice,
invoiceCode: orderInfo.invoiceCode,
deleted: 0
},
create: {
orderId: subOrderId,
orderNumberId: subOrderNumberId,
mainOrderId: orderId,
supplierId,
token,
userId,
aplName: orderInfo.aplName,
createTime: new Date(),
updateTime: new Date(),
deliveryStatus: orderInfo.deliveryStatus,
freightCost: orderInfo.freightCost,
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
refund: orderInfo.refund,
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
orderState: orderInfo.orderState,
totalPrice: orderInfo.totalPrice,
wPayOrAPay: orderInfo.wPayOrAPay,
pointsPrice: orderInfo.pointsPrice,
invoiceCode: orderInfo.invoiceCode
}
})
await tx.supplierOrderProduct.deleteMany({
where: { subOrderId }
})
if (orderProInfos.length) {
await tx.supplierOrderProduct.createMany({
data: orderProInfos.map(item => ({
subOrderId,
sku: item.sku,
number: item.number,
price: item.price,
name: item.name,
taxRate: item.taxRate,
productImgUrl: item.picInfo[0] || null,
fileId: item.fileId,
filePath: item.filePath,
picInfo: item.picInfo
}))
})
}
})
const bridgeResponse = await fetch(`${apiBase}/pointOrder/pendingOrderInfo`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
token
},
body: JSON.stringify({
supplierId,
userId,
orderInfo,
token
})
})
const bridgePayload = (await bridgeResponse.json().catch(() => ({}))) as BridgeOrderResponse
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
return fail(bridgePayload.message || '提交待支付订单失败', 502, 'BRIDGE_PENDING_ORDER_FAILED')
}
return ok({
orderId,
orderNumberId,
subOrderId,
orderInfo
})
})
@@ -0,0 +1,106 @@
import type { Context } from '@netlify/functions'
import { guardMethod } from '../utils/method.js'
import { fail, ok } from '../utils/http.js'
import { readJson } from '../utils/request.js'
import { prisma } from '../utils/prisma.js'
import { withErrorBoundary } from '../utils/handler.js'
type OrderListRequest = {
supplierId?: string
userId?: string
}
type OrderStatusView = {
text: string
statusClass: string
actionText: string
buttonClass: string
}
const statusMap: Record<number, OrderStatusView> = {
0: { text: '待付款', statusClass: 'pending', actionText: '去支付', buttonClass: 'primary-action' },
1: { text: '待发货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' },
2: { text: '待发货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' },
3: { text: '待收货', statusClass: 'pending', actionText: '确认收货', buttonClass: 'dark-action' },
4: { text: '已完成', statusClass: 'done', actionText: '再次购买', buttonClass: 'dark-action' },
5: { text: '已完成', statusClass: 'done', actionText: '再次购买', buttonClass: 'dark-action' }
}
const toYuanText = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => {
if (value && typeof value === 'object' && 'toFixed' in value) {
return value.toFixed(2)
}
return Number(value || 0).toFixed(2)
}
const firstImage = (picInfo: unknown) => {
if (Array.isArray(picInfo)) {
return String(picInfo[0] || '')
}
return ''
}
export default withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
const body = await readJson<OrderListRequest>(request)
const supplierId = String(body.supplierId || '').trim()
const userId = String(body.userId || '').trim()
if (!supplierId || !userId) {
return fail('供应商或用户信息不能为空')
}
const subOrders = await prisma.supplierOrderSub.findMany({
where: {
supplierId,
userId,
deleted: 0
},
include: {
mainOrder: true,
products: {
where: { deleted: 0 },
orderBy: { createAt: 'asc' }
}
},
orderBy: [
{ createTime: 'desc' },
{ createAt: 'desc' }
]
})
const orders = subOrders.map(order => {
const status = statusMap[order.orderState] || statusMap[1]
const products = order.products.map(product => ({
id: product.id,
sku: product.sku,
name: product.name,
number: product.number,
price: toYuanText(product.price),
image: product.productImgUrl || firstImage(product.picInfo)
}))
return {
id: order.orderId,
orderId: order.orderId,
orderNumberId: order.orderNumberId || '',
mainOrderId: order.mainOrderId,
mainOrderNumberId: order.mainOrder?.orderNumberId || '',
supplierId: order.supplierId,
status: status.text,
statusClass: status.statusClass,
actionText: status.actionText,
buttonClass: status.buttonClass,
orderState: order.orderState,
amount: toYuanText(order.totalPrice),
count: products.reduce((total, item) => total + Number(item.number || 0), 0),
products
}
})
return ok({ orders })
})
+139 -2
View File
@@ -1,11 +1,148 @@
import type { Context } from '@netlify/functions'
import { guardMethod } from '../utils/method.js'
import { ok } from '../utils/http.js'
import { fail, ok } from '../utils/http.js'
import { readJson } from '../utils/request.js'
import { prisma } from '../utils/prisma.js'
import { withErrorBoundary } from '../utils/handler.js'
type OrderPayRequest = {
supplierId?: string
token?: string
orderId?: string
amount?: number
totalPrice?: number
wPayOrAPay?: number
pointsPrice?: number
}
type BridgePayResponse = {
resultCode?: number
message?: string
}
const normalizeBase = (base: string) => base.replace(/\/$/, '')
const centsToYuan = (value?: number) => Number((Number(value || 0) / 100).toFixed(2))
export default withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
return ok({ message: '订单支付接口待实现' })
const body = await readJson<OrderPayRequest>(request)
const supplierId = String(body.supplierId || '').trim()
const token = String(body.token || '').trim()
const orderId = String(body.orderId || '').trim()
const totalPrice = Number(body.totalPrice ?? centsToYuan(body.amount))
const wPayOrAPay = Number(body.wPayOrAPay ?? 0)
const pointsPrice = Number(body.pointsPrice ?? totalPrice)
if (!supplierId || !token || !orderId) {
return fail('供应商、授权或订单信息不能为空')
}
if (totalPrice <= 0) {
return fail('支付金额必须大于0')
}
const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
if (!apiBase) {
return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING')
}
const order = await prisma.supplierOrderMain.findUnique({
where: { orderId }
})
if (!order || order.deleted === 1) {
return fail('待支付订单不存在', 404, 'ORDER_NOT_FOUND')
}
const bridgeResponse = await fetch(`${apiBase}/pointOrder/confirmPurchase`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
token
},
body: JSON.stringify({
supplierId,
orderId,
totalPrice,
wPayOrAPay,
pointsPrice,
token
})
})
const bridgePayload = (await bridgeResponse.json().catch(() => ({}))) as BridgePayResponse
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
return fail(bridgePayload.message || '确认购买失败', 502, 'BRIDGE_CONFIRM_PURCHASE_FAILED')
}
await prisma.$transaction(async tx => {
await tx.supplierOrderMain.update({
where: { orderId },
data: {
orderState: 1,
totalPrice,
wPayOrAPay,
pointsPrice,
updateTime: new Date()
}
})
await tx.supplierOrderSub.updateMany({
where: { mainOrderId: orderId, deleted: 0 },
data: {
orderState: 1,
totalPrice,
wPayOrAPay,
pointsPrice,
updateTime: new Date()
}
})
})
const subOrders = await prisma.supplierOrderSub.findMany({
where: {
mainOrderId: orderId,
deleted: 0
},
select: {
orderId: true
}
})
const subOrderIds = subOrders.map(item => item.orderId).filter(Boolean).join(',')
if (subOrderIds) {
const splitResponse = await fetch(`${apiBase}/pointOrder/pushOrderInfoMsg`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
token
},
body: JSON.stringify({
supplierId,
orderType: 2,
orderId: subOrderIds,
mainOrderId: orderId,
userId: order.userId,
time: Date.now(),
token
})
})
const splitPayload = (await splitResponse.json().catch(() => ({}))) as BridgePayResponse
if (!splitResponse.ok || splitPayload.resultCode !== 0) {
return fail(splitPayload.message || '推送拆单信息失败', 502, 'BRIDGE_SPLIT_ORDER_FAILED')
}
}
return ok({
orderId,
subOrderIds,
totalPrice,
wPayOrAPay,
pointsPrice
})
})
@@ -1,11 +1,63 @@
import type { Context } from '@netlify/functions'
import { guardMethod } from '../utils/method.js'
import { ok } from '../utils/http.js'
import { fail, ok } from '../utils/http.js'
import { prisma } from '../utils/prisma.js'
import { withErrorBoundary } from '../utils/handler.js'
const toProductDetail = (product: NonNullable<Awaited<ReturnType<typeof prisma.supplierProduct.findFirst>>>) => {
const picInfo = Array.isArray(product.picInfo) ? product.picInfo : []
const image = product.imageUrl || String(picInfo[0] || '')
const price = product.price.toFixed(2)
return {
id: product.id,
sku: product.sku,
number: product.number,
price,
name: product.name,
taxRate: Number(product.taxRate),
picInfo,
desc: product.description || '',
detail: product.detail || product.description || '',
originPrice: product.originPrice ? product.originPrice.toFixed(2) : price,
image,
imageUrl: image,
categoryId: product.categoryId || '',
category: product.category || '',
tag: product.tag || '',
sales: product.sales || '',
stock: product.stock
}
}
export default withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['GET'])
if (methodResponse) return methodResponse
return ok({ message: '商品详情接口待实现' })
const url = new URL(request.url)
const id = (url.searchParams.get('id') || '').trim()
const sku = (url.searchParams.get('sku') || '').trim()
const numericId = Number(id)
const querySku = sku || (id && !Number.isInteger(numericId) ? id : '')
if (!id && !sku) {
return fail('商品参数不能为空')
}
const product = await prisma.supplierProduct.findFirst({
where: {
enabled: true,
deleted: 0,
OR: [
...(Number.isInteger(numericId) && numericId > 0 ? [{ id: numericId }] : []),
...(querySku ? [{ sku: querySku }] : [])
]
}
})
if (!product) {
return fail('商品不存在或已下架', 404, 'PRODUCT_NOT_FOUND')
}
return ok(toProductDetail(product))
})
@@ -1,11 +1,69 @@
import type { Context } from '@netlify/functions'
import { guardMethod } from '../utils/method.js'
import { ok } from '../utils/http.js'
import { prisma } from '../utils/prisma.js'
import { withErrorBoundary } from '../utils/handler.js'
const categories = [
{ id: 'recommend', name: '推荐' },
{ id: 'homeLiving', name: '居家生活' },
{ id: 'digitalAppliance', name: '数码家电' },
{ id: 'beautyCare', name: '美妆个护' },
{ id: 'foodDrink', name: '食品饮料' },
{ id: 'motherBaby', name: '母婴玩具' },
{ id: 'fashionBag', name: '服饰鞋包' },
{ id: 'sportsOutdoor', name: '运动户外' }
]
const toProductItem = (product: Awaited<ReturnType<typeof prisma.supplierProduct.findMany>>[number]) => {
const picInfo = Array.isArray(product.picInfo) ? product.picInfo : []
const image = product.imageUrl || String(picInfo[0] || '')
const price = product.price.toFixed(2)
return {
id: product.id,
sku: product.sku,
number: product.number,
price,
name: product.name,
taxRate: Number(product.taxRate),
picInfo,
desc: product.description || '',
detail: product.detail || product.description || '',
originPrice: product.originPrice ? product.originPrice.toFixed(2) : price,
image,
imageUrl: image,
categoryId: product.categoryId || '',
category: product.category || '',
tag: product.tag || '',
sales: product.sales || '',
stock: product.stock
}
}
export default withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['GET'])
if (methodResponse) return methodResponse
return ok({ message: '商品列表接口待实现' })
const url = new URL(request.url)
const keyword = (url.searchParams.get('keyword') || '').trim()
const categoryId = (url.searchParams.get('categoryId') || '').trim()
const products = await prisma.supplierProduct.findMany({
where: {
enabled: true,
deleted: 0,
...(keyword ? { name: { contains: keyword } } : {}),
...(categoryId && categoryId !== 'recommend' ? { categoryId } : {})
},
orderBy: [
{ sortOrder: 'asc' },
{ createdAt: 'desc' }
]
})
return ok({
categories,
products: products.map(toProductItem)
})
})
@@ -32,7 +32,6 @@ export default withErrorBoundary(async (request: Request, _context: Context) =>
id: supplier.id,
supplierIdentification: supplier.supplierIdentification,
supplierId: supplier.supplierId,
supplierName: supplier.supplierName,
clientId: supplier.clientId
supplierName: supplier.supplierName
})
})
@@ -0,0 +1,62 @@
import type { Context } from '@netlify/functions'
import { guardMethod } from '../utils/method.js'
import { fail, ok } from '../utils/http.js'
import { readJson } from '../utils/request.js'
import { withErrorBoundary } from '../utils/handler.js'
type SupplierPointsRequest = {
supplierId?: string
token?: string
userId?: string
}
type BridgePointsResponse = {
resultCode?: number
message?: string
userPoints?: number | string
}
const normalizeBase = (base: string) => base.replace(/\/$/, '')
export default withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
const body = await readJson<SupplierPointsRequest>(request)
const supplierId = String(body.supplierId || '').trim()
const token = String(body.token || '').trim()
const userId = String(body.userId || '').trim()
if (!supplierId || !token || !userId) {
return fail('供应商、用户或授权信息不能为空')
}
const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
if (!apiBase) {
return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING')
}
const response = await fetch(`${apiBase}/pointOrder/queryPoints`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
token
},
body: JSON.stringify({
supplierId,
token,
userId
})
})
const pointsPayload = (await response.json().catch(() => ({}))) as BridgePointsResponse
if (!response.ok || pointsPayload.resultCode !== 0) {
return fail(pointsPayload.message || '查询用户积分失败', 502, 'BRIDGE_POINTS_FAILED')
}
return ok({
userPoints: Number(pointsPayload.userPoints || 0)
})
})
@@ -59,7 +59,8 @@ export default withErrorBoundary(async (request: Request, _context: Context) =>
body: JSON.stringify({
supplierId: supplier.supplierId,
clientId: supplier.clientId,
clientSecret: signature,
clientSecret: supplier.clientSecret,
sendSha256: signature,
code: '',
timestamp
})
+1 -1
View File
@@ -5,5 +5,5 @@ export const sha256 = (value: string) => {
}
export const createBridgeSignature = (clientId: string, clientSecret: string, timestamp: string) => {
return sha256(`${clientId}${clientSecret}${timestamp}${clientSecret}`)
return sha256(`${clientId}${clientSecret}${timestamp}`)
}
+2 -1
View File
@@ -9,7 +9,8 @@
"typecheck": "tsc --noEmit",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
"prisma:studio": "prisma studio"
"prisma:studio": "prisma studio",
"seed:products": "node prisma/seed-products.mjs"
},
"dependencies": {
"@neondatabase/serverless": "^1.0.1",
+118 -15
View File
@@ -8,7 +8,7 @@ datasource db {
}
model User {
id String @id @default(cuid())
id Int @id @default(autoincrement())
mobile String @unique
nickname String?
avatarUrl String?
@@ -20,22 +20,33 @@ model User {
orders Order[]
}
model Product {
id String @id @default(cuid())
model SupplierProduct {
id Int @id @default(autoincrement())
sku String @unique
number Int @default(0)
price Decimal @db.Decimal(10, 2)
name String
taxRate Decimal @default(0.13) @db.Decimal(5, 4)
picInfo Json?
description String?
detail String?
price Decimal @db.Decimal(10, 2)
originPrice Decimal? @db.Decimal(10, 2)
imageUrl String?
categoryId String?
category String?
tag String?
sales String?
stock Int @default(0)
sortOrder Int @default(0)
enabled Boolean @default(true)
deleted Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
carts CartItem[]
orderItems OrderItem[]
@@map("supplier_product")
}
model SupplierInfo {
@@ -56,24 +67,24 @@ model SupplierInfo {
}
model CartItem {
id String @id @default(cuid())
userId String
productId String
id Int @id @default(autoincrement())
userId Int
productId Int
skuText String?
quantity Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
product SupplierProduct @relation(fields: [productId], references: [id], onDelete: Cascade)
@@unique([userId, productId, skuText])
}
model Order {
id String @id @default(cuid())
id Int @id @default(autoincrement())
orderNo String @unique
userId String
userId Int
status OrderStatus @default(PENDING_PAYMENT)
totalAmount Decimal @db.Decimal(10, 2)
payType PayType?
@@ -86,17 +97,109 @@ model Order {
}
model OrderItem {
id String @id @default(cuid())
orderId String
productId String
id Int @id @default(autoincrement())
orderId Int
productId Int
name String
imageUrl String?
skuText String?
price Decimal @db.Decimal(10, 2)
quantity Int
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id])
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
product SupplierProduct @relation(fields: [productId], references: [id])
}
model SupplierOrderMain {
id Int @id @default(autoincrement())
orderId String @unique
orderNumberId String?
supplierId String
userId String?
aplName String?
createTime DateTime?
updateTime DateTime?
deliveryTime DateTime?
orderFinishTime DateTime?
completeTime DateTime?
deliveryStatus String?
freightCost Decimal @default(0) @db.Decimal(10, 2)
wPayOrAPayFreightCost Decimal @default(0) @db.Decimal(10, 2)
refund Decimal @default(0) @db.Decimal(10, 2)
wPayOrAPayRefund Decimal @default(0) @db.Decimal(10, 2)
logisticsOrderId String?
crrgBsnNm String?
orderState Int @default(0)
totalPrice Decimal @default(0) @db.Decimal(10, 2)
wPayOrAPay Decimal @default(0) @db.Decimal(10, 2)
pointsPrice Decimal @default(0) @db.Decimal(10, 2)
invoiceCode String?
createAt DateTime @default(now())
updateAt DateTime @updatedAt
deleted Int @default(0)
subOrders SupplierOrderSub[]
@@map("supplier_order_main")
}
model SupplierOrderSub {
id Int @id @default(autoincrement())
orderId String @unique
orderNumberId String?
mainOrderId String
supplierId String
token String?
userId String?
aplName String?
createTime DateTime?
updateTime DateTime?
deliveryTime DateTime?
orderFinishTime DateTime?
completeTime DateTime?
deliveryStatus String?
freightCost Decimal @default(0) @db.Decimal(10, 2)
wPayOrAPayFreightCost Decimal @default(0) @db.Decimal(10, 2)
refund Decimal @default(0) @db.Decimal(10, 2)
wPayOrAPayRefund Decimal @default(0) @db.Decimal(10, 2)
logisticsOrderId String?
crrgBsnNm String?
orderState Int @default(0)
totalPrice Decimal @default(0) @db.Decimal(10, 2)
wPayOrAPay Decimal @default(0) @db.Decimal(10, 2)
pointsPrice Decimal @default(0) @db.Decimal(10, 2)
invoiceCode String?
createAt DateTime @default(now())
updateAt DateTime @updatedAt
deleted Int @default(0)
mainOrder SupplierOrderMain @relation(fields: [mainOrderId], references: [orderId], onDelete: Cascade)
products SupplierOrderProduct[]
@@index([mainOrderId])
@@map("supplier_order_sub")
}
model SupplierOrderProduct {
id Int @id @default(autoincrement())
subOrderId String
sku String
number Int
price Decimal @db.Decimal(10, 2)
name String
taxRate Decimal @default(0.13) @db.Decimal(5, 4)
productImgUrl String?
fileId String?
filePath String?
picInfo Json?
createAt DateTime @default(now())
updateAt DateTime @updatedAt
deleted Int @default(0)
subOrder SupplierOrderSub @relation(fields: [subOrderId], references: [orderId], onDelete: Cascade)
@@index([subOrderId])
@@map("supplier_order_product")
}
enum OrderStatus {
+96
View File
@@ -0,0 +1,96 @@
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
const imagePool = [
'https://ts3.tc.mm.bing.net/th/id/OIP-C.im_WvX9bWGyvagEZp4Pz0AHaGX?w=142&h=108&c=7&qlt=90&bgcl=a01e15&r=0&o=6&dpr=1.3&pid=13.1',
'https://ts3.tc.mm.bing.net/th/id/OIP-C.MkmCbLoY2S4zqzMeHvCDMQHaHD?w=128&h=108&c=7&qlt=90&bgcl=f61537&r=0&o=6&dpr=1.3&pid=13.1',
'https://ts2.tc.mm.bing.net/th/id/OIP-C.CKSBSB33LwT9qJD_Yhb1mwHaKk?w=132&h=108&c=7&qlt=90&bgcl=2e287d&r=0&o=6&dpr=1.3&pid=13.1',
'https://tse1-mm.cn.bing.net/th/id/OIP-C.StrDRqennoZNbzSPZapKZwHaEJ?w=286&h=180&c=7&r=0&o=7&cb=thfc1falcon&dpr=1.3&pid=1.7&rm=3',
'https://tse3-mm.cn.bing.net/th/id/OIP-C.ynSetSr3z884UC6sEp4yiwHaEJ?w=331&h=186&c=7&r=0&o=7&cb=thfc1falcon&dpr=1.3&pid=1.7&rm=3'
]
const categories = {
homeLiving: '居家生活',
digitalAppliance: '数码家电',
beautyCare: '美妆个护',
foodDrink: '食品饮料',
motherBaby: '母婴玩具',
fashionBag: '服饰鞋包',
sportsOutdoor: '运动户外',
recommend: '精选推荐'
}
const rawProducts = [
['SKU001', '蓝牙耳机', '轻盈佩戴,稳定连接,通勤运动都好用', 149.99, 199.99, 'digitalAppliance', '积分兑换', '1.2万件', 2],
['SKU002', '手机壳', '防滑耐磨,贴合机身,日常保护更安心', 10.01, 19.9, 'digitalAppliance', '赠品', '5632件', 1],
['SKU003', '智能音箱 Pro', '语音唤醒,沉浸音效,适合卧室和书房', 99.0, 139.0, 'digitalAppliance', '热门推荐', '2.1万件', 1],
['SKU004', '奶油白迷你烤箱', '小巧不占地,早餐烘焙好帮手', 129.0, 169.0, 'homeLiving', '新品上架', '1.2万件', 1],
['SKU005', '北欧陶瓷餐具套装', '简约釉色,日常用餐更有仪式感', 69.9, 99.9, 'homeLiving', '品质精选', '6453件', 1],
['SKU006', '纯棉舒适四件套', '柔软亲肤,安心睡眠,四季可用', 179.0, 229.0, 'homeLiving', '积分兑换', '8321件', 1],
['SKU007', '植萃保湿护肤礼盒', '补水保湿,清爽不黏腻', 99.9, 139.9, 'beautyCare', '礼盒精选', '4820件', 1],
['SKU008', '香氛洗护旅行套装', '温和清洁,清新留香,出行便携', 59.9, 89.9, 'beautyCare', '新品上架', '7932件', 1],
['SKU009', '便携补光化妆镜', '高清镜面,柔光补妆,随身携带', 49.9, 79.9, 'beautyCare', '热门推荐', '3651件', 1],
['SKU010', '橙意零食礼盒', '精选零食组合,办公室分享刚刚好', 59.9, 89.9, 'foodDrink', '礼盒精选', '6324件', 1],
['SKU011', '咖啡随行杯组合', '锁温防漏,通勤随行更方便', 49.9, 69.9, 'foodDrink', '积分兑换', '3520件', 1],
['SKU012', '下午茶点心礼盒', '多种风味,一盒满足甜咸口味', 69.9, 99.9, 'foodDrink', '品质精选', '4891件', 1],
['SKU013', '安心儿童水杯', '轻巧易握,防摔耐用,出门好携带', 39.9, 59.9, 'motherBaby', '新品上架', '4250件', 1],
['SKU014', '益智积木套装', '色彩柔和,锻炼动手能力', 89.0, 129.0, 'motherBaby', '热门推荐', '2186件', 1],
['SKU015', '柔光儿童夜灯', '温和光线,陪伴睡前时光', 69.9, 99.9, 'motherBaby', '品质精选', '3548件', 1],
['SKU016', '大容量电脑背包', '分区收纳,通勤出行更轻松', 149.0, 199.0, 'fashionBag', '积分兑换', '1.6万件', 1],
['SKU017', '轻巧通勤斜挎包', '简洁百搭,日常出门更实用', 79.9, 109.9, 'fashionBag', '新品上架', '5321件', 1],
['SKU018', '柔软撞色丝巾礼盒', '细腻亲肤,点亮日常穿搭', 69.9, 99.9, 'fashionBag', '礼盒精选', '2680件', 1],
['SKU019', '运动保温水杯', '长效锁温,户外运动随行', 49.9, 79.9, 'sportsOutdoor', '热门推荐', '7342件', 1],
['SKU020', '筋膜放松按摩仪 Mini', '轻巧便携,缓解久坐酸胀', 99.0, 149.0, 'sportsOutdoor', '积分兑换', '3921件', 1],
['SKU021', '户外露营灯', '多档照明,露营夜晚更安心', 79.0, 119.0, 'sportsOutdoor', '新品上架', '2568件', 1],
['SKU022', '悦享精选礼包', '多场景精选好物,福利兑换优选', 59.9, 89.9, 'recommend', '精选推荐', '8820件', 1],
['SKU023', '品质生活组合装', '居家、通勤、日用好物组合', 119.0, 159.0, 'recommend', '热门推荐', '6902件', 1],
['SKU024', '积分兑换惊喜盒', '随机精选商品,兑换更有新鲜感', 39.9, 69.9, 'recommend', '限时秒杀', '5310件', 1]
]
const products = rawProducts.map(([sku, name, description, price, originPrice, categoryId, tag, sales, number], index) => {
const imageUrl = imagePool[index % imagePool.length]
const nextImageUrl = imagePool[(index + 1) % imagePool.length]
return {
sku,
number,
price,
name,
taxRate: 0.13,
picInfo: [imageUrl, nextImageUrl],
description,
detail: `${description}。精选品质商品,适合积分商城兑换和日常福利采购场景。`,
originPrice,
imageUrl,
categoryId,
category: categories[categoryId],
tag,
sales,
stock: 180 + index * 7,
sortOrder: index + 1,
enabled: true,
deleted: 0
}
})
const main = async () => {
for (const product of products) {
await prisma.supplierProduct.upsert({
where: { sku: product.sku },
update: product,
create: product
})
}
}
main()
.then(async () => {
await prisma.$disconnect()
console.log(`已导入 ${products.length} 条商品数据`)
})
.catch(async error => {
console.error(error)
await prisma.$disconnect()
process.exit(1)
})
@@ -0,0 +1,115 @@
CREATE TABLE IF NOT EXISTS `supplier_product` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '商品主键ID',
`sku` VARCHAR(191) NOT NULL COMMENT '商品SKU编码',
`number` INT NOT NULL DEFAULT 0 COMMENT '默认购买数量或导入数量',
`price` DECIMAL(10, 2) NOT NULL COMMENT '商品销售价格',
`name` VARCHAR(191) NOT NULL COMMENT '商品名称',
`taxRate` DECIMAL(5, 4) NOT NULL DEFAULT 0.1300 COMMENT '商品税率',
`picInfo` JSON NULL COMMENT '商品图片列表,JSON数组',
`description` VARCHAR(191) NULL COMMENT '商品简短描述',
`detail` VARCHAR(191) NULL COMMENT '商品详情描述',
`originPrice` DECIMAL(10, 2) NULL COMMENT '商品原价',
`imageUrl` VARCHAR(191) NULL COMMENT '商品主图地址',
`categoryId` VARCHAR(191) NULL COMMENT '商品分类ID',
`category` VARCHAR(191) NULL COMMENT '商品分类名称',
`tag` VARCHAR(191) NULL COMMENT '商品标签',
`sales` VARCHAR(191) NULL COMMENT '商品销量展示文案',
`stock` INT NOT NULL DEFAULT 0 COMMENT '商品库存',
`sortOrder` INT NOT NULL DEFAULT 0 COMMENT '排序值,越小越靠前',
`enabled` BOOLEAN NOT NULL DEFAULT true COMMENT '是否启用',
`deleted` INT NOT NULL DEFAULT 0 COMMENT '删除状态:0未删除,1已删除',
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`updatedAt` DATETIME(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `supplier_product_sku_key` (`sku`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='供应商商品信息表';
CREATE TABLE IF NOT EXISTS `supplier_order_main` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主单主键ID',
`orderId` VARCHAR(191) NOT NULL COMMENT '主订单ID',
`orderNumberId` VARCHAR(191) NULL COMMENT '主订单编号',
`supplierId` VARCHAR(191) NOT NULL COMMENT '供应商ID',
`userId` VARCHAR(191) NULL COMMENT '平台用户ID',
`aplName` VARCHAR(191) NULL COMMENT '应用名称',
`createTime` DATETIME(3) NULL COMMENT '订单创建时间',
`updateTime` DATETIME(3) NULL COMMENT '订单更新时间',
`deliveryTime` DATETIME(3) NULL COMMENT '发货时间',
`orderFinishTime` DATETIME(3) NULL COMMENT '订单完成时间',
`completeTime` DATETIME(3) NULL COMMENT '交易完成时间',
`deliveryStatus` VARCHAR(191) NULL COMMENT '配送状态',
`freightCost` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '运费积分金额',
`wPayOrAPayFreightCost` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '微信或支付宝支付运费金额',
`refund` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '积分退款金额',
`wPayOrAPayRefund` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '微信或支付宝退款金额',
`logisticsOrderId` VARCHAR(191) NULL COMMENT '物流单号',
`crrgBsnNm` VARCHAR(191) NULL COMMENT '承运商名称',
`orderState` INT NOT NULL DEFAULT 0 COMMENT '订单状态',
`totalPrice` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '订单总金额',
`wPayOrAPay` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '微信或支付宝支付金额',
`pointsPrice` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '积分支付金额',
`invoiceCode` VARCHAR(191) 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_order_main_orderId_key` (`orderId`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='供应商主订单表';
CREATE TABLE IF NOT EXISTS `supplier_order_sub` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '子单主键ID',
`orderId` VARCHAR(191) NOT NULL COMMENT '子订单ID',
`orderNumberId` VARCHAR(191) NULL COMMENT '子订单编号',
`mainOrderId` VARCHAR(191) NOT NULL COMMENT '所属主订单ID',
`supplierId` VARCHAR(191) NOT NULL COMMENT '供应商ID',
`token` VARCHAR(191) NULL COMMENT '平台桥接访问令牌快照',
`userId` VARCHAR(191) NULL COMMENT '平台用户ID',
`aplName` VARCHAR(191) NULL COMMENT '应用名称',
`createTime` DATETIME(3) NULL COMMENT '订单创建时间',
`updateTime` DATETIME(3) NULL COMMENT '订单更新时间',
`deliveryTime` DATETIME(3) NULL COMMENT '发货时间',
`orderFinishTime` DATETIME(3) NULL COMMENT '订单完成时间',
`completeTime` DATETIME(3) NULL COMMENT '交易完成时间',
`deliveryStatus` VARCHAR(191) NULL COMMENT '配送状态',
`freightCost` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '运费积分金额',
`wPayOrAPayFreightCost` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '微信或支付宝支付运费金额',
`refund` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '积分退款金额',
`wPayOrAPayRefund` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '微信或支付宝退款金额',
`logisticsOrderId` VARCHAR(191) NULL COMMENT '物流单号',
`crrgBsnNm` VARCHAR(191) NULL COMMENT '承运商名称',
`orderState` INT NOT NULL DEFAULT 0 COMMENT '订单状态',
`totalPrice` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '子订单总金额',
`wPayOrAPay` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '微信或支付宝支付金额',
`pointsPrice` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '积分支付金额',
`invoiceCode` VARCHAR(191) 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_order_sub_orderId_key` (`orderId`),
KEY `supplier_order_sub_mainOrderId_idx` (`mainOrderId`),
CONSTRAINT `supplier_order_sub_mainOrderId_fkey`
FOREIGN KEY (`mainOrderId`) REFERENCES `supplier_order_main` (`orderId`)
ON DELETE CASCADE ON UPDATE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='供应商子订单表';
CREATE TABLE IF NOT EXISTS `supplier_order_product` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '子单商品主键ID',
`subOrderId` VARCHAR(191) NOT NULL COMMENT '所属子订单ID',
`sku` VARCHAR(191) NOT NULL COMMENT '商品SKU编码',
`number` INT NOT NULL COMMENT '商品购买数量',
`price` DECIMAL(10, 2) NOT NULL COMMENT '商品成交单价',
`name` VARCHAR(191) NOT NULL COMMENT '商品名称快照',
`taxRate` DECIMAL(5, 4) NOT NULL DEFAULT 0.1300 COMMENT '商品税率快照',
`productImgUrl` VARCHAR(191) NULL COMMENT '商品图片地址',
`fileId` VARCHAR(191) NULL COMMENT '平台文件ID或图片ID',
`filePath` VARCHAR(191) NULL COMMENT '文件存储路径',
`picInfo` JSON NULL COMMENT '商品图片列表快照,JSON数组',
`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`),
KEY `supplier_order_product_subOrderId_idx` (`subOrderId`),
CONSTRAINT `supplier_order_product_subOrderId_fkey`
FOREIGN KEY (`subOrderId`) REFERENCES `supplier_order_sub` (`orderId`)
ON DELETE CASCADE ON UPDATE CASCADE
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='供应商子订单商品表';