feat: 积分商城-供应商
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
PORT=8050
|
||||
SUPPLIED_SERVICE_BASE_URL=http://127.0.0.1:8888
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "esf-relay",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node src/server.mjs"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import http from 'node:http'
|
||||
|
||||
const port = Number(process.env.PORT || 8050)
|
||||
const suppliedServiceBaseUrl = String(process.env.SUPPLIED_SERVICE_BASE_URL || 'http://127.0.0.1:8888').replace(/\/$/, '')
|
||||
|
||||
const addressRoutes = new Map([
|
||||
['/supplierOpenApi/pointOrder/queryOrderInfo', '/supplierOpenApi/pointOrder/queryOrderInfo'],
|
||||
['/supplierOpenApi/pointOrder/queryReconciliationInfo', '/supplierOpenApi/pointOrder/queryReconciliationInfo'],
|
||||
['/supplierOpenApi/pointOrder/unReconciledMsg', '/supplierOpenApi/pointOrder/unReconciledMsg'],
|
||||
['/supplierOpenApi/pointOrder/reconciliationResult', '/supplierOpenApi/pointOrder/reconciliationResult']
|
||||
])
|
||||
|
||||
const readBody = request => new Promise((resolve, reject) => {
|
||||
const chunks = []
|
||||
request.on('data', chunk => chunks.push(chunk))
|
||||
request.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
|
||||
request.on('error', reject)
|
||||
})
|
||||
|
||||
const getForwardPath = address4 => {
|
||||
if (typeof address4 !== 'string') {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
return addressRoutes.get(new URL(address4).pathname) || null
|
||||
} catch {
|
||||
return addressRoutes.get(address4) || null
|
||||
}
|
||||
}
|
||||
|
||||
const writeJson = (response, statusCode, body) => {
|
||||
response.writeHead(statusCode, { 'Content-Type': 'application/json;charset=UTF-8' })
|
||||
response.end(JSON.stringify(body))
|
||||
}
|
||||
|
||||
const server = http.createServer(async (request, response) => {
|
||||
if (request.method !== 'POST' || request.url !== '/esf/S120020967Api') {
|
||||
writeJson(response, 404, { resultCode: 404, message: '接口不存在' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const bodyText = await readBody(request)
|
||||
const body = JSON.parse(bodyText)
|
||||
const forwardPath = getForwardPath(body.Address4)
|
||||
if (!forwardPath) {
|
||||
writeJson(response, 400, { resultCode: 400, message: '不支持的Address4' })
|
||||
return
|
||||
}
|
||||
|
||||
const upstreamResponse = await fetch(`${suppliedServiceBaseUrl}${forwardPath}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
|
||||
body: bodyText
|
||||
})
|
||||
const responseBody = await upstreamResponse.text()
|
||||
response.writeHead(upstreamResponse.status, {
|
||||
'Content-Type': upstreamResponse.headers.get('content-type') || 'application/json;charset=UTF-8'
|
||||
})
|
||||
response.end(responseBody)
|
||||
} catch (error) {
|
||||
writeJson(response, 502, {
|
||||
resultCode: 502,
|
||||
message: error instanceof Error ? error.message : 'ESF中转请求失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log(`ESF relay running at http://127.0.0.1:${port}/esf/S120020967Api`)
|
||||
})
|
||||
@@ -0,0 +1,2 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>供应商管理后台</title></head><body><div id="app"></div><script type="module" src="/src/main.ts"></script></body></html>
|
||||
Generated
+2697
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "supplied-admin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"ele-admin-plus": "^1.6.0",
|
||||
"element-plus": "^2.9.1",
|
||||
"pinia": "^3.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"sass-embedded": "^1.103.1",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.5",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<template><RouterView /></template>
|
||||
@@ -0,0 +1,16 @@
|
||||
const API_BASE = import.meta.env.VITE_SUPPLIED_ADMIN_API_BASE || 'http://127.0.0.1:8888/api/admin'
|
||||
|
||||
const normalizeBase = (base: string) => base.replace(/\/$/, '')
|
||||
|
||||
export const postAdmin = async <T>(name: string, body: Record<string, unknown>): Promise<T> => {
|
||||
const response = await fetch(`${normalizeBase(API_BASE)}/${name}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
const payload = await response.json().catch(() => ({}))
|
||||
if (!response.ok || payload.success === false) {
|
||||
throw new Error(payload.error?.message || payload.msg || '请求失败')
|
||||
}
|
||||
return payload.data as T
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<section class="page-card"><div class="page-title"><div><h2>{{ title }}</h2><p>{{ description }}</p></div></div><el-form :inline="true" class="query-form"><el-form-item label="供应商 ID"><el-input v-model="supplierId" placeholder="请输入供应商 ID" clearable /></el-form-item><el-form-item><el-button type="primary" :loading="loading" @click="loadRecords">查询</el-button><el-button @click="reset">重置</el-button></el-form-item></el-form><el-table v-loading="loading" :data="records" border><el-table-column prop="serviceOrderId" label="售后单号" min-width="170" /><el-table-column prop="serviceTypeText" label="售后类型" width="110" /><el-table-column prop="serviceStateText" label="状态" width="130" /><el-table-column prop="reason" label="原因" min-width="160" /><el-table-column prop="applyTime" label="申请时间" min-width="170" /><el-table-column label="操作" min-width="160" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openDetail(row)">详情</el-button><el-button v-if="scene === 'audit'" link type="success" @click="audit(row, 1)">通过</el-button><el-button v-if="scene === 'audit'" link type="danger" @click="audit(row, 2)">拒绝</el-button><el-button v-if="canReceive(row)" link type="success" @click="receive(row)">确认收货</el-button><el-button v-if="canShip(row)" link type="primary" @click="openShipDialog(row)">换货发货</el-button></template></el-table-column></el-table><el-empty v-if="!loading && queried && !records.length" description="暂无数据" /><el-dialog v-model="detailVisible" title="售后详情" width="620px"><pre class="detail-json">{{ JSON.stringify(detail, null, 2) }}</pre></el-dialog><el-dialog v-model="shipVisible" title="换货发货" width="460px"><el-form label-width="90px"><el-form-item label="快递公司"><el-input v-model="shipForm.logisticsCompany" /></el-form-item><el-form-item label="快递单号"><el-input v-model="shipForm.logisticsNo" /></el-form-item></el-form><template #footer><el-button @click="shipVisible = false">取消</el-button><el-button type="primary" @click="shipExchange">确认发货</el-button></template></el-dialog></section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
const props = defineProps<{ scene: 'audit' | 'process' | 'history'; title: string; description: string }>()
|
||||
type AfterSale = { serviceOrderId: string; serviceState?: number; serviceType?: number; isSigned?: boolean; serviceTypeText?: string; serviceStateText?: string; reason?: string; applyTime?: string }
|
||||
const supplierId = ref(''); const records = ref<AfterSale[]>([]); const loading = ref(false); const queried = ref(false); const detailVisible = ref(false); const detail = ref<unknown>({}); const shipVisible = ref(false); const current = ref<AfterSale>(); const shipForm = ref({ logisticsCompany: '', logisticsNo: '' })
|
||||
const loadRecords = async () => { if (!supplierId.value.trim()) return ElMessage.warning('请输入供应商 ID'); loading.value = true; try { const data = await postAdmin<{ afterSaleItems?: AfterSale[] }>('merchant-after-sale-list', { supplierId: supplierId.value.trim(), scene: props.scene }); records.value = data.afterSaleItems || []; queried.value = true } catch (error) { ElMessage.error(error instanceof Error ? error.message : '售后查询失败') } finally { loading.value = false } }
|
||||
const reset = () => { supplierId.value = ''; records.value = []; queried.value = false }
|
||||
const openDetail = async (row: AfterSale) => { try { detail.value = await postAdmin('merchant-after-sale-detail', { supplierId: supplierId.value.trim(), serviceOrderId: row.serviceOrderId }); detailVisible.value = true } catch (error) { ElMessage.error(error instanceof Error ? error.message : '售后详情查询失败') } }
|
||||
const audit = async (row: AfterSale, auditStatus: number) => { try { await ElMessageBox.confirm(auditStatus === 1 ? '确认通过该售后申请?' : '确认拒绝该售后申请?', '售后审核'); await postAdmin('merchant-after-sale-audit', { supplierId: supplierId.value.trim(), serviceOrderId: row.serviceOrderId, auditStatus, auditRemark: auditStatus === 1 ? '审核通过' : '商家拒绝' }); ElMessage.success('处理成功'); await loadRecords() } catch (error) { if (error !== 'cancel') ElMessage.error(error instanceof Error ? error.message : '处理失败') } }
|
||||
const canReceive = (row: AfterSale) => props.scene === 'process' && Number(row.serviceState) === 3
|
||||
const canShip = (row: AfterSale) => props.scene === 'process' && Number(row.serviceType) === 5 && (Number(row.serviceState) === 4 || (row.isSigned === false && Number(row.serviceState) === 1))
|
||||
const receive = async (row: AfterSale) => { try { await ElMessageBox.confirm('确认已收到寄回商品?', '售后处理'); await postAdmin('merchant-after-sale-receive', { supplierId: supplierId.value.trim(), serviceOrderId: row.serviceOrderId }); ElMessage.success('已确认收货'); await loadRecords() } catch (error) { if (error !== 'cancel') ElMessage.error(error instanceof Error ? error.message : '操作失败') } }
|
||||
const openShipDialog = (row: AfterSale) => { current.value = row; shipForm.value = { logisticsCompany: '', logisticsNo: '' }; shipVisible.value = true }
|
||||
const shipExchange = async () => { if (!current.value || !shipForm.value.logisticsCompany || !shipForm.value.logisticsNo) return ElMessage.warning('请填写完整发货信息'); try { await postAdmin('merchant-after-sale-ship', { supplierId: supplierId.value.trim(), serviceOrderId: current.value.serviceOrderId, ...shipForm.value }); ElMessage.success('换货已发货'); shipVisible.value = false; await loadRecords() } catch (error) { ElMessage.error(error instanceof Error ? error.message : '发货失败') } }
|
||||
</script>
|
||||
|
||||
<style scoped>.detail-json { max-height: 420px; overflow: auto; padding: 12px; border-radius: 4px; background: #f5f7fa; white-space: pre-wrap; word-break: break-all; }</style>
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-container class="admin-layout">
|
||||
<el-aside :width="appStore.sidebarCollapsed ? '64px' : '224px'" class="sidebar">
|
||||
<div class="brand"><el-icon><ShoppingBag /></el-icon><span v-show="!appStore.sidebarCollapsed">供应商管理</span></div>
|
||||
<el-menu :collapse="appStore.sidebarCollapsed" :default-active="$route.path" background-color="#001529" text-color="#bfcbd9" active-text-color="#ffffff" router>
|
||||
<el-menu-item index="/reconciliation"><el-icon><Tickets /></el-icon><template #title>订单对账</template></el-menu-item>
|
||||
<el-sub-menu index="/orders"><template #title><el-icon><List /></el-icon><span>订单管理</span></template><el-menu-item index="/orders/list">订单列表</el-menu-item><el-menu-item index="/orders/after-sales/audit">售后审核</el-menu-item><el-menu-item index="/orders/after-sales/process">售后处理</el-menu-item><el-menu-item index="/orders/after-sales/history">售后记录</el-menu-item></el-sub-menu>
|
||||
<el-menu-item index="/products"><el-icon><Goods /></el-icon><template #title>商品管理</template></el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-container><el-header class="header"><el-button text @click="appStore.toggleSidebar"><el-icon :size="18"><Expand v-if="appStore.sidebarCollapsed" /><Fold v-else /></el-icon></el-button><el-breadcrumb separator="/"><el-breadcrumb-item>供应商管理</el-breadcrumb-item><el-breadcrumb-item>{{ String($route.meta.title || '') }}</el-breadcrumb-item></el-breadcrumb><div class="header-user">管理员</div></el-header><el-main class="main"><RouterView /></el-main></el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Expand, Fold, Goods, List, ShoppingBag, Tickets } from '@element-plus/icons-vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-layout { min-height: 100vh; }.sidebar { overflow: hidden; transition: width .2s; background: #001529; }.brand { display: flex; align-items: center; gap: 10px; height: 56px; padding: 0 20px; color: #fff; font-size: 16px; font-weight: 600; white-space: nowrap; }.sidebar :deep(.el-menu) { border-right: 0; }.header { display: flex; align-items: center; gap: 16px; height: 56px; border-bottom: 1px solid #ebeef5; background: #fff; }.header-user { margin-left: auto; color: #606266; }.main { min-height: calc(100vh - 56px); padding: 20px; background: #f5f7fa; }
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import ElePage from 'ele-admin-plus/es/ele-page/index.js'
|
||||
import 'element-plus/dist/index.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { pinia } from './stores'
|
||||
import './styles/index.scss'
|
||||
|
||||
createApp(App).component('ElePage', ElePage).use(pinia).use(router).use(ElementPlus).mount('#app')
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue'
|
||||
|
||||
const routes: RouteRecordRaw[] = [{ path: '/', component: AdminLayout, redirect: '/reconciliation', children: [
|
||||
{ path: 'reconciliation', name: 'Reconciliation', component: () => import('@/views/reconciliation/index.vue'), meta: { title: '订单对账' } },
|
||||
{ path: 'orders', redirect: '/orders/list' },
|
||||
{ path: 'orders/list', name: 'Orders', component: () => import('@/views/orders/index.vue'), meta: { title: '订单列表' } },
|
||||
{ path: 'orders/after-sales/audit', name: 'AfterSaleAudit', component: () => import('@/views/after-sales/audit.vue'), meta: { title: '售后审核' } },
|
||||
{ path: 'orders/after-sales/process', name: 'AfterSaleProcess', component: () => import('@/views/after-sales/process.vue'), meta: { title: '售后处理' } },
|
||||
{ path: 'orders/after-sales/history', name: 'AfterSaleHistory', component: () => import('@/views/after-sales/history.vue'), meta: { title: '售后记录' } },
|
||||
{ path: 'products', name: 'Products', component: () => import('@/views/products/index.vue'), meta: { title: '商品管理' } }
|
||||
] }]
|
||||
|
||||
export default createRouter({ history: createWebHistory(), routes })
|
||||
@@ -0,0 +1,2 @@
|
||||
import { defineStore } from 'pinia'
|
||||
export const useAppStore = defineStore('app', { state: () => ({ sidebarCollapsed: false }), actions: { toggleSidebar() { this.sidebarCollapsed = !this.sidebarCollapsed } } })
|
||||
@@ -0,0 +1,2 @@
|
||||
import { createPinia } from 'pinia'
|
||||
export const pinia = createPinia()
|
||||
@@ -0,0 +1 @@
|
||||
html, body, #app { min-width: 1180px; min-height: 100%; margin: 0; } body { color: #303133; font-family: "Microsoft YaHei", Arial, sans-serif; }.page-card { min-height: calc(100vh - 96px); padding: 24px; border-radius: 8px; background: #fff; }.page-title { display: flex; align-items: center; justify-content: space-between; margin-bottom: 26px; }.page-title h2 { margin: 0 0 8px; font-size: 20px; }.page-title p { margin: 0; color: #909399; font-size: 14px; }.query-form { padding: 18px 18px 2px; margin-bottom: 20px; border-radius: 6px; background: #f8fafc; }
|
||||
@@ -0,0 +1,2 @@
|
||||
<template><MerchantAfterSaleTable scene="audit" title="售后审核" description="审核用户提交的退货、换货申请。" /></template>
|
||||
<script setup lang="ts">import MerchantAfterSaleTable from '@/components/MerchantAfterSaleTable.vue'</script>
|
||||
@@ -0,0 +1,2 @@
|
||||
<template><MerchantAfterSaleTable scene="history" title="售后记录" description="查看已完成或已关闭的售后记录。" /></template>
|
||||
<script setup lang="ts">import MerchantAfterSaleTable from '@/components/MerchantAfterSaleTable.vue'</script>
|
||||
@@ -0,0 +1,2 @@
|
||||
<template><MerchantAfterSaleTable scene="process" title="售后处理" description="处理已审核售后,包括确认收货和换货发货。" /></template>
|
||||
<script setup lang="ts">import MerchantAfterSaleTable from '@/components/MerchantAfterSaleTable.vue'</script>
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<section class="page-card">
|
||||
<div class="page-title"><div><h2>订单列表</h2><p>查询订单并对待发货订单确认发货。</p></div></div>
|
||||
<el-form :inline="true" class="query-form"><el-form-item label="供应商"><el-select v-model="query.supplierId" placeholder="请选择供应商" filterable clearable><el-option v-for="supplier in suppliers" :key="supplier.supplierId" :label="`${supplier.supplierName}(${supplier.supplierId})`" :value="supplier.supplierId" /></el-select></el-form-item><el-form-item label="订单号"><el-input v-model="query.orderId" placeholder="订单编号或子订单号" clearable /></el-form-item><el-form-item label="工号"><el-input v-model="query.userId" placeholder="请输入工号" clearable /></el-form-item><el-form-item label="订单状态"><el-select v-model="query.orderState" placeholder="全部" clearable><el-option v-for="status in orderStates" :key="status.value" :label="status.label" :value="status.value" /></el-select></el-form-item><el-form-item><el-button type="primary" :loading="loading" @click="loadOrders">查询</el-button><el-button @click="reset">重置</el-button></el-form-item></el-form>
|
||||
<el-table v-loading="loading" :data="orders" border><el-table-column prop="orderNumberId" label="订单编号" min-width="160" /><el-table-column prop="orderId" label="子订单号" min-width="180" /><el-table-column prop="userId" label="工号" min-width="130" /><el-table-column label="订单状态" width="110"><template #default="{ row }">{{ stateText(row.orderState) }}</template></el-table-column><el-table-column prop="createTime" label="下单时间" min-width="170" /><el-table-column label="商品" min-width="180"><template #default="{ row }">{{ productNames(row) }}</template></el-table-column><el-table-column label="操作" width="120" fixed="right"><template #default="{ row }"><el-button v-if="row.orderState === 1" link type="primary" @click="openShipDialog(row)">确认发货</el-button><span v-else>-</span></template></el-table-column></el-table>
|
||||
<el-empty v-if="!loading && queried && !orders.length" description="暂无订单" />
|
||||
<el-dialog v-model="shipDialogVisible" title="确认发货" width="460px"><el-form label-width="90px"><el-form-item label="快递公司"><el-input v-model="shipForm.crrgBsnNm" placeholder="请输入快递公司" /></el-form-item><el-form-item label="快递单号"><el-input v-model="shipForm.logisticsOrderId" placeholder="请输入快递单号" /></el-form-item></el-form><template #footer><el-button @click="shipDialogVisible = false">取消</el-button><el-button type="primary" :loading="submitting" @click="submitShip">确认发货</el-button></template></el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
|
||||
type OrderProduct = { name?: string }
|
||||
type OrderItem = { orderId: string; orderNumberId?: string; userId?: string; orderState: number; createTime?: string; products?: OrderProduct[] }
|
||||
type SupplierItem = { supplierId: string; supplierName: string }
|
||||
const orderStates = [{ label: '待付款', value: 0 }, { label: '待发货', value: 1 }, { label: '已发货', value: 2 }, { label: '已取消', value: 3 }, { label: '已签收', value: 4 }, { label: '已完成', value: 5 }, { label: '换货', value: 6 }, { label: '已退款', value: 7 }]
|
||||
const query = reactive<{ supplierId: string; orderId: string; userId: string; orderState?: number }>({ supplierId: '', orderId: '', userId: '' })
|
||||
const suppliers = ref<SupplierItem[]>([]); const orders = ref<OrderItem[]>([]); const loading = ref(false); const queried = ref(false); const submitting = ref(false); const shipDialogVisible = ref(false); const currentOrder = ref<OrderItem>(); const shipForm = ref({ crrgBsnNm: '', logisticsOrderId: '' })
|
||||
const stateText = (state: number) => orderStates.find(item => item.value === state)?.label || '未知'
|
||||
const loadSuppliers = async () => { try { const data = await postAdmin<{ suppliers?: SupplierItem[] }>('merchant-supplier-list', {}); suppliers.value = data.suppliers || [] } catch (error) { ElMessage.error(error instanceof Error ? error.message : '供应商加载失败') } }
|
||||
const loadOrders = async () => { if (!query.supplierId) return ElMessage.warning('请选择供应商'); loading.value = true; try { const data = await postAdmin<{ orderItems?: OrderItem[] }>('merchant-order-list', query); orders.value = data.orderItems || []; queried.value = true } catch (error) { ElMessage.error(error instanceof Error ? error.message : '订单查询失败') } finally { loading.value = false } }
|
||||
const reset = () => { query.supplierId = ''; query.orderId = ''; query.userId = ''; query.orderState = undefined; orders.value = []; queried.value = false }
|
||||
const productNames = (order: OrderItem) => order.products?.map(item => item.name).filter(Boolean).join('、') || '-'
|
||||
const openShipDialog = (order: OrderItem) => { currentOrder.value = order; shipForm.value = { crrgBsnNm: '', logisticsOrderId: '' }; shipDialogVisible.value = true }
|
||||
const submitShip = async () => { if (!currentOrder.value || !shipForm.value.crrgBsnNm || !shipForm.value.logisticsOrderId) return ElMessage.warning('请填写完整发货信息'); submitting.value = true; try { await postAdmin('merchant-order-ship', { supplierId: query.supplierId, orderId: currentOrder.value.orderId, userId: currentOrder.value.userId, ...shipForm.value }); ElMessage.success('发货成功'); shipDialogVisible.value = false; await loadOrders() } catch (error) { ElMessage.error(error instanceof Error ? error.message : '发货失败') } finally { submitting.value = false } }
|
||||
onMounted(loadSuppliers)
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<section class="page-card">
|
||||
<div class="page-title"><div><h2>商品管理</h2><p>管理供应商商品的展示、库存和上下架状态。</p></div><el-button type="primary" @click="openCreate">新增商品</el-button></div>
|
||||
<el-form inline class="query-form" @submit.prevent><el-form-item label="商品"><el-input v-model="query.keyword" placeholder="商品名称或 SKU" clearable /></el-form-item><el-form-item label="分类"><el-select v-model="query.categoryId" placeholder="全部" clearable><el-option v-for="item in categories" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item><el-form-item><el-button type="primary" :loading="loading" @click="load">查询</el-button><el-button @click="reset">重置</el-button></el-form-item></el-form>
|
||||
<el-table :data="products" v-loading="loading" border><el-table-column prop="sku" label="SKU" min-width="140" /><el-table-column prop="name" label="商品名称" min-width="180" /><el-table-column prop="category" label="分类" width="120" /><el-table-column prop="price" label="售价" width="100" /><el-table-column prop="stock" label="库存" width="90" /><el-table-column prop="sortOrder" label="排序" width="80" /><el-table-column label="状态" width="90"><template #default="{ row }"><el-tag :type="row.enabled ? 'success' : 'info'">{{ row.enabled ? '上架' : '下架' }}</el-tag></template></el-table-column><el-table-column label="操作" width="130" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openEdit(row)">编辑</el-button><el-button link type="danger" @click="remove(row)">删除</el-button></template></el-table-column></el-table>
|
||||
<el-empty v-if="loaded && !products.length" description="暂无商品" />
|
||||
<el-dialog v-model="visible" :title="form.id ? '编辑商品' : '新增商品'" width="680px" destroy-on-close><el-form :model="form" label-width="90px"><el-row :gutter="16"><el-col :span="12"><el-form-item label="商品SKU" required><el-input v-model="form.sku" /></el-form-item></el-col><el-col :span="12"><el-form-item label="商品名称" required><el-input v-model="form.name" /></el-form-item></el-col><el-col :span="12"><el-form-item label="售价" required><el-input-number v-model="form.price" :min="0" :precision="2" /></el-form-item></el-col><el-col :span="12"><el-form-item label="原价"><el-input-number v-model="form.originPrice" :min="0" :precision="2" /></el-form-item></el-col><el-col :span="12"><el-form-item label="库存"><el-input-number v-model="form.stock" :min="0" :precision="0" /></el-form-item></el-col><el-col :span="12"><el-form-item label="排序"><el-input-number v-model="form.sortOrder" :min="0" :precision="0" /></el-form-item></el-col><el-col :span="12"><el-form-item label="分类"><el-select v-model="form.categoryId" @change="setCategory"><el-option v-for="item in categories" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col><el-col :span="12"><el-form-item label="状态"><el-switch v-model="form.enabled" active-text="上架" inactive-text="下架" /></el-form-item></el-col></el-row><el-form-item label="图片地址"><el-input v-model="form.imageUrl" /></el-form-item><el-form-item label="商品描述"><el-input v-model="form.description" type="textarea" :rows="3" /></el-form-item></el-form><template #footer><el-button @click="visible = false">取消</el-button><el-button type="primary" :loading="saving" @click="save">保存</el-button></template></el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
|
||||
type Category = { id: string; name: string }
|
||||
type Product = { id: number; sku: string; name: string; categoryId: string; category: string; price: string; originPrice: string; stock: number; sortOrder: number; enabled: boolean; imageUrl: string; description: string }
|
||||
type ProductForm = { id?: number; sku: string; name: string; price: number; originPrice?: number; stock: number; sortOrder: number; categoryId: string; category: string; imageUrl: string; description: string; enabled: boolean }
|
||||
const query = reactive({ keyword: '', categoryId: '' })
|
||||
const categories = ref<Category[]>([]); const products = ref<Product[]>([]); const loading = ref(false); const loaded = ref(false); const visible = ref(false); const saving = ref(false)
|
||||
const emptyForm = (): ProductForm => ({ sku: '', name: '', price: 0, stock: 0, sortOrder: 0, categoryId: '', category: '', imageUrl: '', description: '', enabled: true })
|
||||
const form = reactive<ProductForm>(emptyForm())
|
||||
const setForm = (value: ProductForm) => Object.assign(form, value)
|
||||
const load = async () => { loading.value = true; try { const data = await postAdmin<{ categories: Category[]; products: Product[] }>('merchant-product-list', query); categories.value = data.categories || []; products.value = data.products || []; loaded.value = true } catch (error) { ElMessage.error(error instanceof Error ? error.message : '商品查询失败') } finally { loading.value = false } }
|
||||
const reset = () => { query.keyword = ''; query.categoryId = ''; load() }
|
||||
const openCreate = () => { setForm(emptyForm()); visible.value = true }
|
||||
const openEdit = (product: Product) => { setForm({ id: product.id, sku: product.sku, name: product.name, price: Number(product.price), originPrice: product.originPrice ? Number(product.originPrice) : undefined, stock: product.stock, sortOrder: product.sortOrder, categoryId: product.categoryId, category: product.category, imageUrl: product.imageUrl, description: product.description, enabled: product.enabled }); visible.value = true }
|
||||
const setCategory = (categoryId: string) => { form.category = categories.value.find(item => item.id === categoryId)?.name || '' }
|
||||
const save = async () => { saving.value = true; try { await postAdmin('merchant-product-save', form); ElMessage.success('保存成功'); visible.value = false; await load() } catch (error) { ElMessage.error(error instanceof Error ? error.message : '保存失败') } finally { saving.value = false } }
|
||||
const remove = async (product: Product) => { try { await ElMessageBox.confirm(`确认删除商品“${product.name}”?`, '删除商品', { type: 'warning' }); await postAdmin('merchant-product-delete', { id: product.id }); ElMessage.success('删除成功'); await load() } catch (error) { if (error !== 'cancel' && error !== 'close') ElMessage.error(error instanceof Error ? error.message : '删除失败') } }
|
||||
onMounted(load)
|
||||
</script>
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<ele-page>
|
||||
<section class="page-card">
|
||||
<div class="page-title"><div><h2>订单对账</h2><p>查看 ZHGH 回传的子订单对账结果,并处理异常订单。</p></div></div>
|
||||
<el-form inline class="query-form" @submit.prevent>
|
||||
<el-form-item label="供应商"><el-input v-model="query.supplierId" placeholder="供应商 ID" clearable /></el-form-item>
|
||||
<el-form-item label="子订单"><el-input v-model="query.orderId" placeholder="子订单 ID" clearable /></el-form-item>
|
||||
<el-form-item label="对账状态"><el-select v-model="query.reconciliationStatus" clearable placeholder="全部"><el-option label="未对账" :value="0" /><el-option label="对账成功" :value="1" /><el-option label="对账异常" :value="2" /></el-select></el-form-item>
|
||||
<el-form-item><el-button type="primary" :loading="loading" @click="load">查询</el-button><el-button @click="reset">重置</el-button></el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="items" v-loading="loading" border>
|
||||
<el-table-column prop="orderId" label="子订单ID" min-width="180" />
|
||||
<el-table-column prop="mainOrderId" label="主订单ID" min-width="180" />
|
||||
<el-table-column prop="reconciliationStatus" label="对账状态" width="110"><template #default="{ row }"><el-tag :type="statusType(row.reconciliationStatus)">{{ statusText(row.reconciliationStatus) }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="diffReason" label="差异说明" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column label="平台金额" min-width="120"><template #default="{ row }">{{ row.platformTotalPrice || '-' }}</template></el-table-column>
|
||||
<el-table-column label="供应商金额" min-width="120"><template #default="{ row }">{{ row.supplierTotalPrice || '-' }}</template></el-table-column>
|
||||
<el-table-column prop="reconciliationBatchNo" label="对账批次" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="reconciliationTime" label="对账时间" min-width="170" />
|
||||
<el-table-column label="操作" width="90" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openEdit(row)">处理</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="loaded && !items.length" description="暂无对账结果" />
|
||||
</section>
|
||||
<el-dialog v-model="dialogVisible" title="处理对账结果" width="460px">
|
||||
<el-form label-width="90px"><el-form-item label="子订单"><span>{{ editing?.orderId }}</span></el-form-item><el-form-item label="对账状态"><el-select v-model="form.reconciliationStatus"><el-option label="未对账" :value="0" /><el-option label="对账成功" :value="1" /><el-option label="对账异常" :value="2" /></el-select></el-form-item><el-form-item label="差异类型"><el-input-number v-model="form.diffType" :min="0" :precision="0" /></el-form-item><el-form-item label="差异说明"><el-input v-model="form.diffReason" type="textarea" :rows="3" /></el-form-item></el-form>
|
||||
<template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" :loading="saving" @click="save">保存</el-button></template>
|
||||
</el-dialog>
|
||||
</ele-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
|
||||
type ReconciliationItem = { supplierId: string; orderId: string; mainOrderId: string; reconciliationStatus: number; diffType?: number; diffReason: string; platformTotalPrice: string; supplierTotalPrice: string; reconciliationBatchNo: string; reconciliationTime: string }
|
||||
const query = reactive<{ supplierId: string; orderId: string; reconciliationStatus?: number }>({ supplierId: '', orderId: '' })
|
||||
const items = ref<ReconciliationItem[]>([])
|
||||
const loading = ref(false)
|
||||
const loaded = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const saving = ref(false)
|
||||
const editing = ref<ReconciliationItem>()
|
||||
const form = reactive<{ reconciliationStatus: number; diffType?: number; diffReason: string }>({ reconciliationStatus: 0, diffReason: '' })
|
||||
const statusText = (status: number) => ['未对账', '对账成功', '对账异常'][status] || '未知'
|
||||
const statusType = (status: number) => status === 1 ? 'success' : status === 2 ? 'danger' : 'info'
|
||||
|
||||
const load = async () => {
|
||||
if (!query.supplierId.trim()) return ElMessage.warning('请输入供应商 ID')
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await postAdmin<{ reconciliationItems: ReconciliationItem[] }>('merchant-reconciliation-list', query)
|
||||
items.value = data.reconciliationItems || []
|
||||
loaded.value = true
|
||||
} catch (error) { ElMessage.error(error instanceof Error ? error.message : '查询失败') } finally { loading.value = false }
|
||||
}
|
||||
const reset = () => { query.supplierId = ''; query.orderId = ''; query.reconciliationStatus = undefined; items.value = []; loaded.value = false }
|
||||
const openEdit = (row: ReconciliationItem) => { editing.value = row; form.reconciliationStatus = row.reconciliationStatus; form.diffType = row.diffType; form.diffReason = row.diffReason || ''; dialogVisible.value = true }
|
||||
const save = async () => {
|
||||
if (!editing.value) return
|
||||
saving.value = true
|
||||
try {
|
||||
await postAdmin('merchant-reconciliation-update', { supplierId: editing.value.supplierId, orderId: editing.value.orderId, reconciliationStatus: form.reconciliationStatus, diffType: form.diffType, diffReason: form.diffReason })
|
||||
ElMessage.success('保存成功'); dialogVisible.value = false; await load()
|
||||
} catch (error) { ElMessage.error(error instanceof Error ? error.message : '保存失败') } finally { saving.value = false }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "Bundler", "strict": true, "jsx": "preserve", "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "lib": ["ES2020", "DOM"], "baseUrl": ".", "paths": { "@/*": ["src/*"] } },
|
||||
"include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } } })
|
||||
@@ -159,6 +159,37 @@ model SupplierOrderSub {
|
||||
@@map("supplier_order_sub")
|
||||
}
|
||||
|
||||
model SupplierReconciliationOrder {
|
||||
id Int @id @default(autoincrement())
|
||||
supplierId String
|
||||
orderId String
|
||||
mainOrderId String?
|
||||
userId String?
|
||||
reconciliationStatus Int @default(0)
|
||||
diffType Int?
|
||||
diffReason String? @db.Text
|
||||
platformTotalPrice Decimal? @db.Decimal(12, 2)
|
||||
platformCashAmount Decimal? @db.Decimal(12, 2)
|
||||
platformPointsPrice Decimal? @db.Decimal(12, 2)
|
||||
platformRefundPrice Decimal? @db.Decimal(12, 2)
|
||||
supplierTotalPrice Decimal? @db.Decimal(12, 2)
|
||||
supplierCashAmount Decimal? @db.Decimal(12, 2)
|
||||
supplierPointsPrice Decimal? @db.Decimal(12, 2)
|
||||
supplierRefundPrice Decimal? @db.Decimal(12, 2)
|
||||
reconciliationBatchNo String?
|
||||
reconciliationTime DateTime?
|
||||
resultPayload Json?
|
||||
createAt DateTime @default(now())
|
||||
updateAt DateTime @updatedAt
|
||||
deleted Int @default(0)
|
||||
|
||||
@@unique([supplierId, orderId])
|
||||
@@index([supplierId, reconciliationStatus])
|
||||
@@index([reconciliationBatchNo])
|
||||
@@index([mainOrderId])
|
||||
@@map("supplier_reconciliation_order")
|
||||
}
|
||||
|
||||
model SupplierOrderProduct {
|
||||
id Int @id @default(autoincrement())
|
||||
subOrderId String
|
||||
|
||||
@@ -113,3 +113,33 @@ CREATE TABLE IF NOT EXISTS `supplier_order_product` (
|
||||
FOREIGN KEY (`subOrderId`) REFERENCES `supplier_order_sub` (`orderId`)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='供应商子订单商品表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `supplier_reconciliation_order` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`supplierId` VARCHAR(191) NOT NULL COMMENT '供应商ID',
|
||||
`orderId` VARCHAR(191) NOT NULL COMMENT '子订单ID',
|
||||
`mainOrderId` VARCHAR(191) NULL COMMENT '主订单ID',
|
||||
`userId` VARCHAR(191) NULL COMMENT '平台用户ID',
|
||||
`reconciliationStatus` TINYINT NOT NULL DEFAULT 0 COMMENT '对账状态:0未对账,1对账成功,2对账异常',
|
||||
`diffType` INT NULL COMMENT '差异类型',
|
||||
`diffReason` TEXT NULL COMMENT '差异说明',
|
||||
`platformTotalPrice` DECIMAL(12, 2) NULL COMMENT '平台订单总金额',
|
||||
`platformCashAmount` DECIMAL(12, 2) NULL COMMENT '平台现金支付金额',
|
||||
`platformPointsPrice` DECIMAL(12, 2) NULL COMMENT '平台积分支付金额',
|
||||
`platformRefundPrice` DECIMAL(12, 2) NULL COMMENT '平台退款金额',
|
||||
`supplierTotalPrice` DECIMAL(12, 2) NULL COMMENT '供应商订单总金额',
|
||||
`supplierCashAmount` DECIMAL(12, 2) NULL COMMENT '供应商现金支付金额',
|
||||
`supplierPointsPrice` DECIMAL(12, 2) NULL COMMENT '供应商积分支付金额',
|
||||
`supplierRefundPrice` DECIMAL(12, 2) NULL COMMENT '供应商退款金额',
|
||||
`reconciliationBatchNo` VARCHAR(191) NULL COMMENT '对账批次号',
|
||||
`reconciliationTime` DATETIME(3) NULL COMMENT '对账完成时间',
|
||||
`resultPayload` JSON NULL COMMENT '对账回调原始结果',
|
||||
`createAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录创建时间',
|
||||
`updateAt` DATETIME(3) NOT NULL COMMENT '记录更新时间',
|
||||
`deleted` INT NOT NULL DEFAULT 0 COMMENT '删除状态:0未删除,1已删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `supplier_reconciliation_order_supplierId_orderId_key` (`supplierId`, `orderId`),
|
||||
KEY `supplier_reconciliation_order_supplierId_reconciliationStatus_idx` (`supplierId`, `reconciliationStatus`),
|
||||
KEY `supplier_reconciliation_order_reconciliationBatchNo_idx` (`reconciliationBatchNo`),
|
||||
KEY `supplier_reconciliation_order_mainOrderId_idx` (`mainOrderId`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='供应商子订单对账结果表';
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
CREATE TABLE IF NOT EXISTS `supplier_reconciliation_order`
|
||||
(
|
||||
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`supplierId` VARCHAR(191) NOT NULL COMMENT '供应商ID',
|
||||
`orderId` VARCHAR(191) NOT NULL COMMENT '子订单ID',
|
||||
`mainOrderId` VARCHAR(191) DEFAULT NULL COMMENT '主订单ID',
|
||||
`userId` VARCHAR(191) DEFAULT NULL COMMENT '用户ID',
|
||||
|
||||
`reconciliationStatus` TINYINT NOT NULL DEFAULT 0 COMMENT '对账状态:0未对账,1对账成功,2对账异常',
|
||||
`diffType` VARCHAR(64) DEFAULT NULL COMMENT '差异类型',
|
||||
`diffReason` VARCHAR(1000) DEFAULT NULL COMMENT '差异说明',
|
||||
|
||||
`platformTotalPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '平台订单总金额',
|
||||
`platformCashAmount` DECIMAL(12, 2) DEFAULT NULL COMMENT '平台现金支付金额',
|
||||
`platformPointsPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '平台积分支付金额',
|
||||
`platformRefundPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '平台退款金额',
|
||||
|
||||
`supplierTotalPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '供应商订单总金额',
|
||||
`supplierCashAmount` DECIMAL(12, 2) DEFAULT NULL COMMENT '供应商现金支付金额',
|
||||
`supplierPointsPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '供应商积分支付金额',
|
||||
`supplierRefundPrice` DECIMAL(12, 2) DEFAULT NULL COMMENT '供应商退款金额',
|
||||
|
||||
`reconciliationBatchNo` VARCHAR(64) DEFAULT NULL COMMENT '对账批次号',
|
||||
`reconciliationTime` DATETIME(3) DEFAULT NULL COMMENT '对账完成时间',
|
||||
`resultPayload` JSON DEFAULT NULL COMMENT '对账回调原始结果',
|
||||
|
||||
`createAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updateAt` DATETIME(3) NOT NULL,
|
||||
`deleted` INT NOT NULL DEFAULT 0,
|
||||
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_supplier_order` (`supplierId`, `orderId`),
|
||||
KEY `idx_supplier_status` (`supplierId`, `reconciliationStatus`),
|
||||
KEY `idx_batch_no` (`reconciliationBatchNo`),
|
||||
KEY `idx_main_order` (`mainOrderId`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci
|
||||
COMMENT ='供应商子订单对账结果表';
|
||||
@@ -23,6 +23,13 @@ export const merchantOrderListController = withErrorBoundary(async (request: Req
|
||||
return toHttpResponse(await merchantService.listOrders(await readJson<MerchantOrderListInput>(request)))
|
||||
})
|
||||
|
||||
export const merchantSupplierListController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return toHttpResponse(await merchantService.listSuppliers())
|
||||
})
|
||||
|
||||
export const merchantOrderShipController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
@@ -14,7 +14,11 @@ import { orderActionService } from '../services/order-action.service.js'
|
||||
import { orderCreateService, type CheckoutGoods } from '../services/order-create.service.js'
|
||||
import { orderListService } from '../services/order-list.service.js'
|
||||
import { orderPushEventService, type OrderPushEventInput } from '../services/order-push-event.service.js'
|
||||
import { supplierOpenOrderService, type SupplierOrderQueryRequest } from '../services/supplier-open-order.service.js'
|
||||
import {
|
||||
supplierOpenOrderService,
|
||||
type SupplierOrderQueryRequest,
|
||||
type SupplierReconciliationQueryRequest
|
||||
} from '../services/supplier-open-order.service.js'
|
||||
import { toHttpResponse } from './controller-response.js'
|
||||
|
||||
type OrderListRequest = {
|
||||
@@ -239,3 +243,21 @@ export const supplierOrderInfoController = withErrorBoundary(async (request: Req
|
||||
|
||||
return Response.json(await supplierOpenOrderService.queryOrderInfo(body))
|
||||
})
|
||||
|
||||
export const supplierReconciliationInfoController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
let body: SupplierReconciliationQueryRequest
|
||||
try {
|
||||
body = supplierOpenOrderService.parseBody(await readJson<SupplierReconciliationQueryRequest>(request))
|
||||
} catch (error) {
|
||||
return Response.json({
|
||||
resultCode: 400,
|
||||
message: error instanceof Error ? error.message : '请求参数解密失败',
|
||||
orderInfos: []
|
||||
})
|
||||
}
|
||||
|
||||
return Response.json(await supplierOpenOrderService.queryReconciliationInfo(body))
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Context } from '../utils/handler.js'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { readJson } from '../utils/request.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
import { productService } from '../services/product.service.js'
|
||||
import { toHttpResponse } from './controller-response.js'
|
||||
@@ -25,3 +26,28 @@ export const productDetailController = withErrorBoundary(async (request: Request
|
||||
|
||||
return toHttpResponse(await productService.getProductDetail(id, sku))
|
||||
})
|
||||
|
||||
export const merchantProductListController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
const body = await readJson<{ keyword?: string; categoryId?: string }>(request)
|
||||
return toHttpResponse(await productService.listManageProducts(String(body.keyword || '').trim(), String(body.categoryId || '').trim()))
|
||||
})
|
||||
|
||||
export const merchantProductSaveController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return toHttpResponse(await productService.saveManageProduct(
|
||||
await readJson<Parameters<typeof productService.saveManageProduct>[0]>(request)
|
||||
))
|
||||
})
|
||||
|
||||
export const merchantProductDeleteController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
const body = await readJson<{ id?: number }>(request)
|
||||
return toHttpResponse(await productService.deleteManageProduct(Number(body.id || 0)))
|
||||
})
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { Context } from '../utils/handler.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { readJson } from '../utils/request.js'
|
||||
import { toHttpResponse } from './controller-response.js'
|
||||
import { supplierOpenOrderService } from '../services/supplier-open-order.service.js'
|
||||
import {
|
||||
reconciliationResultService,
|
||||
type ReconciliationResultCallbackInput,
|
||||
type ReconciliationResultListInput,
|
||||
type ReconciliationResultUpdateInput
|
||||
} from '../services/reconciliation-result.service.js'
|
||||
|
||||
type UnReconciledMsgInput = {
|
||||
supplierId?: string
|
||||
token?: string
|
||||
timestamp?: number
|
||||
orderIds?: string
|
||||
unReconciledType?: number
|
||||
unReconciledMsg?: string
|
||||
uniqueSeqNo?: string
|
||||
AplParmObjct?: string
|
||||
}
|
||||
|
||||
export const supplierReconciliationResultController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
let body: ReconciliationResultCallbackInput
|
||||
try {
|
||||
body = supplierOpenOrderService.parseBody(await readJson<ReconciliationResultCallbackInput>(request)) as ReconciliationResultCallbackInput
|
||||
} catch (error) {
|
||||
return Response.json({ resultCode: 400, message: error instanceof Error ? error.message : '请求参数解密失败' })
|
||||
}
|
||||
return Response.json(await reconciliationResultService.receiveCallback(body))
|
||||
})
|
||||
|
||||
export const supplierUnReconciledMsgController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
let body: UnReconciledMsgInput
|
||||
try {
|
||||
body = supplierOpenOrderService.parseBody(await readJson<UnReconciledMsgInput>(request)) as UnReconciledMsgInput
|
||||
} catch (error) {
|
||||
return Response.json({ resultCode: 400, message: error instanceof Error ? error.message : '请求参数解密失败' })
|
||||
}
|
||||
const reconciliationResults = String(body.orderIds || '').split(',').map(orderId => orderId.trim()).filter(Boolean).map(orderId => ({
|
||||
orderId,
|
||||
reconciliationStatus: 2,
|
||||
diffType: body.unReconciledType === undefined ? undefined : Number(body.unReconciledType),
|
||||
diffReason: body.unReconciledMsg
|
||||
}))
|
||||
return Response.json(await reconciliationResultService.receiveCallback({
|
||||
supplierId: body.supplierId,
|
||||
token: body.token,
|
||||
timestamp: body.timestamp,
|
||||
reconciliationBatchNo: body.uniqueSeqNo,
|
||||
reconciliationResults
|
||||
}))
|
||||
})
|
||||
|
||||
export const merchantReconciliationListController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return toHttpResponse(await reconciliationResultService.list(await readJson<ReconciliationResultListInput>(request)))
|
||||
})
|
||||
|
||||
export const merchantReconciliationUpdateController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return toHttpResponse(await reconciliationResultService.update(await readJson<ReconciliationResultUpdateInput>(request)))
|
||||
})
|
||||
@@ -137,12 +137,38 @@ export const orderDao = {
|
||||
})
|
||||
},
|
||||
|
||||
findMerchantSubOrders(supplierId: string, orderState?: number) {
|
||||
findOpenApiReconciliationSubOrders(supplierId: string, orderIds: string[], startDate?: Date, endDate?: Date, orderState?: number) {
|
||||
return prisma.supplierOrderSub.findMany({
|
||||
where: {
|
||||
supplierId,
|
||||
deleted: 0,
|
||||
...(Number.isInteger(orderState) ? { orderState } : {})
|
||||
...(Number.isInteger(orderState) ? { orderState } : {}),
|
||||
...(orderIds.length ? { orderId: { in: orderIds } } : {}),
|
||||
...(startDate || endDate ? {
|
||||
completeTime: {
|
||||
...(startDate ? { gte: startDate } : {}),
|
||||
...(endDate ? { lte: endDate } : {})
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
include: {
|
||||
products: {
|
||||
where: { deleted: 0 },
|
||||
orderBy: { createAt: 'asc' }
|
||||
}
|
||||
},
|
||||
orderBy: { createAt: 'asc' }
|
||||
})
|
||||
},
|
||||
|
||||
findMerchantSubOrders(supplierId: string, orderState?: number, orderId?: string, userId?: string) {
|
||||
return prisma.supplierOrderSub.findMany({
|
||||
where: {
|
||||
supplierId,
|
||||
deleted: 0,
|
||||
...(Number.isInteger(orderState) ? { orderState } : {}),
|
||||
...(orderId ? { OR: [{ orderId: { contains: orderId } }, { orderNumberId: { contains: orderId } }] } : {}),
|
||||
...(userId ? { userId: { contains: userId } } : {})
|
||||
},
|
||||
include: {
|
||||
products: {
|
||||
|
||||
@@ -33,5 +33,31 @@ export const productDao = {
|
||||
]
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
findManageProducts(keyword: string, categoryId: string) {
|
||||
return prisma.supplierProduct.findMany({
|
||||
where: {
|
||||
deleted: 0,
|
||||
...(keyword ? { OR: [{ name: { contains: keyword } }, { sku: { contains: keyword } }] } : {}),
|
||||
...(categoryId ? { categoryId } : {})
|
||||
},
|
||||
orderBy: [
|
||||
{ sortOrder: 'asc' },
|
||||
{ createdAt: 'desc' }
|
||||
]
|
||||
})
|
||||
},
|
||||
|
||||
createProduct(data: Parameters<typeof prisma.supplierProduct.create>[0]['data']) {
|
||||
return prisma.supplierProduct.create({ data })
|
||||
},
|
||||
|
||||
updateProduct(id: number, data: Parameters<typeof prisma.supplierProduct.update>[0]['data']) {
|
||||
return prisma.supplierProduct.update({ where: { id }, data })
|
||||
},
|
||||
|
||||
findManageProduct(id: number) {
|
||||
return prisma.supplierProduct.findFirst({ where: { id, deleted: 0 } })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,13 @@ export const supplierDao = {
|
||||
return prisma.supplierInfo.findFirst({
|
||||
where: { supplierId, deleted: 0 }
|
||||
})
|
||||
},
|
||||
|
||||
findActiveSuppliers() {
|
||||
return prisma.supplierInfo.findMany({
|
||||
where: { deleted: 0 },
|
||||
select: { supplierId: true, supplierName: true },
|
||||
orderBy: { supplierName: 'asc' }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,15 @@ import {
|
||||
orderPayController,
|
||||
orderPushEventController,
|
||||
orderShipController,
|
||||
supplierOrderInfoController
|
||||
supplierOrderInfoController,
|
||||
supplierReconciliationInfoController
|
||||
} from './controllers/order.controller.js'
|
||||
import {
|
||||
merchantReconciliationListController,
|
||||
merchantReconciliationUpdateController,
|
||||
supplierReconciliationResultController,
|
||||
supplierUnReconciledMsgController
|
||||
} from './controllers/reconciliation.controller.js'
|
||||
import {
|
||||
merchantAfterSaleAuditController,
|
||||
merchantAfterSaleDetailController,
|
||||
@@ -42,9 +49,16 @@ import {
|
||||
merchantAfterSaleReceiveController,
|
||||
merchantAfterSaleShipController,
|
||||
merchantOrderListController,
|
||||
merchantOrderShipController
|
||||
merchantOrderShipController,
|
||||
merchantSupplierListController
|
||||
} from './controllers/merchant.controller.js'
|
||||
import { productDetailController, productListController } from './controllers/product.controller.js'
|
||||
import {
|
||||
merchantProductDeleteController,
|
||||
merchantProductListController,
|
||||
merchantProductSaveController,
|
||||
productDetailController,
|
||||
productListController
|
||||
} from './controllers/product.controller.js'
|
||||
import {
|
||||
supplierDebugTokensController,
|
||||
supplierInfoController,
|
||||
@@ -75,6 +89,7 @@ const handlers: Record<string, FunctionHandler> = {
|
||||
'merchant-after-sale-ship': merchantAfterSaleShipController,
|
||||
'merchant-order-list': merchantOrderListController,
|
||||
'merchant-order-ship': merchantOrderShipController,
|
||||
'merchant-supplier-list': merchantSupplierListController,
|
||||
'order-cancel-main': orderCancelMainController,
|
||||
'order-cancel-paid-main': orderCancelPaidMainController,
|
||||
'order-cancel-sub': orderCancelSubController,
|
||||
@@ -87,9 +102,17 @@ const handlers: Record<string, FunctionHandler> = {
|
||||
'order-ship': orderShipController,
|
||||
'product-detail': productDetailController,
|
||||
'product-list': productListController,
|
||||
'merchant-product-list': merchantProductListController,
|
||||
'merchant-product-save': merchantProductSaveController,
|
||||
'merchant-product-delete': merchantProductDeleteController,
|
||||
'supplier-debug-tokens': supplierDebugTokensController,
|
||||
'supplier-info': supplierInfoController,
|
||||
'supplier-order-info': supplierOrderInfoController,
|
||||
'supplier-reconciliation-info': supplierReconciliationInfoController,
|
||||
'supplier-reconciliation-result': supplierReconciliationResultController,
|
||||
'supplier-unreconciled-msg': supplierUnReconciledMsgController,
|
||||
'merchant-reconciliation-list': merchantReconciliationListController,
|
||||
'merchant-reconciliation-update': merchantReconciliationUpdateController,
|
||||
'supplier-points': supplierPointsController,
|
||||
'supplier-token': supplierTokenController,
|
||||
upload: uploadController,
|
||||
@@ -174,7 +197,11 @@ router.delete('/api/supplier-logs', ctx => {
|
||||
}
|
||||
})
|
||||
router.all('/api/:name', async ctx => dispatch(ctx.params.name)(ctx))
|
||||
router.all('/api/admin/:name', async ctx => dispatch(ctx.params.name)(ctx))
|
||||
router.post('/supplierOpenApi/pointOrder/queryOrderInfo', dispatch('supplier-order-info'))
|
||||
router.post('/supplierOpenApi/pointOrder/queryReconciliationInfo', dispatch('supplier-reconciliation-info'))
|
||||
router.post('/supplierOpenApi/pointOrder/reconciliationResult', dispatch('supplier-reconciliation-result'))
|
||||
router.post('/supplierOpenApi/pointOrder/unReconciledMsg', dispatch('supplier-unreconciled-msg'))
|
||||
router.post('/supplierOpenApi/debug/tokens', dispatch('supplier-debug-tokens'))
|
||||
|
||||
app.use(router.routes())
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ORDER_STATE
|
||||
} from '../constants/order.js'
|
||||
import { orderDao } from '../dao/order.dao.js'
|
||||
import { supplierDao } from '../dao/supplier.dao.js'
|
||||
import { afterSaleService, type AfterSaleAuditInput, type AfterSaleLogisticsInput } from './after-sale.service.js'
|
||||
import { orderActionService } from './order-action.service.js'
|
||||
import { logger } from '../utils/logger.js'
|
||||
@@ -13,6 +14,8 @@ import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||
export type MerchantOrderListInput = {
|
||||
supplierId?: string
|
||||
orderState?: number
|
||||
orderId?: string
|
||||
userId?: string
|
||||
}
|
||||
|
||||
export type MerchantShipInput = {
|
||||
@@ -105,14 +108,16 @@ const isOrderSigned = (orderState?: number | null) => signedOrderStates.has(Numb
|
||||
export const merchantService = {
|
||||
async listOrders(input: MerchantOrderListInput) {
|
||||
const supplierId = String(input.supplierId || '').trim()
|
||||
const orderState = Number(input.orderState ?? ORDER_STATE.FINISH_PAY)
|
||||
const orderState = input.orderState === undefined ? undefined : Number(input.orderState)
|
||||
const orderId = String(input.orderId || '').trim()
|
||||
const userId = String(input.userId || '').trim()
|
||||
logger.info('merchant order list query', { supplierId, orderState })
|
||||
|
||||
if (!supplierId) {
|
||||
return serviceFail('供应商不能为空')
|
||||
}
|
||||
|
||||
const orders = await orderDao.findMerchantSubOrders(supplierId, orderState)
|
||||
const orders = await orderDao.findMerchantSubOrders(supplierId, orderState, orderId, userId)
|
||||
const afterSales = await orderDao.findMerchantAfterSales(supplierId, pendingShipExcludeAfterSaleStates)
|
||||
const afterSaleSubOrderIds = new Set(afterSales.map(item => item.subOrderId).filter(Boolean))
|
||||
const orderItems = orders.filter(order => !afterSaleSubOrderIds.has(order.orderId)).map(order => ({
|
||||
@@ -139,6 +144,11 @@ export const merchantService = {
|
||||
return serviceOk({ orderItems })
|
||||
},
|
||||
|
||||
async listSuppliers() {
|
||||
const suppliers = await supplierDao.findActiveSuppliers()
|
||||
return serviceOk({ suppliers })
|
||||
},
|
||||
|
||||
shipOrder(input: MerchantShipInput) {
|
||||
return orderActionService.shipOrder({
|
||||
supplierId: String(input.supplierId || '').trim(),
|
||||
|
||||
@@ -35,7 +35,9 @@ const toProductItem = (product: ProductRecord) => {
|
||||
category: product.category || '',
|
||||
tag: product.tag || '',
|
||||
sales: product.sales || '',
|
||||
stock: product.stock
|
||||
stock: product.stock,
|
||||
sortOrder: product.sortOrder,
|
||||
enabled: product.enabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,5 +66,67 @@ export const productService = {
|
||||
}
|
||||
|
||||
return serviceOk(toProductItem(product))
|
||||
},
|
||||
|
||||
async listManageProducts(keyword: string, categoryId: string) {
|
||||
const products = await productDao.findManageProducts(keyword, categoryId)
|
||||
return serviceOk({ categories, products: products.map(toProductItem) })
|
||||
},
|
||||
|
||||
async saveManageProduct(input: {
|
||||
id?: number
|
||||
sku?: string
|
||||
name?: string
|
||||
price?: number
|
||||
number?: number
|
||||
stock?: number
|
||||
taxRate?: number
|
||||
categoryId?: string
|
||||
category?: string
|
||||
imageUrl?: string
|
||||
description?: string
|
||||
detail?: string
|
||||
originPrice?: number
|
||||
tag?: string
|
||||
sales?: string
|
||||
sortOrder?: number
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const sku = String(input.sku || '').trim()
|
||||
const name = String(input.name || '').trim()
|
||||
const price = Number(input.price)
|
||||
if (!sku || !name || Number.isNaN(price) || price < 0) {
|
||||
return serviceFail('商品SKU、名称和价格不能为空')
|
||||
}
|
||||
|
||||
const data = {
|
||||
sku,
|
||||
name,
|
||||
price,
|
||||
number: Math.max(Number(input.number || 0), 0),
|
||||
stock: Math.max(Number(input.stock || 0), 0),
|
||||
taxRate: Number(input.taxRate ?? 0.13),
|
||||
categoryId: String(input.categoryId || '').trim() || null,
|
||||
category: String(input.category || '').trim() || null,
|
||||
imageUrl: String(input.imageUrl || '').trim() || null,
|
||||
description: String(input.description || '').trim() || null,
|
||||
detail: String(input.detail || '').trim() || null,
|
||||
originPrice: input.originPrice === undefined || input.originPrice === null ? null : Number(input.originPrice),
|
||||
tag: String(input.tag || '').trim() || null,
|
||||
sales: String(input.sales || '').trim() || null,
|
||||
sortOrder: Math.max(Number(input.sortOrder || 0), 0),
|
||||
enabled: input.enabled !== false
|
||||
}
|
||||
const id = Number(input.id || 0)
|
||||
const product = id > 0 ? await productDao.updateProduct(id, data) : await productDao.createProduct(data)
|
||||
return serviceOk(toProductItem(product))
|
||||
},
|
||||
|
||||
async deleteManageProduct(id: number) {
|
||||
if (!Number.isInteger(id) || id <= 0) return serviceFail('商品ID不正确')
|
||||
const product = await productDao.findManageProduct(id)
|
||||
if (!product) return serviceFail('商品不存在')
|
||||
await productDao.updateProduct(id, { deleted: 1 })
|
||||
return serviceOk({})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import type { Prisma } from '@prisma/client'
|
||||
import { prisma } from '../utils/prisma.js'
|
||||
import { supplierDao } from '../dao/supplier.dao.js'
|
||||
import { verifySupplierOpenSignature } from '../utils/supplier-open-crypto.js'
|
||||
import { serviceFail, serviceOk } from '../types/service-result.js'
|
||||
|
||||
export type ReconciliationResultItem = {
|
||||
orderId?: string
|
||||
mainOrderId?: string
|
||||
userId?: string
|
||||
reconciliationStatus?: number
|
||||
diffType?: number
|
||||
diffReason?: string
|
||||
platformTotalPrice?: number
|
||||
platformCashAmount?: number
|
||||
platformPointsPrice?: number
|
||||
platformRefundPrice?: number
|
||||
supplierTotalPrice?: number
|
||||
supplierCashAmount?: number
|
||||
supplierPointsPrice?: number
|
||||
supplierRefundPrice?: number
|
||||
}
|
||||
|
||||
export type ReconciliationResultCallbackInput = {
|
||||
supplierId?: string
|
||||
token?: string
|
||||
timestamp?: number
|
||||
reconciliationBatchNo?: string
|
||||
reconciliationTime?: string
|
||||
reconciliationResults?: ReconciliationResultItem[]
|
||||
AplParmObjct?: string
|
||||
}
|
||||
|
||||
export type ReconciliationResultListInput = {
|
||||
supplierId?: string
|
||||
reconciliationStatus?: number
|
||||
orderId?: string
|
||||
}
|
||||
|
||||
export type ReconciliationResultUpdateInput = {
|
||||
supplierId?: string
|
||||
orderId?: string
|
||||
reconciliationStatus?: number
|
||||
diffType?: number
|
||||
diffReason?: string
|
||||
}
|
||||
|
||||
const toNumber = (value?: number) => value === undefined || value === null ? null : Number(value)
|
||||
|
||||
const toTime = (value?: string) => {
|
||||
if (!value) return new Date()
|
||||
const time = new Date(value)
|
||||
return Number.isNaN(time.getTime()) ? new Date() : time
|
||||
}
|
||||
|
||||
const resultItem = (record: Awaited<ReturnType<typeof prisma.supplierReconciliationOrder.findMany>>[number]) => ({
|
||||
supplierId: record.supplierId,
|
||||
orderId: record.orderId,
|
||||
mainOrderId: record.mainOrderId || '',
|
||||
userId: record.userId || '',
|
||||
reconciliationStatus: record.reconciliationStatus,
|
||||
diffType: record.diffType,
|
||||
diffReason: record.diffReason || '',
|
||||
platformTotalPrice: record.platformTotalPrice?.toFixed(2) || '',
|
||||
platformCashAmount: record.platformCashAmount?.toFixed(2) || '',
|
||||
platformPointsPrice: record.platformPointsPrice?.toFixed(2) || '',
|
||||
platformRefundPrice: record.platformRefundPrice?.toFixed(2) || '',
|
||||
supplierTotalPrice: record.supplierTotalPrice?.toFixed(2) || '',
|
||||
supplierCashAmount: record.supplierCashAmount?.toFixed(2) || '',
|
||||
supplierPointsPrice: record.supplierPointsPrice?.toFixed(2) || '',
|
||||
supplierRefundPrice: record.supplierRefundPrice?.toFixed(2) || '',
|
||||
reconciliationBatchNo: record.reconciliationBatchNo || '',
|
||||
reconciliationTime: record.reconciliationTime,
|
||||
updateAt: record.updateAt
|
||||
})
|
||||
|
||||
export const reconciliationResultService = {
|
||||
async receiveCallback(input: ReconciliationResultCallbackInput) {
|
||||
const supplierId = String(input.supplierId || '').trim()
|
||||
const token = String(input.token || '').trim()
|
||||
const timestamp = Number(input.timestamp || 0)
|
||||
const records = Array.isArray(input.reconciliationResults) ? input.reconciliationResults : []
|
||||
if (!supplierId || !token || !timestamp || !records.length) {
|
||||
return { resultCode: 400, message: '供应商、签名或对账结果不能为空' }
|
||||
}
|
||||
|
||||
const supplier = await supplierDao.findActiveBySupplierId(supplierId)
|
||||
if (!supplier) return { resultCode: 404, message: '供应商信息不存在' }
|
||||
if (!verifySupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp, token)) {
|
||||
return { resultCode: 401, message: '签名无效或已过期' }
|
||||
}
|
||||
|
||||
const reconciliationTime = toTime(input.reconciliationTime)
|
||||
for (const record of records) {
|
||||
const orderId = String(record.orderId || '').trim()
|
||||
const status = Number(record.reconciliationStatus)
|
||||
if (!orderId || ![0, 1, 2].includes(status)) {
|
||||
return { resultCode: 400, message: '子订单ID或对账状态不正确' }
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.$transaction(records.map(record => {
|
||||
const orderId = String(record.orderId || '').trim()
|
||||
const data = {
|
||||
mainOrderId: String(record.mainOrderId || '').trim() || null,
|
||||
userId: String(record.userId || '').trim() || null,
|
||||
reconciliationStatus: Number(record.reconciliationStatus),
|
||||
diffType: record.diffType === undefined || record.diffType === null ? null : Number(record.diffType),
|
||||
diffReason: String(record.diffReason || '').trim() || null,
|
||||
platformTotalPrice: toNumber(record.platformTotalPrice),
|
||||
platformCashAmount: toNumber(record.platformCashAmount),
|
||||
platformPointsPrice: toNumber(record.platformPointsPrice),
|
||||
platformRefundPrice: toNumber(record.platformRefundPrice),
|
||||
supplierTotalPrice: toNumber(record.supplierTotalPrice),
|
||||
supplierCashAmount: toNumber(record.supplierCashAmount),
|
||||
supplierPointsPrice: toNumber(record.supplierPointsPrice),
|
||||
supplierRefundPrice: toNumber(record.supplierRefundPrice),
|
||||
reconciliationBatchNo: String(input.reconciliationBatchNo || '').trim() || null,
|
||||
reconciliationTime,
|
||||
resultPayload: record as Prisma.InputJsonValue,
|
||||
deleted: 0
|
||||
}
|
||||
return prisma.supplierReconciliationOrder.upsert({
|
||||
where: { supplierId_orderId: { supplierId, orderId } },
|
||||
update: data,
|
||||
create: { supplierId, orderId, ...data }
|
||||
})
|
||||
}))
|
||||
|
||||
return { resultCode: 0, message: '成功' }
|
||||
},
|
||||
|
||||
async list(input: ReconciliationResultListInput) {
|
||||
const supplierId = String(input.supplierId || '').trim()
|
||||
if (!supplierId) return serviceFail('供应商不能为空')
|
||||
const reconciliationStatus = input.reconciliationStatus === undefined ? undefined : Number(input.reconciliationStatus)
|
||||
if (reconciliationStatus !== undefined && ![0, 1, 2].includes(reconciliationStatus)) {
|
||||
return serviceFail('对账状态不正确')
|
||||
}
|
||||
|
||||
const records = await prisma.supplierReconciliationOrder.findMany({
|
||||
where: {
|
||||
supplierId,
|
||||
deleted: 0,
|
||||
...(reconciliationStatus === undefined ? {} : { reconciliationStatus }),
|
||||
...(String(input.orderId || '').trim() ? { orderId: { contains: String(input.orderId).trim() } } : {})
|
||||
},
|
||||
orderBy: { updateAt: 'desc' }
|
||||
})
|
||||
return serviceOk({ reconciliationItems: records.map(resultItem) })
|
||||
},
|
||||
|
||||
async update(input: ReconciliationResultUpdateInput) {
|
||||
const supplierId = String(input.supplierId || '').trim()
|
||||
const orderId = String(input.orderId || '').trim()
|
||||
const reconciliationStatus = Number(input.reconciliationStatus)
|
||||
if (!supplierId || !orderId || ![0, 1, 2].includes(reconciliationStatus)) {
|
||||
return serviceFail('供应商、子订单或对账状态不正确')
|
||||
}
|
||||
|
||||
const record = await prisma.supplierReconciliationOrder.findUnique({
|
||||
where: { supplierId_orderId: { supplierId, orderId } }
|
||||
})
|
||||
if (!record || record.deleted !== 0) return serviceFail('对账结果不存在')
|
||||
|
||||
await prisma.supplierReconciliationOrder.update({
|
||||
where: { supplierId_orderId: { supplierId, orderId } },
|
||||
data: {
|
||||
reconciliationStatus,
|
||||
diffType: input.diffType === undefined || input.diffType === null ? null : Number(input.diffType),
|
||||
diffReason: String(input.diffReason || '').trim() || null
|
||||
}
|
||||
})
|
||||
return serviceOk({})
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,19 @@ export type SupplierOrderQueryRequest = {
|
||||
AplParmObjct?: string
|
||||
}
|
||||
|
||||
export type SupplierReconciliationQueryRequest = {
|
||||
supplierId?: string
|
||||
token?: string
|
||||
reconciliationType?: number
|
||||
orderId?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
timestamp?: number
|
||||
AplParmObjct?: string
|
||||
}
|
||||
|
||||
const parseBody = (body: SupplierOrderQueryRequest) => {
|
||||
if (!body.AplParmObjct) return body
|
||||
|
||||
@@ -149,5 +162,66 @@ export const supplierOpenOrderService = {
|
||||
}))
|
||||
|
||||
return { resultCode: 0, message: '成功', orderInfo }
|
||||
},
|
||||
|
||||
async queryReconciliationInfo(body: SupplierReconciliationQueryRequest) {
|
||||
const supplierId = String(body.supplierId || '').trim()
|
||||
const token = String(body.token || '').trim()
|
||||
const reconciliationType = Number(body.reconciliationType || 0)
|
||||
const orderIds = String(body.orderId || '')
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean)
|
||||
const timestamp = Number(body.timestamp || 0)
|
||||
|
||||
if (!supplierId || !token || !timestamp) {
|
||||
return { resultCode: 400, message: '供应商或签名信息不能为空', orderInfos: [] }
|
||||
}
|
||||
if (reconciliationType === 1 && !body.startDate) {
|
||||
return { resultCode: 400, message: '请选择开始日期', orderInfos: [] }
|
||||
}
|
||||
if (reconciliationType === 2 && !orderIds.length) {
|
||||
return { resultCode: 400, message: '订单号不能为空', orderInfos: [] }
|
||||
}
|
||||
if (![1, 2].includes(reconciliationType)) {
|
||||
return { resultCode: 400, message: '对账类型不正确', orderInfos: [] }
|
||||
}
|
||||
|
||||
const supplier = await supplierDao.findActiveBySupplierId(supplierId)
|
||||
if (!supplier) {
|
||||
return { resultCode: 404, message: '供应商信息不存在', orderInfos: [] }
|
||||
}
|
||||
if (!verifySupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp, token)) {
|
||||
return { resultCode: 401, message: '签名无效或已过期', orderInfos: [] }
|
||||
}
|
||||
|
||||
const startDate = body.startDate ? new Date(`${body.startDate}T00:00:00`) : undefined
|
||||
const endDate = body.endDate ? new Date(`${body.endDate}T23:59:59`) : undefined
|
||||
if ((startDate && Number.isNaN(startDate.getTime())) || (endDate && Number.isNaN(endDate.getTime()))) {
|
||||
return { resultCode: 400, message: '对账日期格式不正确', orderInfos: [] }
|
||||
}
|
||||
|
||||
const orders = await orderDao.findOpenApiReconciliationSubOrders(
|
||||
supplierId,
|
||||
orderIds,
|
||||
startDate,
|
||||
endDate,
|
||||
reconciliationType === 1 ? ORDER_STATE.COMPLETED : undefined
|
||||
)
|
||||
const page = Math.max(Number(body.page || 1), 1)
|
||||
const pageSize = Math.max(Number(body.pageSize || 100), 1)
|
||||
const totalPage = Math.max(Math.ceil(orders.length / pageSize), 1)
|
||||
const pagedOrders = orders.slice((page - 1) * pageSize, page * pageSize)
|
||||
const orderInfos = pagedOrders.map(order => ({
|
||||
orderId: order.orderId,
|
||||
supplierId: order.supplierId,
|
||||
totalPrice: toMoney(order.totalPrice),
|
||||
pointsPrice: toMoney(order.pointsPrice),
|
||||
wPayOrAPay: toMoney(order.wPayOrAPay),
|
||||
invoiceNakedPrice: toMoney(order.totalPrice),
|
||||
orderProInfos: toOrderProductInfo(order)
|
||||
}))
|
||||
|
||||
return { resultCode: 0, message: '成功', totalPage, currentPage: page, orderInfos }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user