feat: 查积分、支付、拆弹、取消订单
This commit is contained in:
@@ -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 })
|
||||
})
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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='供应商子订单商品表';
|
||||
Reference in New Issue
Block a user