feat: 基本实现拆单收货--
This commit is contained in:
@@ -2,7 +2,15 @@
|
|||||||
<section class="cart-panel" :class="{ 'cart-panel--page': pageMode }">
|
<section class="cart-panel" :class="{ 'cart-panel--page': pageMode }">
|
||||||
<van-notice-bar v-if="showNotice" text="满 199 元包邮,快去凑单吧~" left-icon="info-o" />
|
<van-notice-bar v-if="showNotice" text="满 199 元包邮,快去凑单吧~" left-icon="info-o" />
|
||||||
<section class="cart-list">
|
<section class="cart-list">
|
||||||
<van-empty v-if="!items.length" description="购物车还是空的" />
|
<div v-if="!items.length" class="cart-empty">
|
||||||
|
<van-empty description="购物车还是空的" />
|
||||||
|
<van-button
|
||||||
|
class="cart-empty__button"
|
||||||
|
round
|
||||||
|
type="danger"
|
||||||
|
@click="$router.push('/joyful-selection/products')"
|
||||||
|
>去购物</van-button>
|
||||||
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<article
|
<article
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
@@ -37,10 +45,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</section>
|
</section>
|
||||||
<van-submit-bar
|
<van-submit-bar
|
||||||
|
v-if="items.length"
|
||||||
:fixed="fixedSubmit"
|
:fixed="fixedSubmit"
|
||||||
:price="selectedTotalPrice"
|
:price="selectedTotalPrice"
|
||||||
:button-text="submitText"
|
:button-text="submitText"
|
||||||
@submit="$emit('checkout', selectedItems)"
|
@submit="handleCheckout"
|
||||||
>
|
>
|
||||||
<van-checkbox
|
<van-checkbox
|
||||||
:value="isAllChecked"
|
:value="isAllChecked"
|
||||||
@@ -139,6 +148,14 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.persistCart()
|
this.persistCart()
|
||||||
|
},
|
||||||
|
handleCheckout() {
|
||||||
|
if (!this.items.length || !this.selectedItems.length) {
|
||||||
|
this.$toast('请选择要结算的商品')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('checkout', this.selectedItems)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,6 +190,19 @@ export default {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cart-empty {
|
||||||
|
padding-top: 48px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-empty__button {
|
||||||
|
width: 120px;
|
||||||
|
height: 38px;
|
||||||
|
background: #ff5b20;
|
||||||
|
border-color: #ff5b20;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.cart-item {
|
.cart-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 128px;
|
min-height: 128px;
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
goCheckout(items) {
|
goCheckout(items) {
|
||||||
|
if (!items || !items.length) {
|
||||||
|
this.$toast('请选择要结算的商品')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sessionStorage.setItem('joyful-selection-checkout-items', JSON.stringify(items))
|
sessionStorage.setItem('joyful-selection-checkout-items', JSON.stringify(items))
|
||||||
this.$router.push({ path: '/joyful-selection/checkout', query: { source: 'cart' } })
|
this.$router.push({ path: '/joyful-selection/checkout', query: { source: 'cart' } })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,11 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.goods.length) {
|
||||||
|
this.$toast('暂无可提交的商品')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.submitting = true
|
this.submitting = true
|
||||||
try {
|
try {
|
||||||
await this.fetchBridgeToken()
|
await this.fetchBridgeToken()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<van-sku
|
<van-sku
|
||||||
|
:key="skuKey"
|
||||||
v-model="show"
|
v-model="show"
|
||||||
:class="`sku-popup--${mode}`"
|
:class="`sku-popup--${mode}`"
|
||||||
:sku="sku"
|
:sku="sku"
|
||||||
@@ -46,6 +47,7 @@ export default {
|
|||||||
data: () => ({
|
data: () => ({
|
||||||
show: false,
|
show: false,
|
||||||
mode: 'cart',
|
mode: 'cart',
|
||||||
|
skuKey: 0,
|
||||||
currentGoods: emptyGoods
|
currentGoods: emptyGoods
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
@@ -103,6 +105,7 @@ export default {
|
|||||||
open(goods, mode = 'cart') {
|
open(goods, mode = 'cart') {
|
||||||
this.currentGoods = goods
|
this.currentGoods = goods
|
||||||
this.mode = mode
|
this.mode = mode
|
||||||
|
this.skuKey += 1
|
||||||
this.show = true
|
this.show = true
|
||||||
},
|
},
|
||||||
getCartPayload(data) {
|
getCartPayload(data) {
|
||||||
|
|||||||
@@ -14,71 +14,54 @@
|
|||||||
<van-tab v-for="tab in tabs" :key="tab.label" :title="tab.label" />
|
<van-tab v-for="tab in tabs" :key="tab.label" :title="tab.label" />
|
||||||
</van-tabs>
|
</van-tabs>
|
||||||
|
|
||||||
<section class="order-list">
|
<van-pull-refresh v-model="refreshing" class="order-list" @refresh="onRefresh">
|
||||||
<van-empty v-if="visibleOrders.length === 0" description="暂无订单" />
|
<van-empty v-if="visibleOrders.length === 0" description="暂无订单" />
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<article v-for="order in visibleOrders" :key="order.viewId" class="order-card">
|
<article v-for="order in visibleOrders" :key="order.viewId" class="order-card">
|
||||||
<header class="main-order-header">
|
<header class="order-card__header">
|
||||||
<span>主单号:{{ order.mainOrderId || '-' }}</span>
|
<span>订单号:{{ order.orderNumberId || order.orderId || '-' }}</span>
|
||||||
<van-button
|
<b :class="order.statusClass">{{ order.status }}</b>
|
||||||
v-if="order.canCancel"
|
|
||||||
class="cancel-main-btn"
|
|
||||||
size="mini"
|
|
||||||
round
|
|
||||||
plain
|
|
||||||
:loading="isOperating('cancel-main', order.mainOrderId)"
|
|
||||||
@click.stop="cancelMainOrder(order)"
|
|
||||||
>
|
|
||||||
取消订单
|
|
||||||
</van-button>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="sub-order-list">
|
<div class="order-card__body" @click="goOrderDetail(order)">
|
||||||
<section
|
<div class="order-goods">
|
||||||
v-for="subOrder in order.subOrders"
|
<van-image
|
||||||
:key="subOrder.id"
|
v-if="firstProduct(order)"
|
||||||
class="sub-order-row"
|
class="order-thumb"
|
||||||
@click="goOrderDetail(order, subOrder)"
|
:src="firstProduct(order).image"
|
||||||
>
|
fit="cover"
|
||||||
<div class="sub-order-title">
|
/>
|
||||||
<span>子单号:{{ subOrder.orderId || '-' }}</span>
|
<div class="order-goods__info">
|
||||||
<b :class="subOrder.statusClass">{{ subOrder.status }}</b>
|
<h3>{{ firstProduct(order).name || '-' }}</h3>
|
||||||
|
<p>{{ skuText(firstProduct(order)) }}</p>
|
||||||
|
<p>创建时间:{{ order.createTime || '-' }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-body">
|
</div>
|
||||||
<div class="thumb-row">
|
<div class="order-summary">
|
||||||
<van-image
|
<p>共{{ order.count }}件商品</p>
|
||||||
v-for="item in previewProducts(subOrder.products)"
|
<p>合计:<b>¥{{ order.amount }}</b></p>
|
||||||
:key="item.id"
|
</div>
|
||||||
class="order-thumb"
|
|
||||||
:src="item.image"
|
|
||||||
fit="cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="order-summary">
|
|
||||||
<p>共{{ subOrder.count }}件商品</p>
|
|
||||||
<p>合计:<b>¥{{ subOrder.amount }}</b></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<footer v-if="subActions(subOrder).length">
|
|
||||||
<van-button
|
|
||||||
v-for="action in subActions(subOrder)"
|
|
||||||
:key="action.orderType"
|
|
||||||
size="small"
|
|
||||||
round
|
|
||||||
plain
|
|
||||||
:class="action.buttonClass"
|
|
||||||
:loading="isOperating(action.orderType, subOrder.orderId)"
|
|
||||||
@click.stop="submitSubOrderEvent(order, subOrder, action)"
|
|
||||||
>
|
|
||||||
{{ action.text }}
|
|
||||||
</van-button>
|
|
||||||
</footer>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<footer v-if="orderActions(order).length">
|
||||||
|
<div class="order-actions">
|
||||||
|
<van-button
|
||||||
|
v-for="action in orderActions(order)"
|
||||||
|
:key="action.key"
|
||||||
|
size="small"
|
||||||
|
round
|
||||||
|
:class="action.buttonClass"
|
||||||
|
:loading="isOperating(action.key, action.orderId)"
|
||||||
|
@click.stop="submitOrderAction(order, action)"
|
||||||
|
>
|
||||||
|
{{ action.text }}
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
</section>
|
</van-pull-refresh>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -122,6 +105,17 @@ const ORDER_STATE_VIEW = {
|
|||||||
[ORDER_STATE.REFUNDED]: { text: '退货', statusClass: 'muted' }
|
[ORDER_STATE.REFUNDED]: { text: '退货', statusClass: 'muted' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SKU_COLOR_MAP = {
|
||||||
|
cream: '奶油白',
|
||||||
|
blue: '雾霾蓝',
|
||||||
|
orange: '暖橙色'
|
||||||
|
}
|
||||||
|
|
||||||
|
const SKU_SIZE_MAP = {
|
||||||
|
standard: '标准款',
|
||||||
|
plus: '升级款'
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'JoyfulSelectionOrders',
|
name: 'JoyfulSelectionOrders',
|
||||||
components: {
|
components: {
|
||||||
@@ -139,7 +133,9 @@ export default {
|
|||||||
{ label: '售后', type: 'after-sale' }
|
{ label: '售后', type: 'after-sale' }
|
||||||
],
|
],
|
||||||
orders: [],
|
orders: [],
|
||||||
|
orderItems: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
|
refreshing: false,
|
||||||
operationKey: ''
|
operationKey: ''
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
@@ -150,31 +146,27 @@ export default {
|
|||||||
visibleOrders() {
|
visibleOrders() {
|
||||||
const tab = this.activeTab
|
const tab = this.activeTab
|
||||||
|
|
||||||
|
const orderItems = this.orderItems.length ? this.orderItems : this.buildOrderItems()
|
||||||
|
|
||||||
if (tab.type === 'all') {
|
if (tab.type === 'all') {
|
||||||
return this.orders.map(order => this.normalizeOrder(order, order.subOrders))
|
return orderItems
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab.type === 'pending-main') {
|
if (tab.type === 'pending-main') {
|
||||||
return this.orders
|
return orderItems.filter(order => order.displayType === 'main' && Number(order.orderState) === ORDER_STATE.PENDING_PAY)
|
||||||
.filter(order => Number(order.orderState) === ORDER_STATE.PENDING_PAY)
|
|
||||||
.map(order => this.normalizeOrder(order, order.subOrders))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab.type === 'sub-state') {
|
if (tab.type === 'sub-state') {
|
||||||
return this.splitSubOrdersByState(tab.state)
|
return orderItems.filter(order => order.displayType === 'sub' && Number(order.orderState) === tab.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab.type === 'cancelled') {
|
if (tab.type === 'cancelled') {
|
||||||
return this.cancelledOrders()
|
return orderItems.filter(order => Number(order.orderState) === ORDER_STATE.CANCELLED)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab.type === 'after-sale') {
|
if (tab.type === 'after-sale') {
|
||||||
return this.orders.flatMap(order => {
|
return orderItems.filter(order => {
|
||||||
const subOrders = this.normalizedSubOrders(order).filter(subOrder => {
|
return order.displayType === 'sub' && [ORDER_STATE.EXCHANGE, ORDER_STATE.REFUNDED].includes(Number(order.orderState))
|
||||||
return [ORDER_STATE.EXCHANGE, ORDER_STATE.REFUNDED].includes(Number(subOrder.orderState))
|
|
||||||
})
|
|
||||||
|
|
||||||
return subOrders.map(subOrder => this.normalizeOrder(order, [subOrder]))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,6 +202,38 @@ export default {
|
|||||||
normalizedSubOrders(order) {
|
normalizedSubOrders(order) {
|
||||||
return (order.subOrders || []).map(subOrder => this.normalizeSubOrder(order, subOrder))
|
return (order.subOrders || []).map(subOrder => this.normalizeSubOrder(order, subOrder))
|
||||||
},
|
},
|
||||||
|
buildOrderItems() {
|
||||||
|
return this.orders.flatMap(order => {
|
||||||
|
const subOrders = this.normalizedSubOrders(order)
|
||||||
|
const products = subOrders.flatMap(subOrder => subOrder.products || [])
|
||||||
|
const isMainOrderItem = Number(order.orderState) === ORDER_STATE.PENDING_PAY ||
|
||||||
|
(Number(order.orderState) === ORDER_STATE.CANCELLED && Number(order.isSplit) !== ORDER_SPLIT_STATE.SPLIT)
|
||||||
|
|
||||||
|
if (isMainOrderItem) {
|
||||||
|
return [{
|
||||||
|
...order,
|
||||||
|
viewId: `main-${order.mainOrderId || order.id}`,
|
||||||
|
displayType: 'main',
|
||||||
|
orderId: order.mainOrderId || order.orderId,
|
||||||
|
orderNumberId: order.mainOrderNumberId || order.orderNumberId,
|
||||||
|
status: ORDER_STATE_VIEW[Number(order.orderState)]?.text || order.status,
|
||||||
|
statusClass: ORDER_STATE_VIEW[Number(order.orderState)]?.statusClass || order.statusClass,
|
||||||
|
amount: order.amount || subOrders.reduce((total, subOrder) => total + Number(subOrder.amount || 0), 0).toFixed(2),
|
||||||
|
count: subOrders.reduce((total, subOrder) => total + Number(subOrder.count || 0), 0),
|
||||||
|
products,
|
||||||
|
subOrders
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
return subOrders.map(subOrder => ({
|
||||||
|
...subOrder,
|
||||||
|
viewId: `sub-${subOrder.orderId}`,
|
||||||
|
displayType: 'sub',
|
||||||
|
mainOrderId: order.mainOrderId,
|
||||||
|
mainOrderNumberId: order.mainOrderNumberId
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
},
|
||||||
splitSubOrdersByState(state) {
|
splitSubOrdersByState(state) {
|
||||||
return this.orders.flatMap(order => {
|
return this.orders.flatMap(order => {
|
||||||
if (Number(order.isSplit) !== ORDER_SPLIT_STATE.SPLIT) {
|
if (Number(order.isSplit) !== ORDER_SPLIT_STATE.SPLIT) {
|
||||||
@@ -237,6 +261,17 @@ export default {
|
|||||||
previewProducts(products = []) {
|
previewProducts(products = []) {
|
||||||
return products.slice(0, 3)
|
return products.slice(0, 3)
|
||||||
},
|
},
|
||||||
|
firstProduct(order) {
|
||||||
|
return order.products?.[0] || {}
|
||||||
|
},
|
||||||
|
skuText(product = {}) {
|
||||||
|
const sku = String(product.sku || product.skuText || '').trim()
|
||||||
|
const parts = sku.split('-').slice(-2).filter(Boolean)
|
||||||
|
const color = SKU_COLOR_MAP[parts[0]] || parts[0]
|
||||||
|
const size = SKU_SIZE_MAP[parts[1]] || parts[1]
|
||||||
|
|
||||||
|
return color || size ? [color, size].filter(Boolean).join(' | ') : '-'
|
||||||
|
},
|
||||||
actionKey(type, orderId) {
|
actionKey(type, orderId) {
|
||||||
return `${type}-${orderId}`
|
return `${type}-${orderId}`
|
||||||
},
|
},
|
||||||
@@ -256,13 +291,15 @@ export default {
|
|||||||
actions.push({ orderType: ORDER_EVENT_TYPE.EXCHANGE_ORDER, text: '换货', buttonClass: 'dark-action' })
|
actions.push({ orderType: ORDER_EVENT_TYPE.EXCHANGE_ORDER, text: '换货', buttonClass: 'dark-action' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (![ORDER_STATE.PENDING_PAY, ORDER_STATE.CANCELLED].includes(state)) {
|
|
||||||
actions.push({ orderType: ORDER_EVENT_TYPE.INVOICE_INFO, text: '发票信息', buttonClass: 'dark-action' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
},
|
},
|
||||||
goOrderDetail(order, subOrder) {
|
goOrderDetail(order) {
|
||||||
|
const subOrder = order.displayType === 'main' ? order.subOrders?.[0] : order
|
||||||
|
|
||||||
|
if (!subOrder?.orderId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/joyful-selection/order-detail',
|
path: '/joyful-selection/order-detail',
|
||||||
query: {
|
query: {
|
||||||
@@ -271,8 +308,10 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async loadOrders() {
|
async loadOrders(showLoading = true) {
|
||||||
this.loading = true
|
if (showLoading) {
|
||||||
|
this.loading = true
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.fetchBridgeToken()
|
await this.fetchBridgeToken()
|
||||||
@@ -280,6 +319,7 @@ export default {
|
|||||||
if (!this.supplierId || !this.userId) {
|
if (!this.supplierId || !this.userId) {
|
||||||
Toast('用户信息不完整,请重新进入商城')
|
Toast('用户信息不完整,请重新进入商城')
|
||||||
this.orders = []
|
this.orders = []
|
||||||
|
this.orderItems = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,13 +329,49 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.orders = data?.orders || []
|
this.orders = data?.orders || []
|
||||||
|
this.orderItems = data?.orderItems || []
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Toast(error.message || '查询订单失败,请稍后再试')
|
Toast(error.message || '查询订单失败,请稍后再试')
|
||||||
this.orders = []
|
this.orders = []
|
||||||
|
this.orderItems = []
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
if (showLoading) {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async onRefresh() {
|
||||||
|
try {
|
||||||
|
await this.loadOrders(false)
|
||||||
|
} finally {
|
||||||
|
this.refreshing = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderActions(order) {
|
||||||
|
if (order.displayType === 'main') {
|
||||||
|
return Number(order.orderState) === ORDER_STATE.PENDING_PAY
|
||||||
|
? [{ key: 'cancel-main', orderId: order.mainOrderId, text: '取消订单', buttonClass: 'dark-action' }]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.subActions(order).map(action => ({
|
||||||
|
...action,
|
||||||
|
key: action.orderType,
|
||||||
|
orderId: order.orderId
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
submitOrderAction(order, action) {
|
||||||
|
if (action.key === 'cancel-main') {
|
||||||
|
this.cancelMainOrder(order)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitSubOrderEvent(
|
||||||
|
{ mainOrderId: order.mainOrderId },
|
||||||
|
order,
|
||||||
|
action
|
||||||
|
)
|
||||||
|
},
|
||||||
async cancelMainOrder(order) {
|
async cancelMainOrder(order) {
|
||||||
try {
|
try {
|
||||||
await Dialog.confirm({
|
await Dialog.confirm({
|
||||||
@@ -409,152 +485,159 @@ export default {
|
|||||||
|
|
||||||
.orders-page ::v-deep .van-tab {
|
.orders-page ::v-deep .van-tab {
|
||||||
color: #333;
|
color: #333;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-list {
|
.order-list {
|
||||||
padding: 8px 8px 14px;
|
min-height: calc(100vh - 90px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 12px 10px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card {
|
.order-card {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 14px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-order-header {
|
.order-card__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 12px 12px 10px;
|
padding: 14px 14px 6px;
|
||||||
border-bottom: 1px solid #f4f4f4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-order-header span {
|
.order-card__header span {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #555;
|
color: #555;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-main-btn {
|
.order-card__header b {
|
||||||
flex: none;
|
flex: none;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: #ff5b20;
|
font-size: 14px;
|
||||||
border-color: #ff5b20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-order-row {
|
|
||||||
padding: 10px 12px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-order-row + .sub-order-row {
|
|
||||||
border-top: 1px solid #f4f4f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-order-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-order-title span {
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
color: #777;
|
|
||||||
font-size: 11px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-order-title b {
|
|
||||||
flex: none;
|
|
||||||
margin-left: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-order-title .pending {
|
.order-card__header .pending {
|
||||||
color: #ff5b20;
|
color: #ff5b20;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-order-title .done {
|
.order-card__header .done {
|
||||||
color: #21b36b;
|
color: #21b36b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-order-title .muted {
|
.order-card__header .muted {
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-body {
|
.order-card__body {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
padding: 12px 14px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumb-row {
|
.order-goods {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: 8px;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-thumb {
|
.order-thumb {
|
||||||
width: 62px;
|
width: 70px;
|
||||||
height: 62px;
|
height: 70px;
|
||||||
flex: none;
|
flex: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-goods__info {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-goods__info h3 {
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 0 6px;
|
||||||
|
color: #222;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-goods__info p {
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 4px 0 0;
|
||||||
|
color: #777;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.order-summary {
|
.order-summary {
|
||||||
flex: none;
|
flex: none;
|
||||||
margin-left: 10px;
|
margin-left: 12px;
|
||||||
color: #777;
|
color: #777;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-summary p {
|
.order-summary p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 22px;
|
line-height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-summary b {
|
.order-summary b {
|
||||||
color: #222;
|
color: #222;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-order-row footer {
|
.order-card footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 8px;
|
padding: 0 14px 14px;
|
||||||
padding-top: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-order-row footer .van-button {
|
.order-actions {
|
||||||
min-width: 72px;
|
display: flex;
|
||||||
height: 28px;
|
min-width: 0;
|
||||||
padding: 0 12px;
|
flex: 1;
|
||||||
border-radius: 15px;
|
justify-content: flex-end;
|
||||||
font-size: 11px;
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-actions .van-button {
|
||||||
|
min-width: 88px;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.primary-action {
|
.primary-action {
|
||||||
color: #ff5b20;
|
color: #ff5b20;
|
||||||
border-color: #ff5b20;
|
background: #fff1e8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-action {
|
.dark-action {
|
||||||
color: #333;
|
color: #333;
|
||||||
border-color: #ddd;
|
background: #f6f7f9;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Generated
+2293
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
|||||||
|
// Supplier order states and operation codes shared by order services and controllers.
|
||||||
export const ORDER_STATE = {
|
export const ORDER_STATE = {
|
||||||
PENDING_PAY: 0,
|
PENDING_PAY: 0,
|
||||||
FINISH_PAY: 1,
|
FINISH_PAY: 1,
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Context } from '../utils/handler.js'
|
||||||
|
import { guardMethod } from '../utils/method.js'
|
||||||
|
import { withErrorBoundary } from '../utils/handler.js'
|
||||||
|
import { basicService } from '../services/basic.service.js'
|
||||||
|
import { toHttpResponse } from './controller-response.js'
|
||||||
|
|
||||||
|
export const loginController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(basicService.login())
|
||||||
|
})
|
||||||
|
|
||||||
|
export const cartAddController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(basicService.addCart())
|
||||||
|
})
|
||||||
|
|
||||||
|
export const uploadController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(basicService.upload())
|
||||||
|
})
|
||||||
|
|
||||||
|
export const userInfoController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['GET'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
return toHttpResponse(basicService.userInfo())
|
||||||
|
})
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { fail, ok } from '../utils/http.js'
|
||||||
|
import type { ServiceResult } from '../types/service-result.js'
|
||||||
|
|
||||||
|
export const toHttpResponse = <T>(result: ServiceResult<T>) => {
|
||||||
|
if (result.success) {
|
||||||
|
return ok(result.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fail(result.error.message, result.error.status, result.error.code)
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
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 { 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 { toHttpResponse } from './controller-response.js'
|
||||||
|
|
||||||
|
type OrderListRequest = {
|
||||||
|
supplierId?: string
|
||||||
|
userId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderActionRequest = {
|
||||||
|
supplierId?: string
|
||||||
|
userId?: string
|
||||||
|
orderId?: string
|
||||||
|
mainOrderId?: string
|
||||||
|
amount?: number
|
||||||
|
totalPrice?: number
|
||||||
|
wPayOrAPay?: number
|
||||||
|
pointsPrice?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderCreateRequest = {
|
||||||
|
supplierId?: string
|
||||||
|
userId?: string
|
||||||
|
orderNo?: string
|
||||||
|
orderNumberId?: string
|
||||||
|
amount?: number
|
||||||
|
goods?: CheckoutGoods[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const centsToYuan = (value?: number) => Number((Number(value || 0) / 100).toFixed(2))
|
||||||
|
|
||||||
|
export const orderListController = 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()
|
||||||
|
|
||||||
|
return toHttpResponse(await orderListService.listOrders(supplierId, userId))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const orderCreateController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<OrderCreateRequest>(request)
|
||||||
|
|
||||||
|
return toHttpResponse(await orderCreateService.createOrder({
|
||||||
|
supplierId: String(body.supplierId || '').trim(),
|
||||||
|
userId: String(body.userId || '').trim(),
|
||||||
|
orderNo: String(body.orderNo || '').trim(),
|
||||||
|
orderNumberId: body.orderNumberId,
|
||||||
|
amount: body.amount,
|
||||||
|
goods: Array.isArray(body.goods) ? body.goods : []
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const orderCancelMainController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<OrderActionRequest>(request)
|
||||||
|
|
||||||
|
return toHttpResponse(await orderActionService.cancelPendingMain({
|
||||||
|
supplierId: String(body.supplierId || '').trim(),
|
||||||
|
userId: String(body.userId || '').trim(),
|
||||||
|
orderId: String(body.orderId || '').trim(),
|
||||||
|
mainOrderId: String(body.mainOrderId || body.orderId || '').trim()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const orderCancelPaidMainController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<OrderActionRequest>(request)
|
||||||
|
|
||||||
|
return toHttpResponse(await orderActionService.cancelPaidMain({
|
||||||
|
supplierId: String(body.supplierId || '').trim(),
|
||||||
|
userId: String(body.userId || '').trim(),
|
||||||
|
orderId: String(body.orderId || '').trim(),
|
||||||
|
mainOrderId: String(body.mainOrderId || body.orderId || '').trim()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const orderCancelSubController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<OrderActionRequest>(request)
|
||||||
|
|
||||||
|
return toHttpResponse(await orderActionService.cancelSub({
|
||||||
|
supplierId: String(body.supplierId || '').trim(),
|
||||||
|
userId: String(body.userId || '').trim(),
|
||||||
|
orderId: String(body.orderId || '').trim(),
|
||||||
|
mainOrderId: String(body.mainOrderId || '').trim()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const orderPayController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<OrderActionRequest>(request)
|
||||||
|
const totalPrice = Number(body.totalPrice ?? centsToYuan(body.amount))
|
||||||
|
|
||||||
|
return toHttpResponse(await orderActionService.payOrder({
|
||||||
|
supplierId: String(body.supplierId || '').trim(),
|
||||||
|
orderId: String(body.orderId || '').trim(),
|
||||||
|
totalPrice,
|
||||||
|
wPayOrAPay: Number(body.wPayOrAPay ?? 0),
|
||||||
|
pointsPrice: Number(body.pointsPrice ?? totalPrice)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const orderPushEventController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<OrderPushEventInput>(request)
|
||||||
|
|
||||||
|
return toHttpResponse(await orderPushEventService.pushEvent(body))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const supplierOrderInfoController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
let body: SupplierOrderQueryRequest
|
||||||
|
try {
|
||||||
|
body = supplierOpenOrderService.parseBody(await readJson<SupplierOrderQueryRequest>(request))
|
||||||
|
} catch (error) {
|
||||||
|
return Response.json({
|
||||||
|
resultCode: 400,
|
||||||
|
message: error instanceof Error ? error.message : '请求参数解密失败',
|
||||||
|
orderInfo: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.json(await supplierOpenOrderService.queryOrderInfo(body))
|
||||||
|
})
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type { Context } from '../utils/handler.js'
|
||||||
|
import { guardMethod } from '../utils/method.js'
|
||||||
|
import { withErrorBoundary } from '../utils/handler.js'
|
||||||
|
import { productService } from '../services/product.service.js'
|
||||||
|
import { toHttpResponse } from './controller-response.js'
|
||||||
|
|
||||||
|
export const productListController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['GET'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const url = new URL(request.url)
|
||||||
|
const keyword = (url.searchParams.get('keyword') || '').trim()
|
||||||
|
const categoryId = (url.searchParams.get('categoryId') || '').trim()
|
||||||
|
|
||||||
|
return toHttpResponse(await productService.listProducts(keyword, categoryId))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const productDetailController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['GET'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const url = new URL(request.url)
|
||||||
|
const id = (url.searchParams.get('id') || '').trim()
|
||||||
|
const sku = (url.searchParams.get('sku') || '').trim()
|
||||||
|
|
||||||
|
return toHttpResponse(await productService.getProductDetail(id, sku))
|
||||||
|
})
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
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 { supplierService } from '../services/supplier.service.js'
|
||||||
|
import { toHttpResponse } from './controller-response.js'
|
||||||
|
|
||||||
|
type SupplierInfoRequest = {
|
||||||
|
supplierIdentification?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupplierTokenRequest = {
|
||||||
|
userId?: string
|
||||||
|
supplierIdentification?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupplierPointsRequest = {
|
||||||
|
supplierId?: string
|
||||||
|
userId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupplierDebugTokensRequest = {
|
||||||
|
supplierIdentification?: string
|
||||||
|
forceRefreshZhghToken?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const supplierInfoController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<SupplierInfoRequest>(request)
|
||||||
|
const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase()
|
||||||
|
|
||||||
|
return toHttpResponse(await supplierService.getSupplierInfo(supplierIdentification))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const supplierTokenController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<SupplierTokenRequest>(request)
|
||||||
|
const userId = String(body.userId || '').trim()
|
||||||
|
const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase()
|
||||||
|
|
||||||
|
return toHttpResponse(await supplierService.warmSupplierToken(userId, supplierIdentification))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const supplierPointsController = 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 userId = String(body.userId || '').trim()
|
||||||
|
|
||||||
|
return toHttpResponse(await supplierService.getUserPoints(supplierId, userId))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const supplierDebugTokensController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<SupplierDebugTokensRequest>(request)
|
||||||
|
const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase()
|
||||||
|
|
||||||
|
return toHttpResponse(await supplierService.getDebugTokens(supplierIdentification, Boolean(body.forceRefreshZhghToken)))
|
||||||
|
})
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import type { Prisma } from '@prisma/client'
|
||||||
|
import { prisma } from '../utils/prisma.js'
|
||||||
|
|
||||||
|
export type PrismaTransaction = Prisma.TransactionClient
|
||||||
|
|
||||||
|
export const orderDao = {
|
||||||
|
transaction<T>(handler: (tx: PrismaTransaction) => Promise<T>) {
|
||||||
|
return prisma.$transaction(handler)
|
||||||
|
},
|
||||||
|
|
||||||
|
findMainOrdersWithSubs(supplierId: string, userId: string) {
|
||||||
|
return prisma.supplierOrderMain.findMany({
|
||||||
|
where: {
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
deleted: 0
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
subOrders: {
|
||||||
|
where: { deleted: 0 },
|
||||||
|
include: {
|
||||||
|
products: {
|
||||||
|
where: { deleted: 0 },
|
||||||
|
orderBy: { createAt: 'asc' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderBy: [
|
||||||
|
{ createTime: 'desc' },
|
||||||
|
{ createAt: 'desc' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderBy: [
|
||||||
|
{ createTime: 'desc' },
|
||||||
|
{ createAt: 'desc' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
findMainOrder(supplierId: string, userId: string, mainOrderId: string) {
|
||||||
|
return prisma.supplierOrderMain.findFirst({
|
||||||
|
where: {
|
||||||
|
orderId: mainOrderId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
deleted: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
findSubOrder(supplierId: string, userId: string, orderId: string) {
|
||||||
|
return prisma.supplierOrderSub.findFirst({
|
||||||
|
where: {
|
||||||
|
orderId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
deleted: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
findPayingMainOrder(orderId: string) {
|
||||||
|
return prisma.supplierOrderMain.findUnique({
|
||||||
|
where: { orderId }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
findSubOrderIdsByMain(mainOrderId: string) {
|
||||||
|
return prisma.supplierOrderSub.findMany({
|
||||||
|
where: {
|
||||||
|
mainOrderId,
|
||||||
|
deleted: 0
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
orderId: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
findOpenApiSubOrders(supplierId: string, orderIds: string[]) {
|
||||||
|
return prisma.supplierOrderSub.findMany({
|
||||||
|
where: {
|
||||||
|
supplierId,
|
||||||
|
orderId: { in: orderIds },
|
||||||
|
deleted: 0
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
products: {
|
||||||
|
where: { deleted: 0 },
|
||||||
|
orderBy: { createAt: 'asc' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderBy: { createAt: 'asc' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePushLog(id: number, data: Parameters<typeof prisma.supplierOrderPushLog.update>[0]['data']) {
|
||||||
|
return prisma.supplierOrderPushLog.update({
|
||||||
|
where: { id },
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { prisma } from '../utils/prisma.js'
|
||||||
|
|
||||||
|
export type ProductRecord = Awaited<ReturnType<typeof prisma.supplierProduct.findMany>>[number]
|
||||||
|
|
||||||
|
export const productDao = {
|
||||||
|
// 商品列表查询只负责组装数据库条件,业务格式化放在 service。
|
||||||
|
findEnabledProducts(keyword: string, categoryId: string) {
|
||||||
|
return prisma.supplierProduct.findMany({
|
||||||
|
where: {
|
||||||
|
enabled: true,
|
||||||
|
deleted: 0,
|
||||||
|
...(keyword ? { name: { contains: keyword } } : {}),
|
||||||
|
...(categoryId && categoryId !== 'recommend' ? { categoryId } : {})
|
||||||
|
},
|
||||||
|
orderBy: [
|
||||||
|
{ sortOrder: 'asc' },
|
||||||
|
{ createdAt: 'desc' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
findEnabledProduct(id: string, sku: string) {
|
||||||
|
const numericId = Number(id)
|
||||||
|
const querySku = sku || (id && !Number.isInteger(numericId) ? id : '')
|
||||||
|
|
||||||
|
return prisma.supplierProduct.findFirst({
|
||||||
|
where: {
|
||||||
|
enabled: true,
|
||||||
|
deleted: 0,
|
||||||
|
OR: [
|
||||||
|
...(Number.isInteger(numericId) && numericId > 0 ? [{ id: numericId }] : []),
|
||||||
|
...(querySku ? [{ sku: querySku }] : [])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { prisma } from '../utils/prisma.js'
|
||||||
|
|
||||||
|
export const supplierDao = {
|
||||||
|
findActiveByIdentification(supplierIdentification: string) {
|
||||||
|
return prisma.supplierInfo.findFirst({
|
||||||
|
where: { supplierIdentification, deleted: 0 }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
findActiveBySupplierId(supplierId: string) {
|
||||||
|
return prisma.supplierInfo.findFirst({
|
||||||
|
where: { supplierId, deleted: 0 }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { cartAddController as default } from '../controllers/basic.controller.js'
|
||||||
import { guardMethod } from '../utils/method.js'
|
|
||||||
import { ok } from '../utils/http.js'
|
|
||||||
import { withErrorBoundary } from '../utils/handler.js'
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
return ok({ message: '加入购物车接口待实现' })
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { loginController as default } from '../controllers/basic.controller.js'
|
||||||
import { guardMethod } from '../utils/method.js'
|
|
||||||
import { ok } from '../utils/http.js'
|
|
||||||
import { withErrorBoundary } from '../utils/handler.js'
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
return ok({ message: '登录接口待实现' })
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,100 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { orderCancelMainController as default } from '../controllers/order.controller.js'
|
||||||
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'
|
|
||||||
import { ORDER_EVENT_TYPE, ORDER_STATE } from '../utils/order-state.js'
|
|
||||||
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
|
||||||
|
|
||||||
type OrderCancelMainRequest = {
|
|
||||||
supplierId?: string
|
|
||||||
userId?: string
|
|
||||||
orderId?: string
|
|
||||||
mainOrderId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type BridgeOrderResponse = {
|
|
||||||
resultCode?: number
|
|
||||||
message?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
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 userId = String(body.userId || '').trim()
|
|
||||||
const mainOrderId = String(body.mainOrderId || body.orderId || '').trim()
|
|
||||||
const orderId = String(body.orderId || mainOrderId).trim()
|
|
||||||
|
|
||||||
if (!supplierId || !userId || !mainOrderId) {
|
|
||||||
return fail('供应商、用户或订单信息不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
const order = await prisma.supplierOrderMain.findFirst({
|
|
||||||
where: {
|
|
||||||
orderId: mainOrderId,
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
deleted: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!order) {
|
|
||||||
return fail('主单不存在或已取消', 404, 'MAIN_ORDER_NOT_FOUND')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.orderState !== ORDER_STATE.PENDING_PAY) {
|
|
||||||
return fail('已支付主单不能通过未支付取消接口取消', 400, 'MAIN_ORDER_ALREADY_PAID')
|
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.$transaction(async tx => {
|
|
||||||
await tx.supplierOrderMain.update({
|
|
||||||
where: { orderId: mainOrderId },
|
|
||||||
data: {
|
|
||||||
orderState: ORDER_STATE.CANCELLED,
|
|
||||||
updateTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await tx.supplierOrderSub.updateMany({
|
|
||||||
where: {
|
|
||||||
mainOrderId,
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
deleted: 0
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
orderState: ORDER_STATE.CANCELLED,
|
|
||||||
updateTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const { response: bridgeResponse, payload: bridgePayload } = await bridgeFetchWithAuth<BridgeOrderResponse>(
|
|
||||||
supplierId,
|
|
||||||
'/pointOrder/pushOrderInfoMsg',
|
|
||||||
{
|
|
||||||
orderType: ORDER_EVENT_TYPE.CANCEL_MAIN,
|
|
||||||
orderId,
|
|
||||||
mainOrderId,
|
|
||||||
userId,
|
|
||||||
time: Date.now()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
|
|
||||||
return fail(bridgePayload.message || '取消主单推送失败', 502, 'BRIDGE_CANCEL_MAIN_FAILED')
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok({
|
|
||||||
supplierId,
|
|
||||||
orderType: ORDER_EVENT_TYPE.CANCEL_MAIN,
|
|
||||||
orderId,
|
|
||||||
mainOrderId,
|
|
||||||
userId
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,104 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { orderCancelPaidMainController as default } from '../controllers/order.controller.js'
|
||||||
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'
|
|
||||||
import { ORDER_EVENT_TYPE, ORDER_SPLIT_STATE, ORDER_STATE } from '../utils/order-state.js'
|
|
||||||
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
|
||||||
|
|
||||||
type OrderCancelPaidMainRequest = {
|
|
||||||
supplierId?: string
|
|
||||||
userId?: string
|
|
||||||
orderId?: string
|
|
||||||
mainOrderId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type BridgeOrderResponse = {
|
|
||||||
resultCode?: number
|
|
||||||
message?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
const body = await readJson<OrderCancelPaidMainRequest>(request)
|
|
||||||
const supplierId = String(body.supplierId || '').trim()
|
|
||||||
const userId = String(body.userId || '').trim()
|
|
||||||
const mainOrderId = String(body.mainOrderId || body.orderId || '').trim()
|
|
||||||
const orderId = String(body.orderId || mainOrderId).trim()
|
|
||||||
|
|
||||||
if (!supplierId || !userId || !mainOrderId) {
|
|
||||||
return fail('供应商、用户或订单信息不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
const order = await prisma.supplierOrderMain.findFirst({
|
|
||||||
where: {
|
|
||||||
orderId: mainOrderId,
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
deleted: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!order) {
|
|
||||||
return fail('主单不存在或已取消', 404, 'MAIN_ORDER_NOT_FOUND')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.orderState === ORDER_STATE.PENDING_PAY) {
|
|
||||||
return fail('未支付主单请使用未支付取消接口', 400, 'MAIN_ORDER_NOT_PAID')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.orderState !== ORDER_STATE.FINISH_PAY || order.isSplit === ORDER_SPLIT_STATE.SPLIT) {
|
|
||||||
return fail('该主单已拆单,不能按已支付未拆单整单取消', 400, 'MAIN_ORDER_ALREADY_SPLIT')
|
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.$transaction(async tx => {
|
|
||||||
await tx.supplierOrderMain.update({
|
|
||||||
where: { orderId: mainOrderId },
|
|
||||||
data: {
|
|
||||||
orderState: ORDER_STATE.CANCELLED,
|
|
||||||
updateTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await tx.supplierOrderSub.updateMany({
|
|
||||||
where: {
|
|
||||||
mainOrderId,
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
deleted: 0
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
orderState: ORDER_STATE.CANCELLED,
|
|
||||||
updateTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const { response: bridgeResponse, payload: bridgePayload } = await bridgeFetchWithAuth<BridgeOrderResponse>(
|
|
||||||
supplierId,
|
|
||||||
'/pointOrder/pushOrderInfoMsg',
|
|
||||||
{
|
|
||||||
orderType: ORDER_EVENT_TYPE.CANCEL_PAID_NOT_SPLIT_MAIN,
|
|
||||||
orderId,
|
|
||||||
mainOrderId,
|
|
||||||
userId,
|
|
||||||
time: Date.now()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
|
|
||||||
return fail(bridgePayload.message || '取消已支付未拆单整单推送失败', 502, 'BRIDGE_CANCEL_PAID_MAIN_FAILED')
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok({
|
|
||||||
supplierId,
|
|
||||||
orderType: ORDER_EVENT_TYPE.CANCEL_PAID_NOT_SPLIT_MAIN,
|
|
||||||
orderId,
|
|
||||||
mainOrderId,
|
|
||||||
userId
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,82 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { orderCancelSubController as default } from '../controllers/order.controller.js'
|
||||||
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'
|
|
||||||
import { ORDER_EVENT_TYPE, ORDER_STATE } from '../utils/order-state.js'
|
|
||||||
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
|
||||||
|
|
||||||
type OrderCancelSubRequest = {
|
|
||||||
supplierId?: string
|
|
||||||
userId?: string
|
|
||||||
orderId?: string
|
|
||||||
mainOrderId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type BridgeOrderResponse = {
|
|
||||||
resultCode?: number
|
|
||||||
message?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
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 userId = String(body.userId || '').trim()
|
|
||||||
const orderId = String(body.orderId || '').trim()
|
|
||||||
|
|
||||||
if (!supplierId || !userId || !orderId) {
|
|
||||||
return fail('供应商、用户或子单信息不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
await prisma.supplierOrderSub.update({
|
|
||||||
where: { orderId },
|
|
||||||
data: {
|
|
||||||
orderState: ORDER_STATE.CANCELLED,
|
|
||||||
updateTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const { response: bridgeResponse, payload: bridgePayload } = await bridgeFetchWithAuth<BridgeOrderResponse>(
|
|
||||||
supplierId,
|
|
||||||
'/pointOrder/pushOrderInfoMsg',
|
|
||||||
{
|
|
||||||
orderType: ORDER_EVENT_TYPE.CANCEL_SUB,
|
|
||||||
orderId,
|
|
||||||
mainOrderId,
|
|
||||||
userId,
|
|
||||||
time: Date.now()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
|
|
||||||
return fail(bridgePayload.message || '取消子单推送失败', 502, 'BRIDGE_CANCEL_SUB_FAILED')
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok({
|
|
||||||
supplierId,
|
|
||||||
orderType: ORDER_EVENT_TYPE.CANCEL_SUB,
|
|
||||||
orderId,
|
|
||||||
mainOrderId,
|
|
||||||
userId
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,270 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { orderCreateController as default } from '../controllers/order.controller.js'
|
||||||
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'
|
|
||||||
import { ORDER_SPLIT_STATE, ORDER_STATE } from '../utils/order-state.js'
|
|
||||||
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
|
||||||
|
|
||||||
type CheckoutGoods = {
|
|
||||||
id?: string | number
|
|
||||||
sku?: string | number | Record<string, unknown>
|
|
||||||
count?: number
|
|
||||||
number?: number
|
|
||||||
price?: string | number
|
|
||||||
name?: string
|
|
||||||
image?: string
|
|
||||||
imageUrl?: string
|
|
||||||
picInfo?: string[]
|
|
||||||
taxRate?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type OrderCreateRequest = {
|
|
||||||
supplierId?: string
|
|
||||||
userId?: string
|
|
||||||
orderNo?: string
|
|
||||||
orderNumberId?: string
|
|
||||||
amount?: number
|
|
||||||
goods?: CheckoutGoods[]
|
|
||||||
}
|
|
||||||
|
|
||||||
type BridgeOrderResponse = {
|
|
||||||
resultCode?: number
|
|
||||||
message?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type OrderProductSnapshot = {
|
|
||||||
sku: string
|
|
||||||
number: number
|
|
||||||
price: number
|
|
||||||
name: string
|
|
||||||
taxRate: number
|
|
||||||
picInfo: string[]
|
|
||||||
fileId: string | null
|
|
||||||
filePath: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
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 toMoney = (value: number) => Number(value.toFixed(2))
|
|
||||||
const createSubOrderId = (orderId: string, index: number) => `${orderId}_${String(index + 1).padStart(3, '0')}`
|
|
||||||
const createSubOrderNumberId = (orderNumberId: string, index: number) => `${orderNumberId}_${String(index + 1).padStart(3, '0')}`
|
|
||||||
|
|
||||||
const toOrderProducts = (goods: CheckoutGoods[]) => goods.map((item, index) => {
|
|
||||||
const image = item.image || item.imageUrl || item.picInfo?.[0] || ''
|
|
||||||
const sku = typeof item.sku === 'object' && item.sku
|
|
||||||
? [item.id, item.sku.color, item.sku.size].filter(Boolean).join('-')
|
|
||||||
: item.sku
|
|
||||||
|
|
||||||
return {
|
|
||||||
sku: String(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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const productSubtotal = (item: OrderProductSnapshot) => toMoney(Number(item.price || 0) * Number(item.number || 0))
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
const body = await readJson<OrderCreateRequest>(request)
|
|
||||||
const supplierId = String(body.supplierId || '').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 : []
|
|
||||||
const orderProInfos = toOrderProducts(goods)
|
|
||||||
|
|
||||||
if (!supplierId || !userId || !orderId) {
|
|
||||||
return fail('供应商、用户或订单信息不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount <= 0) {
|
|
||||||
return fail('订单金额必须大于0')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!orderProInfos.length) {
|
|
||||||
return fail('订单商品不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderNumberId = String(body.orderNumberId || createOrderNumberId())
|
|
||||||
const orderTime = nowText()
|
|
||||||
const localOrderState = ORDER_STATE.PENDING_PAY
|
|
||||||
const now = new Date()
|
|
||||||
const subOrders = orderProInfos.map((item, index) => {
|
|
||||||
const subTotal = productSubtotal(item)
|
|
||||||
|
|
||||||
return {
|
|
||||||
orderId: createSubOrderId(orderId, index),
|
|
||||||
orderNumberId: createSubOrderNumberId(orderNumberId, index),
|
|
||||||
product: item,
|
|
||||||
totalPrice: subTotal,
|
|
||||||
pointsPrice: subTotal
|
|
||||||
}
|
|
||||||
})
|
|
||||||
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: ORDER_STATE.PENDING_PAY,
|
|
||||||
totalPrice: amount,
|
|
||||||
wPayOrAPay: 0,
|
|
||||||
pointsPrice: amount,
|
|
||||||
invoiceCode: null,
|
|
||||||
orderProInfos
|
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.$transaction(async tx => {
|
|
||||||
const existingSubOrders = await tx.supplierOrderSub.findMany({
|
|
||||||
where: { mainOrderId: orderId },
|
|
||||||
select: { orderId: true }
|
|
||||||
})
|
|
||||||
const existingSubOrderIds = existingSubOrders.map(item => item.orderId)
|
|
||||||
|
|
||||||
if (existingSubOrderIds.length) {
|
|
||||||
await tx.supplierOrderProduct.deleteMany({
|
|
||||||
where: { subOrderId: { in: existingSubOrderIds } }
|
|
||||||
})
|
|
||||||
await tx.supplierOrderSub.deleteMany({
|
|
||||||
where: { mainOrderId: orderId }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await tx.supplierOrderMain.upsert({
|
|
||||||
where: { orderId },
|
|
||||||
update: {
|
|
||||||
orderNumberId,
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
aplName: orderInfo.aplName,
|
|
||||||
updateTime: now,
|
|
||||||
deliveryStatus: orderInfo.deliveryStatus,
|
|
||||||
freightCost: orderInfo.freightCost,
|
|
||||||
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
|
|
||||||
refund: orderInfo.refund,
|
|
||||||
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
|
|
||||||
orderState: localOrderState,
|
|
||||||
isSplit: ORDER_SPLIT_STATE.NOT_SPLIT,
|
|
||||||
splitTime: null,
|
|
||||||
totalPrice: orderInfo.totalPrice,
|
|
||||||
wPayOrAPay: orderInfo.wPayOrAPay,
|
|
||||||
pointsPrice: orderInfo.pointsPrice,
|
|
||||||
invoiceCode: orderInfo.invoiceCode,
|
|
||||||
deleted: 0
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
orderId,
|
|
||||||
orderNumberId,
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
aplName: orderInfo.aplName,
|
|
||||||
createTime: now,
|
|
||||||
updateTime: now,
|
|
||||||
deliveryStatus: orderInfo.deliveryStatus,
|
|
||||||
freightCost: orderInfo.freightCost,
|
|
||||||
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
|
|
||||||
refund: orderInfo.refund,
|
|
||||||
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
|
|
||||||
orderState: localOrderState,
|
|
||||||
isSplit: ORDER_SPLIT_STATE.NOT_SPLIT,
|
|
||||||
totalPrice: orderInfo.totalPrice,
|
|
||||||
wPayOrAPay: orderInfo.wPayOrAPay,
|
|
||||||
pointsPrice: orderInfo.pointsPrice,
|
|
||||||
invoiceCode: orderInfo.invoiceCode
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await tx.supplierOrderSub.createMany({
|
|
||||||
data: subOrders.map(subOrder => ({
|
|
||||||
orderId: subOrder.orderId,
|
|
||||||
orderNumberId: subOrder.orderNumberId,
|
|
||||||
mainOrderId: orderId,
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
aplName: orderInfo.aplName,
|
|
||||||
createTime: now,
|
|
||||||
updateTime: now,
|
|
||||||
deliveryStatus: orderInfo.deliveryStatus,
|
|
||||||
freightCost: orderInfo.freightCost,
|
|
||||||
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
|
|
||||||
refund: orderInfo.refund,
|
|
||||||
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
|
|
||||||
orderState: localOrderState,
|
|
||||||
totalPrice: subOrder.totalPrice,
|
|
||||||
wPayOrAPay: 0,
|
|
||||||
pointsPrice: subOrder.pointsPrice,
|
|
||||||
invoiceCode: orderInfo.invoiceCode
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
await tx.supplierOrderProduct.createMany({
|
|
||||||
data: subOrders.map(subOrder => ({
|
|
||||||
subOrderId: subOrder.orderId,
|
|
||||||
sku: subOrder.product.sku,
|
|
||||||
number: subOrder.product.number,
|
|
||||||
price: subOrder.product.price,
|
|
||||||
name: subOrder.product.name,
|
|
||||||
taxRate: subOrder.product.taxRate,
|
|
||||||
productImgUrl: subOrder.product.picInfo[0] || null,
|
|
||||||
fileId: subOrder.product.fileId,
|
|
||||||
filePath: subOrder.product.filePath,
|
|
||||||
picInfo: subOrder.product.picInfo
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const { response: bridgeResponse, payload: bridgePayload } = await bridgeFetchWithAuth<BridgeOrderResponse>(
|
|
||||||
supplierId,
|
|
||||||
'/pointOrder/pendingOrderInfo',
|
|
||||||
{
|
|
||||||
userId,
|
|
||||||
orderInfo
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
|
|
||||||
return fail(bridgePayload.message || '提交待支付订单失败', 502, 'BRIDGE_PENDING_ORDER_FAILED')
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok({
|
|
||||||
orderId,
|
|
||||||
orderNumberId,
|
|
||||||
subOrderIds: subOrders.map(item => item.orderId),
|
|
||||||
orderInfo
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,133 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { orderListController as default } from '../controllers/order.controller.js'
|
||||||
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'
|
|
||||||
import { ORDER_STATE } from '../utils/order-state.js'
|
|
||||||
|
|
||||||
type OrderListRequest = {
|
|
||||||
supplierId?: string
|
|
||||||
userId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type OrderStatusView = {
|
|
||||||
text: string
|
|
||||||
statusClass: string
|
|
||||||
actionText: string
|
|
||||||
buttonClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusMap: Record<number, OrderStatusView> = {
|
|
||||||
[ORDER_STATE.PENDING_PAY]: { text: '待付款', statusClass: 'pending', actionText: '去支付', buttonClass: 'primary-action' },
|
|
||||||
[ORDER_STATE.FINISH_PAY]: { text: '待发货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' },
|
|
||||||
[ORDER_STATE.IN_PROGRESS]: { text: '待收货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' },
|
|
||||||
[ORDER_STATE.CANCELLED]: { text: '已取消', statusClass: 'muted', actionText: '', buttonClass: 'dark-action' },
|
|
||||||
[ORDER_STATE.COMPLETED]: { text: '已完成', statusClass: 'done', actionText: '再次购买', buttonClass: 'dark-action' },
|
|
||||||
[ORDER_STATE.EXCHANGE]: { text: '换货', statusClass: 'muted', actionText: '查看订单', buttonClass: 'dark-action' },
|
|
||||||
[ORDER_STATE.REFUNDED]: { text: '退货', statusClass: 'muted', 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 mainOrders = await prisma.supplierOrderMain.findMany({
|
|
||||||
where: {
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
deleted: 0
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
subOrders: {
|
|
||||||
where: { deleted: 0 },
|
|
||||||
include: {
|
|
||||||
products: {
|
|
||||||
where: { deleted: 0 },
|
|
||||||
orderBy: { createAt: 'asc' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
orderBy: [
|
|
||||||
{ createTime: 'desc' },
|
|
||||||
{ createAt: 'desc' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
orderBy: [
|
|
||||||
{ createTime: 'desc' },
|
|
||||||
{ createAt: 'desc' }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const orders = mainOrders.map(mainOrder => {
|
|
||||||
const mainStatus = statusMap[mainOrder.orderState] || statusMap[ORDER_STATE.FINISH_PAY]
|
|
||||||
const subOrders = mainOrder.subOrders.map(subOrder => {
|
|
||||||
const status = statusMap[subOrder.orderState] || mainStatus
|
|
||||||
const products = subOrder.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: subOrder.orderId,
|
|
||||||
orderId: subOrder.orderId,
|
|
||||||
orderNumberId: subOrder.orderNumberId || '',
|
|
||||||
mainOrderId: subOrder.mainOrderId,
|
|
||||||
supplierId: subOrder.supplierId,
|
|
||||||
status: status.text,
|
|
||||||
statusClass: status.statusClass,
|
|
||||||
actionText: status.actionText,
|
|
||||||
buttonClass: status.buttonClass,
|
|
||||||
orderState: subOrder.orderState,
|
|
||||||
amount: toYuanText(subOrder.totalPrice),
|
|
||||||
count: products.reduce((total, item) => total + Number(item.number || 0), 0),
|
|
||||||
products
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: mainOrder.orderId,
|
|
||||||
mainOrderId: mainOrder.orderId,
|
|
||||||
mainOrderNumberId: mainOrder.orderNumberId || '',
|
|
||||||
supplierId: mainOrder.supplierId,
|
|
||||||
status: mainStatus.text,
|
|
||||||
statusClass: mainStatus.statusClass,
|
|
||||||
orderState: mainOrder.orderState,
|
|
||||||
isSplit: mainOrder.isSplit,
|
|
||||||
splitTime: mainOrder.splitTime,
|
|
||||||
canCancel: mainOrder.orderState === ORDER_STATE.PENDING_PAY,
|
|
||||||
subOrders
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return ok({ orders })
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,163 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { orderPayController as default } from '../controllers/order.controller.js'
|
||||||
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'
|
|
||||||
import { ORDER_EVENT_TYPE, ORDER_SPLIT_STATE, ORDER_STATE } from '../utils/order-state.js'
|
|
||||||
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
|
||||||
|
|
||||||
type OrderPayRequest = {
|
|
||||||
supplierId?: string
|
|
||||||
orderId?: string
|
|
||||||
amount?: number
|
|
||||||
totalPrice?: number
|
|
||||||
wPayOrAPay?: number
|
|
||||||
pointsPrice?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type BridgePayResponse = {
|
|
||||||
resultCode?: number
|
|
||||||
message?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
const body = await readJson<OrderPayRequest>(request)
|
|
||||||
const supplierId = String(body.supplierId || '').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 || !orderId) {
|
|
||||||
return fail('供应商或订单信息不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalPrice <= 0) {
|
|
||||||
return fail('支付金额必须大于0')
|
|
||||||
}
|
|
||||||
|
|
||||||
const order = await prisma.supplierOrderMain.findUnique({
|
|
||||||
where: { orderId }
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!order || order.deleted === 1) {
|
|
||||||
return fail('待支付订单不存在', 404, 'ORDER_NOT_FOUND')
|
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.$transaction(async tx => {
|
|
||||||
await tx.supplierOrderMain.update({
|
|
||||||
where: { orderId },
|
|
||||||
data: {
|
|
||||||
orderState: ORDER_STATE.FINISH_PAY,
|
|
||||||
isSplit: ORDER_SPLIT_STATE.NOT_SPLIT,
|
|
||||||
totalPrice,
|
|
||||||
wPayOrAPay,
|
|
||||||
pointsPrice,
|
|
||||||
updateTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await tx.supplierOrderSub.updateMany({
|
|
||||||
where: { mainOrderId: orderId, deleted: 0 },
|
|
||||||
data: {
|
|
||||||
orderState: ORDER_STATE.FINISH_PAY,
|
|
||||||
updateTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const { response: confirmResponse, payload: confirmPayload } = await bridgeFetchWithAuth<BridgePayResponse>(
|
|
||||||
supplierId,
|
|
||||||
'/pointOrder/confirmPurchase',
|
|
||||||
{
|
|
||||||
orderId,
|
|
||||||
totalPrice,
|
|
||||||
wPayOrAPay,
|
|
||||||
pointsPrice
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!confirmResponse.ok || confirmPayload.resultCode !== 0) {
|
|
||||||
return fail(confirmPayload.message || '确认购买推送失败', 502, 'BRIDGE_CONFIRM_PURCHASE_FAILED')
|
|
||||||
}
|
|
||||||
|
|
||||||
const { response: purchaseResultResponse, payload: purchaseResultPayload } = await bridgeFetchWithAuth<BridgePayResponse>(
|
|
||||||
supplierId,
|
|
||||||
'/pointOrder/purchaseResult',
|
|
||||||
{
|
|
||||||
orderId,
|
|
||||||
totalPrice,
|
|
||||||
wPayOrAPay,
|
|
||||||
pointsPrice,
|
|
||||||
purchaseResult: true,
|
|
||||||
message: '支付成功'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!purchaseResultResponse.ok || purchaseResultPayload.resultCode !== 0) {
|
|
||||||
return fail(purchaseResultPayload.message || '支付结果通知推送失败', 502, 'BRIDGE_PURCHASE_RESULT_FAILED')
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
await prisma.$transaction(async tx => {
|
|
||||||
await tx.supplierOrderMain.update({
|
|
||||||
where: { orderId },
|
|
||||||
data: {
|
|
||||||
isSplit: ORDER_SPLIT_STATE.SPLIT,
|
|
||||||
splitTime: new Date(),
|
|
||||||
updateTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await tx.supplierOrderSub.updateMany({
|
|
||||||
where: { mainOrderId: orderId, deleted: 0 },
|
|
||||||
data: {
|
|
||||||
orderState: ORDER_STATE.IN_PROGRESS,
|
|
||||||
updateTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const { response: splitResponse, payload: splitPayload } = await bridgeFetchWithAuth<BridgePayResponse>(
|
|
||||||
supplierId,
|
|
||||||
'/pointOrder/pushOrderInfoMsg',
|
|
||||||
{
|
|
||||||
orderType: ORDER_EVENT_TYPE.SPLIT_SUB_ORDER,
|
|
||||||
orderId: subOrderIds,
|
|
||||||
mainOrderId: orderId,
|
|
||||||
userId: order.userId,
|
|
||||||
time: Date.now()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!splitResponse.ok || splitPayload.resultCode !== 0) {
|
|
||||||
return fail(splitPayload.message || '推送拆单信息失败', 502, 'BRIDGE_SPLIT_ORDER_FAILED')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok({
|
|
||||||
orderId,
|
|
||||||
subOrderIds,
|
|
||||||
totalPrice,
|
|
||||||
wPayOrAPay,
|
|
||||||
pointsPrice
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,310 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { orderPushEventController as default } from '../controllers/order.controller.js'
|
||||||
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'
|
|
||||||
import {
|
|
||||||
ORDER_EVENT_TYPE,
|
|
||||||
ORDER_OPERATE_TYPE,
|
|
||||||
ORDER_PUSH_STATUS,
|
|
||||||
ORDER_STATE
|
|
||||||
} from '../utils/order-state.js'
|
|
||||||
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
|
||||||
|
|
||||||
type OrderPushEventRequest = {
|
|
||||||
supplierId?: string
|
|
||||||
userId?: string
|
|
||||||
orderType?: number
|
|
||||||
orderId?: string
|
|
||||||
mainOrderId?: string
|
|
||||||
subOrderId?: string
|
|
||||||
time?: number
|
|
||||||
amount?: number
|
|
||||||
reason?: string
|
|
||||||
settlementId?: string
|
|
||||||
invoiceBizId?: string
|
|
||||||
invoiceCode?: string
|
|
||||||
invoiceIds?: string
|
|
||||||
orderIds?: string
|
|
||||||
fileId?: string
|
|
||||||
filePath?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type BridgeOrderResponse = {
|
|
||||||
resultCode?: number
|
|
||||||
message?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const PUSH_API_PATH = '/pointOrder/pushOrderInfoMsg'
|
|
||||||
const supportedOrderTypes = new Set<number>([
|
|
||||||
ORDER_EVENT_TYPE.RETURN_ORDER,
|
|
||||||
ORDER_EVENT_TYPE.EXCHANGE_ORDER,
|
|
||||||
ORDER_EVENT_TYPE.CONFIRM_RECEIVE,
|
|
||||||
ORDER_EVENT_TYPE.INVOICE_INFO
|
|
||||||
])
|
|
||||||
|
|
||||||
const splitIds = (value?: string) => String(value || '')
|
|
||||||
.split(',')
|
|
||||||
.map(item => item.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
|
|
||||||
const eventState = (orderType: number) => {
|
|
||||||
if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER) return ORDER_STATE.REFUNDED
|
|
||||||
if (orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) return ORDER_STATE.EXCHANGE
|
|
||||||
if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE) return ORDER_STATE.COMPLETED
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const operateType = (orderType: number) => {
|
|
||||||
if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER) return ORDER_OPERATE_TYPE.REFUND
|
|
||||||
if (orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) return ORDER_OPERATE_TYPE.EXCHANGE
|
|
||||||
if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE) return ORDER_OPERATE_TYPE.COMPLETE
|
|
||||||
if (orderType === ORDER_EVENT_TYPE.INVOICE_INFO) return ORDER_OPERATE_TYPE.INVOICE_INFO
|
|
||||||
return orderType
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildBridgeBody = (body: OrderPushEventRequest) => ({
|
|
||||||
supplierId: String(body.supplierId || '').trim(),
|
|
||||||
orderType: Number(body.orderType),
|
|
||||||
orderId: String(body.orderId || '').trim(),
|
|
||||||
mainOrderId: String(body.mainOrderId || '').trim(),
|
|
||||||
userId: String(body.userId || '').trim(),
|
|
||||||
time: Number(body.time || Date.now())
|
|
||||||
})
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
const body = await readJson<OrderPushEventRequest>(request)
|
|
||||||
const supplierId = String(body.supplierId || '').trim()
|
|
||||||
const userId = String(body.userId || '').trim()
|
|
||||||
const orderType = Number(body.orderType)
|
|
||||||
const orderId = String(body.orderId || '').trim()
|
|
||||||
const mainOrderId = String(body.mainOrderId || '').trim()
|
|
||||||
|
|
||||||
if (!supplierId || !userId || !orderType || !orderId) {
|
|
||||||
return fail('供应商、用户或订单事件信息不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!supportedOrderTypes.has(orderType)) {
|
|
||||||
return fail('暂不支持该订单事件类型', 400, 'ORDER_EVENT_NOT_SUPPORTED')
|
|
||||||
}
|
|
||||||
|
|
||||||
const bridgeBody = buildBridgeBody(body)
|
|
||||||
const now = new Date()
|
|
||||||
let pushLogId = 0
|
|
||||||
let affectedSubOrderIds: string[] = []
|
|
||||||
|
|
||||||
await prisma.$transaction(async tx => {
|
|
||||||
const mainOrder = mainOrderId
|
|
||||||
? await tx.supplierOrderMain.findFirst({
|
|
||||||
where: { orderId: mainOrderId, supplierId, deleted: 0 }
|
|
||||||
})
|
|
||||||
: null
|
|
||||||
|
|
||||||
const explicitSubIds = splitIds(body.subOrderId || '')
|
|
||||||
const orderIds = splitIds(orderId)
|
|
||||||
const subOrdersByOrderId = await tx.supplierOrderSub.findMany({
|
|
||||||
where: {
|
|
||||||
supplierId,
|
|
||||||
deleted: 0,
|
|
||||||
OR: [
|
|
||||||
{ orderId: { in: orderIds } },
|
|
||||||
...(explicitSubIds.length ? [{ orderId: { in: explicitSubIds } }] : []),
|
|
||||||
...(mainOrderId ? [{ mainOrderId }] : [])
|
|
||||||
]
|
|
||||||
},
|
|
||||||
orderBy: { createAt: 'asc' }
|
|
||||||
})
|
|
||||||
|
|
||||||
const directlyMatched = subOrdersByOrderId.filter(item => orderIds.includes(item.orderId) || explicitSubIds.includes(item.orderId))
|
|
||||||
const mainOrderSubs = mainOrderId ? subOrdersByOrderId.filter(item => item.mainOrderId === mainOrderId) : []
|
|
||||||
const targetSubOrders = directlyMatched.length ? directlyMatched : mainOrderSubs.length === 1 ? mainOrderSubs : []
|
|
||||||
affectedSubOrderIds = targetSubOrders.map(item => item.orderId)
|
|
||||||
|
|
||||||
const nextState = eventState(orderType)
|
|
||||||
if (nextState !== null && targetSubOrders.length) {
|
|
||||||
await tx.supplierOrderSub.updateMany({
|
|
||||||
where: {
|
|
||||||
orderId: { in: affectedSubOrderIds },
|
|
||||||
supplierId,
|
|
||||||
deleted: 0
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
orderState: nextState,
|
|
||||||
orderFinishTime: orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE ? now : undefined,
|
|
||||||
completeTime: orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE ? now : undefined,
|
|
||||||
updateTime: now
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await tx.supplierOrderOperateLog.createMany({
|
|
||||||
data: targetSubOrders.map(subOrder => ({
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
mainOrderId: subOrder.mainOrderId,
|
|
||||||
subOrderId: subOrder.orderId,
|
|
||||||
operateType: operateType(orderType),
|
|
||||||
beforeState: subOrder.orderState,
|
|
||||||
afterState: nextState,
|
|
||||||
remark: body.reason || null,
|
|
||||||
operator: 'system'
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER || orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) {
|
|
||||||
await tx.supplierRefundExchange.upsert({
|
|
||||||
where: { serviceOrderId: orderId },
|
|
||||||
update: {
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null,
|
|
||||||
subOrderId: targetSubOrders[0]?.orderId || null,
|
|
||||||
serviceType: orderType,
|
|
||||||
serviceState: 1,
|
|
||||||
amount: Number(body.amount || targetSubOrders[0]?.totalPrice || 0),
|
|
||||||
reason: body.reason || null,
|
|
||||||
requestBody: body,
|
|
||||||
deleted: 0
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
serviceOrderId: orderId,
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null,
|
|
||||||
subOrderId: targetSubOrders[0]?.orderId || null,
|
|
||||||
serviceType: orderType,
|
|
||||||
serviceState: 1,
|
|
||||||
amount: Number(body.amount || targetSubOrders[0]?.totalPrice || 0),
|
|
||||||
reason: body.reason || null,
|
|
||||||
requestBody: body
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orderType === ORDER_EVENT_TYPE.INVOICE_INFO) {
|
|
||||||
const invoiceBizId = String(body.invoiceBizId || body.invoiceCode || orderId).trim()
|
|
||||||
await tx.supplierInvoiceInfo.upsert({
|
|
||||||
where: { invoiceBizId },
|
|
||||||
update: {
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
mainOrderId: mainOrderId || null,
|
|
||||||
subOrderId: targetSubOrders[0]?.orderId || null,
|
|
||||||
settlementId: body.settlementId || null,
|
|
||||||
invoiceCode: body.invoiceCode || null,
|
|
||||||
invoiceIds: body.invoiceIds || orderId,
|
|
||||||
orderIds: body.orderIds || mainOrderId || targetSubOrders[0]?.orderId || null,
|
|
||||||
invoiceStatus: 2,
|
|
||||||
fileId: body.fileId || null,
|
|
||||||
filePath: body.filePath || null,
|
|
||||||
requestBody: body,
|
|
||||||
deleted: 0
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
invoiceBizId,
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
mainOrderId: mainOrderId || null,
|
|
||||||
subOrderId: targetSubOrders[0]?.orderId || null,
|
|
||||||
settlementId: body.settlementId || null,
|
|
||||||
invoiceCode: body.invoiceCode || null,
|
|
||||||
invoiceIds: body.invoiceIds || orderId,
|
|
||||||
orderIds: body.orderIds || mainOrderId || targetSubOrders[0]?.orderId || null,
|
|
||||||
invoiceStatus: 2,
|
|
||||||
fileId: body.fileId || null,
|
|
||||||
filePath: body.filePath || null,
|
|
||||||
requestBody: body
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (body.invoiceCode && mainOrder) {
|
|
||||||
await tx.supplierOrderMain.update({
|
|
||||||
where: { orderId: mainOrder.orderId },
|
|
||||||
data: { invoiceCode: body.invoiceCode, updateTime: now }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE && mainOrderId) {
|
|
||||||
const unfinishedCount = await tx.supplierOrderSub.count({
|
|
||||||
where: {
|
|
||||||
mainOrderId,
|
|
||||||
deleted: 0,
|
|
||||||
orderState: { notIn: [ORDER_STATE.COMPLETED, ORDER_STATE.CANCELLED, ORDER_STATE.REFUNDED] }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (unfinishedCount === 0) {
|
|
||||||
await tx.supplierOrderMain.update({
|
|
||||||
where: { orderId: mainOrderId },
|
|
||||||
data: {
|
|
||||||
orderState: ORDER_STATE.COMPLETED,
|
|
||||||
completeTime: now,
|
|
||||||
updateTime: now
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const pushLog = await tx.supplierOrderPushLog.create({
|
|
||||||
data: {
|
|
||||||
supplierId,
|
|
||||||
userId,
|
|
||||||
mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null,
|
|
||||||
orderId,
|
|
||||||
orderType,
|
|
||||||
apiPath: PUSH_API_PATH,
|
|
||||||
requestBody: bridgeBody,
|
|
||||||
pushStatus: ORDER_PUSH_STATUS.PENDING
|
|
||||||
}
|
|
||||||
})
|
|
||||||
pushLogId = pushLog.id
|
|
||||||
})
|
|
||||||
|
|
||||||
let bridgeResponse: Response | null = null
|
|
||||||
let bridgePayload: BridgeOrderResponse = {}
|
|
||||||
try {
|
|
||||||
const result = await bridgeFetchWithAuth<BridgeOrderResponse>(supplierId, PUSH_API_PATH, bridgeBody)
|
|
||||||
bridgeResponse = result.response
|
|
||||||
bridgePayload = result.payload
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : '订单事件推送失败'
|
|
||||||
await prisma.supplierOrderPushLog.update({
|
|
||||||
where: { id: pushLogId },
|
|
||||||
data: {
|
|
||||||
pushStatus: ORDER_PUSH_STATUS.FAILED,
|
|
||||||
errorMessage: message,
|
|
||||||
lastPushTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return ok({ localSaved: true, pushSuccess: false, message, affectedSubOrderIds })
|
|
||||||
}
|
|
||||||
|
|
||||||
const pushSuccess = bridgeResponse.ok && bridgePayload.resultCode === 0
|
|
||||||
|
|
||||||
await prisma.supplierOrderPushLog.update({
|
|
||||||
where: { id: pushLogId },
|
|
||||||
data: {
|
|
||||||
responseBody: bridgePayload,
|
|
||||||
httpStatus: bridgeResponse.status,
|
|
||||||
resultCode: bridgePayload.resultCode,
|
|
||||||
pushStatus: pushSuccess ? ORDER_PUSH_STATUS.SUCCESS : ORDER_PUSH_STATUS.FAILED,
|
|
||||||
errorMessage: pushSuccess ? null : bridgePayload.message || '订单事件推送失败',
|
|
||||||
lastPushTime: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return ok({
|
|
||||||
localSaved: true,
|
|
||||||
pushSuccess,
|
|
||||||
orderType,
|
|
||||||
orderId,
|
|
||||||
mainOrderId,
|
|
||||||
affectedSubOrderIds,
|
|
||||||
message: pushSuccess ? '订单事件已保存并推送成功' : bridgePayload.message || '订单事件已保存,推送失败'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { productDetailController as default } from '../controllers/product.controller.js'
|
||||||
import { guardMethod } from '../utils/method.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
|
|
||||||
|
|
||||||
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,70 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { productListController as default } from '../controllers/product.controller.js'
|
||||||
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
|
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { supplierDebugTokensController as default } from '../controllers/supplier.controller.js'
|
||||||
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'
|
|
||||||
import {
|
|
||||||
findSupplierByIdentification,
|
|
||||||
getBridgeApiBase,
|
|
||||||
getBridgeToken
|
|
||||||
} from '../utils/bridge-auth.js'
|
|
||||||
import { sha256 } from '../utils/crypto.js'
|
|
||||||
import { createSupplierOpenSignature } from '../utils/supplier-open-crypto.js'
|
|
||||||
|
|
||||||
type SupplierDebugTokensRequest = {
|
|
||||||
supplierIdentification?: string
|
|
||||||
forceRefreshZhghToken?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
const body = await readJson<SupplierDebugTokensRequest>(request)
|
|
||||||
const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase()
|
|
||||||
|
|
||||||
if (!supplierIdentification) {
|
|
||||||
return fail('供应商唯一标识不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error)
|
|
||||||
|
|
||||||
if (supplier instanceof Error) {
|
|
||||||
return fail(supplier.message || '供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED')
|
|
||||||
}
|
|
||||||
|
|
||||||
const timestamp = Date.now()
|
|
||||||
const orderType = 2
|
|
||||||
const orderId = 'SUB_ORDER_ID_1,SUB_ORDER_ID_2'
|
|
||||||
const mainOrderId = 'MAIN_ORDER_ID'
|
|
||||||
const userId = 'USER_ID'
|
|
||||||
const supplierOpenToken = createSupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp)
|
|
||||||
const zhghBridgeToken = await getBridgeToken(supplier.supplierId, Boolean(body.forceRefreshZhghToken))
|
|
||||||
|
|
||||||
return ok({
|
|
||||||
supplierOpen: {
|
|
||||||
name: 'supplierOpenToken',
|
|
||||||
usage: '调用供应商开放接口 /supplierOpenApi/pointOrder/queryOrderInfo',
|
|
||||||
supplierId: supplier.supplierId,
|
|
||||||
clientId: supplier.clientId,
|
|
||||||
timestamp,
|
|
||||||
token: supplierOpenToken,
|
|
||||||
orderType,
|
|
||||||
orderId,
|
|
||||||
queryOrderInfoBody: {
|
|
||||||
supplierId: supplier.supplierId,
|
|
||||||
token: supplierOpenToken,
|
|
||||||
orderType,
|
|
||||||
orderId,
|
|
||||||
timestamp
|
|
||||||
}
|
|
||||||
},
|
|
||||||
zhghBridge: {
|
|
||||||
name: 'zhghBridgeToken',
|
|
||||||
usage: '调用 zhgh /open/pointOrder/* 桥接接口',
|
|
||||||
apiBase: getBridgeApiBase(),
|
|
||||||
supplierId: supplier.supplierId,
|
|
||||||
clientId: supplier.clientId,
|
|
||||||
timestamp,
|
|
||||||
sendSha256: sha256(`${supplier.clientId}${supplier.clientSecret}${timestamp}`),
|
|
||||||
token: zhghBridgeToken,
|
|
||||||
splitOrderBody: {
|
|
||||||
supplierId: supplier.supplierId,
|
|
||||||
token: zhghBridgeToken,
|
|
||||||
orderType,
|
|
||||||
orderId,
|
|
||||||
mainOrderId,
|
|
||||||
userId,
|
|
||||||
time: timestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { supplierInfoController as default } from '../controllers/supplier.controller.js'
|
||||||
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 SupplierInfoRequest = {
|
|
||||||
supplierIdentification?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
const body = await readJson<SupplierInfoRequest>(request)
|
|
||||||
const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase()
|
|
||||||
|
|
||||||
if (!supplierIdentification) {
|
|
||||||
return fail('供应商唯一标识不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
const supplier = await prisma.supplierInfo.findUnique({
|
|
||||||
where: { supplierIdentification }
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!supplier || supplier.deleted === 1) {
|
|
||||||
return fail('供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED')
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok({
|
|
||||||
id: supplier.id,
|
|
||||||
supplierIdentification: supplier.supplierIdentification,
|
|
||||||
supplierId: supplier.supplierId,
|
|
||||||
supplierName: supplier.supplierName
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,143 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { supplierOrderInfoController as default } from '../controllers/order.controller.js'
|
||||||
import { guardMethod } from '../utils/method.js'
|
|
||||||
import { readJson } from '../utils/request.js'
|
|
||||||
import { prisma } from '../utils/prisma.js'
|
|
||||||
import { withErrorBoundary } from '../utils/handler.js'
|
|
||||||
import { sm4CbcDecrypt, verifySupplierOpenSignature } from '../utils/supplier-open-crypto.js'
|
|
||||||
|
|
||||||
type SupplierOrderQueryRequest = {
|
|
||||||
supplierId?: string
|
|
||||||
token?: string
|
|
||||||
orderType?: number
|
|
||||||
orderId?: string
|
|
||||||
timestamp?: number
|
|
||||||
AplParmObjct?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const success = (orderInfo: unknown[]) => Response.json({
|
|
||||||
resultCode: 0,
|
|
||||||
message: '成功',
|
|
||||||
orderInfo
|
|
||||||
})
|
|
||||||
|
|
||||||
const fail = (resultCode: number, message: string) => Response.json({
|
|
||||||
resultCode,
|
|
||||||
message,
|
|
||||||
orderInfo: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const parseBody = (body: SupplierOrderQueryRequest) => {
|
|
||||||
if (!body.AplParmObjct) return body
|
|
||||||
|
|
||||||
const keyHex = process.env.SUPPLIER_OPEN_SM4_KEY_HEX || process.env.SM4CBC_KEY_HEX || ''
|
|
||||||
const ivHex = process.env.SUPPLIER_OPEN_SM4_IV_HEX || process.env.SM4CBC_IV_HEX || ''
|
|
||||||
if (!keyHex || !ivHex) {
|
|
||||||
throw new Error('供应商开放接口SM4配置缺失')
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.parse(sm4CbcDecrypt(body.AplParmObjct, keyHex, ivHex)) as SupplierOrderQueryRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
const toText = (date?: Date | null) => {
|
|
||||||
if (!date) return null
|
|
||||||
const pad = (value: number) => String(value).padStart(2, '0')
|
|
||||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const toMoney = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => Number(value || 0).toFixed(2)
|
|
||||||
|
|
||||||
const productSku = (product: { sku: string; subOrderId: string; name: string }) => {
|
|
||||||
if (product.sku && product.sku !== '[object Object]') {
|
|
||||||
return product.sku
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${product.subOrderId}-${product.name || 'SKU'}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
let body: SupplierOrderQueryRequest
|
|
||||||
try {
|
|
||||||
body = parseBody(await readJson<SupplierOrderQueryRequest>(request))
|
|
||||||
} catch (error) {
|
|
||||||
return fail(400, error instanceof Error ? error.message : '请求参数解密失败')
|
|
||||||
}
|
|
||||||
|
|
||||||
const supplierId = String(body.supplierId || '').trim()
|
|
||||||
const token = String(body.token || '').trim()
|
|
||||||
const orderIds = String(body.orderId || '')
|
|
||||||
.split(',')
|
|
||||||
.map(item => item.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
const timestamp = Number(body.timestamp || 0)
|
|
||||||
|
|
||||||
if (!supplierId || !token || !timestamp || !orderIds.length) {
|
|
||||||
return fail(400, '供应商、订单或签名信息不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
const supplier = await prisma.supplierInfo.findFirst({
|
|
||||||
where: { supplierId, deleted: 0 }
|
|
||||||
})
|
|
||||||
if (!supplier) {
|
|
||||||
return fail(404, '供应商信息不存在')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!verifySupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp, token)) {
|
|
||||||
return fail(401, '签名无效或已过期')
|
|
||||||
}
|
|
||||||
|
|
||||||
const orders = await prisma.supplierOrderSub.findMany({
|
|
||||||
where: {
|
|
||||||
supplierId,
|
|
||||||
orderId: { in: orderIds },
|
|
||||||
deleted: 0
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
products: {
|
|
||||||
where: { deleted: 0 },
|
|
||||||
orderBy: { createAt: 'asc' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
orderBy: { createAt: 'asc' }
|
|
||||||
})
|
|
||||||
|
|
||||||
const orderInfo = orders.map(order => ({
|
|
||||||
orderId: order.orderId,
|
|
||||||
orderNumberId: order.orderNumberId,
|
|
||||||
supplierId: order.supplierId,
|
|
||||||
token: order.token,
|
|
||||||
aplName: order.aplName,
|
|
||||||
createTime: toText(order.createTime),
|
|
||||||
updateTime: toText(order.updateTime),
|
|
||||||
deliveryTime: toText(order.deliveryTime),
|
|
||||||
orderFinishTime: toText(order.orderFinishTime),
|
|
||||||
completeTime: toText(order.completeTime),
|
|
||||||
deliveryStatus: order.deliveryStatus,
|
|
||||||
freightCost: toMoney(order.freightCost),
|
|
||||||
wPayOrAPayFreightCost: toMoney(order.wPayOrAPayFreightCost),
|
|
||||||
refund: toMoney(order.refund),
|
|
||||||
wPayOrAPayRefund: toMoney(order.wPayOrAPayRefund),
|
|
||||||
logisticsOrderId: order.logisticsOrderId,
|
|
||||||
crrgBsnNm: order.crrgBsnNm,
|
|
||||||
orderState: order.orderState,
|
|
||||||
totalPrice: toMoney(order.totalPrice),
|
|
||||||
wPayOrAPay: toMoney(order.wPayOrAPay),
|
|
||||||
pointsPrice: toMoney(order.pointsPrice),
|
|
||||||
invoiceCode: order.invoiceCode,
|
|
||||||
orderProInfos: order.products.map(product => ({
|
|
||||||
sku: productSku(product),
|
|
||||||
number: product.number,
|
|
||||||
price: toMoney(product.price),
|
|
||||||
name: product.name,
|
|
||||||
taxRate: Number(product.taxRate || 0),
|
|
||||||
picInfo: Array.isArray(product.picInfo) ? product.picInfo : product.productImgUrl ? [product.productImgUrl] : [],
|
|
||||||
fileId: product.fileId,
|
|
||||||
filePath: product.filePath
|
|
||||||
}))
|
|
||||||
}))
|
|
||||||
|
|
||||||
return success(orderInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { supplierPointsController as default } from '../controllers/supplier.controller.js'
|
||||||
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'
|
|
||||||
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
|
||||||
|
|
||||||
type SupplierPointsRequest = {
|
|
||||||
supplierId?: string
|
|
||||||
userId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type BridgePointsResponse = {
|
|
||||||
resultCode?: number
|
|
||||||
message?: string
|
|
||||||
userPoints?: number | string
|
|
||||||
}
|
|
||||||
|
|
||||||
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 userId = String(body.userId || '').trim()
|
|
||||||
|
|
||||||
if (!supplierId || !userId) {
|
|
||||||
return fail('供应商或用户信息不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
const { response, payload: pointsPayload } = await bridgeFetchWithAuth<BridgePointsResponse>(
|
|
||||||
supplierId,
|
|
||||||
'/pointOrder/queryPoints',
|
|
||||||
{ userId }
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!response.ok || pointsPayload.resultCode !== 0) {
|
|
||||||
return fail(pointsPayload.message || '查询用户积分失败', 502, 'BRIDGE_POINTS_FAILED')
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok({
|
|
||||||
userPoints: Number(pointsPayload.userPoints || 0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { supplierTokenController as default } from '../controllers/supplier.controller.js'
|
||||||
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'
|
|
||||||
import { findSupplierByIdentification, warmBridgeToken } from '../utils/bridge-auth.js'
|
|
||||||
|
|
||||||
type SupplierTokenRequest = {
|
|
||||||
userId?: string
|
|
||||||
supplierIdentification?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
const body = await readJson<SupplierTokenRequest>(request)
|
|
||||||
const userId = String(body.userId || '').trim()
|
|
||||||
const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase()
|
|
||||||
|
|
||||||
if (!userId || !supplierIdentification) {
|
|
||||||
return fail('用户信息和供应商唯一标识不能为空')
|
|
||||||
}
|
|
||||||
|
|
||||||
const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error)
|
|
||||||
|
|
||||||
if (supplier instanceof Error) {
|
|
||||||
return fail(supplier.message || '供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED')
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await warmBridgeToken(supplier)
|
|
||||||
} catch (error) {
|
|
||||||
return fail(error instanceof Error ? error.message : '获取平台授权令牌失败', 502, 'BRIDGE_TOKEN_FAILED')
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok({
|
|
||||||
supplierIdentification,
|
|
||||||
supplierId: supplier.supplierId,
|
|
||||||
supplierName: supplier.supplierName,
|
|
||||||
userId
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { uploadController as default } from '../controllers/basic.controller.js'
|
||||||
import { guardMethod } from '../utils/method.js'
|
|
||||||
import { ok } from '../utils/http.js'
|
|
||||||
import { withErrorBoundary } from '../utils/handler.js'
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
return ok({ message: '上传接口待实现' })
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1 @@
|
|||||||
import type { Context } from '../utils/handler.js'
|
export { userInfoController as default } from '../controllers/basic.controller.js'
|
||||||
import { guardMethod } from '../utils/method.js'
|
|
||||||
import { ok } from '../utils/http.js'
|
|
||||||
import { withErrorBoundary } from '../utils/handler.js'
|
|
||||||
|
|
||||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
|
||||||
const methodResponse = guardMethod(request, ['GET'])
|
|
||||||
if (methodResponse) return methodResponse
|
|
||||||
|
|
||||||
return ok({ message: '用户信息接口待实现' })
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,48 +3,54 @@ import Koa from 'koa'
|
|||||||
import Router from '@koa/router'
|
import Router from '@koa/router'
|
||||||
import bodyParser from 'koa-bodyparser'
|
import bodyParser from 'koa-bodyparser'
|
||||||
import type { FunctionHandler } from './utils/handler.js'
|
import type { FunctionHandler } from './utils/handler.js'
|
||||||
import cartAdd from './functions/cart-add.js'
|
import { logger } from './utils/logger.js'
|
||||||
import login from './functions/login.js'
|
import {
|
||||||
import orderCancelMain from './functions/order-cancel-main.js'
|
cartAddController,
|
||||||
import orderCancelPaidMain from './functions/order-cancel-paid-main.js'
|
loginController,
|
||||||
import orderCancelSub from './functions/order-cancel-sub.js'
|
uploadController,
|
||||||
import orderCreate from './functions/order-create.js'
|
userInfoController
|
||||||
import orderList from './functions/order-list.js'
|
} from './controllers/basic.controller.js'
|
||||||
import orderPay from './functions/order-pay.js'
|
import {
|
||||||
import orderPushEvent from './functions/order-push-event.js'
|
orderCancelMainController,
|
||||||
import productDetail from './functions/product-detail.js'
|
orderCancelPaidMainController,
|
||||||
import productList from './functions/product-list.js'
|
orderCancelSubController,
|
||||||
import supplierDebugTokens from './functions/supplier-debug-tokens.js'
|
orderCreateController,
|
||||||
import supplierInfo from './functions/supplier-info.js'
|
orderListController,
|
||||||
import supplierOrderInfo from './functions/supplier-order-info.js'
|
orderPayController,
|
||||||
import supplierPoints from './functions/supplier-points.js'
|
orderPushEventController,
|
||||||
import supplierToken from './functions/supplier-token.js'
|
supplierOrderInfoController
|
||||||
import upload from './functions/upload.js'
|
} from './controllers/order.controller.js'
|
||||||
import userInfo from './functions/user-info.js'
|
import { productDetailController, productListController } from './controllers/product.controller.js'
|
||||||
|
import {
|
||||||
|
supplierDebugTokensController,
|
||||||
|
supplierInfoController,
|
||||||
|
supplierPointsController,
|
||||||
|
supplierTokenController
|
||||||
|
} from './controllers/supplier.controller.js'
|
||||||
|
|
||||||
const app = new Koa()
|
const app = new Koa()
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
const port = Number(process.env.PORT || 8888)
|
const port = Number(process.env.PORT || 8888)
|
||||||
|
|
||||||
const handlers: Record<string, FunctionHandler> = {
|
const handlers: Record<string, FunctionHandler> = {
|
||||||
'cart-add': cartAdd,
|
'cart-add': cartAddController,
|
||||||
login,
|
login: loginController,
|
||||||
'order-cancel-main': orderCancelMain,
|
'order-cancel-main': orderCancelMainController,
|
||||||
'order-cancel-paid-main': orderCancelPaidMain,
|
'order-cancel-paid-main': orderCancelPaidMainController,
|
||||||
'order-cancel-sub': orderCancelSub,
|
'order-cancel-sub': orderCancelSubController,
|
||||||
'order-create': orderCreate,
|
'order-create': orderCreateController,
|
||||||
'order-list': orderList,
|
'order-list': orderListController,
|
||||||
'order-pay': orderPay,
|
'order-pay': orderPayController,
|
||||||
'order-push-event': orderPushEvent,
|
'order-push-event': orderPushEventController,
|
||||||
'product-detail': productDetail,
|
'product-detail': productDetailController,
|
||||||
'product-list': productList,
|
'product-list': productListController,
|
||||||
'supplier-debug-tokens': supplierDebugTokens,
|
'supplier-debug-tokens': supplierDebugTokensController,
|
||||||
'supplier-info': supplierInfo,
|
'supplier-info': supplierInfoController,
|
||||||
'supplier-order-info': supplierOrderInfo,
|
'supplier-order-info': supplierOrderInfoController,
|
||||||
'supplier-points': supplierPoints,
|
'supplier-points': supplierPointsController,
|
||||||
'supplier-token': supplierToken,
|
'supplier-token': supplierTokenController,
|
||||||
upload,
|
upload: uploadController,
|
||||||
'user-info': userInfo
|
'user-info': userInfoController
|
||||||
}
|
}
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
@@ -75,6 +81,7 @@ const writeResponse = async (ctx: Koa.Context, response: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = (name: string) => async (ctx: Koa.Context) => {
|
const dispatch = (name: string) => async (ctx: Koa.Context) => {
|
||||||
|
logger.info('dispatch api request', { name, method: ctx.method, path: ctx.path })
|
||||||
const handler = handlers[name]
|
const handler = handlers[name]
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
ctx.status = 404
|
ctx.status = 404
|
||||||
@@ -103,5 +110,5 @@ app.use(router.routes())
|
|||||||
app.use(router.allowedMethods())
|
app.use(router.allowedMethods())
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`supplied-service Koa server running at http://localhost:${port}`)
|
logger.info(`Koa server running at http://localhost:${port}`)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { logger } from '../utils/logger.js'
|
||||||
|
import { serviceOk } from '../types/service-result.js'
|
||||||
|
|
||||||
|
export const basicService = {
|
||||||
|
login() {
|
||||||
|
logger.info('login placeholder called')
|
||||||
|
return serviceOk({ message: '登录接口待实现' })
|
||||||
|
},
|
||||||
|
|
||||||
|
addCart() {
|
||||||
|
logger.info('cart add placeholder called')
|
||||||
|
return serviceOk({ message: '加入购物车接口待实现' })
|
||||||
|
},
|
||||||
|
|
||||||
|
upload() {
|
||||||
|
logger.info('upload placeholder called')
|
||||||
|
return serviceOk({ message: '上传接口待实现' })
|
||||||
|
},
|
||||||
|
|
||||||
|
userInfo() {
|
||||||
|
logger.info('user info placeholder called')
|
||||||
|
return serviceOk({ message: '用户信息接口待实现' })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
import { ORDER_EVENT_TYPE, ORDER_SPLIT_STATE, ORDER_STATE } from '../constants/order.js'
|
||||||
|
import { orderDao } from '../dao/order.dao.js'
|
||||||
|
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
||||||
|
import { logger } from '../utils/logger.js'
|
||||||
|
import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||||
|
|
||||||
|
type BridgeOrderResponse = {
|
||||||
|
resultCode?: number
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancelMainInput = {
|
||||||
|
supplierId: string
|
||||||
|
userId: string
|
||||||
|
orderId: string
|
||||||
|
mainOrderId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancelSubInput = {
|
||||||
|
supplierId: string
|
||||||
|
userId: string
|
||||||
|
orderId: string
|
||||||
|
mainOrderId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PayOrderInput = {
|
||||||
|
supplierId: string
|
||||||
|
orderId: string
|
||||||
|
totalPrice: number
|
||||||
|
wPayOrAPay: number
|
||||||
|
pointsPrice: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const orderActionService = {
|
||||||
|
async cancelPendingMain(input: CancelMainInput) {
|
||||||
|
const { supplierId, userId, mainOrderId } = input
|
||||||
|
const orderId = input.orderId || mainOrderId
|
||||||
|
logger.info('cancel pending main order', { supplierId, userId, mainOrderId })
|
||||||
|
|
||||||
|
if (!supplierId || !userId || !mainOrderId) {
|
||||||
|
return serviceFail('供应商、用户或订单信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = await orderDao.findMainOrder(supplierId, userId, mainOrderId)
|
||||||
|
if (!order) {
|
||||||
|
return serviceFail('主单不存在或已取消', 404, 'MAIN_ORDER_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.orderState !== ORDER_STATE.PENDING_PAY) {
|
||||||
|
return serviceFail('已支付主单不能通过未支付取消接口取消', 400, 'MAIN_ORDER_ALREADY_PAID')
|
||||||
|
}
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierOrderMain.update({
|
||||||
|
where: { orderId: mainOrderId },
|
||||||
|
data: { orderState: ORDER_STATE.CANCELLED, updateTime: new Date() }
|
||||||
|
})
|
||||||
|
await tx.supplierOrderSub.updateMany({
|
||||||
|
where: { mainOrderId, supplierId, userId, deleted: 0 },
|
||||||
|
data: { orderState: ORDER_STATE.CANCELLED, updateTime: new Date() }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const { response, payload } = await bridgeFetchWithAuth<BridgeOrderResponse>(supplierId, '/pointOrder/pushOrderInfoMsg', {
|
||||||
|
orderType: ORDER_EVENT_TYPE.CANCEL_MAIN,
|
||||||
|
orderId,
|
||||||
|
mainOrderId,
|
||||||
|
userId,
|
||||||
|
time: Date.now()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok || payload.resultCode !== 0) {
|
||||||
|
return serviceFail(payload.message || '取消主单推送失败', 502, 'BRIDGE_CANCEL_MAIN_FAILED')
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk({ supplierId, orderType: ORDER_EVENT_TYPE.CANCEL_MAIN, orderId, mainOrderId, userId })
|
||||||
|
},
|
||||||
|
|
||||||
|
async cancelPaidMain(input: CancelMainInput) {
|
||||||
|
const { supplierId, userId, mainOrderId } = input
|
||||||
|
const orderId = input.orderId || mainOrderId
|
||||||
|
logger.info('cancel paid unsplit main order', { supplierId, userId, mainOrderId })
|
||||||
|
|
||||||
|
if (!supplierId || !userId || !mainOrderId) {
|
||||||
|
return serviceFail('供应商、用户或订单信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = await orderDao.findMainOrder(supplierId, userId, mainOrderId)
|
||||||
|
if (!order) {
|
||||||
|
return serviceFail('主单不存在或已取消', 404, 'MAIN_ORDER_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.orderState === ORDER_STATE.PENDING_PAY) {
|
||||||
|
return serviceFail('未支付主单请使用未支付取消接口', 400, 'MAIN_ORDER_NOT_PAID')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.orderState !== ORDER_STATE.FINISH_PAY || order.isSplit === ORDER_SPLIT_STATE.SPLIT) {
|
||||||
|
return serviceFail('该主单已拆单,不能按已支付未拆单整单取消', 400, 'MAIN_ORDER_ALREADY_SPLIT')
|
||||||
|
}
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierOrderMain.update({
|
||||||
|
where: { orderId: mainOrderId },
|
||||||
|
data: { orderState: ORDER_STATE.CANCELLED, updateTime: new Date() }
|
||||||
|
})
|
||||||
|
await tx.supplierOrderSub.updateMany({
|
||||||
|
where: { mainOrderId, supplierId, userId, deleted: 0 },
|
||||||
|
data: { orderState: ORDER_STATE.CANCELLED, updateTime: new Date() }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const { response, payload } = await bridgeFetchWithAuth<BridgeOrderResponse>(supplierId, '/pointOrder/pushOrderInfoMsg', {
|
||||||
|
orderType: ORDER_EVENT_TYPE.CANCEL_PAID_NOT_SPLIT_MAIN,
|
||||||
|
orderId,
|
||||||
|
mainOrderId,
|
||||||
|
userId,
|
||||||
|
time: Date.now()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok || payload.resultCode !== 0) {
|
||||||
|
return serviceFail(payload.message || '取消已支付未拆单整单推送失败', 502, 'BRIDGE_CANCEL_PAID_MAIN_FAILED')
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk({ supplierId, orderType: ORDER_EVENT_TYPE.CANCEL_PAID_NOT_SPLIT_MAIN, orderId, mainOrderId, userId })
|
||||||
|
},
|
||||||
|
|
||||||
|
async cancelSub(input: CancelSubInput) {
|
||||||
|
const { supplierId, userId, orderId } = input
|
||||||
|
logger.info('cancel sub order', { supplierId, userId, orderId })
|
||||||
|
|
||||||
|
if (!supplierId || !userId || !orderId) {
|
||||||
|
return serviceFail('供应商、用户或子单信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const subOrder = await orderDao.findSubOrder(supplierId, userId, orderId)
|
||||||
|
if (!subOrder) {
|
||||||
|
return serviceFail('子单不存在或已取消', 404, 'SUB_ORDER_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainOrderId = input.mainOrderId || subOrder.mainOrderId
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierOrderSub.update({
|
||||||
|
where: { orderId },
|
||||||
|
data: { orderState: ORDER_STATE.CANCELLED, updateTime: new Date() }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const { response, payload } = await bridgeFetchWithAuth<BridgeOrderResponse>(supplierId, '/pointOrder/pushOrderInfoMsg', {
|
||||||
|
orderType: ORDER_EVENT_TYPE.CANCEL_SUB,
|
||||||
|
orderId,
|
||||||
|
mainOrderId,
|
||||||
|
userId,
|
||||||
|
time: Date.now()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok || payload.resultCode !== 0) {
|
||||||
|
return serviceFail(payload.message || '取消子单推送失败', 502, 'BRIDGE_CANCEL_SUB_FAILED')
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk({ supplierId, orderType: ORDER_EVENT_TYPE.CANCEL_SUB, orderId, mainOrderId, userId })
|
||||||
|
},
|
||||||
|
|
||||||
|
async payOrder(input: PayOrderInput) {
|
||||||
|
const { supplierId, orderId, totalPrice, wPayOrAPay, pointsPrice } = input
|
||||||
|
logger.info('pay order', { supplierId, orderId, totalPrice, wPayOrAPay, pointsPrice })
|
||||||
|
|
||||||
|
if (!supplierId || !orderId) {
|
||||||
|
return serviceFail('供应商或订单信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalPrice <= 0) {
|
||||||
|
return serviceFail('支付金额必须大于0')
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = await orderDao.findPayingMainOrder(orderId)
|
||||||
|
if (!order || order.deleted === 1) {
|
||||||
|
return serviceFail('待支付订单不存在', 404, 'ORDER_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.orderState !== ORDER_STATE.PENDING_PAY) {
|
||||||
|
logger.warn('order is not payable', { supplierId, orderId, orderState: order.orderState })
|
||||||
|
return serviceFail('当前订单状态不可支付,请刷新订单状态', 400, 'ORDER_NOT_PAYABLE')
|
||||||
|
}
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierOrderMain.update({
|
||||||
|
where: { orderId },
|
||||||
|
data: {
|
||||||
|
orderState: ORDER_STATE.FINISH_PAY,
|
||||||
|
isSplit: ORDER_SPLIT_STATE.NOT_SPLIT,
|
||||||
|
totalPrice,
|
||||||
|
wPayOrAPay,
|
||||||
|
pointsPrice,
|
||||||
|
updateTime: new Date()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await tx.supplierOrderSub.updateMany({
|
||||||
|
where: { mainOrderId: orderId, deleted: 0 },
|
||||||
|
data: { orderState: ORDER_STATE.FINISH_PAY, updateTime: new Date() }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const confirm = await bridgeFetchWithAuth<BridgeOrderResponse>(supplierId, '/pointOrder/confirmPurchase', {
|
||||||
|
orderId,
|
||||||
|
totalPrice,
|
||||||
|
wPayOrAPay,
|
||||||
|
pointsPrice
|
||||||
|
})
|
||||||
|
if (!confirm.response.ok || confirm.payload.resultCode !== 0) {
|
||||||
|
return serviceFail(confirm.payload.message || '确认购买推送失败', 502, 'BRIDGE_CONFIRM_PURCHASE_FAILED')
|
||||||
|
}
|
||||||
|
|
||||||
|
const purchaseResult = await bridgeFetchWithAuth<BridgeOrderResponse>(supplierId, '/pointOrder/purchaseResult', {
|
||||||
|
orderId,
|
||||||
|
totalPrice,
|
||||||
|
wPayOrAPay,
|
||||||
|
pointsPrice,
|
||||||
|
purchaseResult: true,
|
||||||
|
message: '支付成功'
|
||||||
|
})
|
||||||
|
if (!purchaseResult.response.ok || purchaseResult.payload.resultCode !== 0) {
|
||||||
|
return serviceFail(purchaseResult.payload.message || '支付结果通知推送失败', 502, 'BRIDGE_PURCHASE_RESULT_FAILED')
|
||||||
|
}
|
||||||
|
|
||||||
|
const subOrders = await orderDao.findSubOrderIdsByMain(orderId)
|
||||||
|
const subOrderIds = subOrders.map(item => item.orderId).filter(Boolean).join(',')
|
||||||
|
|
||||||
|
if (subOrderIds) {
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierOrderMain.update({
|
||||||
|
where: { orderId },
|
||||||
|
data: { isSplit: ORDER_SPLIT_STATE.SPLIT, splitTime: new Date(), updateTime: new Date() }
|
||||||
|
})
|
||||||
|
await tx.supplierOrderSub.updateMany({
|
||||||
|
where: { mainOrderId: orderId, deleted: 0 },
|
||||||
|
data: { orderState: ORDER_STATE.IN_PROGRESS, updateTime: new Date() }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const split = await bridgeFetchWithAuth<BridgeOrderResponse>(supplierId, '/pointOrder/pushOrderInfoMsg', {
|
||||||
|
orderType: ORDER_EVENT_TYPE.SPLIT_SUB_ORDER,
|
||||||
|
orderId: subOrderIds,
|
||||||
|
mainOrderId: orderId,
|
||||||
|
userId: order.userId,
|
||||||
|
time: Date.now()
|
||||||
|
})
|
||||||
|
if (!split.response.ok || split.payload.resultCode !== 0) {
|
||||||
|
return serviceFail(split.payload.message || '推送拆单信息失败', 502, 'BRIDGE_SPLIT_ORDER_FAILED')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk({ orderId, subOrderIds, totalPrice, wPayOrAPay, pointsPrice })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
import { ORDER_SPLIT_STATE, ORDER_STATE } from '../constants/order.js'
|
||||||
|
import { orderDao } from '../dao/order.dao.js'
|
||||||
|
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
||||||
|
import { logger } from '../utils/logger.js'
|
||||||
|
import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||||
|
|
||||||
|
export type CheckoutGoods = {
|
||||||
|
id?: string | number
|
||||||
|
sku?: string | number | Record<string, unknown>
|
||||||
|
count?: number
|
||||||
|
number?: number
|
||||||
|
price?: string | number
|
||||||
|
name?: string
|
||||||
|
image?: string
|
||||||
|
imageUrl?: string
|
||||||
|
picInfo?: string[]
|
||||||
|
taxRate?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderCreateInput = {
|
||||||
|
supplierId: string
|
||||||
|
userId: string
|
||||||
|
orderNo: string
|
||||||
|
orderNumberId?: string
|
||||||
|
amount?: number
|
||||||
|
goods: CheckoutGoods[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type BridgeOrderResponse = {
|
||||||
|
resultCode?: number
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderProductSnapshot = {
|
||||||
|
sku: string
|
||||||
|
number: number
|
||||||
|
price: number
|
||||||
|
name: string
|
||||||
|
taxRate: number
|
||||||
|
picInfo: string[]
|
||||||
|
fileId: string | null
|
||||||
|
filePath: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
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 toMoney = (value: number) => Number(value.toFixed(2))
|
||||||
|
const createSubOrderId = (orderId: string, index: number) => `${orderId}_${String(index + 1).padStart(3, '0')}`
|
||||||
|
const createSubOrderNumberId = (orderNumberId: string, index: number) => `${orderNumberId}_${String(index + 1).padStart(3, '0')}`
|
||||||
|
|
||||||
|
const toOrderProducts = (goods: CheckoutGoods[]) => goods.map((item, index) => {
|
||||||
|
const image = item.image || item.imageUrl || item.picInfo?.[0] || ''
|
||||||
|
const sku = typeof item.sku === 'object' && item.sku
|
||||||
|
? [item.id, item.sku.color, item.sku.size].filter(Boolean).join('-')
|
||||||
|
: item.sku
|
||||||
|
|
||||||
|
return {
|
||||||
|
sku: String(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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const productSubtotal = (item: OrderProductSnapshot) => toMoney(Number(item.price || 0) * Number(item.number || 0))
|
||||||
|
|
||||||
|
export const orderCreateService = {
|
||||||
|
async createOrder(input: OrderCreateInput) {
|
||||||
|
const supplierId = input.supplierId
|
||||||
|
const userId = input.userId
|
||||||
|
const orderId = input.orderNo
|
||||||
|
const amount = toYuan(input.amount)
|
||||||
|
const goods = Array.isArray(input.goods) ? input.goods : []
|
||||||
|
const orderProInfos = toOrderProducts(goods)
|
||||||
|
logger.info('create pending order', { supplierId, userId, orderId, goodsCount: goods.length, amount })
|
||||||
|
|
||||||
|
if (!supplierId || !userId || !orderId) {
|
||||||
|
return serviceFail('供应商、用户或订单信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
return serviceFail('订单金额必须大于0')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!orderProInfos.length) {
|
||||||
|
return serviceFail('订单商品不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderNumberId = String(input.orderNumberId || createOrderNumberId())
|
||||||
|
const orderTime = nowText()
|
||||||
|
const now = new Date()
|
||||||
|
const subOrders = orderProInfos.map((item, index) => {
|
||||||
|
const subTotal = productSubtotal(item)
|
||||||
|
|
||||||
|
return {
|
||||||
|
orderId: createSubOrderId(orderId, index),
|
||||||
|
orderNumberId: createSubOrderNumberId(orderNumberId, index),
|
||||||
|
product: item,
|
||||||
|
totalPrice: subTotal,
|
||||||
|
pointsPrice: subTotal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
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: ORDER_STATE.PENDING_PAY,
|
||||||
|
totalPrice: amount,
|
||||||
|
wPayOrAPay: 0,
|
||||||
|
pointsPrice: amount,
|
||||||
|
invoiceCode: null,
|
||||||
|
orderProInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
const existingSubOrders = await tx.supplierOrderSub.findMany({
|
||||||
|
where: { mainOrderId: orderId },
|
||||||
|
select: { orderId: true }
|
||||||
|
})
|
||||||
|
const existingSubOrderIds = existingSubOrders.map(item => item.orderId)
|
||||||
|
|
||||||
|
if (existingSubOrderIds.length) {
|
||||||
|
await tx.supplierOrderProduct.deleteMany({ where: { subOrderId: { in: existingSubOrderIds } } })
|
||||||
|
await tx.supplierOrderSub.deleteMany({ where: { mainOrderId: orderId } })
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx.supplierOrderMain.upsert({
|
||||||
|
where: { orderId },
|
||||||
|
update: {
|
||||||
|
orderNumberId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
aplName: orderInfo.aplName,
|
||||||
|
updateTime: now,
|
||||||
|
deliveryStatus: orderInfo.deliveryStatus,
|
||||||
|
freightCost: orderInfo.freightCost,
|
||||||
|
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
|
||||||
|
refund: orderInfo.refund,
|
||||||
|
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
|
||||||
|
orderState: ORDER_STATE.PENDING_PAY,
|
||||||
|
isSplit: ORDER_SPLIT_STATE.NOT_SPLIT,
|
||||||
|
splitTime: null,
|
||||||
|
totalPrice: orderInfo.totalPrice,
|
||||||
|
wPayOrAPay: orderInfo.wPayOrAPay,
|
||||||
|
pointsPrice: orderInfo.pointsPrice,
|
||||||
|
invoiceCode: orderInfo.invoiceCode,
|
||||||
|
deleted: 0
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
orderId,
|
||||||
|
orderNumberId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
aplName: orderInfo.aplName,
|
||||||
|
createTime: now,
|
||||||
|
updateTime: now,
|
||||||
|
deliveryStatus: orderInfo.deliveryStatus,
|
||||||
|
freightCost: orderInfo.freightCost,
|
||||||
|
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
|
||||||
|
refund: orderInfo.refund,
|
||||||
|
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
|
||||||
|
orderState: ORDER_STATE.PENDING_PAY,
|
||||||
|
isSplit: ORDER_SPLIT_STATE.NOT_SPLIT,
|
||||||
|
totalPrice: orderInfo.totalPrice,
|
||||||
|
wPayOrAPay: orderInfo.wPayOrAPay,
|
||||||
|
pointsPrice: orderInfo.pointsPrice,
|
||||||
|
invoiceCode: orderInfo.invoiceCode
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await tx.supplierOrderSub.createMany({
|
||||||
|
data: subOrders.map(subOrder => ({
|
||||||
|
orderId: subOrder.orderId,
|
||||||
|
orderNumberId: subOrder.orderNumberId,
|
||||||
|
mainOrderId: orderId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
aplName: orderInfo.aplName,
|
||||||
|
createTime: now,
|
||||||
|
updateTime: now,
|
||||||
|
deliveryStatus: orderInfo.deliveryStatus,
|
||||||
|
freightCost: orderInfo.freightCost,
|
||||||
|
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
|
||||||
|
refund: orderInfo.refund,
|
||||||
|
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
|
||||||
|
orderState: ORDER_STATE.PENDING_PAY,
|
||||||
|
totalPrice: subOrder.totalPrice,
|
||||||
|
wPayOrAPay: 0,
|
||||||
|
pointsPrice: subOrder.pointsPrice,
|
||||||
|
invoiceCode: orderInfo.invoiceCode
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
await tx.supplierOrderProduct.createMany({
|
||||||
|
data: subOrders.map(subOrder => ({
|
||||||
|
subOrderId: subOrder.orderId,
|
||||||
|
sku: subOrder.product.sku,
|
||||||
|
number: subOrder.product.number,
|
||||||
|
price: subOrder.product.price,
|
||||||
|
name: subOrder.product.name,
|
||||||
|
taxRate: subOrder.product.taxRate,
|
||||||
|
productImgUrl: subOrder.product.picInfo[0] || null,
|
||||||
|
fileId: subOrder.product.fileId,
|
||||||
|
filePath: subOrder.product.filePath,
|
||||||
|
picInfo: subOrder.product.picInfo
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const { response, payload } = await bridgeFetchWithAuth<BridgeOrderResponse>(supplierId, '/pointOrder/pendingOrderInfo', {
|
||||||
|
userId,
|
||||||
|
orderInfo
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok || payload.resultCode !== 0) {
|
||||||
|
return serviceFail(payload.message || '提交待支付订单失败', 502, 'BRIDGE_PENDING_ORDER_FAILED')
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk({
|
||||||
|
orderId,
|
||||||
|
orderNumberId,
|
||||||
|
subOrderIds: subOrders.map(item => item.orderId),
|
||||||
|
orderInfo
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import { ORDER_SPLIT_STATE, ORDER_STATE } from '../constants/order.js'
|
||||||
|
import { orderDao } from '../dao/order.dao.js'
|
||||||
|
import { logger } from '../utils/logger.js'
|
||||||
|
import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||||
|
|
||||||
|
type OrderStatusView = {
|
||||||
|
text: string
|
||||||
|
statusClass: string
|
||||||
|
actionText: string
|
||||||
|
buttonClass: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusMap: Record<number, OrderStatusView> = {
|
||||||
|
[ORDER_STATE.PENDING_PAY]: { text: '待付款', statusClass: 'pending', actionText: '去支付', buttonClass: 'primary-action' },
|
||||||
|
[ORDER_STATE.FINISH_PAY]: { text: '待发货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' },
|
||||||
|
[ORDER_STATE.IN_PROGRESS]: { text: '待收货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' },
|
||||||
|
[ORDER_STATE.CANCELLED]: { text: '已取消', statusClass: 'muted', actionText: '', buttonClass: 'dark-action' },
|
||||||
|
[ORDER_STATE.COMPLETED]: { text: '已完成', statusClass: 'done', actionText: '再次购买', buttonClass: 'dark-action' },
|
||||||
|
[ORDER_STATE.EXCHANGE]: { text: '换货', statusClass: 'muted', actionText: '查看订单', buttonClass: 'dark-action' },
|
||||||
|
[ORDER_STATE.REFUNDED]: { text: '退货', statusClass: 'muted', 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 ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDateTimeText = (date?: Date | null) => {
|
||||||
|
if (!date) return ''
|
||||||
|
const pad = (value: number) => String(value).padStart(2, '0')
|
||||||
|
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const sumProductsCount = (subOrders: Array<{ products: Array<{ number: number | null }> }>) => {
|
||||||
|
return subOrders.reduce((total, subOrder) => {
|
||||||
|
return total + subOrder.products.reduce((subTotal, product) => subTotal + Number(product.number || 0), 0)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const orderListService = {
|
||||||
|
async listOrders(supplierId: string, userId: string) {
|
||||||
|
logger.info('order list query', { supplierId, userId })
|
||||||
|
|
||||||
|
if (!supplierId || !userId) {
|
||||||
|
return serviceFail('供应商或用户信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainOrders = await orderDao.findMainOrdersWithSubs(supplierId, userId)
|
||||||
|
const orders = mainOrders.map(mainOrder => {
|
||||||
|
const mainStatus = statusMap[mainOrder.orderState] || statusMap[ORDER_STATE.FINISH_PAY]
|
||||||
|
const subOrders = mainOrder.subOrders.map(subOrder => {
|
||||||
|
const status = statusMap[subOrder.orderState] || mainStatus
|
||||||
|
const products = subOrder.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: subOrder.orderId,
|
||||||
|
orderId: subOrder.orderId,
|
||||||
|
orderNumberId: subOrder.orderNumberId || '',
|
||||||
|
mainOrderId: subOrder.mainOrderId,
|
||||||
|
supplierId: subOrder.supplierId,
|
||||||
|
createTime: toDateTimeText(subOrder.createTime || subOrder.createAt),
|
||||||
|
status: status.text,
|
||||||
|
statusClass: status.statusClass,
|
||||||
|
actionText: status.actionText,
|
||||||
|
buttonClass: status.buttonClass,
|
||||||
|
orderState: subOrder.orderState,
|
||||||
|
amount: toYuanText(subOrder.totalPrice),
|
||||||
|
count: products.reduce((total, item) => total + Number(item.number || 0), 0),
|
||||||
|
products
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const products = subOrders.flatMap(subOrder => subOrder.products)
|
||||||
|
const isMainOrderItem = mainOrder.orderState === ORDER_STATE.PENDING_PAY ||
|
||||||
|
(mainOrder.orderState === ORDER_STATE.CANCELLED && mainOrder.isSplit !== ORDER_SPLIT_STATE.SPLIT)
|
||||||
|
const orderItems = isMainOrderItem
|
||||||
|
? [{
|
||||||
|
id: mainOrder.orderId,
|
||||||
|
viewId: `main-${mainOrder.orderId}`,
|
||||||
|
displayType: 'main',
|
||||||
|
orderId: mainOrder.orderId,
|
||||||
|
mainOrderId: mainOrder.orderId,
|
||||||
|
orderNumberId: mainOrder.orderNumberId || '',
|
||||||
|
mainOrderNumberId: mainOrder.orderNumberId || '',
|
||||||
|
supplierId: mainOrder.supplierId,
|
||||||
|
createTime: toDateTimeText(mainOrder.createTime || mainOrder.createAt),
|
||||||
|
status: mainStatus.text,
|
||||||
|
statusClass: mainStatus.statusClass,
|
||||||
|
actionText: mainStatus.actionText,
|
||||||
|
buttonClass: mainStatus.buttonClass,
|
||||||
|
orderState: mainOrder.orderState,
|
||||||
|
amount: toYuanText(mainOrder.totalPrice),
|
||||||
|
count: sumProductsCount(subOrders),
|
||||||
|
products,
|
||||||
|
subOrders
|
||||||
|
}]
|
||||||
|
: subOrders.map(subOrder => ({
|
||||||
|
...subOrder,
|
||||||
|
viewId: `sub-${subOrder.orderId}`,
|
||||||
|
displayType: 'sub',
|
||||||
|
mainOrderNumberId: mainOrder.orderNumberId || ''
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: mainOrder.orderId,
|
||||||
|
mainOrderId: mainOrder.orderId,
|
||||||
|
mainOrderNumberId: mainOrder.orderNumberId || '',
|
||||||
|
supplierId: mainOrder.supplierId,
|
||||||
|
createTime: toDateTimeText(mainOrder.createTime || mainOrder.createAt),
|
||||||
|
status: mainStatus.text,
|
||||||
|
statusClass: mainStatus.statusClass,
|
||||||
|
orderState: mainOrder.orderState,
|
||||||
|
isSplit: mainOrder.isSplit,
|
||||||
|
splitTime: mainOrder.splitTime,
|
||||||
|
canCancel: mainOrder.orderState === ORDER_STATE.PENDING_PAY,
|
||||||
|
products,
|
||||||
|
amount: toYuanText(mainOrder.totalPrice),
|
||||||
|
count: sumProductsCount(subOrders),
|
||||||
|
orderItems,
|
||||||
|
subOrders
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info('order list result', { supplierId, userId, count: orders.length })
|
||||||
|
return serviceOk({ orders, orderItems: orders.flatMap(order => order.orderItems) })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
import {
|
||||||
|
ORDER_EVENT_TYPE,
|
||||||
|
ORDER_OPERATE_TYPE,
|
||||||
|
ORDER_PUSH_STATUS,
|
||||||
|
ORDER_STATE
|
||||||
|
} from '../constants/order.js'
|
||||||
|
import { orderDao } from '../dao/order.dao.js'
|
||||||
|
import { bridgeFetchWithAuth } from '../utils/bridge-auth.js'
|
||||||
|
import { logger } from '../utils/logger.js'
|
||||||
|
import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||||
|
|
||||||
|
export type OrderPushEventInput = {
|
||||||
|
supplierId?: string
|
||||||
|
userId?: string
|
||||||
|
orderType?: number
|
||||||
|
orderId?: string
|
||||||
|
mainOrderId?: string
|
||||||
|
subOrderId?: string
|
||||||
|
time?: number
|
||||||
|
amount?: number
|
||||||
|
reason?: string
|
||||||
|
settlementId?: string
|
||||||
|
invoiceBizId?: string
|
||||||
|
invoiceCode?: string
|
||||||
|
invoiceIds?: string
|
||||||
|
orderIds?: string
|
||||||
|
fileId?: string
|
||||||
|
filePath?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BridgeOrderResponse = {
|
||||||
|
resultCode?: number
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const PUSH_API_PATH = '/pointOrder/pushOrderInfoMsg'
|
||||||
|
const supportedOrderTypes = new Set<number>([
|
||||||
|
ORDER_EVENT_TYPE.RETURN_ORDER,
|
||||||
|
ORDER_EVENT_TYPE.EXCHANGE_ORDER,
|
||||||
|
ORDER_EVENT_TYPE.CONFIRM_RECEIVE,
|
||||||
|
ORDER_EVENT_TYPE.INVOICE_INFO
|
||||||
|
])
|
||||||
|
|
||||||
|
const splitIds = (value?: string) => String(value || '')
|
||||||
|
.split(',')
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
const eventState = (orderType: number) => {
|
||||||
|
if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER) return ORDER_STATE.REFUNDED
|
||||||
|
if (orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) return ORDER_STATE.EXCHANGE
|
||||||
|
if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE) return ORDER_STATE.COMPLETED
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const operateType = (orderType: number) => {
|
||||||
|
if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER) return ORDER_OPERATE_TYPE.REFUND
|
||||||
|
if (orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) return ORDER_OPERATE_TYPE.EXCHANGE
|
||||||
|
if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE) return ORDER_OPERATE_TYPE.COMPLETE
|
||||||
|
if (orderType === ORDER_EVENT_TYPE.INVOICE_INFO) return ORDER_OPERATE_TYPE.INVOICE_INFO
|
||||||
|
return orderType
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildBridgeBody = (body: OrderPushEventInput) => ({
|
||||||
|
supplierId: String(body.supplierId || '').trim(),
|
||||||
|
orderType: Number(body.orderType),
|
||||||
|
orderId: String(body.orderId || '').trim(),
|
||||||
|
mainOrderId: String(body.mainOrderId || '').trim(),
|
||||||
|
userId: String(body.userId || '').trim(),
|
||||||
|
time: Number(body.time || Date.now())
|
||||||
|
})
|
||||||
|
|
||||||
|
export const orderPushEventService = {
|
||||||
|
async pushEvent(body: OrderPushEventInput) {
|
||||||
|
const supplierId = String(body.supplierId || '').trim()
|
||||||
|
const userId = String(body.userId || '').trim()
|
||||||
|
const orderType = Number(body.orderType)
|
||||||
|
const orderId = String(body.orderId || '').trim()
|
||||||
|
const mainOrderId = String(body.mainOrderId || '').trim()
|
||||||
|
logger.info('push order event', { supplierId, userId, orderType, orderId, mainOrderId })
|
||||||
|
|
||||||
|
if (!supplierId || !userId || !orderType || !orderId) {
|
||||||
|
return serviceFail('供应商、用户或订单事件信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supportedOrderTypes.has(orderType)) {
|
||||||
|
return serviceFail('暂不支持该订单事件类型', 400, 'ORDER_EVENT_NOT_SUPPORTED')
|
||||||
|
}
|
||||||
|
|
||||||
|
const bridgeBody = buildBridgeBody(body)
|
||||||
|
const now = new Date()
|
||||||
|
let pushLogId = 0
|
||||||
|
let affectedSubOrderIds: string[] = []
|
||||||
|
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
const mainOrder = mainOrderId
|
||||||
|
? await tx.supplierOrderMain.findFirst({ where: { orderId: mainOrderId, supplierId, deleted: 0 } })
|
||||||
|
: null
|
||||||
|
|
||||||
|
const explicitSubIds = splitIds(body.subOrderId || '')
|
||||||
|
const orderIds = splitIds(orderId)
|
||||||
|
const subOrdersByOrderId = await tx.supplierOrderSub.findMany({
|
||||||
|
where: {
|
||||||
|
supplierId,
|
||||||
|
deleted: 0,
|
||||||
|
OR: [
|
||||||
|
{ orderId: { in: orderIds } },
|
||||||
|
...(explicitSubIds.length ? [{ orderId: { in: explicitSubIds } }] : []),
|
||||||
|
...(mainOrderId ? [{ mainOrderId }] : [])
|
||||||
|
]
|
||||||
|
},
|
||||||
|
orderBy: { createAt: 'asc' }
|
||||||
|
})
|
||||||
|
|
||||||
|
const directlyMatched = subOrdersByOrderId.filter(item => orderIds.includes(item.orderId) || explicitSubIds.includes(item.orderId))
|
||||||
|
const mainOrderSubs = mainOrderId ? subOrdersByOrderId.filter(item => item.mainOrderId === mainOrderId) : []
|
||||||
|
const targetSubOrders = directlyMatched.length ? directlyMatched : mainOrderSubs.length === 1 ? mainOrderSubs : []
|
||||||
|
affectedSubOrderIds = targetSubOrders.map(item => item.orderId)
|
||||||
|
|
||||||
|
const nextState = eventState(orderType)
|
||||||
|
if (nextState !== null && targetSubOrders.length) {
|
||||||
|
await tx.supplierOrderSub.updateMany({
|
||||||
|
where: { orderId: { in: affectedSubOrderIds }, supplierId, deleted: 0 },
|
||||||
|
data: {
|
||||||
|
orderState: nextState,
|
||||||
|
orderFinishTime: orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE ? now : undefined,
|
||||||
|
completeTime: orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE ? now : undefined,
|
||||||
|
updateTime: now
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await tx.supplierOrderOperateLog.createMany({
|
||||||
|
data: targetSubOrders.map(subOrder => ({
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
mainOrderId: subOrder.mainOrderId,
|
||||||
|
subOrderId: subOrder.orderId,
|
||||||
|
operateType: operateType(orderType),
|
||||||
|
beforeState: subOrder.orderState,
|
||||||
|
afterState: nextState,
|
||||||
|
remark: body.reason || null,
|
||||||
|
operator: 'system'
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER || orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) {
|
||||||
|
await tx.supplierRefundExchange.upsert({
|
||||||
|
where: { serviceOrderId: orderId },
|
||||||
|
update: {
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null,
|
||||||
|
subOrderId: targetSubOrders[0]?.orderId || null,
|
||||||
|
serviceType: orderType,
|
||||||
|
serviceState: 1,
|
||||||
|
amount: Number(body.amount || targetSubOrders[0]?.totalPrice || 0),
|
||||||
|
reason: body.reason || null,
|
||||||
|
requestBody: body,
|
||||||
|
deleted: 0
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
serviceOrderId: orderId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null,
|
||||||
|
subOrderId: targetSubOrders[0]?.orderId || null,
|
||||||
|
serviceType: orderType,
|
||||||
|
serviceState: 1,
|
||||||
|
amount: Number(body.amount || targetSubOrders[0]?.totalPrice || 0),
|
||||||
|
reason: body.reason || null,
|
||||||
|
requestBody: body
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderType === ORDER_EVENT_TYPE.INVOICE_INFO) {
|
||||||
|
const invoiceBizId = String(body.invoiceBizId || body.invoiceCode || orderId).trim()
|
||||||
|
await tx.supplierInvoiceInfo.upsert({
|
||||||
|
where: { invoiceBizId },
|
||||||
|
update: {
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
mainOrderId: mainOrderId || null,
|
||||||
|
subOrderId: targetSubOrders[0]?.orderId || null,
|
||||||
|
settlementId: body.settlementId || null,
|
||||||
|
invoiceCode: body.invoiceCode || null,
|
||||||
|
invoiceIds: body.invoiceIds || orderId,
|
||||||
|
orderIds: body.orderIds || mainOrderId || targetSubOrders[0]?.orderId || null,
|
||||||
|
invoiceStatus: 2,
|
||||||
|
fileId: body.fileId || null,
|
||||||
|
filePath: body.filePath || null,
|
||||||
|
requestBody: body,
|
||||||
|
deleted: 0
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
invoiceBizId,
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
mainOrderId: mainOrderId || null,
|
||||||
|
subOrderId: targetSubOrders[0]?.orderId || null,
|
||||||
|
settlementId: body.settlementId || null,
|
||||||
|
invoiceCode: body.invoiceCode || null,
|
||||||
|
invoiceIds: body.invoiceIds || orderId,
|
||||||
|
orderIds: body.orderIds || mainOrderId || targetSubOrders[0]?.orderId || null,
|
||||||
|
invoiceStatus: 2,
|
||||||
|
fileId: body.fileId || null,
|
||||||
|
filePath: body.filePath || null,
|
||||||
|
requestBody: body
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (body.invoiceCode && mainOrder) {
|
||||||
|
await tx.supplierOrderMain.update({
|
||||||
|
where: { orderId: mainOrder.orderId },
|
||||||
|
data: { invoiceCode: body.invoiceCode, updateTime: now }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE && mainOrderId) {
|
||||||
|
const unfinishedCount = await tx.supplierOrderSub.count({
|
||||||
|
where: {
|
||||||
|
mainOrderId,
|
||||||
|
deleted: 0,
|
||||||
|
orderState: { notIn: [ORDER_STATE.COMPLETED, ORDER_STATE.CANCELLED, ORDER_STATE.REFUNDED] }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (unfinishedCount === 0) {
|
||||||
|
await tx.supplierOrderMain.update({
|
||||||
|
where: { orderId: mainOrderId },
|
||||||
|
data: { orderState: ORDER_STATE.COMPLETED, completeTime: now, updateTime: now }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushLog = await tx.supplierOrderPushLog.create({
|
||||||
|
data: {
|
||||||
|
supplierId,
|
||||||
|
userId,
|
||||||
|
mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null,
|
||||||
|
orderId,
|
||||||
|
orderType,
|
||||||
|
apiPath: PUSH_API_PATH,
|
||||||
|
requestBody: bridgeBody,
|
||||||
|
pushStatus: ORDER_PUSH_STATUS.PENDING
|
||||||
|
}
|
||||||
|
})
|
||||||
|
pushLogId = pushLog.id
|
||||||
|
})
|
||||||
|
|
||||||
|
let bridgeResponse: Response | null = null
|
||||||
|
let bridgePayload: BridgeOrderResponse = {}
|
||||||
|
try {
|
||||||
|
const result = await bridgeFetchWithAuth<BridgeOrderResponse>(supplierId, PUSH_API_PATH, bridgeBody)
|
||||||
|
bridgeResponse = result.response
|
||||||
|
bridgePayload = result.payload
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : '订单事件推送失败'
|
||||||
|
await orderDao.updatePushLog(pushLogId, {
|
||||||
|
pushStatus: ORDER_PUSH_STATUS.FAILED,
|
||||||
|
errorMessage: message,
|
||||||
|
lastPushTime: new Date()
|
||||||
|
})
|
||||||
|
logger.warn('push order event bridge failed', { supplierId, orderId, message })
|
||||||
|
return serviceOk({ localSaved: true, pushSuccess: false, message, affectedSubOrderIds })
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushSuccess = bridgeResponse.ok && bridgePayload.resultCode === 0
|
||||||
|
|
||||||
|
await orderDao.updatePushLog(pushLogId, {
|
||||||
|
responseBody: bridgePayload,
|
||||||
|
httpStatus: bridgeResponse.status,
|
||||||
|
resultCode: bridgePayload.resultCode,
|
||||||
|
pushStatus: pushSuccess ? ORDER_PUSH_STATUS.SUCCESS : ORDER_PUSH_STATUS.FAILED,
|
||||||
|
errorMessage: pushSuccess ? null : bridgePayload.message || '订单事件推送失败',
|
||||||
|
lastPushTime: new Date()
|
||||||
|
})
|
||||||
|
|
||||||
|
return serviceOk({
|
||||||
|
localSaved: true,
|
||||||
|
pushSuccess,
|
||||||
|
orderType,
|
||||||
|
orderId,
|
||||||
|
mainOrderId,
|
||||||
|
affectedSubOrderIds,
|
||||||
|
message: pushSuccess ? '订单事件已保存并推送成功' : bridgePayload.message || '订单事件已保存,推送失败'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { productDao, type ProductRecord } from '../dao/product.dao.js'
|
||||||
|
import { logger } from '../utils/logger.js'
|
||||||
|
import { serviceFail, serviceOk } from '../types/service-result.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: ProductRecord) => {
|
||||||
|
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 const productService = {
|
||||||
|
async listProducts(keyword: string, categoryId: string) {
|
||||||
|
logger.info('product list query', { keyword, categoryId })
|
||||||
|
const products = await productDao.findEnabledProducts(keyword, categoryId)
|
||||||
|
|
||||||
|
return serviceOk({
|
||||||
|
categories,
|
||||||
|
products: products.map(toProductItem)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async getProductDetail(id: string, sku: string) {
|
||||||
|
logger.info('product detail query', { id, sku })
|
||||||
|
|
||||||
|
if (!id && !sku) {
|
||||||
|
return serviceFail('商品参数不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const product = await productDao.findEnabledProduct(id, sku)
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
return serviceFail('商品不存在或已下架', 404, 'PRODUCT_NOT_FOUND')
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk(toProductItem(product))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { supplierDao } from '../dao/supplier.dao.js'
|
||||||
|
import { orderDao } from '../dao/order.dao.js'
|
||||||
|
import { logger } from '../utils/logger.js'
|
||||||
|
import { sm4CbcDecrypt, verifySupplierOpenSignature } from '../utils/supplier-open-crypto.js'
|
||||||
|
|
||||||
|
export type SupplierOrderQueryRequest = {
|
||||||
|
supplierId?: string
|
||||||
|
token?: string
|
||||||
|
orderType?: number
|
||||||
|
orderId?: string
|
||||||
|
timestamp?: number
|
||||||
|
AplParmObjct?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseBody = (body: SupplierOrderQueryRequest) => {
|
||||||
|
if (!body.AplParmObjct) return body
|
||||||
|
|
||||||
|
const keyHex = process.env.SUPPLIER_OPEN_SM4_KEY_HEX || process.env.SM4CBC_KEY_HEX || ''
|
||||||
|
const ivHex = process.env.SUPPLIER_OPEN_SM4_IV_HEX || process.env.SM4CBC_IV_HEX || ''
|
||||||
|
if (!keyHex || !ivHex) {
|
||||||
|
throw new Error('供应商开放接口SM4配置缺失')
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(sm4CbcDecrypt(body.AplParmObjct, keyHex, ivHex)) as SupplierOrderQueryRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
const toText = (date?: Date | null) => {
|
||||||
|
if (!date) return null
|
||||||
|
const pad = (value: number) => String(value).padStart(2, '0')
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const toMoney = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => Number(value || 0).toFixed(2)
|
||||||
|
|
||||||
|
const productSku = (product: { sku: string; subOrderId: string; name: string }) => {
|
||||||
|
if (product.sku && product.sku !== '[object Object]') {
|
||||||
|
return product.sku
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${product.subOrderId}-${product.name || 'SKU'}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const supplierOpenOrderService = {
|
||||||
|
parseBody,
|
||||||
|
|
||||||
|
async queryOrderInfo(body: SupplierOrderQueryRequest) {
|
||||||
|
const supplierId = String(body.supplierId || '').trim()
|
||||||
|
const token = String(body.token || '').trim()
|
||||||
|
const orderIds = String(body.orderId || '')
|
||||||
|
.split(',')
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
const timestamp = Number(body.timestamp || 0)
|
||||||
|
|
||||||
|
logger.info('supplier open order query', { supplierId, orderCount: orderIds.length, timestamp })
|
||||||
|
|
||||||
|
if (!supplierId || !token || !timestamp || !orderIds.length) {
|
||||||
|
return { resultCode: 400, message: '供应商、订单或签名信息不能为空', orderInfo: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const supplier = await supplierDao.findActiveBySupplierId(supplierId)
|
||||||
|
if (!supplier) {
|
||||||
|
return { resultCode: 404, message: '供应商信息不存在', orderInfo: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!verifySupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp, token)) {
|
||||||
|
return { resultCode: 401, message: '签名无效或已过期', orderInfo: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const orders = await orderDao.findOpenApiSubOrders(supplierId, orderIds)
|
||||||
|
const orderInfo = orders.map(order => ({
|
||||||
|
orderId: order.orderId,
|
||||||
|
orderNumberId: order.orderNumberId,
|
||||||
|
supplierId: order.supplierId,
|
||||||
|
token: order.token,
|
||||||
|
aplName: order.aplName,
|
||||||
|
createTime: toText(order.createTime),
|
||||||
|
updateTime: toText(order.updateTime),
|
||||||
|
deliveryTime: toText(order.deliveryTime),
|
||||||
|
orderFinishTime: toText(order.orderFinishTime),
|
||||||
|
completeTime: toText(order.completeTime),
|
||||||
|
deliveryStatus: order.deliveryStatus,
|
||||||
|
freightCost: toMoney(order.freightCost),
|
||||||
|
wPayOrAPayFreightCost: toMoney(order.wPayOrAPayFreightCost),
|
||||||
|
refund: toMoney(order.refund),
|
||||||
|
wPayOrAPayRefund: toMoney(order.wPayOrAPayRefund),
|
||||||
|
logisticsOrderId: order.logisticsOrderId,
|
||||||
|
crrgBsnNm: order.crrgBsnNm,
|
||||||
|
orderState: order.orderState,
|
||||||
|
totalPrice: toMoney(order.totalPrice),
|
||||||
|
wPayOrAPay: toMoney(order.wPayOrAPay),
|
||||||
|
pointsPrice: toMoney(order.pointsPrice),
|
||||||
|
invoiceCode: order.invoiceCode,
|
||||||
|
orderProInfos: order.products.map(product => ({
|
||||||
|
sku: productSku(product),
|
||||||
|
number: product.number,
|
||||||
|
price: toMoney(product.price),
|
||||||
|
name: product.name,
|
||||||
|
taxRate: Number(product.taxRate || 0),
|
||||||
|
picInfo: Array.isArray(product.picInfo) ? product.picInfo : product.productImgUrl ? [product.productImgUrl] : [],
|
||||||
|
fileId: product.fileId,
|
||||||
|
filePath: product.filePath
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
|
return { resultCode: 0, message: '成功', orderInfo }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import { supplierDao } from '../dao/supplier.dao.js'
|
||||||
|
import { bridgeFetchWithAuth, findSupplierByIdentification, getBridgeApiBase, getBridgeToken, warmBridgeToken } from '../utils/bridge-auth.js'
|
||||||
|
import { sha256 } from '../utils/crypto.js'
|
||||||
|
import { logger } from '../utils/logger.js'
|
||||||
|
import { createSupplierOpenSignature } from '../utils/supplier-open-crypto.js'
|
||||||
|
import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||||
|
|
||||||
|
type BridgePointsResponse = {
|
||||||
|
resultCode?: number
|
||||||
|
message?: string
|
||||||
|
userPoints?: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const supplierService = {
|
||||||
|
async getSupplierInfo(supplierIdentification: string) {
|
||||||
|
logger.info('supplier info query', { supplierIdentification })
|
||||||
|
|
||||||
|
if (!supplierIdentification) {
|
||||||
|
return serviceFail('供应商唯一标识不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const supplier = await supplierDao.findActiveByIdentification(supplierIdentification)
|
||||||
|
|
||||||
|
if (!supplier) {
|
||||||
|
return serviceFail('供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED')
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk({
|
||||||
|
id: supplier.id,
|
||||||
|
supplierIdentification: supplier.supplierIdentification,
|
||||||
|
supplierId: supplier.supplierId,
|
||||||
|
supplierName: supplier.supplierName
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async warmSupplierToken(userId: string, supplierIdentification: string) {
|
||||||
|
logger.info('supplier token warmup', { userId, supplierIdentification })
|
||||||
|
|
||||||
|
if (!userId || !supplierIdentification) {
|
||||||
|
return serviceFail('用户信息和供应商唯一标识不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error)
|
||||||
|
|
||||||
|
if (supplier instanceof Error) {
|
||||||
|
return serviceFail(supplier.message || '供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await warmBridgeToken(supplier)
|
||||||
|
} catch (error) {
|
||||||
|
return serviceFail(error instanceof Error ? error.message : '获取平台授权令牌失败', 502, 'BRIDGE_TOKEN_FAILED')
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk({
|
||||||
|
supplierIdentification,
|
||||||
|
supplierId: supplier.supplierId,
|
||||||
|
supplierName: supplier.supplierName,
|
||||||
|
userId
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async getUserPoints(supplierId: string, userId: string) {
|
||||||
|
logger.info('supplier points query', { supplierId, userId })
|
||||||
|
|
||||||
|
if (!supplierId || !userId) {
|
||||||
|
return serviceFail('供应商或用户信息不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { response, payload: pointsPayload } = await bridgeFetchWithAuth<BridgePointsResponse>(
|
||||||
|
supplierId,
|
||||||
|
'/pointOrder/queryPoints',
|
||||||
|
{ userId }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!response.ok || pointsPayload.resultCode !== 0) {
|
||||||
|
return serviceFail(pointsPayload.message || '查询用户积分失败', 502, 'BRIDGE_POINTS_FAILED')
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk({
|
||||||
|
userPoints: Number(pointsPayload.userPoints || 0)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async getDebugTokens(supplierIdentification: string, forceRefreshZhghToken: boolean) {
|
||||||
|
logger.info('supplier debug tokens query', { supplierIdentification, forceRefreshZhghToken })
|
||||||
|
|
||||||
|
if (!supplierIdentification) {
|
||||||
|
return serviceFail('供应商唯一标识不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error)
|
||||||
|
|
||||||
|
if (supplier instanceof Error) {
|
||||||
|
return serviceFail(supplier.message || '供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED')
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = Date.now()
|
||||||
|
const orderType = 2
|
||||||
|
const orderId = 'SUB_ORDER_ID_1,SUB_ORDER_ID_2'
|
||||||
|
const mainOrderId = 'MAIN_ORDER_ID'
|
||||||
|
const userId = 'USER_ID'
|
||||||
|
const supplierOpenToken = createSupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp)
|
||||||
|
const zhghBridgeToken = await getBridgeToken(supplier.supplierId, forceRefreshZhghToken)
|
||||||
|
|
||||||
|
return serviceOk({
|
||||||
|
supplierOpen: {
|
||||||
|
name: 'supplierOpenToken',
|
||||||
|
usage: '调用供应商开放接口 /supplierOpenApi/pointOrder/queryOrderInfo',
|
||||||
|
supplierId: supplier.supplierId,
|
||||||
|
clientId: supplier.clientId,
|
||||||
|
timestamp,
|
||||||
|
token: supplierOpenToken,
|
||||||
|
orderType,
|
||||||
|
orderId,
|
||||||
|
queryOrderInfoBody: {
|
||||||
|
supplierId: supplier.supplierId,
|
||||||
|
token: supplierOpenToken,
|
||||||
|
orderType,
|
||||||
|
orderId,
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zhghBridge: {
|
||||||
|
name: 'zhghBridgeToken',
|
||||||
|
usage: '调用 zhgh /open/pointOrder/* 桥接接口',
|
||||||
|
apiBase: getBridgeApiBase(),
|
||||||
|
supplierId: supplier.supplierId,
|
||||||
|
clientId: supplier.clientId,
|
||||||
|
timestamp,
|
||||||
|
sendSha256: sha256(`${supplier.clientId}${supplier.clientSecret}${timestamp}`),
|
||||||
|
token: zhghBridgeToken,
|
||||||
|
splitOrderBody: {
|
||||||
|
supplierId: supplier.supplierId,
|
||||||
|
token: zhghBridgeToken,
|
||||||
|
orderType,
|
||||||
|
orderId,
|
||||||
|
mainOrderId,
|
||||||
|
userId,
|
||||||
|
time: timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export type ServiceError = {
|
||||||
|
message: string
|
||||||
|
status?: number
|
||||||
|
code?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceResult<T = unknown> =
|
||||||
|
| { success: true; data: T }
|
||||||
|
| { success: false; error: ServiceError }
|
||||||
|
|
||||||
|
export const serviceOk = <T>(data: T): ServiceResult<T> => ({ success: true, data })
|
||||||
|
|
||||||
|
export const serviceFail = (message: string, status?: number, code?: string): ServiceResult<never> => ({
|
||||||
|
success: false,
|
||||||
|
error: { message, status, code }
|
||||||
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { SupplierInfo } from '@prisma/client'
|
import type { SupplierInfo } from '@prisma/client'
|
||||||
|
import { supplierDao } from '../dao/supplier.dao.js'
|
||||||
import { createBridgeSignature } from './crypto.js'
|
import { createBridgeSignature } from './crypto.js'
|
||||||
import { prisma } from './prisma.js'
|
|
||||||
import { getRedis } from './redis.js'
|
import { getRedis } from './redis.js'
|
||||||
|
|
||||||
type BridgeTokenResponse = {
|
type BridgeTokenResponse = {
|
||||||
@@ -33,12 +33,7 @@ const TOKEN_EXPIRE_BUFFER_MS = 60_000
|
|||||||
export const getBridgeApiBase = () => normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
|
export const getBridgeApiBase = () => normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
|
||||||
|
|
||||||
export const findSupplierForBridge = async (supplierId: string) => {
|
export const findSupplierForBridge = async (supplierId: string) => {
|
||||||
const supplier = await prisma.supplierInfo.findFirst({
|
const supplier = await supplierDao.findActiveBySupplierId(supplierId)
|
||||||
where: {
|
|
||||||
supplierId,
|
|
||||||
deleted: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!supplier) {
|
if (!supplier) {
|
||||||
throw new Error('供应商信息未配置或已停用')
|
throw new Error('供应商信息未配置或已停用')
|
||||||
@@ -47,13 +42,9 @@ export const findSupplierForBridge = async (supplierId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const findSupplierByIdentification = async (supplierIdentification: string) => {
|
export const findSupplierByIdentification = async (supplierIdentification: string) => {
|
||||||
const supplier = await prisma.supplierInfo.findUnique({
|
const supplier = await supplierDao.findActiveByIdentification(supplierIdentification.toLowerCase())
|
||||||
where: {
|
|
||||||
supplierIdentification: supplierIdentification.toLowerCase()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!supplier || supplier.deleted === 1) {
|
if (!supplier) {
|
||||||
throw new Error('供应商信息未配置或已停用')
|
throw new Error('供应商信息未配置或已停用')
|
||||||
}
|
}
|
||||||
return supplier
|
return supplier
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { fail } from './http.js'
|
import { fail } from './http.js'
|
||||||
|
import { logger } from './logger.js'
|
||||||
|
|
||||||
export type Context = Record<string, unknown>
|
export type Context = Record<string, unknown>
|
||||||
|
|
||||||
@@ -9,9 +10,8 @@ export const withErrorBoundary = (handler: FunctionHandler): FunctionHandler =>
|
|||||||
try {
|
try {
|
||||||
return await handler(request, context)
|
return await handler(request, context)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
logger.error('unhandled request error', error)
|
||||||
return fail('服务异常,请稍后再试', 500, 'INTERNAL_SERVER_ERROR')
|
return fail('服务异常,请稍后再试', 500, 'INTERNAL_SERVER_ERROR')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
type LogLevel = 'info' | 'warn' | 'error'
|
||||||
|
|
||||||
|
const write = (level: LogLevel, message: string, meta?: unknown) => {
|
||||||
|
const line = `[supplied-service] ${new Date().toISOString()} ${level.toUpperCase()} ${message}`
|
||||||
|
|
||||||
|
if (level === 'error') {
|
||||||
|
console.error(line, meta || '')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level === 'warn') {
|
||||||
|
console.warn(line, meta || '')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(line, meta || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logger = {
|
||||||
|
info: (message: string, meta?: unknown) => write('info', message, meta),
|
||||||
|
warn: (message: string, meta?: unknown) => write('warn', message, meta),
|
||||||
|
error: (message: string, meta?: unknown) => write('error', message, meta)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user