feat: 积分商城-供应商-对账-发票
This commit is contained in:
@@ -7,6 +7,8 @@ const addressRoutes = new Map([
|
|||||||
['/supplierOpenApi/pointOrder/queryOrderInfo', '/supplierOpenApi/pointOrder/queryOrderInfo'],
|
['/supplierOpenApi/pointOrder/queryOrderInfo', '/supplierOpenApi/pointOrder/queryOrderInfo'],
|
||||||
['/supplierOpenApi/pointOrder/queryReconciliationInfo', '/supplierOpenApi/pointOrder/queryReconciliationInfo'],
|
['/supplierOpenApi/pointOrder/queryReconciliationInfo', '/supplierOpenApi/pointOrder/queryReconciliationInfo'],
|
||||||
['/supplierOpenApi/pointOrder/applyForInvoices', '/supplierOpenApi/pointOrder/applyForInvoices'],
|
['/supplierOpenApi/pointOrder/applyForInvoices', '/supplierOpenApi/pointOrder/applyForInvoices'],
|
||||||
|
['/supplierOpenApi/pointOrder/queryInvoicesInfo', '/supplierOpenApi/pointOrder/queryInvoicesInfo'],
|
||||||
|
['/supplierOpenApi/pointOrder/queryFileBase64Info', '/supplierOpenApi/pointOrder/queryFileBase64Info'],
|
||||||
['/supplierOpenApi/pointOrder/unReconciledMsg', '/supplierOpenApi/pointOrder/unReconciledMsg'],
|
['/supplierOpenApi/pointOrder/unReconciledMsg', '/supplierOpenApi/pointOrder/unReconciledMsg'],
|
||||||
['/supplierOpenApi/pointOrder/reconciliationResult', '/supplierOpenApi/pointOrder/reconciliationResult']
|
['/supplierOpenApi/pointOrder/reconciliationResult', '/supplierOpenApi/pointOrder/reconciliationResult']
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ const saveUser = data => {
|
|||||||
export const useUserStore = defineStore('joyfulSelectionUser', {
|
export const useUserStore = defineStore('joyfulSelectionUser', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
userId: '',
|
userId: '',
|
||||||
encryptedParam: '',
|
|
||||||
supplierIdentification: '',
|
supplierIdentification: '',
|
||||||
supplierId: '',
|
supplierId: '',
|
||||||
supplierName: '',
|
supplierName: '',
|
||||||
@@ -54,16 +53,27 @@ export const useUserStore = defineStore('joyfulSelectionUser', {
|
|||||||
})
|
})
|
||||||
this.loaded = true
|
this.loaded = true
|
||||||
},
|
},
|
||||||
hydrateFromRoute(query = {}) {
|
async hydrateFromRoute(query = {}) {
|
||||||
if (!this.loaded) {
|
if (!this.loaded) {
|
||||||
this.loadUser()
|
this.loadUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
const encryptedParam = query.AplParmObjct || query.aplParmObjct || ''
|
const keyword = query.keyword || new URLSearchParams(window.location.search).get('keyword') || ''
|
||||||
|
let nextUserId = query.userId || ''
|
||||||
|
|
||||||
if (encryptedParam) {
|
if (keyword) {
|
||||||
this.encryptedParam = encryptedParam
|
const sessionKeyword = String(keyword).replace(/ /g, '+')
|
||||||
this.userId = ''
|
console.log('[supplier-session] keyword', {
|
||||||
|
routeKeyword: query.keyword,
|
||||||
|
searchKeyword: new URLSearchParams(window.location.search).get('keyword') || '',
|
||||||
|
sessionKeyword
|
||||||
|
})
|
||||||
|
const data = await postFunction('supplier-session', { keyword: sessionKeyword })
|
||||||
|
nextUserId = data?.userId || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextUserId && nextUserId !== this.userId) {
|
||||||
|
this.userId = nextUserId
|
||||||
this.pointsBalance = 0
|
this.pointsBalance = 0
|
||||||
}
|
}
|
||||||
if (!this.supplierIdentification) {
|
if (!this.supplierIdentification) {
|
||||||
@@ -92,10 +102,7 @@ export const useUserStore = defineStore('joyfulSelectionUser', {
|
|||||||
if (!this.loaded) {
|
if (!this.loaded) {
|
||||||
this.loadUser()
|
this.loadUser()
|
||||||
}
|
}
|
||||||
if (!this.supplierIdentification || (!this.encryptedParam && this.hasToken)) {
|
if (!this.userId || !this.supplierIdentification || this.hasToken) {
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!this.encryptedParam) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.loadingToken && bridgeTokenPromise) {
|
if (this.loadingToken && bridgeTokenPromise) {
|
||||||
@@ -109,12 +116,10 @@ export const useUserStore = defineStore('joyfulSelectionUser', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await postFunction('supplier-token', {
|
const data = await postFunction('supplier-token', {
|
||||||
AplParmObjct: this.encryptedParam,
|
userId: this.userId,
|
||||||
supplierIdentification: this.supplierIdentification
|
supplierIdentification: this.supplierIdentification
|
||||||
})
|
})
|
||||||
|
|
||||||
this.userId = data?.userId || ''
|
|
||||||
this.encryptedParam = ''
|
|
||||||
this.supplierId = data?.supplierId || this.supplierId
|
this.supplierId = data?.supplierId || this.supplierId
|
||||||
this.supplierName = data?.supplierName || this.supplierName
|
this.supplierName = data?.supplierName || this.supplierName
|
||||||
this.persistUser()
|
this.persistUser()
|
||||||
|
|||||||
@@ -49,15 +49,17 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
...mapActions(useCartStore, ['loadCart']),
|
...mapActions(useCartStore, ['loadCart']),
|
||||||
...mapActions(useUserStore, ['hydrateFromRoute', 'fetchBridgeToken']),
|
...mapActions(useUserStore, ['hydrateFromRoute', 'fetchBridgeToken']),
|
||||||
prepareUserSession() {
|
async prepareUserSession() {
|
||||||
this.sessionLoading = true
|
this.sessionLoading = true
|
||||||
this.hydrateFromRoute(this.$route.query)
|
try {
|
||||||
this.fetchBridgeToken().catch(error => {
|
await this.hydrateFromRoute(this.$route.query)
|
||||||
|
await this.fetchBridgeToken()
|
||||||
|
} catch (error) {
|
||||||
console.warn('获取平台授权令牌失败', error)
|
console.warn('获取平台授权令牌失败', error)
|
||||||
this.$toast(error.message || '供应商验证失败,请稍后再试')
|
this.$toast(error.message || '供应商验证失败,请稍后再试')
|
||||||
}).finally(() => {
|
} finally {
|
||||||
this.sessionLoading = false
|
this.sessionLoading = false
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<van-tabs v-model="activeTab" sticky offset-top="46" color="#ff5b20" @change="loadCurrentTab">
|
<van-tabs v-model="activeTab" sticky offset-top="46" color="#ff5b20" @change="loadCurrentTab">
|
||||||
<van-tab title="待发货" name="ship" :badge="tabBadge('ship')">
|
<van-tab title="待发货" name="ship" :badge="tabBadge('ship')">
|
||||||
<section class="list-panel">
|
<section class="list-panel">
|
||||||
|
<button v-if="orderItems.length" type="button" class="batch-ship-button" @click="quickShipAll">一键发货</button>
|
||||||
<section v-if="loading" class="skeleton-card">
|
<section v-if="loading" class="skeleton-card">
|
||||||
<van-skeleton title :row="3" />
|
<van-skeleton title :row="3" />
|
||||||
<van-skeleton title :row="3" />
|
<van-skeleton title :row="3" />
|
||||||
@@ -359,11 +360,26 @@ export default {
|
|||||||
openShipPopup(item) {
|
openShipPopup(item) {
|
||||||
this.currentOrder = item
|
this.currentOrder = item
|
||||||
this.shipForm = {
|
this.shipForm = {
|
||||||
crrgBsnNm: item.crrgBsnNm || '',
|
crrgBsnNm: item.crrgBsnNm || '韵达快递',
|
||||||
logisticsOrderId: item.logisticsOrderId || ''
|
logisticsOrderId: item.logisticsOrderId || 'YT20260903142053001'
|
||||||
}
|
}
|
||||||
this.showShipPopup = true
|
this.showShipPopup = true
|
||||||
},
|
},
|
||||||
|
async quickShipAll() {
|
||||||
|
await Dialog.confirm({
|
||||||
|
title: '确认一键发货?',
|
||||||
|
message: `将为当前 ${this.orderItems.length} 个待发货订单提交发货。`
|
||||||
|
})
|
||||||
|
await Promise.all(this.orderItems.map(item => postFunction('merchant-order-ship', {
|
||||||
|
supplierId: this.supplierId,
|
||||||
|
userId: item.userId,
|
||||||
|
orderId: item.orderId,
|
||||||
|
crrgBsnNm: item.crrgBsnNm || '韵达快递',
|
||||||
|
logisticsOrderId: item.logisticsOrderId || 'YT20260903142053001'
|
||||||
|
})))
|
||||||
|
Toast('批量发货成功')
|
||||||
|
await this.loadOrders()
|
||||||
|
},
|
||||||
async submitShip() {
|
async submitShip() {
|
||||||
if (!this.shipForm.crrgBsnNm || !this.shipForm.logisticsOrderId) {
|
if (!this.shipForm.crrgBsnNm || !this.shipForm.logisticsOrderId) {
|
||||||
Toast('请填写完整发货信息')
|
Toast('请填写完整发货信息')
|
||||||
@@ -467,6 +483,18 @@ export default {
|
|||||||
padding: 12px 10px 18px;
|
padding: 12px 10px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.batch-ship-button {
|
||||||
|
width: 100%;
|
||||||
|
height: 38px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(90deg, #ff6a2a 0%, #ff4b18 100%);
|
||||||
|
}
|
||||||
|
|
||||||
.work-card {
|
.work-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|||||||
@@ -15,6 +15,15 @@
|
|||||||
</van-tabs>
|
</van-tabs>
|
||||||
|
|
||||||
<van-pull-refresh v-model="refreshing" class="order-list" @refresh="onRefresh">
|
<van-pull-refresh v-model="refreshing" class="order-list" @refresh="onRefresh">
|
||||||
|
<van-button
|
||||||
|
v-if="receivableOrders.length"
|
||||||
|
block
|
||||||
|
round
|
||||||
|
class="batch-receive-button"
|
||||||
|
@click="confirmReceiveAll"
|
||||||
|
>
|
||||||
|
一键收货({{ receivableOrders.length }})
|
||||||
|
</van-button>
|
||||||
<van-empty v-if="visibleOrders.length === 0" description="暂无订单" />
|
<van-empty v-if="visibleOrders.length === 0" description="暂无订单" />
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -164,6 +173,11 @@ export default {
|
|||||||
activeTab() {
|
activeTab() {
|
||||||
return this.tabs[this.active] || this.tabs[0]
|
return this.tabs[this.active] || this.tabs[0]
|
||||||
},
|
},
|
||||||
|
receivableOrders() {
|
||||||
|
return this.visibleOrders.filter(order => {
|
||||||
|
return order.displayType === 'sub' && Number(order.orderState) === ORDER_STATE.IN_PROGRESS
|
||||||
|
})
|
||||||
|
},
|
||||||
visibleOrders() {
|
visibleOrders() {
|
||||||
const tab = this.activeTab
|
const tab = this.activeTab
|
||||||
|
|
||||||
@@ -321,6 +335,44 @@ export default {
|
|||||||
this.refreshing = false
|
this.refreshing = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async confirmReceiveAll() {
|
||||||
|
try {
|
||||||
|
await Dialog.confirm({
|
||||||
|
title: '一键收货',
|
||||||
|
message: `确定将当前 ${this.receivableOrders.length} 个订单全部确认收货吗?`
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.operationKey = 'batch-confirm-receive'
|
||||||
|
try {
|
||||||
|
await this.fetchBridgeToken()
|
||||||
|
if (!this.supplierId || !this.userId) {
|
||||||
|
Toast('用户信息不完整,请重新进入商城')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(this.receivableOrders.map(order => postFunction('order-push-event', {
|
||||||
|
supplierId: this.supplierId,
|
||||||
|
userId: this.userId,
|
||||||
|
orderType: ORDER_EVENT_TYPE.CONFIRM_RECEIVE,
|
||||||
|
orderId: order.orderId,
|
||||||
|
mainOrderId: order.mainOrderId,
|
||||||
|
subOrderId: order.orderId,
|
||||||
|
amount: Number(order.amount || 0),
|
||||||
|
reason: '确认收货',
|
||||||
|
time: Date.now()
|
||||||
|
})))
|
||||||
|
|
||||||
|
Toast('批量收货成功')
|
||||||
|
await this.loadOrders()
|
||||||
|
} catch (error) {
|
||||||
|
Toast(error.message || '批量收货失败,请稍后再试')
|
||||||
|
} finally {
|
||||||
|
this.operationKey = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
orderActions(order) {
|
orderActions(order) {
|
||||||
if (order.displayType === 'main') {
|
if (order.displayType === 'main') {
|
||||||
return Number(order.orderState) === ORDER_STATE.PENDING_PAY
|
return Number(order.orderState) === ORDER_STATE.PENDING_PAY
|
||||||
@@ -583,6 +635,12 @@ export default {
|
|||||||
padding: 12px 10px 18px;
|
padding: 12px 10px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.batch-receive-button {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #ff5b20;
|
||||||
|
background: #fff1e8;
|
||||||
|
}
|
||||||
|
|
||||||
.order-card {
|
.order-card {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
|
|||||||
Generated
+334
@@ -17,6 +17,7 @@
|
|||||||
"ioredis": "^5.4.2",
|
"ioredis": "^5.4.2",
|
||||||
"koa": "^2.15.3",
|
"koa": "^2.15.3",
|
||||||
"koa-bodyparser": "^4.4.1",
|
"koa-bodyparser": "^4.4.1",
|
||||||
|
"minio": "^8.0.5",
|
||||||
"ws": "^8.18.3"
|
"ws": "^8.18.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -510,6 +511,18 @@
|
|||||||
"node": ">=19.0.0"
|
"node": ">=19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nodable/entities": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/nodable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@prisma/adapter-neon": {
|
"node_modules/@prisma/adapter-neon": {
|
||||||
"version": "6.19.3",
|
"version": "6.19.3",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/adapter-neon/-/adapter-neon-6.19.3.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/adapter-neon/-/adapter-neon-6.19.3.tgz",
|
||||||
@@ -840,6 +853,48 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/anynum": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/async": {
|
||||||
|
"version": "3.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||||
|
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/block-stream2": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"readable-stream": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/browser-or-node": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/buffer-crc32": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -1074,6 +1129,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decode-uri-component": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/deep-equal": {
|
"node_modules/deep-equal": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
|
||||||
@@ -1278,6 +1342,12 @@
|
|||||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/exsolve": {
|
"node_modules/exsolve": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz",
|
||||||
@@ -1308,6 +1378,54 @@
|
|||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-xml-builder": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"path-expression-matcher": "^1.6.2",
|
||||||
|
"xml-naming": "^0.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fast-xml-parser": {
|
||||||
|
"version": "5.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.1.tgz",
|
||||||
|
"integrity": "sha512-TBw6K/fxoQGGjCmZDw9w/ZwP3uDcnTM4YH/g+PFRWr8sbe5idXtxNN6vITh4+1ruCZaho6uBFurElsA7F0zzgw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@nodable/entities": "^3.0.0",
|
||||||
|
"fast-xml-builder": "^1.2.0",
|
||||||
|
"is-unsafe": "^2.0.0",
|
||||||
|
"path-expression-matcher": "^1.6.2",
|
||||||
|
"strnum": "^2.4.2",
|
||||||
|
"xml-naming": "^0.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"fxparser": "src/cli/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/filter-obj": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fresh": {
|
"node_modules/fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
@@ -1572,6 +1690,15 @@
|
|||||||
"url": "https://opencollective.com/ioredis"
|
"url": "https://opencollective.com/ioredis"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ipaddr.js": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-generator-function": {
|
"node_modules/is-generator-function": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
|
||||||
@@ -1609,6 +1736,18 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-unsafe": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/jiti": {
|
"node_modules/jiti": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
||||||
@@ -1732,6 +1871,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||||
|
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@@ -1771,6 +1916,30 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/minio": {
|
||||||
|
"version": "8.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/minio/-/minio-8.0.7.tgz",
|
||||||
|
"integrity": "sha512-E737MgufW8CeQAsTAtnEMrxZ9scMSf29kkhZoXzDTKj/Jszzo2SfeZUH9wbDQH2Rsq6TCtl/yQL0+XdVKZansQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"async": "^3.2.4",
|
||||||
|
"block-stream2": "^2.1.0",
|
||||||
|
"browser-or-node": "^2.1.1",
|
||||||
|
"buffer-crc32": "^1.0.0",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"fast-xml-parser": "^5.3.4",
|
||||||
|
"ipaddr.js": "^2.0.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
|
"query-string": "^7.1.3",
|
||||||
|
"stream-json": "^1.8.0",
|
||||||
|
"through2": "^4.0.2",
|
||||||
|
"xml2js": "^0.5.0 || ^0.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16 || ^18 || >=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -1863,6 +2032,21 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-expression-matcher": {
|
||||||
|
"version": "1.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz",
|
||||||
|
"integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "6.3.0",
|
"version": "6.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
|
||||||
@@ -1963,6 +2147,24 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/query-string": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"decode-uri-component": "^0.2.2",
|
||||||
|
"filter-obj": "^1.1.0",
|
||||||
|
"split-on-first": "^1.0.0",
|
||||||
|
"strict-uri-encode": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/raw-body": {
|
"node_modules/raw-body": {
|
||||||
"version": "2.5.3",
|
"version": "2.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
||||||
@@ -1989,6 +2191,20 @@
|
|||||||
"destr": "^2.0.3"
|
"destr": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/readable-stream": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||||
@@ -2067,6 +2283,15 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/sax": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/setprototypeof": {
|
"node_modules/setprototypeof": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
@@ -2145,6 +2370,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/split-on-first": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/standard-as-callback": {
|
"node_modules/standard-as-callback": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||||
@@ -2160,6 +2394,63 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stream-chain": {
|
||||||
|
"version": "2.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz",
|
||||||
|
"integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/stream-json": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"stream-chain": "^2.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strict-uri-encode": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strnum": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"anynum": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/through2": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"readable-stream": "3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyexec": {
|
"node_modules/tinyexec": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
|
||||||
@@ -2250,6 +2541,12 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
@@ -2280,6 +2577,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xml-naming": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xml2js": {
|
||||||
|
"version": "0.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||||
|
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sax": ">=0.6.0",
|
||||||
|
"xmlbuilder": "~11.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xmlbuilder": {
|
||||||
|
"version": "11.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||||
|
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ylru": {
|
"node_modules/ylru": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@neondatabase/serverless": "^1.0.1",
|
"@neondatabase/serverless": "^1.0.1",
|
||||||
"@prisma/adapter-neon": "6.19.3",
|
"@prisma/adapter-neon": "6.19.3",
|
||||||
"@prisma/client": "6.19.3",
|
"@prisma/client": "6.19.3",
|
||||||
|
"minio": "^8.0.5",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"ioredis": "^5.4.2",
|
"ioredis": "^5.4.2",
|
||||||
|
|||||||
@@ -166,8 +166,8 @@ model SupplierReconciliationOrder {
|
|||||||
mainOrderId String?
|
mainOrderId String?
|
||||||
userId String?
|
userId String?
|
||||||
reconciliationStatus Int @default(0)
|
reconciliationStatus Int @default(0)
|
||||||
diffType Int?
|
diffType String? @db.VarChar(64)
|
||||||
diffReason String? @db.Text
|
diffReason String? @db.VarChar(1000)
|
||||||
platformTotalPrice Decimal? @db.Decimal(12, 2)
|
platformTotalPrice Decimal? @db.Decimal(12, 2)
|
||||||
platformCashAmount Decimal? @db.Decimal(12, 2)
|
platformCashAmount Decimal? @db.Decimal(12, 2)
|
||||||
platformPointsPrice Decimal? @db.Decimal(12, 2)
|
platformPointsPrice Decimal? @db.Decimal(12, 2)
|
||||||
@@ -373,6 +373,23 @@ model SupplierAfterSale {
|
|||||||
@@map("supplier_after_sale")
|
@@map("supplier_after_sale")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model SupplierFile {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
fileId String @unique
|
||||||
|
supplierId String?
|
||||||
|
fileName String
|
||||||
|
objectName String
|
||||||
|
bucketName String
|
||||||
|
contentType String?
|
||||||
|
fileSize Int?
|
||||||
|
createTime DateTime @default(now())
|
||||||
|
updateTime DateTime @updatedAt
|
||||||
|
deleted Int @default(0)
|
||||||
|
|
||||||
|
@@index([supplierId, deleted])
|
||||||
|
@@map("supplier_file")
|
||||||
|
}
|
||||||
|
|
||||||
model SupplierInvoiceInfo {
|
model SupplierInvoiceInfo {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
invoiceBizId String @unique
|
invoiceBizId String @unique
|
||||||
@@ -382,11 +399,21 @@ model SupplierInvoiceInfo {
|
|||||||
subOrderId String?
|
subOrderId String?
|
||||||
settlementId String?
|
settlementId String?
|
||||||
invoiceCode String?
|
invoiceCode String?
|
||||||
|
taxIdNumber String?
|
||||||
invoiceIds String? @db.VarChar(1000)
|
invoiceIds String? @db.VarChar(1000)
|
||||||
orderIds String? @db.VarChar(1000)
|
orderIds String? @db.VarChar(1000)
|
||||||
invoiceStatus Int @default(0)
|
invoiceStatus Int @default(0)
|
||||||
|
invoiceDate DateTime?
|
||||||
|
invoiceType Int?
|
||||||
|
invoiceNakeAmount Decimal? @db.Decimal(12, 2)
|
||||||
|
invoiceTaxRate Decimal? @db.Decimal(8, 4)
|
||||||
|
invoiceTaxAmount Decimal? @db.Decimal(12, 2)
|
||||||
|
invoiceAmount Decimal? @db.Decimal(12, 2)
|
||||||
|
checkCode String?
|
||||||
|
fileType String? @db.VarChar(16)
|
||||||
fileId String?
|
fileId String?
|
||||||
filePath String?
|
filePath String?
|
||||||
|
failureMessage String? @db.Text
|
||||||
requestBody Json?
|
requestBody Json?
|
||||||
createTime DateTime @default(now())
|
createTime DateTime @default(now())
|
||||||
updateTime DateTime @updatedAt
|
updateTime DateTime @updatedAt
|
||||||
|
|||||||
@@ -28,12 +28,22 @@ CREATE TABLE IF NOT EXISTS `supplier_invoice_info` (
|
|||||||
`mainOrderId` VARCHAR(191) NULL COMMENT '关联主单ID',
|
`mainOrderId` VARCHAR(191) NULL COMMENT '关联主单ID',
|
||||||
`subOrderId` VARCHAR(191) NULL COMMENT '关联子单ID',
|
`subOrderId` VARCHAR(191) NULL COMMENT '关联子单ID',
|
||||||
`settlementId` VARCHAR(191) NULL COMMENT '结算单号',
|
`settlementId` VARCHAR(191) NULL COMMENT '结算单号',
|
||||||
`invoiceCode` VARCHAR(191) NULL COMMENT '发票代码或纳税识别号',
|
`invoiceCode` VARCHAR(191) NULL COMMENT '发票代码',
|
||||||
|
`taxIdNumber` VARCHAR(191) NULL COMMENT '购买方纳税人识别号',
|
||||||
`invoiceIds` VARCHAR(1000) NULL COMMENT '发票号码列表,多个用英文逗号分隔',
|
`invoiceIds` VARCHAR(1000) NULL COMMENT '发票号码列表,多个用英文逗号分隔',
|
||||||
`orderIds` VARCHAR(1000) NULL COMMENT '关联订单号列表,多个用英文逗号分隔',
|
`orderIds` VARCHAR(1000) NULL COMMENT '关联订单号列表,多个用英文逗号分隔',
|
||||||
`invoiceStatus` INT NOT NULL DEFAULT 0 COMMENT '发票状态:0已记录,1已申请,2已开票,3失败',
|
`invoiceStatus` INT NOT NULL DEFAULT 0 COMMENT '发票状态:0已记录,1已申请,2已开票,3失败',
|
||||||
|
`invoiceDate` DATETIME(3) NULL COMMENT '开票日期',
|
||||||
|
`invoiceType` INT NULL COMMENT '发票类型',
|
||||||
|
`invoiceNakeAmount` DECIMAL(12, 2) NULL COMMENT '不含税金额',
|
||||||
|
`invoiceTaxRate` DECIMAL(8, 4) NULL COMMENT '发票税率',
|
||||||
|
`invoiceTaxAmount` DECIMAL(12, 2) NULL COMMENT '发票税额',
|
||||||
|
`invoiceAmount` DECIMAL(12, 2) NULL COMMENT '价税合计',
|
||||||
|
`checkCode` VARCHAR(191) NULL COMMENT '发票校验码',
|
||||||
|
`fileType` VARCHAR(16) NULL COMMENT '发票文件类型',
|
||||||
`fileId` VARCHAR(191) NULL COMMENT '发票文件ID',
|
`fileId` VARCHAR(191) NULL COMMENT '发票文件ID',
|
||||||
`filePath` VARCHAR(191) NULL COMMENT '发票文件路径',
|
`filePath` VARCHAR(191) NULL COMMENT '发票文件路径',
|
||||||
|
`failureMessage` TEXT NULL COMMENT '开票失败原因',
|
||||||
`requestBody` JSON NULL COMMENT '原始业务请求体',
|
`requestBody` JSON NULL COMMENT '原始业务请求体',
|
||||||
`createTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
`createTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||||
`updateTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
`updateTime` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
CREATE TABLE `supplier_file` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`fileId` varchar(191) NOT NULL,
|
||||||
|
`supplierId` varchar(191) DEFAULT NULL,
|
||||||
|
`fileName` varchar(191) NOT NULL,
|
||||||
|
`objectName` varchar(500) NOT NULL,
|
||||||
|
`bucketName` varchar(191) NOT NULL,
|
||||||
|
`contentType` varchar(100) DEFAULT NULL,
|
||||||
|
`fileSize` int DEFAULT NULL,
|
||||||
|
`createTime` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updateTime` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`deleted` int NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `supplier_file_fileId_key` (`fileId`),
|
||||||
|
KEY `supplier_file_supplierId_deleted_idx` (`supplierId`,`deleted`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商文件存储记录';
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
ALTER TABLE `supplier_invoice_info`
|
||||||
|
MODIFY COLUMN `invoiceCode` VARCHAR(191) NULL COMMENT '发票代码',
|
||||||
|
ADD COLUMN `taxIdNumber` VARCHAR(191) NULL COMMENT '购买方纳税人识别号' AFTER `invoiceCode`,
|
||||||
|
ADD COLUMN `invoiceDate` DATETIME(3) NULL COMMENT '开票日期' AFTER `invoiceStatus`,
|
||||||
|
ADD COLUMN `invoiceType` INT NULL COMMENT '发票类型' AFTER `invoiceDate`,
|
||||||
|
ADD COLUMN `invoiceNakeAmount` DECIMAL(12, 2) NULL COMMENT '不含税金额' AFTER `invoiceType`,
|
||||||
|
ADD COLUMN `invoiceTaxRate` DECIMAL(8, 4) NULL COMMENT '发票税率' AFTER `invoiceNakeAmount`,
|
||||||
|
ADD COLUMN `invoiceTaxAmount` DECIMAL(12, 2) NULL COMMENT '发票税额' AFTER `invoiceTaxRate`,
|
||||||
|
ADD COLUMN `invoiceAmount` DECIMAL(12, 2) NULL COMMENT '价税合计' AFTER `invoiceTaxAmount`,
|
||||||
|
ADD COLUMN `checkCode` VARCHAR(191) NULL COMMENT '发票校验码' AFTER `invoiceAmount`,
|
||||||
|
ADD COLUMN `fileType` VARCHAR(16) NULL COMMENT '发票文件类型' AFTER `checkCode`,
|
||||||
|
ADD COLUMN `failureMessage` TEXT NULL COMMENT '开票失败原因' AFTER `filePath`;
|
||||||
@@ -16,6 +16,7 @@ import { orderListService } from '../services/order-list.service.js'
|
|||||||
import { orderPushEventService, type OrderPushEventInput } from '../services/order-push-event.service.js'
|
import { orderPushEventService, type OrderPushEventInput } from '../services/order-push-event.service.js'
|
||||||
import {
|
import {
|
||||||
supplierOpenOrderService,
|
supplierOpenOrderService,
|
||||||
|
type SupplierFileBase64QueryRequest,
|
||||||
type SupplierInvoiceApplyRequest,
|
type SupplierInvoiceApplyRequest,
|
||||||
type SupplierInvoiceQueryRequest,
|
type SupplierInvoiceQueryRequest,
|
||||||
type SupplierOrderQueryRequest,
|
type SupplierOrderQueryRequest,
|
||||||
@@ -270,7 +271,7 @@ export const supplierInvoiceApplyController = withErrorBoundary(async (request:
|
|||||||
|
|
||||||
let body: SupplierInvoiceApplyRequest
|
let body: SupplierInvoiceApplyRequest
|
||||||
try {
|
try {
|
||||||
body = supplierOpenOrderService.parseBody(await readJson<SupplierInvoiceApplyRequest>(request)) as SupplierInvoiceApplyRequest
|
body = supplierOpenOrderService.parseBody(await readJson<SupplierInvoiceApplyRequest>(request))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Response.json({
|
return Response.json({
|
||||||
resultCode: 400,
|
resultCode: 400,
|
||||||
@@ -287,7 +288,7 @@ export const supplierInvoiceInfoController = withErrorBoundary(async (request: R
|
|||||||
|
|
||||||
let body: SupplierInvoiceQueryRequest
|
let body: SupplierInvoiceQueryRequest
|
||||||
try {
|
try {
|
||||||
body = supplierOpenOrderService.parseBody(await readJson<SupplierInvoiceQueryRequest>(request)) as SupplierInvoiceQueryRequest
|
body = supplierOpenOrderService.parseBody(await readJson<SupplierInvoiceQueryRequest>(request))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Response.json({
|
return Response.json({
|
||||||
resultCode: 400,
|
resultCode: 400,
|
||||||
@@ -295,5 +296,24 @@ export const supplierInvoiceInfoController = withErrorBoundary(async (request: R
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json(await supplierOpenOrderService.queryInvoicesInfo(body))
|
return Response.json(await supplierOpenOrderService.queryInvoiceInfo(body))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const supplierFileBase64InfoController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
let body: SupplierFileBase64QueryRequest
|
||||||
|
try {
|
||||||
|
body = supplierOpenOrderService.parseBody(await readJson<SupplierFileBase64QueryRequest>(request))
|
||||||
|
} catch (error) {
|
||||||
|
return Response.json({
|
||||||
|
resultCode: 400,
|
||||||
|
message: error instanceof Error ? error.message : '请求参数解密失败',
|
||||||
|
fileType: 'JPG',
|
||||||
|
imageEncodeMap: {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.json(await supplierOpenOrderService.queryFileBase64Info(body))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ type SupplierInfoRequest = {
|
|||||||
supplierIdentification?: string
|
supplierIdentification?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SupplierSessionRequest = {
|
||||||
|
keyword?: string
|
||||||
|
}
|
||||||
|
|
||||||
type SupplierTokenRequest = {
|
type SupplierTokenRequest = {
|
||||||
AplParmObjct?: string
|
userId?: string
|
||||||
supplierIdentification?: string
|
supplierIdentification?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,15 +38,25 @@ export const supplierInfoController = withErrorBoundary(async (request: Request,
|
|||||||
return toHttpResponse(await supplierService.getSupplierInfo(supplierIdentification))
|
return toHttpResponse(await supplierService.getSupplierInfo(supplierIdentification))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const supplierSessionController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
|
const body = await readJson<SupplierSessionRequest>(request)
|
||||||
|
const keyword = String(body.keyword || '')
|
||||||
|
|
||||||
|
return toHttpResponse(supplierService.decryptSupplierSession(keyword))
|
||||||
|
})
|
||||||
|
|
||||||
export const supplierTokenController = withErrorBoundary(async (request: Request, _context: Context) => {
|
export const supplierTokenController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
const methodResponse = guardMethod(request, ['POST'])
|
const methodResponse = guardMethod(request, ['POST'])
|
||||||
if (methodResponse) return methodResponse
|
if (methodResponse) return methodResponse
|
||||||
|
|
||||||
const body = await readJson<SupplierTokenRequest>(request)
|
const body = await readJson<SupplierTokenRequest>(request)
|
||||||
const encryptedParam = String(body.AplParmObjct || '').trim()
|
const userId = String(body.userId || '').trim()
|
||||||
const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase()
|
const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase()
|
||||||
|
|
||||||
return toHttpResponse(await supplierService.warmSupplierToken(encryptedParam, supplierIdentification))
|
return toHttpResponse(await supplierService.warmSupplierToken(userId, supplierIdentification))
|
||||||
})
|
})
|
||||||
|
|
||||||
export const supplierPointsController = withErrorBoundary(async (request: Request, _context: Context) => {
|
export const supplierPointsController = withErrorBoundary(async (request: Request, _context: Context) => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export type PrismaTransaction = Prisma.TransactionClient
|
|||||||
|
|
||||||
export const orderDao = {
|
export const orderDao = {
|
||||||
transaction<T>(handler: (tx: PrismaTransaction) => Promise<T>) {
|
transaction<T>(handler: (tx: PrismaTransaction) => Promise<T>) {
|
||||||
return prisma.$transaction(handler)
|
return prisma.$transaction(handler, { maxWait: 10000, timeout: 20000 })
|
||||||
},
|
},
|
||||||
|
|
||||||
findMainOrdersWithSubs(supplierId: string, userId: string) {
|
findMainOrdersWithSubs(supplierId: string, userId: string) {
|
||||||
@@ -137,6 +137,16 @@ export const orderDao = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
findInvoiceBySettlementId(supplierId: string, settlementId: string) {
|
||||||
|
return prisma.supplierInvoiceInfo.findFirst({
|
||||||
|
where: {
|
||||||
|
supplierId,
|
||||||
|
settlementId,
|
||||||
|
deleted: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
findOpenApiReconciliationSubOrders(supplierId: string, orderIds: string[], startDate?: Date, endDate?: Date, orderState?: number) {
|
findOpenApiReconciliationSubOrders(supplierId: string, orderIds: string[], startDate?: Date, endDate?: Date, orderState?: number) {
|
||||||
return prisma.supplierOrderSub.findMany({
|
return prisma.supplierOrderSub.findMany({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
orderPayController,
|
orderPayController,
|
||||||
orderPushEventController,
|
orderPushEventController,
|
||||||
orderShipController,
|
orderShipController,
|
||||||
|
supplierFileBase64InfoController,
|
||||||
supplierInvoiceApplyController,
|
supplierInvoiceApplyController,
|
||||||
supplierInvoiceInfoController,
|
supplierInvoiceInfoController,
|
||||||
supplierOrderInfoController,
|
supplierOrderInfoController,
|
||||||
@@ -65,6 +66,7 @@ import {
|
|||||||
supplierDebugTokensController,
|
supplierDebugTokensController,
|
||||||
supplierInfoController,
|
supplierInfoController,
|
||||||
supplierPointsController,
|
supplierPointsController,
|
||||||
|
supplierSessionController,
|
||||||
supplierTokenController
|
supplierTokenController
|
||||||
} from './controllers/supplier.controller.js'
|
} from './controllers/supplier.controller.js'
|
||||||
|
|
||||||
@@ -108,9 +110,10 @@ const handlers: Record<string, FunctionHandler> = {
|
|||||||
'merchant-product-save': merchantProductSaveController,
|
'merchant-product-save': merchantProductSaveController,
|
||||||
'merchant-product-delete': merchantProductDeleteController,
|
'merchant-product-delete': merchantProductDeleteController,
|
||||||
'supplier-debug-tokens': supplierDebugTokensController,
|
'supplier-debug-tokens': supplierDebugTokensController,
|
||||||
'supplier-info': supplierInfoController,
|
'supplier-file-base64-info': supplierFileBase64InfoController,
|
||||||
'supplier-invoice-apply': supplierInvoiceApplyController,
|
'supplier-invoice-apply': supplierInvoiceApplyController,
|
||||||
'supplier-invoice-info': supplierInvoiceInfoController,
|
'supplier-invoice-info': supplierInvoiceInfoController,
|
||||||
|
'supplier-info': supplierInfoController,
|
||||||
'supplier-order-info': supplierOrderInfoController,
|
'supplier-order-info': supplierOrderInfoController,
|
||||||
'supplier-reconciliation-info': supplierReconciliationInfoController,
|
'supplier-reconciliation-info': supplierReconciliationInfoController,
|
||||||
'supplier-reconciliation-result': supplierReconciliationResultController,
|
'supplier-reconciliation-result': supplierReconciliationResultController,
|
||||||
@@ -118,6 +121,7 @@ const handlers: Record<string, FunctionHandler> = {
|
|||||||
'merchant-reconciliation-list': merchantReconciliationListController,
|
'merchant-reconciliation-list': merchantReconciliationListController,
|
||||||
'merchant-reconciliation-update': merchantReconciliationUpdateController,
|
'merchant-reconciliation-update': merchantReconciliationUpdateController,
|
||||||
'supplier-points': supplierPointsController,
|
'supplier-points': supplierPointsController,
|
||||||
|
'supplier-session': supplierSessionController,
|
||||||
'supplier-token': supplierTokenController,
|
'supplier-token': supplierTokenController,
|
||||||
upload: uploadController,
|
upload: uploadController,
|
||||||
'upload-preview': uploadPreviewController,
|
'upload-preview': uploadPreviewController,
|
||||||
@@ -144,11 +148,29 @@ const toRequest = (ctx: Koa.Context) =>
|
|||||||
body: requestBody(ctx)
|
body: requestBody(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
const writeResponse = async (ctx: Koa.Context, response: Response) => {
|
const ba7ResponseBody = (content: string) => ({
|
||||||
|
status: 200,
|
||||||
|
msg: 'success',
|
||||||
|
data: {
|
||||||
|
Body: {
|
||||||
|
RspSvcHeader: {
|
||||||
|
ReturnCode: '000000000000',
|
||||||
|
ReturnMsg: '交易成功'
|
||||||
|
},
|
||||||
|
RspSvcBody: {
|
||||||
|
ReturnStCd: '200',
|
||||||
|
ReturnCntnt: content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const writeResponse = async (ctx: Koa.Context, response: Response, wrapBa7 = false) => {
|
||||||
ctx.status = response.status
|
ctx.status = response.status
|
||||||
Object.entries(corsHeaders).forEach(([key, value]) => ctx.set(key, value))
|
Object.entries(corsHeaders).forEach(([key, value]) => ctx.set(key, value))
|
||||||
response.headers.forEach((value, key) => ctx.set(key, value))
|
response.headers.forEach((value, key) => ctx.set(key, value))
|
||||||
ctx.body = response.body ? await response.text() : undefined
|
const responseText = response.body ? await response.text() : ''
|
||||||
|
ctx.body = wrapBa7 ? ba7ResponseBody(responseText) : responseText || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = (name: string) => async (ctx: Koa.Context) => {
|
const dispatch = (name: string) => async (ctx: Koa.Context) => {
|
||||||
@@ -170,7 +192,7 @@ const dispatch = (name: string) => async (ctx: Koa.Context) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const response = await handler(toRequest(ctx), {})
|
const response = await handler(toRequest(ctx), {})
|
||||||
await writeResponse(ctx, response)
|
await writeResponse(ctx, response, ctx.path.startsWith('/supplierOpenApi/'))
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(bodyParser({ jsonLimit: '10mb' }))
|
app.use(bodyParser({ jsonLimit: '10mb' }))
|
||||||
@@ -206,6 +228,7 @@ router.post('/supplierOpenApi/pointOrder/queryOrderInfo', dispatch('supplier-ord
|
|||||||
router.post('/supplierOpenApi/pointOrder/queryReconciliationInfo', dispatch('supplier-reconciliation-info'))
|
router.post('/supplierOpenApi/pointOrder/queryReconciliationInfo', dispatch('supplier-reconciliation-info'))
|
||||||
router.post('/supplierOpenApi/pointOrder/applyForInvoices', dispatch('supplier-invoice-apply'))
|
router.post('/supplierOpenApi/pointOrder/applyForInvoices', dispatch('supplier-invoice-apply'))
|
||||||
router.post('/supplierOpenApi/pointOrder/queryInvoicesInfo', dispatch('supplier-invoice-info'))
|
router.post('/supplierOpenApi/pointOrder/queryInvoicesInfo', dispatch('supplier-invoice-info'))
|
||||||
|
router.post('/supplierOpenApi/pointOrder/queryFileBase64Info', dispatch('supplier-file-base64-info'))
|
||||||
router.post('/supplierOpenApi/pointOrder/reconciliationResult', dispatch('supplier-reconciliation-result'))
|
router.post('/supplierOpenApi/pointOrder/reconciliationResult', dispatch('supplier-reconciliation-result'))
|
||||||
router.post('/supplierOpenApi/pointOrder/unReconciledMsg', dispatch('supplier-unreconciled-msg'))
|
router.post('/supplierOpenApi/pointOrder/unReconciledMsg', dispatch('supplier-unreconciled-msg'))
|
||||||
router.post('/supplierOpenApi/debug/tokens', dispatch('supplier-debug-tokens'))
|
router.post('/supplierOpenApi/debug/tokens', dispatch('supplier-debug-tokens'))
|
||||||
|
|||||||
@@ -190,7 +190,6 @@ export const orderPushEventService = {
|
|||||||
invoiceStatus: 2,
|
invoiceStatus: 2,
|
||||||
fileId: body.fileId || null,
|
fileId: body.fileId || null,
|
||||||
filePath: body.filePath || null,
|
filePath: body.filePath || null,
|
||||||
requestBody: body,
|
|
||||||
deleted: 0
|
deleted: 0
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Prisma } from '@prisma/client'
|
import { Prisma } from '@prisma/client'
|
||||||
import { prisma } from '../utils/prisma.js'
|
import { prisma } from '../utils/prisma.js'
|
||||||
import { supplierDao } from '../dao/supplier.dao.js'
|
import { supplierDao } from '../dao/supplier.dao.js'
|
||||||
import { verifySupplierOpenSignature } from '../utils/supplier-open-crypto.js'
|
import { verifySupplierOpenSignature } from '../utils/supplier-open-crypto.js'
|
||||||
@@ -9,7 +9,7 @@ export type ReconciliationResultItem = {
|
|||||||
mainOrderId?: string
|
mainOrderId?: string
|
||||||
userId?: string
|
userId?: string
|
||||||
reconciliationStatus?: number
|
reconciliationStatus?: number
|
||||||
diffType?: number
|
diffType?: string | number
|
||||||
diffReason?: string
|
diffReason?: string
|
||||||
platformTotalPrice?: number
|
platformTotalPrice?: number
|
||||||
platformCashAmount?: number
|
platformCashAmount?: number
|
||||||
@@ -41,7 +41,7 @@ export type ReconciliationResultUpdateInput = {
|
|||||||
supplierId?: string
|
supplierId?: string
|
||||||
orderId?: string
|
orderId?: string
|
||||||
reconciliationStatus?: number
|
reconciliationStatus?: number
|
||||||
diffType?: number
|
diffType?: string | number
|
||||||
diffReason?: string
|
diffReason?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,38 @@ const toTime = (value?: string) => {
|
|||||||
return Number.isNaN(time.getTime()) ? new Date() : time
|
return Number.isNaN(time.getTime()) ? new Date() : time
|
||||||
}
|
}
|
||||||
|
|
||||||
const resultItem = (record: Awaited<ReturnType<typeof prisma.supplierReconciliationOrder.findMany>>[number]) => ({
|
type DecimalValue = { toFixed: (fractionDigits?: number) => string } | number | string
|
||||||
|
|
||||||
|
type SupplierReconciliationOrderRecord = {
|
||||||
|
supplierId: string
|
||||||
|
orderId: string
|
||||||
|
mainOrderId: string | null
|
||||||
|
userId: string | null
|
||||||
|
reconciliationStatus: number
|
||||||
|
diffType: string | null
|
||||||
|
diffReason: string | null
|
||||||
|
platformTotalPrice: DecimalValue | null
|
||||||
|
platformCashAmount: DecimalValue | null
|
||||||
|
platformPointsPrice: DecimalValue | null
|
||||||
|
platformRefundPrice: DecimalValue | null
|
||||||
|
supplierTotalPrice: DecimalValue | null
|
||||||
|
supplierCashAmount: DecimalValue | null
|
||||||
|
supplierPointsPrice: DecimalValue | null
|
||||||
|
supplierRefundPrice: DecimalValue | null
|
||||||
|
reconciliationBatchNo: string | null
|
||||||
|
reconciliationTime: Date | null
|
||||||
|
updateAt: Date
|
||||||
|
deleted?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatAmount = (value: DecimalValue | null) => {
|
||||||
|
if (value === undefined || value === null) return ''
|
||||||
|
if (typeof value === 'object') return value.toFixed(2)
|
||||||
|
const numberValue = Number(value)
|
||||||
|
return Number.isNaN(numberValue) ? '' : numberValue.toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultItem = (record: SupplierReconciliationOrderRecord) => ({
|
||||||
supplierId: record.supplierId,
|
supplierId: record.supplierId,
|
||||||
orderId: record.orderId,
|
orderId: record.orderId,
|
||||||
mainOrderId: record.mainOrderId || '',
|
mainOrderId: record.mainOrderId || '',
|
||||||
@@ -61,14 +92,14 @@ const resultItem = (record: Awaited<ReturnType<typeof prisma.supplierReconciliat
|
|||||||
reconciliationStatus: record.reconciliationStatus,
|
reconciliationStatus: record.reconciliationStatus,
|
||||||
diffType: record.diffType,
|
diffType: record.diffType,
|
||||||
diffReason: record.diffReason || '',
|
diffReason: record.diffReason || '',
|
||||||
platformTotalPrice: record.platformTotalPrice?.toFixed(2) || '',
|
platformTotalPrice: formatAmount(record.platformTotalPrice),
|
||||||
platformCashAmount: record.platformCashAmount?.toFixed(2) || '',
|
platformCashAmount: formatAmount(record.platformCashAmount),
|
||||||
platformPointsPrice: record.platformPointsPrice?.toFixed(2) || '',
|
platformPointsPrice: formatAmount(record.platformPointsPrice),
|
||||||
platformRefundPrice: record.platformRefundPrice?.toFixed(2) || '',
|
platformRefundPrice: formatAmount(record.platformRefundPrice),
|
||||||
supplierTotalPrice: record.supplierTotalPrice?.toFixed(2) || '',
|
supplierTotalPrice: formatAmount(record.supplierTotalPrice),
|
||||||
supplierCashAmount: record.supplierCashAmount?.toFixed(2) || '',
|
supplierCashAmount: formatAmount(record.supplierCashAmount),
|
||||||
supplierPointsPrice: record.supplierPointsPrice?.toFixed(2) || '',
|
supplierPointsPrice: formatAmount(record.supplierPointsPrice),
|
||||||
supplierRefundPrice: record.supplierRefundPrice?.toFixed(2) || '',
|
supplierRefundPrice: formatAmount(record.supplierRefundPrice),
|
||||||
reconciliationBatchNo: record.reconciliationBatchNo || '',
|
reconciliationBatchNo: record.reconciliationBatchNo || '',
|
||||||
reconciliationTime: record.reconciliationTime,
|
reconciliationTime: record.reconciliationTime,
|
||||||
updateAt: record.updateAt
|
updateAt: record.updateAt
|
||||||
@@ -105,7 +136,7 @@ export const reconciliationResultService = {
|
|||||||
mainOrderId: String(record.mainOrderId || '').trim() || null,
|
mainOrderId: String(record.mainOrderId || '').trim() || null,
|
||||||
userId: String(record.userId || '').trim() || null,
|
userId: String(record.userId || '').trim() || null,
|
||||||
reconciliationStatus: Number(record.reconciliationStatus),
|
reconciliationStatus: Number(record.reconciliationStatus),
|
||||||
diffType: record.diffType === undefined || record.diffType === null ? null : Number(record.diffType),
|
diffType: record.diffType === undefined || record.diffType === null ? null : String(record.diffType).trim() || null,
|
||||||
diffReason: String(record.diffReason || '').trim() || null,
|
diffReason: String(record.diffReason || '').trim() || null,
|
||||||
platformTotalPrice: toNumber(record.platformTotalPrice),
|
platformTotalPrice: toNumber(record.platformTotalPrice),
|
||||||
platformCashAmount: toNumber(record.platformCashAmount),
|
platformCashAmount: toNumber(record.platformCashAmount),
|
||||||
@@ -117,14 +148,40 @@ export const reconciliationResultService = {
|
|||||||
supplierRefundPrice: toNumber(record.supplierRefundPrice),
|
supplierRefundPrice: toNumber(record.supplierRefundPrice),
|
||||||
reconciliationBatchNo: String(input.reconciliationBatchNo || '').trim() || null,
|
reconciliationBatchNo: String(input.reconciliationBatchNo || '').trim() || null,
|
||||||
reconciliationTime,
|
reconciliationTime,
|
||||||
resultPayload: record as Prisma.InputJsonValue,
|
resultPayload: JSON.stringify(record),
|
||||||
deleted: 0
|
deleted: 0
|
||||||
}
|
}
|
||||||
return prisma.supplierReconciliationOrder.upsert({
|
return prisma.$executeRaw`
|
||||||
where: { supplierId_orderId: { supplierId, orderId } },
|
INSERT INTO supplier_reconciliation_order
|
||||||
update: data,
|
(supplierId, orderId, mainOrderId, userId, reconciliationStatus, diffType, diffReason,
|
||||||
create: { supplierId, orderId, ...data }
|
platformTotalPrice, platformCashAmount, platformPointsPrice, platformRefundPrice,
|
||||||
})
|
supplierTotalPrice, supplierCashAmount, supplierPointsPrice, supplierRefundPrice,
|
||||||
|
reconciliationBatchNo, reconciliationTime, resultPayload, deleted, createAt, updateAt)
|
||||||
|
VALUES
|
||||||
|
(${supplierId}, ${orderId}, ${data.mainOrderId}, ${data.userId}, ${data.reconciliationStatus}, ${data.diffType}, ${data.diffReason},
|
||||||
|
${data.platformTotalPrice}, ${data.platformCashAmount}, ${data.platformPointsPrice}, ${data.platformRefundPrice},
|
||||||
|
${data.supplierTotalPrice}, ${data.supplierCashAmount}, ${data.supplierPointsPrice}, ${data.supplierRefundPrice},
|
||||||
|
${data.reconciliationBatchNo}, ${data.reconciliationTime}, ${data.resultPayload}, ${data.deleted}, CURRENT_TIMESTAMP(3), CURRENT_TIMESTAMP(3))
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
mainOrderId = VALUES(mainOrderId),
|
||||||
|
userId = VALUES(userId),
|
||||||
|
reconciliationStatus = VALUES(reconciliationStatus),
|
||||||
|
diffType = VALUES(diffType),
|
||||||
|
diffReason = VALUES(diffReason),
|
||||||
|
platformTotalPrice = VALUES(platformTotalPrice),
|
||||||
|
platformCashAmount = VALUES(platformCashAmount),
|
||||||
|
platformPointsPrice = VALUES(platformPointsPrice),
|
||||||
|
platformRefundPrice = VALUES(platformRefundPrice),
|
||||||
|
supplierTotalPrice = VALUES(supplierTotalPrice),
|
||||||
|
supplierCashAmount = VALUES(supplierCashAmount),
|
||||||
|
supplierPointsPrice = VALUES(supplierPointsPrice),
|
||||||
|
supplierRefundPrice = VALUES(supplierRefundPrice),
|
||||||
|
reconciliationBatchNo = VALUES(reconciliationBatchNo),
|
||||||
|
reconciliationTime = VALUES(reconciliationTime),
|
||||||
|
resultPayload = VALUES(resultPayload),
|
||||||
|
deleted = VALUES(deleted),
|
||||||
|
updateAt = CURRENT_TIMESTAMP(3)
|
||||||
|
`
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return { resultCode: 0, message: '成功' }
|
return { resultCode: 0, message: '成功' }
|
||||||
@@ -138,15 +195,19 @@ export const reconciliationResultService = {
|
|||||||
return serviceFail('对账状态不正确')
|
return serviceFail('对账状态不正确')
|
||||||
}
|
}
|
||||||
|
|
||||||
const records = await prisma.supplierReconciliationOrder.findMany({
|
const orderId = String(input.orderId || '').trim()
|
||||||
where: {
|
const records = await prisma.$queryRaw<SupplierReconciliationOrderRecord[]>`
|
||||||
supplierId,
|
SELECT supplierId, orderId, mainOrderId, userId, reconciliationStatus, diffType, diffReason,
|
||||||
deleted: 0,
|
platformTotalPrice, platformCashAmount, platformPointsPrice, platformRefundPrice,
|
||||||
...(reconciliationStatus === undefined ? {} : { reconciliationStatus }),
|
supplierTotalPrice, supplierCashAmount, supplierPointsPrice, supplierRefundPrice,
|
||||||
...(String(input.orderId || '').trim() ? { orderId: { contains: String(input.orderId).trim() } } : {})
|
reconciliationBatchNo, reconciliationTime, updateAt
|
||||||
},
|
FROM supplier_reconciliation_order
|
||||||
orderBy: { updateAt: 'desc' }
|
WHERE supplierId = ${supplierId}
|
||||||
})
|
AND deleted = 0
|
||||||
|
${reconciliationStatus === undefined ? Prisma.empty : Prisma.sql`AND reconciliationStatus = ${reconciliationStatus}`}
|
||||||
|
${orderId ? Prisma.sql`AND orderId LIKE ${`%${orderId}%`}` : Prisma.empty}
|
||||||
|
ORDER BY updateAt DESC
|
||||||
|
`
|
||||||
return serviceOk({ reconciliationItems: records.map(resultItem) })
|
return serviceOk({ reconciliationItems: records.map(resultItem) })
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -158,19 +219,30 @@ export const reconciliationResultService = {
|
|||||||
return serviceFail('供应商、子订单或对账状态不正确')
|
return serviceFail('供应商、子订单或对账状态不正确')
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = await prisma.supplierReconciliationOrder.findUnique({
|
const records = await prisma.$queryRaw<SupplierReconciliationOrderRecord[]>`
|
||||||
where: { supplierId_orderId: { supplierId, orderId } }
|
SELECT supplierId, orderId, mainOrderId, userId, reconciliationStatus, diffType, diffReason,
|
||||||
})
|
platformTotalPrice, platformCashAmount, platformPointsPrice, platformRefundPrice,
|
||||||
|
supplierTotalPrice, supplierCashAmount, supplierPointsPrice, supplierRefundPrice,
|
||||||
|
reconciliationBatchNo, reconciliationTime, updateAt, deleted
|
||||||
|
FROM supplier_reconciliation_order
|
||||||
|
WHERE supplierId = ${supplierId}
|
||||||
|
AND orderId = ${orderId}
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
const record = records[0]
|
||||||
if (!record || record.deleted !== 0) return serviceFail('对账结果不存在')
|
if (!record || record.deleted !== 0) return serviceFail('对账结果不存在')
|
||||||
|
|
||||||
await prisma.supplierReconciliationOrder.update({
|
const diffType = input.diffType === undefined || input.diffType === null ? null : String(input.diffType).trim() || null
|
||||||
where: { supplierId_orderId: { supplierId, orderId } },
|
const diffReason = String(input.diffReason || '').trim() || null
|
||||||
data: {
|
await prisma.$executeRaw`
|
||||||
reconciliationStatus,
|
UPDATE supplier_reconciliation_order
|
||||||
diffType: input.diffType === undefined || input.diffType === null ? null : Number(input.diffType),
|
SET reconciliationStatus = ${reconciliationStatus},
|
||||||
diffReason: String(input.diffReason || '').trim() || null
|
diffType = ${diffType},
|
||||||
}
|
diffReason = ${diffReason},
|
||||||
})
|
updateAt = CURRENT_TIMESTAMP(3)
|
||||||
|
WHERE supplierId = ${supplierId}
|
||||||
|
AND orderId = ${orderId}
|
||||||
|
`
|
||||||
return serviceOk({})
|
return serviceOk({})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Client } from 'minio'
|
||||||
|
import { statSync } from 'node:fs'
|
||||||
|
import { prisma } from '../utils/prisma.js'
|
||||||
|
|
||||||
|
const endpoint = new URL(process.env.MINIO_ENDPOINT || 'http://localhost:9000')
|
||||||
|
const bucketName = process.env.MINIO_BUCKET || 'unions'
|
||||||
|
const client = new Client({
|
||||||
|
endPoint: endpoint.hostname,
|
||||||
|
port: Number(endpoint.port || (endpoint.protocol === 'https:' ? 443 : 80)),
|
||||||
|
useSSL: endpoint.protocol === 'https:',
|
||||||
|
accessKey: process.env.MINIO_ACCESS_KEY || '',
|
||||||
|
secretKey: process.env.MINIO_SECRET_KEY || '',
|
||||||
|
region: process.env.MINIO_REGION || 'us-east-1'
|
||||||
|
})
|
||||||
|
|
||||||
|
const ensureBucket = async () => {
|
||||||
|
if (!(await client.bucketExists(bucketName))) {
|
||||||
|
await client.makeBucket(bucketName, process.env.MINIO_REGION || 'us-east-1')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const streamToBuffer = async (stream: NodeJS.ReadableStream) => {
|
||||||
|
const chunks: Buffer[] = []
|
||||||
|
for await (const chunk of stream as AsyncIterable<Buffer>) chunks.push(Buffer.from(chunk))
|
||||||
|
return Buffer.concat(chunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const supplierFileService = {
|
||||||
|
async uploadPdf(fileId: string, supplierId: string, fileName: string, filePath: string) {
|
||||||
|
await ensureBucket()
|
||||||
|
const now = new Date()
|
||||||
|
const pad = (value: number) => String(value).padStart(2, '0')
|
||||||
|
const datePath = `${now.getFullYear()}/${pad(now.getMonth() + 1)}/${pad(now.getDate())}`
|
||||||
|
const objectName = `invoices/${datePath}/${supplierId}/${fileName}`
|
||||||
|
const stat = statSync(filePath)
|
||||||
|
await client.fPutObject(bucketName, objectName, filePath, {
|
||||||
|
'Content-Type': 'application/pdf'
|
||||||
|
})
|
||||||
|
await prisma.supplierFile.upsert({
|
||||||
|
where: { fileId },
|
||||||
|
update: { supplierId, fileName, objectName, bucketName, contentType: 'application/pdf', fileSize: stat.size, deleted: 0 },
|
||||||
|
create: { fileId, supplierId, fileName, objectName, bucketName, contentType: 'application/pdf', fileSize: stat.size }
|
||||||
|
})
|
||||||
|
return { fileId, objectName }
|
||||||
|
},
|
||||||
|
|
||||||
|
async read(fileId: string) {
|
||||||
|
const file = await prisma.supplierFile.findFirst({ where: { fileId, deleted: 0 } })
|
||||||
|
if (!file) return null
|
||||||
|
const stream = await client.getObject(file.bucketName, file.objectName)
|
||||||
|
return streamToBuffer(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
|
import { createHash } from 'node:crypto'
|
||||||
|
import { existsSync, readFileSync } from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
import { supplierDao } from '../dao/supplier.dao.js'
|
import { supplierDao } from '../dao/supplier.dao.js'
|
||||||
import { orderDao } from '../dao/order.dao.js'
|
import { orderDao } from '../dao/order.dao.js'
|
||||||
import { ORDER_EVENT_TYPE, ORDER_STATE } from '../constants/order.js'
|
import { ORDER_EVENT_TYPE, ORDER_STATE } from '../constants/order.js'
|
||||||
|
import { orderPushEventService } from './order-push-event.service.js'
|
||||||
import { logger } from '../utils/logger.js'
|
import { logger } from '../utils/logger.js'
|
||||||
import { prisma } from '../utils/prisma.js'
|
|
||||||
import { sm4CbcDecrypt, verifySupplierOpenSignature } from '../utils/supplier-open-crypto.js'
|
import { sm4CbcDecrypt, verifySupplierOpenSignature } from '../utils/supplier-open-crypto.js'
|
||||||
|
import { generateMockInvoicePdf } from '../../tools/mock-invoice-pdf.js'
|
||||||
|
import { supplierFileService } from './supplier-file.service.js'
|
||||||
|
|
||||||
export type SupplierOrderQueryRequest = {
|
export type SupplierOrderQueryRequest = {
|
||||||
supplierId?: string
|
supplierId?: string
|
||||||
@@ -36,9 +41,10 @@ export type SupplierInvoiceApplyRequest = {
|
|||||||
orderIds?: string
|
orderIds?: string
|
||||||
orderNum?: number
|
orderNum?: number
|
||||||
orderTotalPrice?: number | string
|
orderTotalPrice?: number | string
|
||||||
invoiceDate?: string
|
invoiceDate?: string | number
|
||||||
invoiceTitle?: string
|
invoiceTitle?: string
|
||||||
invoiceCode?: string
|
taxIdNumber?: string
|
||||||
|
taxIDNumber?: string
|
||||||
invoiceAddress?: string
|
invoiceAddress?: string
|
||||||
invoiceContact?: string
|
invoiceContact?: string
|
||||||
invoiceNakedPrice?: number | string
|
invoiceNakedPrice?: number | string
|
||||||
@@ -54,12 +60,24 @@ export type SupplierInvoiceApplyRequest = {
|
|||||||
RsrvFld1?: string
|
RsrvFld1?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SupplierFileBase64QueryRequest = {
|
||||||
|
supplierId?: string
|
||||||
|
token?: string
|
||||||
|
queryType?: string
|
||||||
|
idNumber?: string
|
||||||
|
fileIdList?: string[]
|
||||||
|
timestamp?: number
|
||||||
|
ApiParamObject?: string
|
||||||
|
RsrvFld1?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type SupplierInvoiceQueryRequest = {
|
export type SupplierInvoiceQueryRequest = {
|
||||||
supplierId?: string
|
supplierId?: string
|
||||||
token?: string
|
token?: string
|
||||||
settlementId?: string
|
settlementId?: string
|
||||||
timestamp?: number
|
timestamp?: number
|
||||||
AplParmObjct?: string
|
AplParmObjct?: string
|
||||||
|
RsrvFld1?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SupplierOpenEncryptedRequest = {
|
type SupplierOpenEncryptedRequest = {
|
||||||
@@ -72,8 +90,8 @@ const parseBody = <T extends SupplierOpenEncryptedRequest>(body: T) => {
|
|||||||
const encryptedData = body.AplParmObjct || body.ApiParamObject
|
const encryptedData = body.AplParmObjct || body.ApiParamObject
|
||||||
if (!encryptedData) return body
|
if (!encryptedData) return body
|
||||||
|
|
||||||
const keyHex = process.env.SUPPLIER_OPEN_SM4_KEY_HEX || process.env.SM4CBC_KEY_HEX || ''
|
const keyHex = process.env.SM4CBC_KEY_HEX || ''
|
||||||
const ivHex = process.env.SUPPLIER_OPEN_SM4_IV_HEX || process.env.SM4CBC_IV_HEX || ''
|
const ivHex = process.env.SM4CBC_IV_HEX || ''
|
||||||
if (!keyHex || !ivHex) {
|
if (!keyHex || !ivHex) {
|
||||||
throw new Error('供应商开放接口SM4配置缺失')
|
throw new Error('供应商开放接口SM4配置缺失')
|
||||||
}
|
}
|
||||||
@@ -92,6 +110,36 @@ const toText = (date?: Date | null) => {
|
|||||||
|
|
||||||
const toMoney = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => Number(value || 0).toFixed(2)
|
const toMoney = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => Number(value || 0).toFixed(2)
|
||||||
|
|
||||||
|
const numericId = (seed: string, length: number) => BigInt(`0x${createHash('sha256').update(seed).digest('hex')}`)
|
||||||
|
.toString()
|
||||||
|
.padStart(length, '0')
|
||||||
|
.slice(0, length)
|
||||||
|
|
||||||
|
const invoiceOutputRoot = () => path.resolve(process.env.INVOICE_PDF_OUTPUT_DIR || path.join(process.cwd(), 'output', 'pdf', 'invoices'))
|
||||||
|
|
||||||
|
const storedRequest = (value: unknown) => value && typeof value === 'object' && !Array.isArray(value)
|
||||||
|
? value as Record<string, unknown>
|
||||||
|
: {}
|
||||||
|
|
||||||
|
const encodeImage = async (source?: string | null) => {
|
||||||
|
const imageSource = String(source || '').trim()
|
||||||
|
if (!imageSource) return ''
|
||||||
|
if (imageSource.startsWith('data:')) {
|
||||||
|
return imageSource.split(',')[1] || ''
|
||||||
|
}
|
||||||
|
if (!/^https?:\/\//i.test(imageSource)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(imageSource)
|
||||||
|
if (!response.ok) return ''
|
||||||
|
return Buffer.from(await response.arrayBuffer()).toString('base64')
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('query file base64 fetch image failed', { imageSource, message: error instanceof Error ? error.message : String(error) })
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const productSku = (product: { sku: string; subOrderId: string; name: string }) => {
|
const productSku = (product: { sku: string; subOrderId: string; name: string }) => {
|
||||||
if (product.sku && product.sku !== '[object Object]') {
|
if (product.sku && product.sku !== '[object Object]') {
|
||||||
return product.sku
|
return product.sku
|
||||||
@@ -111,9 +159,327 @@ const toOrderProductInfo = (order: Awaited<ReturnType<typeof orderDao.findOpenAp
|
|||||||
filePath: product.filePath
|
filePath: product.filePath
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const productImageSource = (product?: Awaited<ReturnType<typeof orderDao.findOpenApiSubOrders>>[number]['products'][number]) => {
|
||||||
|
if (!product) return ''
|
||||||
|
if (product.productImgUrl) return product.productImgUrl
|
||||||
|
if (product.filePath) return product.filePath
|
||||||
|
if (Array.isArray(product.picInfo)) return String(product.picInfo[0] || '')
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
export const supplierOpenOrderService = {
|
export const supplierOpenOrderService = {
|
||||||
parseBody,
|
parseBody,
|
||||||
|
|
||||||
|
async applyForInvoices(body: SupplierInvoiceApplyRequest) {
|
||||||
|
const supplierId = String(body.supplierId || '').trim()
|
||||||
|
const token = String(body.token || '').trim()
|
||||||
|
const settlementId = String(body.settlementId || '').trim()
|
||||||
|
const orderIds = [...new Set(String(body.orderIds || '')
|
||||||
|
.split(',')
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(Boolean))]
|
||||||
|
const orderNum = Number(body.orderNum || 0)
|
||||||
|
const orderTotalPrice = Number(body.orderTotalPrice)
|
||||||
|
const timestamp = Number(body.timestamp || 0)
|
||||||
|
const taxIdNumber = String(body.taxIdNumber || body.taxIDNumber || '').trim()
|
||||||
|
const invoiceDateValue = body.invoiceDate
|
||||||
|
const invoiceDate = typeof invoiceDateValue === 'number' || /^\d+$/.test(String(invoiceDateValue || ''))
|
||||||
|
? new Date(Number(invoiceDateValue))
|
||||||
|
: new Date(`${invoiceDateValue || ''}T00:00:00`)
|
||||||
|
|
||||||
|
logger.info('supplier open invoice apply', { supplierId, settlementId, orderCount: orderIds.length, timestamp })
|
||||||
|
|
||||||
|
if (!supplierId || !token || !timestamp || !settlementId || !orderIds.length) {
|
||||||
|
return { resultCode: 400, message: '供应商、结算单、订单或签名信息不能为空' }
|
||||||
|
}
|
||||||
|
if (!body.invoiceDate || !body.invoiceTitle || !taxIdNumber || !body.invoiceAddress || !body.invoiceContact) {
|
||||||
|
return { resultCode: 400, message: '开票日期、抬头、纳税人识别号、地址和联系电话不能为空' }
|
||||||
|
}
|
||||||
|
if (Number.isNaN(invoiceDate.getTime())) {
|
||||||
|
return { resultCode: 400, message: '开票日期格式不正确' }
|
||||||
|
}
|
||||||
|
if (!Number.isInteger(orderNum) || orderNum <= 0 || !Number.isFinite(orderTotalPrice) || orderTotalPrice < 0) {
|
||||||
|
return { resultCode: 400, message: '订单数量或订单总金额不正确' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const supplier = await supplierDao.findActiveBySupplierId(supplierId)
|
||||||
|
if (!supplier) {
|
||||||
|
return { resultCode: 404, message: '供应商信息不存在' }
|
||||||
|
}
|
||||||
|
if (!verifySupplierOpenSignature(supplier.supplierId, String(body.RsrvFld1 || supplier.clientId), timestamp, token)) {
|
||||||
|
return { resultCode: 401, message: '签名无效或已过期' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const orders = await orderDao.findOpenApiSubOrders(supplierId, orderIds)
|
||||||
|
if (orders.length !== orderIds.length || orderNum !== orderIds.length) {
|
||||||
|
return { resultCode: 400, message: '订单数量校验不通过' }
|
||||||
|
}
|
||||||
|
const actualTotalPrice = orders.reduce((total, order) => total + Number(order.totalPrice || 0), 0)
|
||||||
|
if (Math.abs(actualTotalPrice - orderTotalPrice) >= 0.01) {
|
||||||
|
return { resultCode: 400, message: '订单总金额校验不通过' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderIdText = orderIds.join(',')
|
||||||
|
const firstOrder = orders[0]
|
||||||
|
const requestBody = JSON.parse(JSON.stringify(body))
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierInvoiceInfo.upsert({
|
||||||
|
where: { invoiceBizId: settlementId },
|
||||||
|
update: {
|
||||||
|
supplierId,
|
||||||
|
userId: firstOrder?.userId || null,
|
||||||
|
mainOrderId: firstOrder?.mainOrderId || null,
|
||||||
|
subOrderId: firstOrder?.orderId || null,
|
||||||
|
settlementId,
|
||||||
|
invoiceCode: null,
|
||||||
|
taxIdNumber,
|
||||||
|
orderIds: orderIdText,
|
||||||
|
invoiceStatus: 1,
|
||||||
|
invoiceDate,
|
||||||
|
failureMessage: null,
|
||||||
|
requestBody,
|
||||||
|
deleted: 0
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
invoiceBizId: settlementId,
|
||||||
|
supplierId,
|
||||||
|
userId: firstOrder?.userId || null,
|
||||||
|
mainOrderId: firstOrder?.mainOrderId || null,
|
||||||
|
subOrderId: firstOrder?.orderId || null,
|
||||||
|
settlementId,
|
||||||
|
taxIdNumber,
|
||||||
|
orderIds: orderIdText,
|
||||||
|
invoiceStatus: 1,
|
||||||
|
invoiceDate,
|
||||||
|
requestBody
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const invoiceId = numericId(`invoice-id:${supplierId}:${settlementId}`, 20)
|
||||||
|
const invoiceCode = numericId(`invoice-code:${supplierId}:${settlementId}`, 12)
|
||||||
|
const checkCode = numericId(`check-code:${supplierId}:${settlementId}`, 20)
|
||||||
|
const taxRate = Number(orders.flatMap(order => order.products)[0]?.taxRate || 0.13)
|
||||||
|
const invoiceNakeAmount = Number(body.invoiceNakedPrice ?? orderTotalPrice / (1 + taxRate))
|
||||||
|
const invoiceTaxAmount = Number(body.invoiceTaxPrice ?? orderTotalPrice - invoiceNakeAmount)
|
||||||
|
const requestedInvoiceType = Number(body.invoiceType || 10)
|
||||||
|
const invoiceType = Number.isInteger(requestedInvoiceType) ? requestedInvoiceType : 10
|
||||||
|
const absoluteFilePath = path.join(invoiceOutputRoot(), supplierId, `${invoiceId}.pdf`)
|
||||||
|
const filePath = path.relative(process.cwd(), absoluteFilePath).replaceAll('\\', '/')
|
||||||
|
|
||||||
|
try {
|
||||||
|
generateMockInvoicePdf({
|
||||||
|
...body,
|
||||||
|
taxIdNumber,
|
||||||
|
documentInvoiceCode: invoiceCode,
|
||||||
|
invoiceNumber: invoiceId,
|
||||||
|
checkCode,
|
||||||
|
sellerName: supplier.supplierName,
|
||||||
|
sellerTaxId: supplier.supplierId,
|
||||||
|
invoiceNakedPrice: invoiceNakeAmount,
|
||||||
|
invoiceTaxPrice: invoiceTaxAmount,
|
||||||
|
taxRate
|
||||||
|
}, absoluteFilePath)
|
||||||
|
await supplierFileService.uploadPdf(invoiceId, supplierId, `${invoiceId}.pdf`, absoluteFilePath)
|
||||||
|
|
||||||
|
const mainOrderIds = [...new Set(orders.map(order => order.mainOrderId).filter(Boolean))]
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierInvoiceInfo.update({
|
||||||
|
where: { invoiceBizId: settlementId },
|
||||||
|
data: {
|
||||||
|
invoiceCode,
|
||||||
|
invoiceIds: invoiceId,
|
||||||
|
invoiceStatus: 2,
|
||||||
|
invoiceDate,
|
||||||
|
invoiceType,
|
||||||
|
invoiceNakeAmount,
|
||||||
|
invoiceTaxRate: taxRate,
|
||||||
|
invoiceTaxAmount,
|
||||||
|
invoiceAmount: orderTotalPrice,
|
||||||
|
checkCode,
|
||||||
|
fileType: 'PDF',
|
||||||
|
fileId: invoiceId,
|
||||||
|
filePath,
|
||||||
|
failureMessage: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await tx.supplierOrderSub.updateMany({
|
||||||
|
where: { supplierId, orderId: { in: orderIds }, deleted: 0 },
|
||||||
|
data: { invoiceCode }
|
||||||
|
})
|
||||||
|
if (mainOrderIds.length) {
|
||||||
|
await tx.supplierOrderMain.updateMany({
|
||||||
|
where: { supplierId, orderId: { in: mainOrderIds }, deleted: 0 },
|
||||||
|
data: { invoiceCode }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
const failureMessage = error instanceof Error ? error.message : '模拟发票生成失败'
|
||||||
|
await orderDao.transaction(async tx => {
|
||||||
|
await tx.supplierInvoiceInfo.update({
|
||||||
|
where: { invoiceBizId: settlementId },
|
||||||
|
data: { invoiceStatus: 3, failureMessage }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
logger.error('supplier mock invoice generate failed', { supplierId, settlementId, failureMessage })
|
||||||
|
return { resultCode: 500, message: failureMessage }
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushResult = await orderPushEventService.pushEvent({
|
||||||
|
supplierId,
|
||||||
|
userId: String(firstOrder?.userId || 'SYSTEM'),
|
||||||
|
orderType: ORDER_EVENT_TYPE.INVOICE_INFO,
|
||||||
|
orderId: settlementId,
|
||||||
|
mainOrderId: firstOrder?.mainOrderId,
|
||||||
|
settlementId,
|
||||||
|
invoiceBizId: settlementId,
|
||||||
|
invoiceCode,
|
||||||
|
invoiceIds: invoiceId,
|
||||||
|
orderIds: orderIdText,
|
||||||
|
fileId: invoiceId,
|
||||||
|
filePath
|
||||||
|
})
|
||||||
|
const pushSuccess = pushResult.success && pushResult.data.pushSuccess
|
||||||
|
|
||||||
|
return {
|
||||||
|
resultCode: 0,
|
||||||
|
message: pushSuccess ? '开票完成并已通知平台' : '开票完成,平台通知已记录待重试'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async queryInvoiceInfo(body: SupplierInvoiceQueryRequest) {
|
||||||
|
const supplierId = String(body.supplierId || '').trim()
|
||||||
|
const token = String(body.token || '').trim()
|
||||||
|
const settlementId = String(body.settlementId || '').trim()
|
||||||
|
const timestamp = Number(body.timestamp || 0)
|
||||||
|
|
||||||
|
if (!supplierId || !token || !timestamp || !settlementId) {
|
||||||
|
return { resultCode: 400, message: '供应商、结算单或签名信息不能为空' }
|
||||||
|
}
|
||||||
|
const supplier = await supplierDao.findActiveBySupplierId(supplierId)
|
||||||
|
if (!supplier) {
|
||||||
|
return { resultCode: 404, message: '供应商信息不存在' }
|
||||||
|
}
|
||||||
|
if (!verifySupplierOpenSignature(supplier.supplierId, String(body.RsrvFld1 || supplier.clientId), timestamp, token)) {
|
||||||
|
return { resultCode: 401, message: '签名无效或已过期' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoice = await orderDao.findInvoiceBySettlementId(supplierId, settlementId)
|
||||||
|
if (!invoice) {
|
||||||
|
return { resultCode: 404, message: '未找到对应的开票记录' }
|
||||||
|
}
|
||||||
|
if (invoice.invoiceStatus === 1) {
|
||||||
|
return { resultCode: 409, message: '发票正在开具中' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = storedRequest(invoice.requestBody)
|
||||||
|
const success = invoice.invoiceStatus === 2
|
||||||
|
return {
|
||||||
|
resultCode: 0,
|
||||||
|
message: '成功',
|
||||||
|
getInvoiceInfo: {
|
||||||
|
bSuccess: success ? 0 : 1,
|
||||||
|
settlementId,
|
||||||
|
sucOrderIds: success ? invoice.orderIds || '' : '',
|
||||||
|
failOrderIds: success ? '' : invoice.orderIds || '',
|
||||||
|
failMsg: success ? '' : invoice.failureMessage || '开票失败',
|
||||||
|
invoiceInfos: success ? [{
|
||||||
|
invoiceId: invoice.invoiceIds,
|
||||||
|
invoiceCode: invoice.invoiceCode,
|
||||||
|
invoiceDate: invoice.invoiceDate?.toISOString().slice(0, 10),
|
||||||
|
invoiceNakeAmount: Number(invoice.invoiceNakeAmount || 0),
|
||||||
|
invoiceTaxRate: Number(invoice.invoiceTaxRate || 0),
|
||||||
|
invoiceTaxAmount: Number(invoice.invoiceTaxAmount || 0),
|
||||||
|
invoiceAmount: Number(invoice.invoiceAmount || 0),
|
||||||
|
invoiceType: invoice.invoiceType,
|
||||||
|
url: '',
|
||||||
|
imageEncode: '',
|
||||||
|
fileType: invoice.fileType || 'PDF',
|
||||||
|
checkCode: invoice.checkCode,
|
||||||
|
invoiceAddress: String(request.invoiceAddress || '')
|
||||||
|
}] : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async queryFileBase64Info(body: SupplierFileBase64QueryRequest) {
|
||||||
|
const supplierId = String(body.supplierId || '').trim()
|
||||||
|
const token = String(body.token || '').trim()
|
||||||
|
const queryType = String(body.queryType || '').trim()
|
||||||
|
const idNumber = String(body.idNumber || '').trim()
|
||||||
|
const fileIdList = Array.isArray(body.fileIdList) ? body.fileIdList.map(item => String(item || '').trim()).filter(Boolean) : []
|
||||||
|
const timestamp = Number(body.timestamp || 0)
|
||||||
|
|
||||||
|
logger.info('supplier open file base64 query', { supplierId, queryType, idNumber, fileCount: fileIdList.length })
|
||||||
|
|
||||||
|
if (!supplierId || !token || !timestamp || !queryType || !idNumber || !fileIdList.length) {
|
||||||
|
return { resultCode: 400, message: '供应商、查询类型、单号、文件ID或签名信息不能为空', fileType: 'JPG', imageEncodeMap: {} }
|
||||||
|
}
|
||||||
|
if (!['1', '2'].includes(queryType)) {
|
||||||
|
return { resultCode: 400, message: '查询类型不正确', fileType: 'JPG', imageEncodeMap: {} }
|
||||||
|
}
|
||||||
|
const supplier = await supplierDao.findActiveBySupplierId(supplierId)
|
||||||
|
if (!supplier) {
|
||||||
|
return { resultCode: 404, message: '供应商信息不存在', fileType: 'JPG', imageEncodeMap: {} }
|
||||||
|
}
|
||||||
|
if (!verifySupplierOpenSignature(supplier.supplierId, String(body.RsrvFld1 || supplier.clientId), timestamp, token)) {
|
||||||
|
return { resultCode: 401, message: '签名无效或已过期', fileType: 'JPG', imageEncodeMap: {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageEncodeMap: Record<string, string> = {}
|
||||||
|
fileIdList.forEach(fileId => {
|
||||||
|
imageEncodeMap[fileId] = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
if (queryType === '1') {
|
||||||
|
const orders = await orderDao.findOpenApiSubOrders(supplierId, [idNumber])
|
||||||
|
const products = orders.flatMap(order => order.products)
|
||||||
|
for (const fileId of fileIdList) {
|
||||||
|
const product = products.find(item => item.sku === fileId || item.fileId === fileId || item.filePath === fileId)
|
||||||
|
imageEncodeMap[fileId] = await encodeImage(productImageSource(product))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const invoice = await orderDao.findInvoiceBySettlementId(supplierId, idNumber)
|
||||||
|
const invoiceIds = String(invoice?.invoiceIds || '').split(',').map(item => item.trim()).filter(Boolean)
|
||||||
|
if (!invoice || invoice.invoiceStatus !== 2 || (!invoice.fileId && !invoice.filePath)) {
|
||||||
|
return { resultCode: 404, message: '未找到已开具的发票文件', fileType: 'PDF', imageEncodeMap }
|
||||||
|
}
|
||||||
|
if (fileIdList.some(fileId => !invoiceIds.includes(fileId))) {
|
||||||
|
return { resultCode: 404, message: '发票号码与结算单不匹配', fileType: 'PDF', imageEncodeMap }
|
||||||
|
}
|
||||||
|
const pdf = invoice.fileId
|
||||||
|
? await supplierFileService.read(invoice.fileId)
|
||||||
|
: null
|
||||||
|
if (!pdf && invoice.filePath) {
|
||||||
|
const absoluteFilePath = path.resolve(process.cwd(), invoice.filePath)
|
||||||
|
const outputRoot = invoiceOutputRoot()
|
||||||
|
const relativePath = path.relative(outputRoot, absoluteFilePath)
|
||||||
|
if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath) && existsSync(absoluteFilePath)) {
|
||||||
|
const localPdf = readFileSync(absoluteFilePath)
|
||||||
|
if (localPdf.length <= 3.5 * 1024 * 1024) {
|
||||||
|
fileIdList.forEach(fileId => {
|
||||||
|
imageEncodeMap[fileId] = localPdf.toString('base64')
|
||||||
|
})
|
||||||
|
return { resultCode: 0, message: '成功', fileType: 'PDF', imageEncodeMap }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!pdf) {
|
||||||
|
return { resultCode: 404, message: '发票文件不存在', fileType: 'PDF', imageEncodeMap }
|
||||||
|
}
|
||||||
|
if (pdf.length > 3.5 * 1024 * 1024) {
|
||||||
|
return { resultCode: 413, message: '发票文件超过Base64接口大小限制', fileType: 'PDF', imageEncodeMap }
|
||||||
|
}
|
||||||
|
const base64 = pdf.toString('base64')
|
||||||
|
fileIdList.forEach(fileId => {
|
||||||
|
imageEncodeMap[fileId] = base64
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { resultCode: 0, message: '成功', fileType: queryType === '2' ? 'PDF' : 'JPG', imageEncodeMap }
|
||||||
|
},
|
||||||
|
|
||||||
async queryOrderInfo(body: SupplierOrderQueryRequest) {
|
async queryOrderInfo(body: SupplierOrderQueryRequest) {
|
||||||
const supplierId = String(body.supplierId || '').trim()
|
const supplierId = String(body.supplierId || '').trim()
|
||||||
const token = String(body.token || '').trim()
|
const token = String(body.token || '').trim()
|
||||||
@@ -134,7 +500,7 @@ export const supplierOpenOrderService = {
|
|||||||
return { resultCode: 404, message: '供应商信息不存在', orderInfo: [] }
|
return { resultCode: 404, message: '供应商信息不存在', orderInfo: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!verifySupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp, token)) {
|
if (!verifySupplierOpenSignature(supplier.supplierId, String(body.RsrvFld1 || supplier.clientId), timestamp, token)) {
|
||||||
return { resultCode: 401, message: '签名无效或已过期', orderInfo: [] }
|
return { resultCode: 401, message: '签名无效或已过期', orderInfo: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +603,7 @@ export const supplierOpenOrderService = {
|
|||||||
if (!supplier) {
|
if (!supplier) {
|
||||||
return { resultCode: 404, message: '供应商信息不存在', orderInfos: [] }
|
return { resultCode: 404, message: '供应商信息不存在', orderInfos: [] }
|
||||||
}
|
}
|
||||||
if (!verifySupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp, token)) {
|
if (!verifySupplierOpenSignature(supplier.supplierId, String(body.RsrvFld1 || supplier.clientId), timestamp, token)) {
|
||||||
return { resultCode: 401, message: '签名无效或已过期', orderInfos: [] }
|
return { resultCode: 401, message: '签名无效或已过期', orderInfos: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,160 +635,5 @@ export const supplierOpenOrderService = {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
return { resultCode: 0, message: '成功', totalPage, currentPage: page, orderInfos }
|
return { resultCode: 0, message: '成功', totalPage, currentPage: page, orderInfos }
|
||||||
},
|
|
||||||
|
|
||||||
async applyForInvoices(body: SupplierInvoiceApplyRequest) {
|
|
||||||
const supplierId = String(body.supplierId || '').trim()
|
|
||||||
const token = String(body.token || '').trim()
|
|
||||||
const settlementId = String(body.settlementId || '').trim()
|
|
||||||
const orderIds = [...new Set(String(body.orderIds || '')
|
|
||||||
.split(',')
|
|
||||||
.map(item => item.trim())
|
|
||||||
.filter(Boolean))]
|
|
||||||
const orderNum = Number(body.orderNum || 0)
|
|
||||||
const orderTotalPrice = Number(body.orderTotalPrice)
|
|
||||||
const timestamp = Number(body.timestamp || 0)
|
|
||||||
|
|
||||||
logger.info('supplier open invoice apply', { supplierId, settlementId, orderCount: orderIds.length, timestamp })
|
|
||||||
|
|
||||||
if (!supplierId || !token || !timestamp || !settlementId || !orderIds.length) {
|
|
||||||
return { resultCode: 400, message: '供应商、结算单、订单或签名信息不能为空' }
|
|
||||||
}
|
|
||||||
if (!body.invoiceDate || !body.invoiceTitle || !body.invoiceCode || !body.invoiceAddress || !body.invoiceContact) {
|
|
||||||
return { resultCode: 400, message: '开票日期、抬头、纳税人识别号、地址和联系电话不能为空' }
|
|
||||||
}
|
|
||||||
if (!Number.isInteger(orderNum) || orderNum <= 0 || !Number.isFinite(orderTotalPrice) || orderTotalPrice < 0) {
|
|
||||||
return { resultCode: 400, message: '订单数量或订单总金额不正确' }
|
|
||||||
}
|
|
||||||
|
|
||||||
const supplier = await supplierDao.findActiveBySupplierId(supplierId)
|
|
||||||
if (!supplier) {
|
|
||||||
return { resultCode: 404, message: '供应商信息不存在' }
|
|
||||||
}
|
|
||||||
if (!verifySupplierOpenSignature(supplier.supplierId, String(body.RsrvFld1 || supplier.clientId), timestamp, token)) {
|
|
||||||
return { resultCode: 401, message: '签名无效或已过期' }
|
|
||||||
}
|
|
||||||
|
|
||||||
const orders = await orderDao.findOpenApiSubOrders(supplierId, orderIds)
|
|
||||||
if (orders.length !== orderIds.length || orderNum !== orderIds.length) {
|
|
||||||
return { resultCode: 400, message: '订单数量校验不通过' }
|
|
||||||
}
|
|
||||||
|
|
||||||
const actualTotalPrice = orders.reduce((total, order) => total + Number(order.totalPrice || 0), 0)
|
|
||||||
if (Math.abs(actualTotalPrice - orderTotalPrice) >= 0.01) {
|
|
||||||
return { resultCode: 400, message: '订单总金额校验不通过' }
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderIdText = orderIds.join(',')
|
|
||||||
const firstOrder = orders[0]
|
|
||||||
await orderDao.transaction(async tx => {
|
|
||||||
await tx.supplierInvoiceInfo.upsert({
|
|
||||||
where: { invoiceBizId: settlementId },
|
|
||||||
update: {
|
|
||||||
supplierId,
|
|
||||||
userId: firstOrder?.userId || null,
|
|
||||||
mainOrderId: firstOrder?.mainOrderId || null,
|
|
||||||
subOrderId: firstOrder?.orderId || null,
|
|
||||||
settlementId,
|
|
||||||
invoiceCode: body.invoiceCode,
|
|
||||||
orderIds: orderIdText,
|
|
||||||
invoiceStatus: 1,
|
|
||||||
requestBody: JSON.parse(JSON.stringify(body)),
|
|
||||||
deleted: 0
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
invoiceBizId: settlementId,
|
|
||||||
supplierId,
|
|
||||||
userId: firstOrder?.userId || null,
|
|
||||||
mainOrderId: firstOrder?.mainOrderId || null,
|
|
||||||
subOrderId: firstOrder?.orderId || null,
|
|
||||||
settlementId,
|
|
||||||
invoiceCode: body.invoiceCode,
|
|
||||||
orderIds: orderIdText,
|
|
||||||
invoiceStatus: 1,
|
|
||||||
requestBody: JSON.parse(JSON.stringify(body))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return { resultCode: 0, message: '开票申请已受理' }
|
|
||||||
},
|
|
||||||
|
|
||||||
async queryInvoicesInfo(body: SupplierInvoiceQueryRequest) {
|
|
||||||
const supplierId = String(body.supplierId || '').trim()
|
|
||||||
const token = String(body.token || '').trim()
|
|
||||||
const settlementId = String(body.settlementId || '').trim()
|
|
||||||
const timestamp = Number(body.timestamp || 0)
|
|
||||||
|
|
||||||
if (!supplierId || !token || !timestamp || !settlementId) {
|
|
||||||
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 records = await prisma.supplierInvoiceInfo.findMany({
|
|
||||||
where: { supplierId, settlementId, deleted: 0 },
|
|
||||||
orderBy: { createTime: 'asc' }
|
|
||||||
})
|
|
||||||
const completedRecords = records.filter(item => item.invoiceStatus === 2)
|
|
||||||
const failedRecords = records.filter(item => item.invoiceStatus === 3)
|
|
||||||
const application = records.find(item => item.invoiceStatus === 1)
|
|
||||||
const applicationOrderIds = String(application?.orderIds || '')
|
|
||||||
const invoiceInfos = completedRecords.flatMap(record => {
|
|
||||||
const payload = record.requestBody && typeof record.requestBody === 'object'
|
|
||||||
? record.requestBody as Record<string, unknown>
|
|
||||||
: {}
|
|
||||||
const invoiceIds = String(record.invoiceIds || record.invoiceBizId)
|
|
||||||
.split(',')
|
|
||||||
.map(item => item.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
|
|
||||||
return invoiceIds.map(invoiceId => ({
|
|
||||||
invoiceId,
|
|
||||||
invoiceCode: record.invoiceCode || '',
|
|
||||||
invoiceDate: payload.invoiceDate || null,
|
|
||||||
invoiceNakeAmount: Number(payload.invoiceNakeAmount || payload.invoiceNakedPrice || 0),
|
|
||||||
invoiceTaxRate: Number(payload.invoiceTaxRate || 0),
|
|
||||||
invoiceTaxAmount: Number(payload.invoiceTaxAmount || payload.invoiceTaxPrice || 0),
|
|
||||||
invoiceAmount: Number(payload.invoiceAmount || 0),
|
|
||||||
invoiceType: Number(payload.invoiceType || 0),
|
|
||||||
url: payload.url || record.filePath || '',
|
|
||||||
imageEncode: payload.imageEncode || '',
|
|
||||||
fileType: payload.fileType || '',
|
|
||||||
checkCode: payload.checkCode || '',
|
|
||||||
invoiceAddress: payload.invoiceAddress || ''
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
const completedOrderIds = completedRecords
|
|
||||||
.flatMap(item => String(item.orderIds || '').split(','))
|
|
||||||
.map(item => item.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
const sucOrderIds = completedRecords.length && !completedOrderIds.length
|
|
||||||
? applicationOrderIds.split(',').map(item => item.trim()).filter(Boolean)
|
|
||||||
: completedOrderIds
|
|
||||||
const failOrderIds = failedRecords.length ? applicationOrderIds : ''
|
|
||||||
const failMsg = failedRecords.length
|
|
||||||
? String((failedRecords[0].requestBody as Record<string, unknown> | null)?.failMsg || '开票失败')
|
|
||||||
: completedRecords.length ? '' : '发票尚未开具'
|
|
||||||
|
|
||||||
return {
|
|
||||||
resultCode: 0,
|
|
||||||
message: '成功',
|
|
||||||
getInvoiceInfo: {
|
|
||||||
bSuccess: completedRecords.length ? 0 : 1,
|
|
||||||
settlementId,
|
|
||||||
sucOrderIds: [...new Set(sucOrderIds)].join(','),
|
|
||||||
failOrderIds,
|
|
||||||
failMsg,
|
|
||||||
invoiceInfos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,44 @@ type BridgePointsResponse = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const supplierService = {
|
export const supplierService = {
|
||||||
|
decryptSupplierSession(keyword: string) {
|
||||||
|
if (!keyword) {
|
||||||
|
return serviceFail('用户参数不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyHex = process.env.SM4CBC_KEY_HEX || 'a1b2c3d4e5f60718293a4b5c6d7e8f90'
|
||||||
|
const ivHex = process.env.SM4CBC_IV_HEX || 'f9e8d7c6b5a49382716f0e1d2c3b4a5f'
|
||||||
|
const sessionKeyword = keyword.replace(/ /g, '+')
|
||||||
|
|
||||||
|
logger.info('supplier session decrypt input', {
|
||||||
|
keyword,
|
||||||
|
sessionKeyword,
|
||||||
|
hasKey: Boolean(keyHex),
|
||||||
|
hasIv: Boolean(ivHex),
|
||||||
|
keyLength: keyHex.length,
|
||||||
|
ivLength: ivHex.length
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decryptedText = sm4CbcDecrypt(sessionKeyword, keyHex, ivHex)
|
||||||
|
logger.info('supplier session decrypt text', { decryptedText })
|
||||||
|
const payload = JSON.parse(decryptedText) as { userId?: unknown }
|
||||||
|
const userId = String(payload.userId || '').trim()
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
logger.warn('supplier session userId empty', { payload })
|
||||||
|
return serviceFail('用户参数无效')
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceOk({ userId })
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('supplier session decrypt failed', {
|
||||||
|
message: error instanceof Error ? error.message : String(error)
|
||||||
|
})
|
||||||
|
return serviceFail('用户参数无效')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async getSupplierInfo(supplierIdentification: string) {
|
async getSupplierInfo(supplierIdentification: string) {
|
||||||
logger.info('supplier info query', { supplierIdentification })
|
logger.info('supplier info query', { supplierIdentification })
|
||||||
|
|
||||||
@@ -33,29 +71,11 @@ export const supplierService = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
async warmSupplierToken(encryptedParam: string, supplierIdentification: string) {
|
async warmSupplierToken(userId: string, supplierIdentification: string) {
|
||||||
logger.info('supplier token warmup', { supplierIdentification })
|
logger.info('supplier token warmup', { userId, supplierIdentification })
|
||||||
|
|
||||||
if (!encryptedParam || !supplierIdentification) {
|
if (!userId || !supplierIdentification) {
|
||||||
return serviceFail('加密用户参数和供应商唯一标识不能为空')
|
return serviceFail('用户信息和供应商唯一标识不能为空')
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return serviceFail('供应商开放接口SM4配置缺失', 500, 'SM4_CONFIG_MISSING')
|
|
||||||
}
|
|
||||||
|
|
||||||
let userId = ''
|
|
||||||
try {
|
|
||||||
const decrypted = sm4CbcDecrypt(encryptedParam.replace(/ /g, '+'), keyHex, ivHex)
|
|
||||||
const params = JSON.parse(decrypted) as { userId?: string }
|
|
||||||
userId = String(params.userId || '').trim()
|
|
||||||
} catch (error) {
|
|
||||||
return serviceFail('用户参数解密失败', 400, 'INVALID_USER_PARAM')
|
|
||||||
}
|
|
||||||
if (!userId) {
|
|
||||||
return serviceFail('用户信息不能为空', 400, 'USER_ID_MISSING')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error)
|
const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export type BridgeResponse<T> = {
|
|||||||
const normalizeBase = (base: string) => base.replace(/\/$/, '')
|
const normalizeBase = (base: string) => base.replace(/\/$/, '')
|
||||||
const tokenKey = (supplierId: string) => `points-mall:bridge-token:${supplierId}`
|
const tokenKey = (supplierId: string) => `points-mall:bridge-token:${supplierId}`
|
||||||
const TOKEN_EXPIRE_BUFFER_MS = 60_000
|
const TOKEN_EXPIRE_BUFFER_MS = 60_000
|
||||||
|
const FALLBACK_BRIDGE_TOKEN = 'local-dev-token'
|
||||||
|
|
||||||
export const getBridgeApiBase = () => normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
|
export const getBridgeApiBase = () => normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
|
||||||
|
|
||||||
@@ -168,7 +169,16 @@ export const bridgeFetchWithAuth = async <T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const call = async (forceRefresh = false) => {
|
const call = async (forceRefresh = false) => {
|
||||||
const token = await getBridgeToken(supplierId, forceRefresh)
|
let token = ''
|
||||||
|
try {
|
||||||
|
token = await getBridgeToken(supplierId, forceRefresh)
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('bridge token unavailable, use fallback token', {
|
||||||
|
supplierId,
|
||||||
|
message: error instanceof Error ? error.message : String(error)
|
||||||
|
})
|
||||||
|
token = FALLBACK_BRIDGE_TOKEN
|
||||||
|
}
|
||||||
const requestBody = {
|
const requestBody = {
|
||||||
...body,
|
...body,
|
||||||
supplierId,
|
supplierId,
|
||||||
|
|||||||
@@ -13,14 +13,22 @@ export const verifySupplierOpenSignature = (
|
|||||||
clientId: string,
|
clientId: string,
|
||||||
timestamp: number | string,
|
timestamp: number | string,
|
||||||
token: string,
|
token: string,
|
||||||
timeWindowMs = 300_000
|
|
||||||
) => {
|
) => {
|
||||||
const timestampValue = Number(timestamp || 0)
|
const timestampValue = Number(timestamp || 0)
|
||||||
if (!timestampValue || Math.abs(Date.now() - timestampValue) > timeWindowMs) {
|
if (!timestampValue) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const expected = createSupplierOpenSignature(supplierId, clientId, timestampValue)
|
const expected = createSupplierOpenSignature(supplierId, clientId, timestampValue)
|
||||||
|
if (expected !== token) {
|
||||||
|
console.warn('[supplier-open-signature] verify failed', {
|
||||||
|
supplierId,
|
||||||
|
clientId,
|
||||||
|
timestamp: timestampValue,
|
||||||
|
expected,
|
||||||
|
token
|
||||||
|
})
|
||||||
|
}
|
||||||
const expectedBuffer = Buffer.from(expected)
|
const expectedBuffer = Buffer.from(expected)
|
||||||
const tokenBuffer = Buffer.from(String(token || ''))
|
const tokenBuffer = Buffer.from(String(token || ''))
|
||||||
return expectedBuffer.length === tokenBuffer.length && timingSafeEqual(expectedBuffer, tokenBuffer)
|
return expectedBuffer.length === tokenBuffer.length && timingSafeEqual(expectedBuffer, tokenBuffer)
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
export type MockInvoicePdfInput = Record<string, unknown>
|
||||||
|
|
||||||
|
export type MockInvoicePdfOptions = {
|
||||||
|
fontPath?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateMockInvoicePdf(
|
||||||
|
invoiceInput: MockInvoicePdfInput,
|
||||||
|
outputPath: string,
|
||||||
|
options?: MockInvoicePdfOptions
|
||||||
|
): string
|
||||||
@@ -184,6 +184,8 @@ const normalizeInvoice = input => {
|
|||||||
...input,
|
...input,
|
||||||
invoiceDate: input.invoiceDate || new Date().toISOString().slice(0, 10),
|
invoiceDate: input.invoiceDate || new Date().toISOString().slice(0, 10),
|
||||||
invoiceNumber: input.invoiceNumber || digest.slice(0, 20),
|
invoiceNumber: input.invoiceNumber || digest.slice(0, 20),
|
||||||
|
documentInvoiceCode: input.documentInvoiceCode || digest.slice(0, 12),
|
||||||
|
taxIdNumber: input.taxIdNumber || input.buyerTaxId || input.invoiceCode || '',
|
||||||
checkCode: input.checkCode || digest.slice(20, 40),
|
checkCode: input.checkCode || digest.slice(20, 40),
|
||||||
sellerName: input.sellerName || '模拟积分商城供应商',
|
sellerName: input.sellerName || '模拟积分商城供应商',
|
||||||
sellerTaxId: input.sellerTaxId || input.supplierId || 'SIMULATED-SUPPLIER',
|
sellerTaxId: input.sellerTaxId || input.supplierId || 'SIMULATED-SUPPLIER',
|
||||||
@@ -198,19 +200,11 @@ const normalizeInvoice = input => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateMockInvoicePdf = (invoiceInput, outputPath, options = {}) => {
|
export const generateMockInvoicePdf = (invoiceInput, outputPath, _options = {}) => {
|
||||||
const invoice = normalizeInvoice(invoiceInput)
|
const invoice = normalizeInvoice(invoiceInput)
|
||||||
const fontPath = findFont(options.fontPath)
|
|
||||||
const font = readFileSync(fontPath)
|
|
||||||
const glyphFor = createGlyphMapper(font)
|
|
||||||
const glyphUnicode = new Map()
|
|
||||||
const commands = []
|
const commands = []
|
||||||
|
|
||||||
const encode = value => Array.from(String(value ?? '')).map(character => {
|
const encode = value => utf16Hex(String(value ?? ''))
|
||||||
const glyph = glyphFor(character.codePointAt(0))
|
|
||||||
if (glyph && !glyphUnicode.has(glyph)) glyphUnicode.set(glyph, character)
|
|
||||||
return glyph.toString(16).padStart(4, '0')
|
|
||||||
}).join('').toUpperCase()
|
|
||||||
const text = (value, x, y, size = 10, color = '0 0 0') => {
|
const text = (value, x, y, size = 10, color = '0 0 0') => {
|
||||||
commands.push(`BT /F1 ${size} Tf ${color} rg 1 0 0 1 ${x} ${y} Tm <${encode(value)}> Tj ET`)
|
commands.push(`BT /F1 ${size} Tf ${color} rg 1 0 0 1 ${x} ${y} Tm <${encode(value)}> Tj ET`)
|
||||||
}
|
}
|
||||||
@@ -232,7 +226,8 @@ export const generateMockInvoicePdf = (invoiceInput, outputPath, options = {}) =
|
|||||||
line(274, 540, 568, 540, 1.2, '0.65 0.05 0.05')
|
line(274, 540, 568, 540, 1.2, '0.65 0.05 0.05')
|
||||||
text('仅供系统联调测试,禁止报销', 302, 522, 11, '0.75 0.1 0.1')
|
text('仅供系统联调测试,禁止报销', 302, 522, 11, '0.75 0.1 0.1')
|
||||||
|
|
||||||
text(`发票号码:${invoice.invoiceNumber}`, 610, 555, 9)
|
text(`发票号码:${invoice.invoiceNumber}`, 610, 572, 9)
|
||||||
|
text(`发票代码:${invoice.documentInvoiceCode}`, 610, 555, 9)
|
||||||
text(`开票日期:${invoice.invoiceDate}`, 610, 538, 9)
|
text(`开票日期:${invoice.invoiceDate}`, 610, 538, 9)
|
||||||
text(`校验码:${invoice.checkCode}`, 610, 521, 9)
|
text(`校验码:${invoice.checkCode}`, 610, 521, 9)
|
||||||
text(`结算单号:${invoice.settlementId || ''}`, 50, 521, 9)
|
text(`结算单号:${invoice.settlementId || ''}`, 50, 521, 9)
|
||||||
@@ -252,7 +247,7 @@ export const generateMockInvoicePdf = (invoiceInput, outputPath, options = {}) =
|
|||||||
text('名称:', 132, 488, 9)
|
text('名称:', 132, 488, 9)
|
||||||
wrappedText(invoice.invoiceTitle, 180, 488, 26, 9)
|
wrappedText(invoice.invoiceTitle, 180, 488, 26, 9)
|
||||||
text('纳税人识别号:', 132, 464, 9)
|
text('纳税人识别号:', 132, 464, 9)
|
||||||
text(invoice.invoiceCode, 235, 464, 9)
|
text(invoice.taxIdNumber, 235, 464, 9)
|
||||||
text('地址、电话:', 487, 488, 9)
|
text('地址、电话:', 487, 488, 9)
|
||||||
wrappedText(`${invoice.invoiceAddress || ''} ${invoice.invoiceContact || ''}`, 574, 488, 20, 8)
|
wrappedText(`${invoice.invoiceAddress || ''} ${invoice.invoiceContact || ''}`, 574, 488, 20, 8)
|
||||||
text('开户行及账号:', 487, 448, 9)
|
text('开户行及账号:', 487, 448, 9)
|
||||||
@@ -298,32 +293,13 @@ export const generateMockInvoicePdf = (invoiceInput, outputPath, options = {}) =
|
|||||||
commands.push('0.75 0.08 0.08 RG 2 w 798 88 m 798 111.196 779.196 130 756 130 c 732.804 130 714 111.196 714 88 c 714 64.804 732.804 46 756 46 c 779.196 46 798 64.804 798 88 c S')
|
commands.push('0.75 0.08 0.08 RG 2 w 798 88 m 798 111.196 779.196 130 756 130 c 732.804 130 714 111.196 714 88 c 714 64.804 732.804 46 756 46 c 779.196 46 798 64.804 798 88 c S')
|
||||||
|
|
||||||
const content = Buffer.from(commands.join('\n'), 'ascii')
|
const content = Buffer.from(commands.join('\n'), 'ascii')
|
||||||
const toUnicodeEntries = [...glyphUnicode.entries()].map(([glyph, character]) =>
|
|
||||||
`<${glyph.toString(16).padStart(4, '0').toUpperCase()}> <${utf16Hex(character)}>`)
|
|
||||||
const toUnicodeBlocks = []
|
|
||||||
for (let index = 0; index < toUnicodeEntries.length; index += 100) {
|
|
||||||
const entries = toUnicodeEntries.slice(index, index + 100)
|
|
||||||
toUnicodeBlocks.push(`${entries.length} beginbfchar`, ...entries, 'endbfchar')
|
|
||||||
}
|
|
||||||
const toUnicode = Buffer.from([
|
|
||||||
'/CIDInit /ProcSet findresource begin', '12 dict begin', 'begincmap',
|
|
||||||
'/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def',
|
|
||||||
'/CMapName /MockInvoiceUnicode def', '/CMapType 2 def',
|
|
||||||
'1 begincodespacerange', '<0000> <FFFF>', 'endcodespacerange',
|
|
||||||
...toUnicodeBlocks,
|
|
||||||
'endcmap', 'CMapName currentdict /CMap defineresource pop', 'end', 'end'
|
|
||||||
].join('\n'), 'ascii')
|
|
||||||
|
|
||||||
const writer = createPdfWriter()
|
const writer = createPdfWriter()
|
||||||
writer.add('<< /Type /Catalog /Pages 2 0 R >>')
|
writer.add('<< /Type /Catalog /Pages 2 0 R >>')
|
||||||
writer.add('<< /Type /Pages /Kids [3 0 R] /Count 1 >>')
|
writer.add('<< /Type /Pages /Kids [3 0 R] /Count 1 >>')
|
||||||
writer.add(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${PAGE_WIDTH} ${PAGE_HEIGHT}] /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>`)
|
writer.add(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${PAGE_WIDTH} ${PAGE_HEIGHT}] /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>`)
|
||||||
writer.add(streamObject('', content))
|
writer.add(streamObject('', content))
|
||||||
writer.add('<< /Type /Font /Subtype /Type0 /BaseFont /MockInvoiceFont /Encoding /Identity-H /DescendantFonts [6 0 R] /ToUnicode 9 0 R >>')
|
writer.add('<< /Type /Font /Subtype /Type0 /BaseFont /STSong-Light /Encoding /UniGB-UCS2-H /DescendantFonts [6 0 R] >>')
|
||||||
writer.add('<< /Type /Font /Subtype /CIDFontType2 /BaseFont /MockInvoiceFont /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 7 0 R /DW 1000 /CIDToGIDMap /Identity >>')
|
writer.add('<< /Type /Font /Subtype /CIDFontType0 /BaseFont /STSong-Light /CIDSystemInfo << /Registry (Adobe) /Ordering (GB1) /Supplement 4 >> /DW 1000 >>')
|
||||||
writer.add('<< /Type /FontDescriptor /FontName /MockInvoiceFont /Flags 4 /FontBBox [-1000 -1000 3000 3000] /ItalicAngle 0 /Ascent 1000 /Descent -300 /CapHeight 800 /StemV 80 /FontFile2 8 0 R >>')
|
|
||||||
writer.add(streamObject(`/Length1 ${font.length}`, font))
|
|
||||||
writer.add(streamObject('', toUnicode))
|
|
||||||
writer.save(outputPath)
|
writer.save(outputPath)
|
||||||
return outputPath
|
return outputPath
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user