diff --git a/supplied-h5/src/view/joyful-selection/cart/components/JoyfulCartPanel.vue b/supplied-h5/src/view/joyful-selection/cart/components/JoyfulCartPanel.vue index 6f1275f..d30a988 100644 --- a/supplied-h5/src/view/joyful-selection/cart/components/JoyfulCartPanel.vue +++ b/supplied-h5/src/view/joyful-selection/cart/components/JoyfulCartPanel.vue @@ -2,7 +2,15 @@
- +
+ + 去购物 +
@@ -122,6 +105,17 @@ const ORDER_STATE_VIEW = { [ORDER_STATE.REFUNDED]: { text: '退货', statusClass: 'muted' } } +const SKU_COLOR_MAP = { + cream: '奶油白', + blue: '雾霾蓝', + orange: '暖橙色' +} + +const SKU_SIZE_MAP = { + standard: '标准款', + plus: '升级款' +} + export default { name: 'JoyfulSelectionOrders', components: { @@ -139,7 +133,9 @@ export default { { label: '售后', type: 'after-sale' } ], orders: [], + orderItems: [], loading: false, + refreshing: false, operationKey: '' }), computed: { @@ -150,31 +146,27 @@ export default { visibleOrders() { const tab = this.activeTab + const orderItems = this.orderItems.length ? this.orderItems : this.buildOrderItems() + if (tab.type === 'all') { - return this.orders.map(order => this.normalizeOrder(order, order.subOrders)) + return orderItems } if (tab.type === 'pending-main') { - return this.orders - .filter(order => Number(order.orderState) === ORDER_STATE.PENDING_PAY) - .map(order => this.normalizeOrder(order, order.subOrders)) + return orderItems.filter(order => order.displayType === 'main' && Number(order.orderState) === ORDER_STATE.PENDING_PAY) } if (tab.type === 'sub-state') { - return this.splitSubOrdersByState(tab.state) + return orderItems.filter(order => order.displayType === 'sub' && Number(order.orderState) === tab.state) } if (tab.type === 'cancelled') { - return this.cancelledOrders() + return orderItems.filter(order => Number(order.orderState) === ORDER_STATE.CANCELLED) } if (tab.type === 'after-sale') { - return this.orders.flatMap(order => { - const subOrders = this.normalizedSubOrders(order).filter(subOrder => { - return [ORDER_STATE.EXCHANGE, ORDER_STATE.REFUNDED].includes(Number(subOrder.orderState)) - }) - - return subOrders.map(subOrder => this.normalizeOrder(order, [subOrder])) + return orderItems.filter(order => { + return order.displayType === 'sub' && [ORDER_STATE.EXCHANGE, ORDER_STATE.REFUNDED].includes(Number(order.orderState)) }) } @@ -210,6 +202,38 @@ export default { normalizedSubOrders(order) { return (order.subOrders || []).map(subOrder => this.normalizeSubOrder(order, subOrder)) }, + buildOrderItems() { + return this.orders.flatMap(order => { + const subOrders = this.normalizedSubOrders(order) + const products = subOrders.flatMap(subOrder => subOrder.products || []) + const isMainOrderItem = Number(order.orderState) === ORDER_STATE.PENDING_PAY || + (Number(order.orderState) === ORDER_STATE.CANCELLED && Number(order.isSplit) !== ORDER_SPLIT_STATE.SPLIT) + + if (isMainOrderItem) { + return [{ + ...order, + viewId: `main-${order.mainOrderId || order.id}`, + displayType: 'main', + orderId: order.mainOrderId || order.orderId, + orderNumberId: order.mainOrderNumberId || order.orderNumberId, + status: ORDER_STATE_VIEW[Number(order.orderState)]?.text || order.status, + statusClass: ORDER_STATE_VIEW[Number(order.orderState)]?.statusClass || order.statusClass, + amount: order.amount || subOrders.reduce((total, subOrder) => total + Number(subOrder.amount || 0), 0).toFixed(2), + count: subOrders.reduce((total, subOrder) => total + Number(subOrder.count || 0), 0), + products, + subOrders + }] + } + + return subOrders.map(subOrder => ({ + ...subOrder, + viewId: `sub-${subOrder.orderId}`, + displayType: 'sub', + mainOrderId: order.mainOrderId, + mainOrderNumberId: order.mainOrderNumberId + })) + }) + }, splitSubOrdersByState(state) { return this.orders.flatMap(order => { if (Number(order.isSplit) !== ORDER_SPLIT_STATE.SPLIT) { @@ -237,6 +261,17 @@ export default { previewProducts(products = []) { return products.slice(0, 3) }, + firstProduct(order) { + return order.products?.[0] || {} + }, + skuText(product = {}) { + const sku = String(product.sku || product.skuText || '').trim() + const parts = sku.split('-').slice(-2).filter(Boolean) + const color = SKU_COLOR_MAP[parts[0]] || parts[0] + const size = SKU_SIZE_MAP[parts[1]] || parts[1] + + return color || size ? [color, size].filter(Boolean).join(' | ') : '-' + }, actionKey(type, orderId) { return `${type}-${orderId}` }, @@ -256,13 +291,15 @@ export default { actions.push({ orderType: ORDER_EVENT_TYPE.EXCHANGE_ORDER, text: '换货', buttonClass: 'dark-action' }) } - if (![ORDER_STATE.PENDING_PAY, ORDER_STATE.CANCELLED].includes(state)) { - actions.push({ orderType: ORDER_EVENT_TYPE.INVOICE_INFO, text: '发票信息', buttonClass: 'dark-action' }) - } - return actions }, - goOrderDetail(order, subOrder) { + goOrderDetail(order) { + const subOrder = order.displayType === 'main' ? order.subOrders?.[0] : order + + if (!subOrder?.orderId) { + return + } + this.$router.push({ path: '/joyful-selection/order-detail', query: { @@ -271,8 +308,10 @@ export default { } }) }, - async loadOrders() { - this.loading = true + async loadOrders(showLoading = true) { + if (showLoading) { + this.loading = true + } try { await this.fetchBridgeToken() @@ -280,6 +319,7 @@ export default { if (!this.supplierId || !this.userId) { Toast('用户信息不完整,请重新进入商城') this.orders = [] + this.orderItems = [] return } @@ -289,13 +329,49 @@ export default { }) this.orders = data?.orders || [] + this.orderItems = data?.orderItems || [] } catch (error) { Toast(error.message || '查询订单失败,请稍后再试') this.orders = [] + this.orderItems = [] } finally { - this.loading = false + if (showLoading) { + this.loading = false + } } }, + async onRefresh() { + try { + await this.loadOrders(false) + } finally { + this.refreshing = false + } + }, + orderActions(order) { + if (order.displayType === 'main') { + return Number(order.orderState) === ORDER_STATE.PENDING_PAY + ? [{ key: 'cancel-main', orderId: order.mainOrderId, text: '取消订单', buttonClass: 'dark-action' }] + : [] + } + + return this.subActions(order).map(action => ({ + ...action, + key: action.orderType, + orderId: order.orderId + })) + }, + submitOrderAction(order, action) { + if (action.key === 'cancel-main') { + this.cancelMainOrder(order) + return + } + + this.submitSubOrderEvent( + { mainOrderId: order.mainOrderId }, + order, + action + ) + }, async cancelMainOrder(order) { try { await Dialog.confirm({ @@ -409,152 +485,159 @@ export default { .orders-page ::v-deep .van-tab { color: #333; - font-size: 12px; + font-size: 13px; } .order-list { - padding: 8px 8px 14px; + min-height: calc(100vh - 90px); + box-sizing: border-box; + padding: 12px 10px 18px; } .order-card { overflow: hidden; - margin-bottom: 10px; + margin-bottom: 14px; border-radius: 8px; background: #fff; } -.main-order-header { +.order-card__header { display: flex; align-items: center; justify-content: space-between; - padding: 12px 12px 10px; - border-bottom: 1px solid #f4f4f4; + padding: 14px 14px 6px; } -.main-order-header span { +.order-card__header span { min-width: 0; overflow: hidden; color: #555; - font-size: 12px; + font-size: 13px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; } -.cancel-main-btn { +.order-card__header b { flex: none; margin-left: 10px; - color: #ff5b20; - border-color: #ff5b20; -} - -.sub-order-row { - padding: 10px 12px 12px; -} - -.sub-order-row + .sub-order-row { - border-top: 1px solid #f4f4f4; -} - -.sub-order-title { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 8px; -} - -.sub-order-title span { - min-width: 0; - overflow: hidden; - color: #777; - font-size: 11px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.sub-order-title b { - flex: none; - margin-left: 10px; - font-size: 12px; + font-size: 14px; font-weight: 700; } -.sub-order-title .pending { +.order-card__header .pending { color: #ff5b20; } -.sub-order-title .done { +.order-card__header .done { color: #21b36b; } -.sub-order-title .muted { +.order-card__header .muted { color: #999; } -.order-body { +.order-card__body { display: flex; align-items: flex-end; justify-content: space-between; + padding: 12px 14px 10px; } -.thumb-row { +.order-goods { display: flex; min-width: 0; flex: 1; - gap: 8px; + align-items: center; } .order-thumb { - width: 62px; - height: 62px; + width: 70px; + height: 70px; flex: none; overflow: hidden; border-radius: 4px; background: #f5f5f5; } +.order-goods__info { + min-width: 0; + flex: 1; + padding-left: 10px; +} + +.order-goods__info h3 { + overflow: hidden; + margin: 0 0 6px; + color: #222; + font-size: 14px; + font-weight: 700; + line-height: 20px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.order-goods__info p { + overflow: hidden; + margin: 4px 0 0; + color: #777; + font-size: 12px; + line-height: 18px; + text-overflow: ellipsis; + white-space: nowrap; +} + .order-summary { flex: none; - margin-left: 10px; + margin-left: 12px; color: #777; - font-size: 11px; + font-size: 13px; text-align: right; white-space: nowrap; } .order-summary p { margin: 0; - line-height: 22px; + line-height: 25px; } .order-summary b { color: #222; - font-size: 12px; + font-size: 14px; } -.sub-order-row footer { +.order-card footer { display: flex; - flex-wrap: wrap; + align-items: center; justify-content: flex-end; - gap: 8px; - padding-top: 10px; + padding: 0 14px 14px; } -.sub-order-row footer .van-button { - min-width: 72px; - height: 28px; - padding: 0 12px; - border-radius: 15px; - font-size: 11px; +.order-actions { + display: flex; + min-width: 0; + flex: 1; + justify-content: flex-end; + gap: 10px; +} + +.order-actions .van-button { + min-width: 88px; + height: 34px; + padding: 0 16px; + border: 0; + border-radius: 6px; + font-size: 14px; + font-weight: 700; } .primary-action { color: #ff5b20; - border-color: #ff5b20; + background: #fff1e8; } .dark-action { color: #333; - border-color: #ddd; + background: #f6f7f9; } diff --git a/supplied-service/package-lock.json b/supplied-service/package-lock.json new file mode 100644 index 0000000..9a59a39 --- /dev/null +++ b/supplied-service/package-lock.json @@ -0,0 +1,2293 @@ +{ + "name": "supplied-service", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "supplied-service", + "version": "0.1.0", + "dependencies": { + "@koa/router": "^13.1.1", + "@neondatabase/serverless": "^1.0.1", + "@prisma/adapter-neon": "6.19.3", + "@prisma/client": "6.19.3", + "crypto-js": "^4.2.0", + "dotenv": "^17.2.3", + "ioredis": "^5.4.2", + "koa": "^2.15.3", + "koa-bodyparser": "^4.4.1", + "ws": "^8.18.3" + }, + "devDependencies": { + "@types/crypto-js": "^4.2.2", + "@types/koa": "^2.15.0", + "@types/koa__router": "^12.0.4", + "@types/koa-bodyparser": "^4.3.12", + "@types/node": "^24.10.1", + "@types/ws": "^8.18.1", + "prisma": "6.19.3", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hapi/bourne": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", + "license": "BSD-3-Clause" + }, + "node_modules/@ioredis/commands": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.10.0.tgz", + "integrity": "sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==", + "license": "MIT" + }, + "node_modules/@koa/router": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.1.tgz", + "integrity": "sha512-JQEuMANYRVHs7lm7KY9PCIjkgJk73h4m4J+g2mkw2Vo1ugPZ17UJVqEH8F+HeAdjKz5do1OaLe7ArDz+z308gw==", + "deprecated": "Please upgrade to v15 or higher. All reported bugs in this version are fixed in newer releases, dependencies have been updated, and security has been improved.", + "license": "MIT", + "dependencies": { + "debug": "^4.4.1", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "path-to-regexp": "^6.3.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@neondatabase/serverless": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-1.1.0.tgz", + "integrity": "sha512-r3ZZhRjEcfEdKIZnoB1RusNgvHuaBRqfCzV4Gi+5A9yUX0S4HTws/ASWqt13wL4y4I+0rqsWGdA2w7EQXHi3+Q==", + "license": "MIT", + "engines": { + "node": ">=19.0.0" + } + }, + "node_modules/@prisma/adapter-neon": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/adapter-neon/-/adapter-neon-6.19.3.tgz", + "integrity": "sha512-QpvQHKP6JpvJJ+Gmt6nuucIVMZFDGDvv8vaKrO8bhn2IeracQvBdINlh5F0h5goq7w2Ym7HkDIGXMkrlutcB7A==", + "license": "Apache-2.0", + "dependencies": { + "@neondatabase/serverless": ">0.6.0 <2", + "@prisma/driver-adapter-utils": "6.19.3", + "postgres-array": "3.0.4" + } + }, + "node_modules/@prisma/client": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.3.tgz", + "integrity": "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.3.tgz", + "integrity": "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.21.0", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.3.tgz", + "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/driver-adapter-utils": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-6.19.3.tgz", + "integrity": "sha512-UUxn6VLfKKVqm5n9vexOQgFJ9TCBIxupb7F5FzLQ6iM2VV0WZxhgzbQVqBDsGY+VmQLmqaSoKRusEfy3O5KZpA==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3" + } + }, + "node_modules/@prisma/engines": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.3.tgz", + "integrity": "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/fetch-engine": "6.19.3", + "@prisma/get-platform": "6.19.3" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz", + "integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.3.tgz", + "integrity": "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/get-platform": "6.19.3" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.3.tgz", + "integrity": "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-disposition": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz", + "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cookies": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.2.tgz", + "integrity": "sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.2.tgz", + "integrity": "sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/koa": { + "version": "2.15.2", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.2.tgz", + "integrity": "sha512-CB+iyjjh1uS5N6/CKwXvw0qA7USMS2WVc4Tjf660yCjhdvqzNr8gdFcIawB41zGGptOQ+d1fnpaQWIIUXYxR3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa__router": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/@types/koa__router/-/koa__router-12.0.5.tgz", + "integrity": "sha512-1HeLxuDn4n5it1yZYCSyOYXo++73zT0ffoviXnPxbwbxLbvDFEvWD9ZzpRiIpK4oKR0pi+K+Mk/ZjyROjW3HSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/koa-bodyparser": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@types/koa-bodyparser/-/koa-bodyparser-4.3.13.tgz", + "integrity": "sha512-yW9afsDnV1ymnhMBfAlzsKZ45sYnvIlTRS+eF5MUZOQ8ePIIj3ajwi+9b4a0vdyYvh6uJKYibU8R1zrUIpCmCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.9.tgz", + "integrity": "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "license": "MIT", + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.1.tgz", + "integrity": "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-body": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.2.0.tgz", + "integrity": "sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==", + "license": "MIT", + "dependencies": { + "@hapi/bourne": "^3.0.0", + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-to": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", + "integrity": "sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==", + "license": "MIT" + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "license": "MIT" + }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/effect": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz", + "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", + "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "license": "MIT", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflation": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz", + "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ioredis": { + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.11.1.tgz", + "integrity": "sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "1.10.0", + "cluster-key-slot": "1.1.1", + "debug": "4.4.3", + "denque": "2.1.0", + "redis-errors": "1.2.0", + "redis-parser": "3.0.0", + "standard-as-callback": "2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "license": "MIT", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa": { + "version": "2.16.4", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.4.tgz", + "integrity": "sha512-3An0GCLDSR34tsCO4H8Tef8Pp2ngtaZDAZnsWJYelqXUK5wyiHvGItgK/xcSkmHLSTn1Jcho1mRQs2ehRzvKKw==", + "license": "MIT", + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-bodyparser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.4.1.tgz", + "integrity": "sha512-kBH3IYPMb+iAXnrxIhXnW+gXV8OTzCu8VPDqvcDHW9SQrbkHmqPQtiZwrltNmSq6/lpipHnT7k7PsjlVD7kK0w==", + "license": "MIT", + "dependencies": { + "co-body": "^6.0.0", + "copy-to": "^2.0.1", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "license": "MIT" + }, + "node_modules/koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "license": "MIT", + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/nypm": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.9.tgz", + "integrity": "sha512-zxlE2yvSWZWmHcNdT3+5zV2lrCogeE9YOklHrR3dFjqutq5wO7GFDYLFDRXLsYnJzwvy/im9fYoxePvS0VTW0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.2", + "pathe": "^2.0.3", + "tinyexec": "^1.2.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/prisma": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.3.tgz", + "integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.19.3", + "@prisma/engines": "6.19.3" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/tsx": { + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/ylru": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + } + } +} diff --git a/supplied-service/src/utils/order-state.ts b/supplied-service/src/constants/order.ts similarity index 92% rename from supplied-service/src/utils/order-state.ts rename to supplied-service/src/constants/order.ts index b378049..795709a 100644 --- a/supplied-service/src/utils/order-state.ts +++ b/supplied-service/src/constants/order.ts @@ -1,3 +1,4 @@ +// Supplier order states and operation codes shared by order services and controllers. export const ORDER_STATE = { PENDING_PAY: 0, FINISH_PAY: 1, diff --git a/supplied-service/src/controllers/basic.controller.ts b/supplied-service/src/controllers/basic.controller.ts new file mode 100644 index 0000000..50d3e25 --- /dev/null +++ b/supplied-service/src/controllers/basic.controller.ts @@ -0,0 +1,33 @@ +import type { Context } from '../utils/handler.js' +import { guardMethod } from '../utils/method.js' +import { withErrorBoundary } from '../utils/handler.js' +import { basicService } from '../services/basic.service.js' +import { toHttpResponse } from './controller-response.js' + +export const loginController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + return toHttpResponse(basicService.login()) +}) + +export const cartAddController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + return toHttpResponse(basicService.addCart()) +}) + +export const uploadController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + return toHttpResponse(basicService.upload()) +}) + +export const userInfoController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['GET']) + if (methodResponse) return methodResponse + + return toHttpResponse(basicService.userInfo()) +}) diff --git a/supplied-service/src/controllers/controller-response.ts b/supplied-service/src/controllers/controller-response.ts new file mode 100644 index 0000000..4fdb3b7 --- /dev/null +++ b/supplied-service/src/controllers/controller-response.ts @@ -0,0 +1,10 @@ +import { fail, ok } from '../utils/http.js' +import type { ServiceResult } from '../types/service-result.js' + +export const toHttpResponse = (result: ServiceResult) => { + if (result.success) { + return ok(result.data) + } + + return fail(result.error.message, result.error.status, result.error.code) +} diff --git a/supplied-service/src/controllers/order.controller.ts b/supplied-service/src/controllers/order.controller.ts new file mode 100644 index 0000000..67dff04 --- /dev/null +++ b/supplied-service/src/controllers/order.controller.ts @@ -0,0 +1,149 @@ +import type { Context } from '../utils/handler.js' +import { guardMethod } from '../utils/method.js' +import { readJson } from '../utils/request.js' +import { withErrorBoundary } from '../utils/handler.js' +import { orderActionService } from '../services/order-action.service.js' +import { orderCreateService, type CheckoutGoods } from '../services/order-create.service.js' +import { orderListService } from '../services/order-list.service.js' +import { orderPushEventService, type OrderPushEventInput } from '../services/order-push-event.service.js' +import { supplierOpenOrderService, type SupplierOrderQueryRequest } from '../services/supplier-open-order.service.js' +import { toHttpResponse } from './controller-response.js' + +type OrderListRequest = { + supplierId?: string + userId?: string +} + +type OrderActionRequest = { + supplierId?: string + userId?: string + orderId?: string + mainOrderId?: string + amount?: number + totalPrice?: number + wPayOrAPay?: number + pointsPrice?: number +} + +type OrderCreateRequest = { + supplierId?: string + userId?: string + orderNo?: string + orderNumberId?: string + amount?: number + goods?: CheckoutGoods[] +} + +const centsToYuan = (value?: number) => Number((Number(value || 0) / 100).toFixed(2)) + +export const orderListController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + const supplierId = String(body.supplierId || '').trim() + const userId = String(body.userId || '').trim() + + return toHttpResponse(await orderListService.listOrders(supplierId, userId)) +}) + +export const orderCreateController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + + return toHttpResponse(await orderCreateService.createOrder({ + supplierId: String(body.supplierId || '').trim(), + userId: String(body.userId || '').trim(), + orderNo: String(body.orderNo || '').trim(), + orderNumberId: body.orderNumberId, + amount: body.amount, + goods: Array.isArray(body.goods) ? body.goods : [] + })) +}) + +export const orderCancelMainController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + + return toHttpResponse(await orderActionService.cancelPendingMain({ + supplierId: String(body.supplierId || '').trim(), + userId: String(body.userId || '').trim(), + orderId: String(body.orderId || '').trim(), + mainOrderId: String(body.mainOrderId || body.orderId || '').trim() + })) +}) + +export const orderCancelPaidMainController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + + return toHttpResponse(await orderActionService.cancelPaidMain({ + supplierId: String(body.supplierId || '').trim(), + userId: String(body.userId || '').trim(), + orderId: String(body.orderId || '').trim(), + mainOrderId: String(body.mainOrderId || body.orderId || '').trim() + })) +}) + +export const orderCancelSubController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + + return toHttpResponse(await orderActionService.cancelSub({ + supplierId: String(body.supplierId || '').trim(), + userId: String(body.userId || '').trim(), + orderId: String(body.orderId || '').trim(), + mainOrderId: String(body.mainOrderId || '').trim() + })) +}) + +export const orderPayController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + const totalPrice = Number(body.totalPrice ?? centsToYuan(body.amount)) + + return toHttpResponse(await orderActionService.payOrder({ + supplierId: String(body.supplierId || '').trim(), + orderId: String(body.orderId || '').trim(), + totalPrice, + wPayOrAPay: Number(body.wPayOrAPay ?? 0), + pointsPrice: Number(body.pointsPrice ?? totalPrice) + })) +}) + +export const orderPushEventController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + + return toHttpResponse(await orderPushEventService.pushEvent(body)) +}) + +export const supplierOrderInfoController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + let body: SupplierOrderQueryRequest + try { + body = supplierOpenOrderService.parseBody(await readJson(request)) + } catch (error) { + return Response.json({ + resultCode: 400, + message: error instanceof Error ? error.message : '请求参数解密失败', + orderInfo: [] + }) + } + + return Response.json(await supplierOpenOrderService.queryOrderInfo(body)) +}) diff --git a/supplied-service/src/controllers/product.controller.ts b/supplied-service/src/controllers/product.controller.ts new file mode 100644 index 0000000..295ff74 --- /dev/null +++ b/supplied-service/src/controllers/product.controller.ts @@ -0,0 +1,27 @@ +import type { Context } from '../utils/handler.js' +import { guardMethod } from '../utils/method.js' +import { withErrorBoundary } from '../utils/handler.js' +import { productService } from '../services/product.service.js' +import { toHttpResponse } from './controller-response.js' + +export const productListController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['GET']) + if (methodResponse) return methodResponse + + const url = new URL(request.url) + const keyword = (url.searchParams.get('keyword') || '').trim() + const categoryId = (url.searchParams.get('categoryId') || '').trim() + + return toHttpResponse(await productService.listProducts(keyword, categoryId)) +}) + +export const productDetailController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['GET']) + if (methodResponse) return methodResponse + + const url = new URL(request.url) + const id = (url.searchParams.get('id') || '').trim() + const sku = (url.searchParams.get('sku') || '').trim() + + return toHttpResponse(await productService.getProductDetail(id, sku)) +}) diff --git a/supplied-service/src/controllers/supplier.controller.ts b/supplied-service/src/controllers/supplier.controller.ts new file mode 100644 index 0000000..e1452dd --- /dev/null +++ b/supplied-service/src/controllers/supplier.controller.ts @@ -0,0 +1,67 @@ +import type { Context } from '../utils/handler.js' +import { guardMethod } from '../utils/method.js' +import { readJson } from '../utils/request.js' +import { withErrorBoundary } from '../utils/handler.js' +import { supplierService } from '../services/supplier.service.js' +import { toHttpResponse } from './controller-response.js' + +type SupplierInfoRequest = { + supplierIdentification?: string +} + +type SupplierTokenRequest = { + userId?: string + supplierIdentification?: string +} + +type SupplierPointsRequest = { + supplierId?: string + userId?: string +} + +type SupplierDebugTokensRequest = { + supplierIdentification?: string + forceRefreshZhghToken?: boolean +} + +export const supplierInfoController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase() + + return toHttpResponse(await supplierService.getSupplierInfo(supplierIdentification)) +}) + +export const supplierTokenController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + const userId = String(body.userId || '').trim() + const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase() + + return toHttpResponse(await supplierService.warmSupplierToken(userId, supplierIdentification)) +}) + +export const supplierPointsController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + const supplierId = String(body.supplierId || '').trim() + const userId = String(body.userId || '').trim() + + return toHttpResponse(await supplierService.getUserPoints(supplierId, userId)) +}) + +export const supplierDebugTokensController = withErrorBoundary(async (request: Request, _context: Context) => { + const methodResponse = guardMethod(request, ['POST']) + if (methodResponse) return methodResponse + + const body = await readJson(request) + const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase() + + return toHttpResponse(await supplierService.getDebugTokens(supplierIdentification, Boolean(body.forceRefreshZhghToken))) +}) diff --git a/supplied-service/src/dao/order.dao.ts b/supplied-service/src/dao/order.dao.ts new file mode 100644 index 0000000..10bc7c6 --- /dev/null +++ b/supplied-service/src/dao/order.dao.ts @@ -0,0 +1,103 @@ +import type { Prisma } from '@prisma/client' +import { prisma } from '../utils/prisma.js' + +export type PrismaTransaction = Prisma.TransactionClient + +export const orderDao = { + transaction(handler: (tx: PrismaTransaction) => Promise) { + return prisma.$transaction(handler) + }, + + findMainOrdersWithSubs(supplierId: string, userId: string) { + return prisma.supplierOrderMain.findMany({ + where: { + supplierId, + userId, + deleted: 0 + }, + include: { + subOrders: { + where: { deleted: 0 }, + include: { + products: { + where: { deleted: 0 }, + orderBy: { createAt: 'asc' } + } + }, + orderBy: [ + { createTime: 'desc' }, + { createAt: 'desc' } + ] + } + }, + orderBy: [ + { createTime: 'desc' }, + { createAt: 'desc' } + ] + }) + }, + + findMainOrder(supplierId: string, userId: string, mainOrderId: string) { + return prisma.supplierOrderMain.findFirst({ + where: { + orderId: mainOrderId, + supplierId, + userId, + deleted: 0 + } + }) + }, + + findSubOrder(supplierId: string, userId: string, orderId: string) { + return prisma.supplierOrderSub.findFirst({ + where: { + orderId, + supplierId, + userId, + deleted: 0 + } + }) + }, + + findPayingMainOrder(orderId: string) { + return prisma.supplierOrderMain.findUnique({ + where: { orderId } + }) + }, + + findSubOrderIdsByMain(mainOrderId: string) { + return prisma.supplierOrderSub.findMany({ + where: { + mainOrderId, + deleted: 0 + }, + select: { + orderId: true + } + }) + }, + + findOpenApiSubOrders(supplierId: string, orderIds: string[]) { + return prisma.supplierOrderSub.findMany({ + where: { + supplierId, + orderId: { in: orderIds }, + deleted: 0 + }, + include: { + products: { + where: { deleted: 0 }, + orderBy: { createAt: 'asc' } + } + }, + orderBy: { createAt: 'asc' } + }) + }, + + updatePushLog(id: number, data: Parameters[0]['data']) { + return prisma.supplierOrderPushLog.update({ + where: { id }, + data + }) + } +} diff --git a/supplied-service/src/dao/product.dao.ts b/supplied-service/src/dao/product.dao.ts new file mode 100644 index 0000000..dcc5723 --- /dev/null +++ b/supplied-service/src/dao/product.dao.ts @@ -0,0 +1,37 @@ +import { prisma } from '../utils/prisma.js' + +export type ProductRecord = Awaited>[number] + +export const productDao = { + // 商品列表查询只负责组装数据库条件,业务格式化放在 service。 + findEnabledProducts(keyword: string, categoryId: string) { + return prisma.supplierProduct.findMany({ + where: { + enabled: true, + deleted: 0, + ...(keyword ? { name: { contains: keyword } } : {}), + ...(categoryId && categoryId !== 'recommend' ? { categoryId } : {}) + }, + orderBy: [ + { sortOrder: 'asc' }, + { createdAt: 'desc' } + ] + }) + }, + + findEnabledProduct(id: string, sku: string) { + const numericId = Number(id) + const querySku = sku || (id && !Number.isInteger(numericId) ? id : '') + + return prisma.supplierProduct.findFirst({ + where: { + enabled: true, + deleted: 0, + OR: [ + ...(Number.isInteger(numericId) && numericId > 0 ? [{ id: numericId }] : []), + ...(querySku ? [{ sku: querySku }] : []) + ] + } + }) + } +} diff --git a/supplied-service/src/dao/supplier.dao.ts b/supplied-service/src/dao/supplier.dao.ts new file mode 100644 index 0000000..a15d020 --- /dev/null +++ b/supplied-service/src/dao/supplier.dao.ts @@ -0,0 +1,15 @@ +import { prisma } from '../utils/prisma.js' + +export const supplierDao = { + findActiveByIdentification(supplierIdentification: string) { + return prisma.supplierInfo.findFirst({ + where: { supplierIdentification, deleted: 0 } + }) + }, + + findActiveBySupplierId(supplierId: string) { + return prisma.supplierInfo.findFirst({ + where: { supplierId, deleted: 0 } + }) + } +} diff --git a/supplied-service/src/functions/cart-add.ts b/supplied-service/src/functions/cart-add.ts index 0b6b42b..588cf5f 100644 --- a/supplied-service/src/functions/cart-add.ts +++ b/supplied-service/src/functions/cart-add.ts @@ -1,12 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { ok } from '../utils/http.js' -import { withErrorBoundary } from '../utils/handler.js' - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - return ok({ message: '加入购物车接口待实现' }) -}) - +export { cartAddController as default } from '../controllers/basic.controller.js' diff --git a/supplied-service/src/functions/login.ts b/supplied-service/src/functions/login.ts index 91e3d1c..52b9f55 100644 --- a/supplied-service/src/functions/login.ts +++ b/supplied-service/src/functions/login.ts @@ -1,12 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { ok } from '../utils/http.js' -import { withErrorBoundary } from '../utils/handler.js' - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - return ok({ message: '登录接口待实现' }) -}) - +export { loginController as default } from '../controllers/basic.controller.js' diff --git a/supplied-service/src/functions/order-cancel-main.ts b/supplied-service/src/functions/order-cancel-main.ts index 5c31c76..5efc470 100644 --- a/supplied-service/src/functions/order-cancel-main.ts +++ b/supplied-service/src/functions/order-cancel-main.ts @@ -1,100 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' -import { ORDER_EVENT_TYPE, ORDER_STATE } from '../utils/order-state.js' -import { bridgeFetchWithAuth } from '../utils/bridge-auth.js' - -type OrderCancelMainRequest = { - supplierId?: string - userId?: string - orderId?: string - mainOrderId?: string -} - -type BridgeOrderResponse = { - resultCode?: number - message?: string -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const supplierId = String(body.supplierId || '').trim() - const userId = String(body.userId || '').trim() - const mainOrderId = String(body.mainOrderId || body.orderId || '').trim() - const orderId = String(body.orderId || mainOrderId).trim() - - if (!supplierId || !userId || !mainOrderId) { - return fail('供应商、用户或订单信息不能为空') - } - - const order = await prisma.supplierOrderMain.findFirst({ - where: { - orderId: mainOrderId, - supplierId, - userId, - deleted: 0 - } - }) - - if (!order) { - return fail('主单不存在或已取消', 404, 'MAIN_ORDER_NOT_FOUND') - } - - if (order.orderState !== ORDER_STATE.PENDING_PAY) { - return fail('已支付主单不能通过未支付取消接口取消', 400, 'MAIN_ORDER_ALREADY_PAID') - } - - await prisma.$transaction(async tx => { - await tx.supplierOrderMain.update({ - where: { orderId: mainOrderId }, - data: { - orderState: ORDER_STATE.CANCELLED, - updateTime: new Date() - } - }) - - await tx.supplierOrderSub.updateMany({ - where: { - mainOrderId, - supplierId, - userId, - deleted: 0 - }, - data: { - orderState: ORDER_STATE.CANCELLED, - updateTime: new Date() - } - }) - }) - - const { response: bridgeResponse, payload: bridgePayload } = await bridgeFetchWithAuth( - supplierId, - '/pointOrder/pushOrderInfoMsg', - { - orderType: ORDER_EVENT_TYPE.CANCEL_MAIN, - orderId, - mainOrderId, - userId, - time: Date.now() - } - ) - - if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) { - return fail(bridgePayload.message || '取消主单推送失败', 502, 'BRIDGE_CANCEL_MAIN_FAILED') - } - - return ok({ - supplierId, - orderType: ORDER_EVENT_TYPE.CANCEL_MAIN, - orderId, - mainOrderId, - userId - }) -}) - +export { orderCancelMainController as default } from '../controllers/order.controller.js' diff --git a/supplied-service/src/functions/order-cancel-paid-main.ts b/supplied-service/src/functions/order-cancel-paid-main.ts index 7926217..f77d9d9 100644 --- a/supplied-service/src/functions/order-cancel-paid-main.ts +++ b/supplied-service/src/functions/order-cancel-paid-main.ts @@ -1,104 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' -import { ORDER_EVENT_TYPE, ORDER_SPLIT_STATE, ORDER_STATE } from '../utils/order-state.js' -import { bridgeFetchWithAuth } from '../utils/bridge-auth.js' - -type OrderCancelPaidMainRequest = { - supplierId?: string - userId?: string - orderId?: string - mainOrderId?: string -} - -type BridgeOrderResponse = { - resultCode?: number - message?: string -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const supplierId = String(body.supplierId || '').trim() - const userId = String(body.userId || '').trim() - const mainOrderId = String(body.mainOrderId || body.orderId || '').trim() - const orderId = String(body.orderId || mainOrderId).trim() - - if (!supplierId || !userId || !mainOrderId) { - return fail('供应商、用户或订单信息不能为空') - } - - const order = await prisma.supplierOrderMain.findFirst({ - where: { - orderId: mainOrderId, - supplierId, - userId, - deleted: 0 - } - }) - - if (!order) { - return fail('主单不存在或已取消', 404, 'MAIN_ORDER_NOT_FOUND') - } - - if (order.orderState === ORDER_STATE.PENDING_PAY) { - return fail('未支付主单请使用未支付取消接口', 400, 'MAIN_ORDER_NOT_PAID') - } - - if (order.orderState !== ORDER_STATE.FINISH_PAY || order.isSplit === ORDER_SPLIT_STATE.SPLIT) { - return fail('该主单已拆单,不能按已支付未拆单整单取消', 400, 'MAIN_ORDER_ALREADY_SPLIT') - } - - await prisma.$transaction(async tx => { - await tx.supplierOrderMain.update({ - where: { orderId: mainOrderId }, - data: { - orderState: ORDER_STATE.CANCELLED, - updateTime: new Date() - } - }) - - await tx.supplierOrderSub.updateMany({ - where: { - mainOrderId, - supplierId, - userId, - deleted: 0 - }, - data: { - orderState: ORDER_STATE.CANCELLED, - updateTime: new Date() - } - }) - }) - - const { response: bridgeResponse, payload: bridgePayload } = await bridgeFetchWithAuth( - supplierId, - '/pointOrder/pushOrderInfoMsg', - { - orderType: ORDER_EVENT_TYPE.CANCEL_PAID_NOT_SPLIT_MAIN, - orderId, - mainOrderId, - userId, - time: Date.now() - } - ) - - if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) { - return fail(bridgePayload.message || '取消已支付未拆单整单推送失败', 502, 'BRIDGE_CANCEL_PAID_MAIN_FAILED') - } - - return ok({ - supplierId, - orderType: ORDER_EVENT_TYPE.CANCEL_PAID_NOT_SPLIT_MAIN, - orderId, - mainOrderId, - userId - }) -}) - +export { orderCancelPaidMainController as default } from '../controllers/order.controller.js' diff --git a/supplied-service/src/functions/order-cancel-sub.ts b/supplied-service/src/functions/order-cancel-sub.ts index 1bfcbd2..25261ae 100644 --- a/supplied-service/src/functions/order-cancel-sub.ts +++ b/supplied-service/src/functions/order-cancel-sub.ts @@ -1,82 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' -import { ORDER_EVENT_TYPE, ORDER_STATE } from '../utils/order-state.js' -import { bridgeFetchWithAuth } from '../utils/bridge-auth.js' - -type OrderCancelSubRequest = { - supplierId?: string - userId?: string - orderId?: string - mainOrderId?: string -} - -type BridgeOrderResponse = { - resultCode?: number - message?: string -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const supplierId = String(body.supplierId || '').trim() - const userId = String(body.userId || '').trim() - const orderId = String(body.orderId || '').trim() - - if (!supplierId || !userId || !orderId) { - return fail('供应商、用户或子单信息不能为空') - } - - const subOrder = await prisma.supplierOrderSub.findFirst({ - where: { - orderId, - supplierId, - userId, - deleted: 0 - } - }) - - if (!subOrder) { - return fail('子单不存在或已取消', 404, 'SUB_ORDER_NOT_FOUND') - } - - const mainOrderId = String(body.mainOrderId || subOrder.mainOrderId).trim() - - await prisma.supplierOrderSub.update({ - where: { orderId }, - data: { - orderState: ORDER_STATE.CANCELLED, - updateTime: new Date() - } - }) - - const { response: bridgeResponse, payload: bridgePayload } = await bridgeFetchWithAuth( - supplierId, - '/pointOrder/pushOrderInfoMsg', - { - orderType: ORDER_EVENT_TYPE.CANCEL_SUB, - orderId, - mainOrderId, - userId, - time: Date.now() - } - ) - - if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) { - return fail(bridgePayload.message || '取消子单推送失败', 502, 'BRIDGE_CANCEL_SUB_FAILED') - } - - return ok({ - supplierId, - orderType: ORDER_EVENT_TYPE.CANCEL_SUB, - orderId, - mainOrderId, - userId - }) -}) - +export { orderCancelSubController as default } from '../controllers/order.controller.js' diff --git a/supplied-service/src/functions/order-create.ts b/supplied-service/src/functions/order-create.ts index aebbade..da71d35 100644 --- a/supplied-service/src/functions/order-create.ts +++ b/supplied-service/src/functions/order-create.ts @@ -1,270 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' -import { ORDER_SPLIT_STATE, ORDER_STATE } from '../utils/order-state.js' -import { bridgeFetchWithAuth } from '../utils/bridge-auth.js' - -type CheckoutGoods = { - id?: string | number - sku?: string | number | Record - count?: number - number?: number - price?: string | number - name?: string - image?: string - imageUrl?: string - picInfo?: string[] - taxRate?: number -} - -type OrderCreateRequest = { - supplierId?: string - userId?: string - orderNo?: string - orderNumberId?: string - amount?: number - goods?: CheckoutGoods[] -} - -type BridgeOrderResponse = { - resultCode?: number - message?: string -} - -type OrderProductSnapshot = { - sku: string - number: number - price: number - name: string - taxRate: number - picInfo: string[] - fileId: string | null - filePath: string | null -} - -const nowText = () => { - const now = new Date() - const pad = (value: number) => String(value).padStart(2, '0') - - return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}` -} - -const createOrderNumberId = () => { - const now = new Date() - const pad = (value: number, size = 2) => String(value).padStart(size, '0') - - return `TR${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}${pad(now.getMilliseconds(), 3)}` -} - -const toYuan = (centAmount?: number) => Number((Number(centAmount || 0) / 100).toFixed(2)) -const toMoney = (value: number) => Number(value.toFixed(2)) -const createSubOrderId = (orderId: string, index: number) => `${orderId}_${String(index + 1).padStart(3, '0')}` -const createSubOrderNumberId = (orderNumberId: string, index: number) => `${orderNumberId}_${String(index + 1).padStart(3, '0')}` - -const toOrderProducts = (goods: CheckoutGoods[]) => goods.map((item, index) => { - const image = item.image || item.imageUrl || item.picInfo?.[0] || '' - const sku = typeof item.sku === 'object' && item.sku - ? [item.id, item.sku.color, item.sku.size].filter(Boolean).join('-') - : item.sku - - return { - sku: String(sku || item.id || `SKU_${index + 1}`), - number: Number(item.count || item.number || 1), - price: Number(item.price || 0), - name: String(item.name || ''), - taxRate: Number(item.taxRate || 0.13), - picInfo: item.picInfo?.length ? item.picInfo : image ? [image] : [], - fileId: image || null, - filePath: image || null - } -}) - -const productSubtotal = (item: OrderProductSnapshot) => toMoney(Number(item.price || 0) * Number(item.number || 0)) - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const supplierId = String(body.supplierId || '').trim() - const userId = String(body.userId || '').trim() - const orderId = String(body.orderNo || '').trim() - const amount = toYuan(body.amount) - const goods = Array.isArray(body.goods) ? body.goods : [] - const orderProInfos = toOrderProducts(goods) - - if (!supplierId || !userId || !orderId) { - return fail('供应商、用户或订单信息不能为空') - } - - if (amount <= 0) { - return fail('订单金额必须大于0') - } - - if (!orderProInfos.length) { - return fail('订单商品不能为空') - } - - const orderNumberId = String(body.orderNumberId || createOrderNumberId()) - const orderTime = nowText() - const localOrderState = ORDER_STATE.PENDING_PAY - const now = new Date() - const subOrders = orderProInfos.map((item, index) => { - const subTotal = productSubtotal(item) - - return { - orderId: createSubOrderId(orderId, index), - orderNumberId: createSubOrderNumberId(orderNumberId, index), - product: item, - totalPrice: subTotal, - pointsPrice: subTotal - } - }) - const orderInfo = { - orderId, - orderNumberId, - supplierId, - aplName: '积分商城', - createTime: orderTime, - updateTime: orderTime, - deliveryTime: null, - orderFinishTime: null, - completeTime: null, - deliveryStatus: '0', - freightCost: 0, - wPayOrAPayFreightCost: 0, - refund: 0, - wPayOrAPayRefund: 0, - logisticsOrderId: null, - crrgBsnNm: null, - orderState: ORDER_STATE.PENDING_PAY, - totalPrice: amount, - wPayOrAPay: 0, - pointsPrice: amount, - invoiceCode: null, - orderProInfos - } - - await prisma.$transaction(async tx => { - const existingSubOrders = await tx.supplierOrderSub.findMany({ - where: { mainOrderId: orderId }, - select: { orderId: true } - }) - const existingSubOrderIds = existingSubOrders.map(item => item.orderId) - - if (existingSubOrderIds.length) { - await tx.supplierOrderProduct.deleteMany({ - where: { subOrderId: { in: existingSubOrderIds } } - }) - await tx.supplierOrderSub.deleteMany({ - where: { mainOrderId: orderId } - }) - } - - await tx.supplierOrderMain.upsert({ - where: { orderId }, - update: { - orderNumberId, - supplierId, - userId, - aplName: orderInfo.aplName, - updateTime: now, - deliveryStatus: orderInfo.deliveryStatus, - freightCost: orderInfo.freightCost, - wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost, - refund: orderInfo.refund, - wPayOrAPayRefund: orderInfo.wPayOrAPayRefund, - orderState: localOrderState, - isSplit: ORDER_SPLIT_STATE.NOT_SPLIT, - splitTime: null, - totalPrice: orderInfo.totalPrice, - wPayOrAPay: orderInfo.wPayOrAPay, - pointsPrice: orderInfo.pointsPrice, - invoiceCode: orderInfo.invoiceCode, - deleted: 0 - }, - create: { - orderId, - orderNumberId, - supplierId, - userId, - aplName: orderInfo.aplName, - createTime: now, - updateTime: now, - deliveryStatus: orderInfo.deliveryStatus, - freightCost: orderInfo.freightCost, - wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost, - refund: orderInfo.refund, - wPayOrAPayRefund: orderInfo.wPayOrAPayRefund, - orderState: localOrderState, - isSplit: ORDER_SPLIT_STATE.NOT_SPLIT, - totalPrice: orderInfo.totalPrice, - wPayOrAPay: orderInfo.wPayOrAPay, - pointsPrice: orderInfo.pointsPrice, - invoiceCode: orderInfo.invoiceCode - } - }) - - await tx.supplierOrderSub.createMany({ - data: subOrders.map(subOrder => ({ - orderId: subOrder.orderId, - orderNumberId: subOrder.orderNumberId, - mainOrderId: orderId, - supplierId, - userId, - aplName: orderInfo.aplName, - createTime: now, - updateTime: now, - deliveryStatus: orderInfo.deliveryStatus, - freightCost: orderInfo.freightCost, - wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost, - refund: orderInfo.refund, - wPayOrAPayRefund: orderInfo.wPayOrAPayRefund, - orderState: localOrderState, - totalPrice: subOrder.totalPrice, - wPayOrAPay: 0, - pointsPrice: subOrder.pointsPrice, - invoiceCode: orderInfo.invoiceCode - })) - }) - - await tx.supplierOrderProduct.createMany({ - data: subOrders.map(subOrder => ({ - subOrderId: subOrder.orderId, - sku: subOrder.product.sku, - number: subOrder.product.number, - price: subOrder.product.price, - name: subOrder.product.name, - taxRate: subOrder.product.taxRate, - productImgUrl: subOrder.product.picInfo[0] || null, - fileId: subOrder.product.fileId, - filePath: subOrder.product.filePath, - picInfo: subOrder.product.picInfo - })) - }) - }) - - const { response: bridgeResponse, payload: bridgePayload } = await bridgeFetchWithAuth( - supplierId, - '/pointOrder/pendingOrderInfo', - { - userId, - orderInfo - } - ) - - if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) { - return fail(bridgePayload.message || '提交待支付订单失败', 502, 'BRIDGE_PENDING_ORDER_FAILED') - } - - return ok({ - orderId, - orderNumberId, - subOrderIds: subOrders.map(item => item.orderId), - orderInfo - }) -}) - +export { orderCreateController as default } from '../controllers/order.controller.js' diff --git a/supplied-service/src/functions/order-list.ts b/supplied-service/src/functions/order-list.ts index b747d19..69db13f 100644 --- a/supplied-service/src/functions/order-list.ts +++ b/supplied-service/src/functions/order-list.ts @@ -1,133 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' -import { ORDER_STATE } from '../utils/order-state.js' - -type OrderListRequest = { - supplierId?: string - userId?: string -} - -type OrderStatusView = { - text: string - statusClass: string - actionText: string - buttonClass: string -} - -const statusMap: Record = { - [ORDER_STATE.PENDING_PAY]: { text: '待付款', statusClass: 'pending', actionText: '去支付', buttonClass: 'primary-action' }, - [ORDER_STATE.FINISH_PAY]: { text: '待发货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' }, - [ORDER_STATE.IN_PROGRESS]: { text: '待收货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' }, - [ORDER_STATE.CANCELLED]: { text: '已取消', statusClass: 'muted', actionText: '', buttonClass: 'dark-action' }, - [ORDER_STATE.COMPLETED]: { text: '已完成', statusClass: 'done', actionText: '再次购买', buttonClass: 'dark-action' }, - [ORDER_STATE.EXCHANGE]: { text: '换货', statusClass: 'muted', actionText: '查看订单', buttonClass: 'dark-action' }, - [ORDER_STATE.REFUNDED]: { text: '退货', statusClass: 'muted', actionText: '查看订单', buttonClass: 'dark-action' } -} - -const toYuanText = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => { - if (value && typeof value === 'object' && 'toFixed' in value) { - return value.toFixed(2) - } - - return Number(value || 0).toFixed(2) -} - -const firstImage = (picInfo: unknown) => { - if (Array.isArray(picInfo)) { - return String(picInfo[0] || '') - } - - return '' -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const supplierId = String(body.supplierId || '').trim() - const userId = String(body.userId || '').trim() - - if (!supplierId || !userId) { - return fail('供应商或用户信息不能为空') - } - - const mainOrders = await prisma.supplierOrderMain.findMany({ - where: { - supplierId, - userId, - deleted: 0 - }, - include: { - subOrders: { - where: { deleted: 0 }, - include: { - products: { - where: { deleted: 0 }, - orderBy: { createAt: 'asc' } - } - }, - orderBy: [ - { createTime: 'desc' }, - { createAt: 'desc' } - ] - } - }, - orderBy: [ - { createTime: 'desc' }, - { createAt: 'desc' } - ] - }) - - const orders = mainOrders.map(mainOrder => { - const mainStatus = statusMap[mainOrder.orderState] || statusMap[ORDER_STATE.FINISH_PAY] - const subOrders = mainOrder.subOrders.map(subOrder => { - const status = statusMap[subOrder.orderState] || mainStatus - const products = subOrder.products.map(product => ({ - id: product.id, - sku: product.sku, - name: product.name, - number: product.number, - price: toYuanText(product.price), - image: product.productImgUrl || firstImage(product.picInfo) - })) - - return { - id: subOrder.orderId, - orderId: subOrder.orderId, - orderNumberId: subOrder.orderNumberId || '', - mainOrderId: subOrder.mainOrderId, - supplierId: subOrder.supplierId, - status: status.text, - statusClass: status.statusClass, - actionText: status.actionText, - buttonClass: status.buttonClass, - orderState: subOrder.orderState, - amount: toYuanText(subOrder.totalPrice), - count: products.reduce((total, item) => total + Number(item.number || 0), 0), - products - } - }) - - return { - id: mainOrder.orderId, - mainOrderId: mainOrder.orderId, - mainOrderNumberId: mainOrder.orderNumberId || '', - supplierId: mainOrder.supplierId, - status: mainStatus.text, - statusClass: mainStatus.statusClass, - orderState: mainOrder.orderState, - isSplit: mainOrder.isSplit, - splitTime: mainOrder.splitTime, - canCancel: mainOrder.orderState === ORDER_STATE.PENDING_PAY, - subOrders - } - }) - - return ok({ orders }) -}) - +export { orderListController as default } from '../controllers/order.controller.js' diff --git a/supplied-service/src/functions/order-pay.ts b/supplied-service/src/functions/order-pay.ts index b94241e..42b6360 100644 --- a/supplied-service/src/functions/order-pay.ts +++ b/supplied-service/src/functions/order-pay.ts @@ -1,163 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' -import { ORDER_EVENT_TYPE, ORDER_SPLIT_STATE, ORDER_STATE } from '../utils/order-state.js' -import { bridgeFetchWithAuth } from '../utils/bridge-auth.js' - -type OrderPayRequest = { - supplierId?: string - orderId?: string - amount?: number - totalPrice?: number - wPayOrAPay?: number - pointsPrice?: number -} - -type BridgePayResponse = { - resultCode?: number - message?: string -} - -const centsToYuan = (value?: number) => Number((Number(value || 0) / 100).toFixed(2)) - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const supplierId = String(body.supplierId || '').trim() - const orderId = String(body.orderId || '').trim() - const totalPrice = Number(body.totalPrice ?? centsToYuan(body.amount)) - const wPayOrAPay = Number(body.wPayOrAPay ?? 0) - const pointsPrice = Number(body.pointsPrice ?? totalPrice) - - if (!supplierId || !orderId) { - return fail('供应商或订单信息不能为空') - } - - if (totalPrice <= 0) { - return fail('支付金额必须大于0') - } - - const order = await prisma.supplierOrderMain.findUnique({ - where: { orderId } - }) - - if (!order || order.deleted === 1) { - return fail('待支付订单不存在', 404, 'ORDER_NOT_FOUND') - } - - await prisma.$transaction(async tx => { - await tx.supplierOrderMain.update({ - where: { orderId }, - data: { - orderState: ORDER_STATE.FINISH_PAY, - isSplit: ORDER_SPLIT_STATE.NOT_SPLIT, - totalPrice, - wPayOrAPay, - pointsPrice, - updateTime: new Date() - } - }) - - await tx.supplierOrderSub.updateMany({ - where: { mainOrderId: orderId, deleted: 0 }, - data: { - orderState: ORDER_STATE.FINISH_PAY, - updateTime: new Date() - } - }) - }) - - const { response: confirmResponse, payload: confirmPayload } = await bridgeFetchWithAuth( - supplierId, - '/pointOrder/confirmPurchase', - { - orderId, - totalPrice, - wPayOrAPay, - pointsPrice - } - ) - - if (!confirmResponse.ok || confirmPayload.resultCode !== 0) { - return fail(confirmPayload.message || '确认购买推送失败', 502, 'BRIDGE_CONFIRM_PURCHASE_FAILED') - } - - const { response: purchaseResultResponse, payload: purchaseResultPayload } = await bridgeFetchWithAuth( - supplierId, - '/pointOrder/purchaseResult', - { - orderId, - totalPrice, - wPayOrAPay, - pointsPrice, - purchaseResult: true, - message: '支付成功' - } - ) - - if (!purchaseResultResponse.ok || purchaseResultPayload.resultCode !== 0) { - return fail(purchaseResultPayload.message || '支付结果通知推送失败', 502, 'BRIDGE_PURCHASE_RESULT_FAILED') - } - - const subOrders = await prisma.supplierOrderSub.findMany({ - where: { - mainOrderId: orderId, - deleted: 0 - }, - select: { - orderId: true - } - }) - const subOrderIds = subOrders.map(item => item.orderId).filter(Boolean).join(',') - - if (subOrderIds) { - await prisma.$transaction(async tx => { - await tx.supplierOrderMain.update({ - where: { orderId }, - data: { - isSplit: ORDER_SPLIT_STATE.SPLIT, - splitTime: new Date(), - updateTime: new Date() - } - }) - - await tx.supplierOrderSub.updateMany({ - where: { mainOrderId: orderId, deleted: 0 }, - data: { - orderState: ORDER_STATE.IN_PROGRESS, - updateTime: new Date() - } - }) - }) - - const { response: splitResponse, payload: splitPayload } = await bridgeFetchWithAuth( - supplierId, - '/pointOrder/pushOrderInfoMsg', - { - orderType: ORDER_EVENT_TYPE.SPLIT_SUB_ORDER, - orderId: subOrderIds, - mainOrderId: orderId, - userId: order.userId, - time: Date.now() - } - ) - - if (!splitResponse.ok || splitPayload.resultCode !== 0) { - return fail(splitPayload.message || '推送拆单信息失败', 502, 'BRIDGE_SPLIT_ORDER_FAILED') - } - } - - return ok({ - orderId, - subOrderIds, - totalPrice, - wPayOrAPay, - pointsPrice - }) -}) - +export { orderPayController as default } from '../controllers/order.controller.js' diff --git a/supplied-service/src/functions/order-push-event.ts b/supplied-service/src/functions/order-push-event.ts index 39e0611..4515912 100644 --- a/supplied-service/src/functions/order-push-event.ts +++ b/supplied-service/src/functions/order-push-event.ts @@ -1,310 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' -import { - ORDER_EVENT_TYPE, - ORDER_OPERATE_TYPE, - ORDER_PUSH_STATUS, - ORDER_STATE -} from '../utils/order-state.js' -import { bridgeFetchWithAuth } from '../utils/bridge-auth.js' - -type OrderPushEventRequest = { - supplierId?: string - userId?: string - orderType?: number - orderId?: string - mainOrderId?: string - subOrderId?: string - time?: number - amount?: number - reason?: string - settlementId?: string - invoiceBizId?: string - invoiceCode?: string - invoiceIds?: string - orderIds?: string - fileId?: string - filePath?: string -} - -type BridgeOrderResponse = { - resultCode?: number - message?: string -} - -const PUSH_API_PATH = '/pointOrder/pushOrderInfoMsg' -const supportedOrderTypes = new Set([ - ORDER_EVENT_TYPE.RETURN_ORDER, - ORDER_EVENT_TYPE.EXCHANGE_ORDER, - ORDER_EVENT_TYPE.CONFIRM_RECEIVE, - ORDER_EVENT_TYPE.INVOICE_INFO -]) - -const splitIds = (value?: string) => String(value || '') - .split(',') - .map(item => item.trim()) - .filter(Boolean) - -const eventState = (orderType: number) => { - if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER) return ORDER_STATE.REFUNDED - if (orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) return ORDER_STATE.EXCHANGE - if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE) return ORDER_STATE.COMPLETED - return null -} - -const operateType = (orderType: number) => { - if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER) return ORDER_OPERATE_TYPE.REFUND - if (orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) return ORDER_OPERATE_TYPE.EXCHANGE - if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE) return ORDER_OPERATE_TYPE.COMPLETE - if (orderType === ORDER_EVENT_TYPE.INVOICE_INFO) return ORDER_OPERATE_TYPE.INVOICE_INFO - return orderType -} - -const buildBridgeBody = (body: OrderPushEventRequest) => ({ - supplierId: String(body.supplierId || '').trim(), - orderType: Number(body.orderType), - orderId: String(body.orderId || '').trim(), - mainOrderId: String(body.mainOrderId || '').trim(), - userId: String(body.userId || '').trim(), - time: Number(body.time || Date.now()) -}) - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const supplierId = String(body.supplierId || '').trim() - const userId = String(body.userId || '').trim() - const orderType = Number(body.orderType) - const orderId = String(body.orderId || '').trim() - const mainOrderId = String(body.mainOrderId || '').trim() - - if (!supplierId || !userId || !orderType || !orderId) { - return fail('供应商、用户或订单事件信息不能为空') - } - - if (!supportedOrderTypes.has(orderType)) { - return fail('暂不支持该订单事件类型', 400, 'ORDER_EVENT_NOT_SUPPORTED') - } - - const bridgeBody = buildBridgeBody(body) - const now = new Date() - let pushLogId = 0 - let affectedSubOrderIds: string[] = [] - - await prisma.$transaction(async tx => { - const mainOrder = mainOrderId - ? await tx.supplierOrderMain.findFirst({ - where: { orderId: mainOrderId, supplierId, deleted: 0 } - }) - : null - - const explicitSubIds = splitIds(body.subOrderId || '') - const orderIds = splitIds(orderId) - const subOrdersByOrderId = await tx.supplierOrderSub.findMany({ - where: { - supplierId, - deleted: 0, - OR: [ - { orderId: { in: orderIds } }, - ...(explicitSubIds.length ? [{ orderId: { in: explicitSubIds } }] : []), - ...(mainOrderId ? [{ mainOrderId }] : []) - ] - }, - orderBy: { createAt: 'asc' } - }) - - const directlyMatched = subOrdersByOrderId.filter(item => orderIds.includes(item.orderId) || explicitSubIds.includes(item.orderId)) - const mainOrderSubs = mainOrderId ? subOrdersByOrderId.filter(item => item.mainOrderId === mainOrderId) : [] - const targetSubOrders = directlyMatched.length ? directlyMatched : mainOrderSubs.length === 1 ? mainOrderSubs : [] - affectedSubOrderIds = targetSubOrders.map(item => item.orderId) - - const nextState = eventState(orderType) - if (nextState !== null && targetSubOrders.length) { - await tx.supplierOrderSub.updateMany({ - where: { - orderId: { in: affectedSubOrderIds }, - supplierId, - deleted: 0 - }, - data: { - orderState: nextState, - orderFinishTime: orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE ? now : undefined, - completeTime: orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE ? now : undefined, - updateTime: now - } - }) - - await tx.supplierOrderOperateLog.createMany({ - data: targetSubOrders.map(subOrder => ({ - supplierId, - userId, - mainOrderId: subOrder.mainOrderId, - subOrderId: subOrder.orderId, - operateType: operateType(orderType), - beforeState: subOrder.orderState, - afterState: nextState, - remark: body.reason || null, - operator: 'system' - })) - }) - } - - if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER || orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) { - await tx.supplierRefundExchange.upsert({ - where: { serviceOrderId: orderId }, - update: { - supplierId, - userId, - mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null, - subOrderId: targetSubOrders[0]?.orderId || null, - serviceType: orderType, - serviceState: 1, - amount: Number(body.amount || targetSubOrders[0]?.totalPrice || 0), - reason: body.reason || null, - requestBody: body, - deleted: 0 - }, - create: { - serviceOrderId: orderId, - supplierId, - userId, - mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null, - subOrderId: targetSubOrders[0]?.orderId || null, - serviceType: orderType, - serviceState: 1, - amount: Number(body.amount || targetSubOrders[0]?.totalPrice || 0), - reason: body.reason || null, - requestBody: body - } - }) - } - - if (orderType === ORDER_EVENT_TYPE.INVOICE_INFO) { - const invoiceBizId = String(body.invoiceBizId || body.invoiceCode || orderId).trim() - await tx.supplierInvoiceInfo.upsert({ - where: { invoiceBizId }, - update: { - supplierId, - userId, - mainOrderId: mainOrderId || null, - subOrderId: targetSubOrders[0]?.orderId || null, - settlementId: body.settlementId || null, - invoiceCode: body.invoiceCode || null, - invoiceIds: body.invoiceIds || orderId, - orderIds: body.orderIds || mainOrderId || targetSubOrders[0]?.orderId || null, - invoiceStatus: 2, - fileId: body.fileId || null, - filePath: body.filePath || null, - requestBody: body, - deleted: 0 - }, - create: { - invoiceBizId, - supplierId, - userId, - mainOrderId: mainOrderId || null, - subOrderId: targetSubOrders[0]?.orderId || null, - settlementId: body.settlementId || null, - invoiceCode: body.invoiceCode || null, - invoiceIds: body.invoiceIds || orderId, - orderIds: body.orderIds || mainOrderId || targetSubOrders[0]?.orderId || null, - invoiceStatus: 2, - fileId: body.fileId || null, - filePath: body.filePath || null, - requestBody: body - } - }) - - if (body.invoiceCode && mainOrder) { - await tx.supplierOrderMain.update({ - where: { orderId: mainOrder.orderId }, - data: { invoiceCode: body.invoiceCode, updateTime: now } - }) - } - } - - if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE && mainOrderId) { - const unfinishedCount = await tx.supplierOrderSub.count({ - where: { - mainOrderId, - deleted: 0, - orderState: { notIn: [ORDER_STATE.COMPLETED, ORDER_STATE.CANCELLED, ORDER_STATE.REFUNDED] } - } - }) - if (unfinishedCount === 0) { - await tx.supplierOrderMain.update({ - where: { orderId: mainOrderId }, - data: { - orderState: ORDER_STATE.COMPLETED, - completeTime: now, - updateTime: now - } - }) - } - } - - const pushLog = await tx.supplierOrderPushLog.create({ - data: { - supplierId, - userId, - mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null, - orderId, - orderType, - apiPath: PUSH_API_PATH, - requestBody: bridgeBody, - pushStatus: ORDER_PUSH_STATUS.PENDING - } - }) - pushLogId = pushLog.id - }) - - let bridgeResponse: Response | null = null - let bridgePayload: BridgeOrderResponse = {} - try { - const result = await bridgeFetchWithAuth(supplierId, PUSH_API_PATH, bridgeBody) - bridgeResponse = result.response - bridgePayload = result.payload - } catch (error) { - const message = error instanceof Error ? error.message : '订单事件推送失败' - await prisma.supplierOrderPushLog.update({ - where: { id: pushLogId }, - data: { - pushStatus: ORDER_PUSH_STATUS.FAILED, - errorMessage: message, - lastPushTime: new Date() - } - }) - return ok({ localSaved: true, pushSuccess: false, message, affectedSubOrderIds }) - } - - const pushSuccess = bridgeResponse.ok && bridgePayload.resultCode === 0 - - await prisma.supplierOrderPushLog.update({ - where: { id: pushLogId }, - data: { - responseBody: bridgePayload, - httpStatus: bridgeResponse.status, - resultCode: bridgePayload.resultCode, - pushStatus: pushSuccess ? ORDER_PUSH_STATUS.SUCCESS : ORDER_PUSH_STATUS.FAILED, - errorMessage: pushSuccess ? null : bridgePayload.message || '订单事件推送失败', - lastPushTime: new Date() - } - }) - - return ok({ - localSaved: true, - pushSuccess, - orderType, - orderId, - mainOrderId, - affectedSubOrderIds, - message: pushSuccess ? '订单事件已保存并推送成功' : bridgePayload.message || '订单事件已保存,推送失败' - }) -}) - +export { orderPushEventController as default } from '../controllers/order.controller.js' diff --git a/supplied-service/src/functions/product-detail.ts b/supplied-service/src/functions/product-detail.ts index aa98c37..9988f6d 100644 --- a/supplied-service/src/functions/product-detail.ts +++ b/supplied-service/src/functions/product-detail.ts @@ -1,64 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' - -const toProductDetail = (product: NonNullable>>) => { - const picInfo = Array.isArray(product.picInfo) ? product.picInfo : [] - const image = product.imageUrl || String(picInfo[0] || '') - const price = product.price.toFixed(2) - - return { - id: product.id, - sku: product.sku, - number: product.number, - price, - name: product.name, - taxRate: Number(product.taxRate), - picInfo, - desc: product.description || '', - detail: product.detail || product.description || '', - originPrice: product.originPrice ? product.originPrice.toFixed(2) : price, - image, - imageUrl: image, - categoryId: product.categoryId || '', - category: product.category || '', - tag: product.tag || '', - sales: product.sales || '', - stock: product.stock - } -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['GET']) - if (methodResponse) return methodResponse - - const url = new URL(request.url) - const id = (url.searchParams.get('id') || '').trim() - const sku = (url.searchParams.get('sku') || '').trim() - const numericId = Number(id) - const querySku = sku || (id && !Number.isInteger(numericId) ? id : '') - - if (!id && !sku) { - return fail('商品参数不能为空') - } - - const product = await prisma.supplierProduct.findFirst({ - where: { - enabled: true, - deleted: 0, - OR: [ - ...(Number.isInteger(numericId) && numericId > 0 ? [{ id: numericId }] : []), - ...(querySku ? [{ sku: querySku }] : []) - ] - } - }) - - if (!product) { - return fail('商品不存在或已下架', 404, 'PRODUCT_NOT_FOUND') - } - - return ok(toProductDetail(product)) -}) - +export { productDetailController as default } from '../controllers/product.controller.js' diff --git a/supplied-service/src/functions/product-list.ts b/supplied-service/src/functions/product-list.ts index 58afbf3..5e5cc83 100644 --- a/supplied-service/src/functions/product-list.ts +++ b/supplied-service/src/functions/product-list.ts @@ -1,70 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { ok } from '../utils/http.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' - -const categories = [ - { id: 'recommend', name: '推荐' }, - { id: 'homeLiving', name: '居家生活' }, - { id: 'digitalAppliance', name: '数码家电' }, - { id: 'beautyCare', name: '美妆个护' }, - { id: 'foodDrink', name: '食品饮料' }, - { id: 'motherBaby', name: '母婴玩具' }, - { id: 'fashionBag', name: '服饰鞋包' }, - { id: 'sportsOutdoor', name: '运动户外' } -] - -const toProductItem = (product: Awaited>[number]) => { - const picInfo = Array.isArray(product.picInfo) ? product.picInfo : [] - const image = product.imageUrl || String(picInfo[0] || '') - const price = product.price.toFixed(2) - - return { - id: product.id, - sku: product.sku, - number: product.number, - price, - name: product.name, - taxRate: Number(product.taxRate), - picInfo, - desc: product.description || '', - detail: product.detail || product.description || '', - originPrice: product.originPrice ? product.originPrice.toFixed(2) : price, - image, - imageUrl: image, - categoryId: product.categoryId || '', - category: product.category || '', - tag: product.tag || '', - sales: product.sales || '', - stock: product.stock - } -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['GET']) - if (methodResponse) return methodResponse - - const url = new URL(request.url) - const keyword = (url.searchParams.get('keyword') || '').trim() - const categoryId = (url.searchParams.get('categoryId') || '').trim() - - const products = await prisma.supplierProduct.findMany({ - where: { - enabled: true, - deleted: 0, - ...(keyword ? { name: { contains: keyword } } : {}), - ...(categoryId && categoryId !== 'recommend' ? { categoryId } : {}) - }, - orderBy: [ - { sortOrder: 'asc' }, - { createdAt: 'desc' } - ] - }) - - return ok({ - categories, - products: products.map(toProductItem) - }) -}) - +export { productListController as default } from '../controllers/product.controller.js' diff --git a/supplied-service/src/functions/supplier-debug-tokens.ts b/supplied-service/src/functions/supplier-debug-tokens.ts index 78c551b..e12068d 100644 --- a/supplied-service/src/functions/supplier-debug-tokens.ts +++ b/supplied-service/src/functions/supplier-debug-tokens.ts @@ -1,83 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { withErrorBoundary } from '../utils/handler.js' -import { - findSupplierByIdentification, - getBridgeApiBase, - getBridgeToken -} from '../utils/bridge-auth.js' -import { sha256 } from '../utils/crypto.js' -import { createSupplierOpenSignature } from '../utils/supplier-open-crypto.js' - -type SupplierDebugTokensRequest = { - supplierIdentification?: string - forceRefreshZhghToken?: boolean -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase() - - if (!supplierIdentification) { - return fail('供应商唯一标识不能为空') - } - - const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error) - - if (supplier instanceof Error) { - return fail(supplier.message || '供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED') - } - - const timestamp = Date.now() - const orderType = 2 - const orderId = 'SUB_ORDER_ID_1,SUB_ORDER_ID_2' - const mainOrderId = 'MAIN_ORDER_ID' - const userId = 'USER_ID' - const supplierOpenToken = createSupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp) - const zhghBridgeToken = await getBridgeToken(supplier.supplierId, Boolean(body.forceRefreshZhghToken)) - - return ok({ - supplierOpen: { - name: 'supplierOpenToken', - usage: '调用供应商开放接口 /supplierOpenApi/pointOrder/queryOrderInfo', - supplierId: supplier.supplierId, - clientId: supplier.clientId, - timestamp, - token: supplierOpenToken, - orderType, - orderId, - queryOrderInfoBody: { - supplierId: supplier.supplierId, - token: supplierOpenToken, - orderType, - orderId, - timestamp - } - }, - zhghBridge: { - name: 'zhghBridgeToken', - usage: '调用 zhgh /open/pointOrder/* 桥接接口', - apiBase: getBridgeApiBase(), - supplierId: supplier.supplierId, - clientId: supplier.clientId, - timestamp, - sendSha256: sha256(`${supplier.clientId}${supplier.clientSecret}${timestamp}`), - token: zhghBridgeToken, - splitOrderBody: { - supplierId: supplier.supplierId, - token: zhghBridgeToken, - orderType, - orderId, - mainOrderId, - userId, - time: timestamp - } - } - }) -}) - +export { supplierDebugTokensController as default } from '../controllers/supplier.controller.js' diff --git a/supplied-service/src/functions/supplier-info.ts b/supplied-service/src/functions/supplier-info.ts index b0ee6cc..0a08d57 100644 --- a/supplied-service/src/functions/supplier-info.ts +++ b/supplied-service/src/functions/supplier-info.ts @@ -1,38 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' - -type SupplierInfoRequest = { - supplierIdentification?: string -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase() - - if (!supplierIdentification) { - return fail('供应商唯一标识不能为空') - } - - const supplier = await prisma.supplierInfo.findUnique({ - where: { supplierIdentification } - }) - - if (!supplier || supplier.deleted === 1) { - return fail('供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED') - } - - return ok({ - id: supplier.id, - supplierIdentification: supplier.supplierIdentification, - supplierId: supplier.supplierId, - supplierName: supplier.supplierName - }) -}) - +export { supplierInfoController as default } from '../controllers/supplier.controller.js' diff --git a/supplied-service/src/functions/supplier-order-info.ts b/supplied-service/src/functions/supplier-order-info.ts index e2f727f..f8568e8 100644 --- a/supplied-service/src/functions/supplier-order-info.ts +++ b/supplied-service/src/functions/supplier-order-info.ts @@ -1,143 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { readJson } from '../utils/request.js' -import { prisma } from '../utils/prisma.js' -import { withErrorBoundary } from '../utils/handler.js' -import { sm4CbcDecrypt, verifySupplierOpenSignature } from '../utils/supplier-open-crypto.js' - -type SupplierOrderQueryRequest = { - supplierId?: string - token?: string - orderType?: number - orderId?: string - timestamp?: number - AplParmObjct?: string -} - -const success = (orderInfo: unknown[]) => Response.json({ - resultCode: 0, - message: '成功', - orderInfo -}) - -const fail = (resultCode: number, message: string) => Response.json({ - resultCode, - message, - orderInfo: [] -}) - -const parseBody = (body: SupplierOrderQueryRequest) => { - if (!body.AplParmObjct) return body - - const keyHex = process.env.SUPPLIER_OPEN_SM4_KEY_HEX || process.env.SM4CBC_KEY_HEX || '' - const ivHex = process.env.SUPPLIER_OPEN_SM4_IV_HEX || process.env.SM4CBC_IV_HEX || '' - if (!keyHex || !ivHex) { - throw new Error('供应商开放接口SM4配置缺失') - } - - return JSON.parse(sm4CbcDecrypt(body.AplParmObjct, keyHex, ivHex)) as SupplierOrderQueryRequest -} - -const toText = (date?: Date | null) => { - if (!date) return null - const pad = (value: number) => String(value).padStart(2, '0') - return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` -} - -const toMoney = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => Number(value || 0).toFixed(2) - -const productSku = (product: { sku: string; subOrderId: string; name: string }) => { - if (product.sku && product.sku !== '[object Object]') { - return product.sku - } - - return `${product.subOrderId}-${product.name || 'SKU'}` -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - let body: SupplierOrderQueryRequest - try { - body = parseBody(await readJson(request)) - } catch (error) { - return fail(400, error instanceof Error ? error.message : '请求参数解密失败') - } - - const supplierId = String(body.supplierId || '').trim() - const token = String(body.token || '').trim() - const orderIds = String(body.orderId || '') - .split(',') - .map(item => item.trim()) - .filter(Boolean) - const timestamp = Number(body.timestamp || 0) - - if (!supplierId || !token || !timestamp || !orderIds.length) { - return fail(400, '供应商、订单或签名信息不能为空') - } - - const supplier = await prisma.supplierInfo.findFirst({ - where: { supplierId, deleted: 0 } - }) - if (!supplier) { - return fail(404, '供应商信息不存在') - } - - if (!verifySupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp, token)) { - return fail(401, '签名无效或已过期') - } - - const orders = await prisma.supplierOrderSub.findMany({ - where: { - supplierId, - orderId: { in: orderIds }, - deleted: 0 - }, - include: { - products: { - where: { deleted: 0 }, - orderBy: { createAt: 'asc' } - } - }, - orderBy: { createAt: 'asc' } - }) - - const orderInfo = orders.map(order => ({ - orderId: order.orderId, - orderNumberId: order.orderNumberId, - supplierId: order.supplierId, - token: order.token, - aplName: order.aplName, - createTime: toText(order.createTime), - updateTime: toText(order.updateTime), - deliveryTime: toText(order.deliveryTime), - orderFinishTime: toText(order.orderFinishTime), - completeTime: toText(order.completeTime), - deliveryStatus: order.deliveryStatus, - freightCost: toMoney(order.freightCost), - wPayOrAPayFreightCost: toMoney(order.wPayOrAPayFreightCost), - refund: toMoney(order.refund), - wPayOrAPayRefund: toMoney(order.wPayOrAPayRefund), - logisticsOrderId: order.logisticsOrderId, - crrgBsnNm: order.crrgBsnNm, - orderState: order.orderState, - totalPrice: toMoney(order.totalPrice), - wPayOrAPay: toMoney(order.wPayOrAPay), - pointsPrice: toMoney(order.pointsPrice), - invoiceCode: order.invoiceCode, - orderProInfos: order.products.map(product => ({ - sku: productSku(product), - number: product.number, - price: toMoney(product.price), - name: product.name, - taxRate: Number(product.taxRate || 0), - picInfo: Array.isArray(product.picInfo) ? product.picInfo : product.productImgUrl ? [product.productImgUrl] : [], - fileId: product.fileId, - filePath: product.filePath - })) - })) - - return success(orderInfo) -}) - +export { supplierOrderInfoController as default } from '../controllers/order.controller.js' diff --git a/supplied-service/src/functions/supplier-points.ts b/supplied-service/src/functions/supplier-points.ts index 51554e6..2ff1a66 100644 --- a/supplied-service/src/functions/supplier-points.ts +++ b/supplied-service/src/functions/supplier-points.ts @@ -1,45 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { withErrorBoundary } from '../utils/handler.js' -import { bridgeFetchWithAuth } from '../utils/bridge-auth.js' - -type SupplierPointsRequest = { - supplierId?: string - userId?: string -} - -type BridgePointsResponse = { - resultCode?: number - message?: string - userPoints?: number | string -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const supplierId = String(body.supplierId || '').trim() - const userId = String(body.userId || '').trim() - - if (!supplierId || !userId) { - return fail('供应商或用户信息不能为空') - } - - const { response, payload: pointsPayload } = await bridgeFetchWithAuth( - supplierId, - '/pointOrder/queryPoints', - { userId } - ) - - if (!response.ok || pointsPayload.resultCode !== 0) { - return fail(pointsPayload.message || '查询用户积分失败', 502, 'BRIDGE_POINTS_FAILED') - } - - return ok({ - userPoints: Number(pointsPayload.userPoints || 0) - }) -}) - +export { supplierPointsController as default } from '../controllers/supplier.controller.js' diff --git a/supplied-service/src/functions/supplier-token.ts b/supplied-service/src/functions/supplier-token.ts index 6f7497e..b2ef576 100644 --- a/supplied-service/src/functions/supplier-token.ts +++ b/supplied-service/src/functions/supplier-token.ts @@ -1,44 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { fail, ok } from '../utils/http.js' -import { readJson } from '../utils/request.js' -import { withErrorBoundary } from '../utils/handler.js' -import { findSupplierByIdentification, warmBridgeToken } from '../utils/bridge-auth.js' - -type SupplierTokenRequest = { - userId?: string - supplierIdentification?: string -} - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - const body = await readJson(request) - const userId = String(body.userId || '').trim() - const supplierIdentification = String(body.supplierIdentification || '').trim().toLowerCase() - - if (!userId || !supplierIdentification) { - return fail('用户信息和供应商唯一标识不能为空') - } - - const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error) - - if (supplier instanceof Error) { - return fail(supplier.message || '供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED') - } - - try { - await warmBridgeToken(supplier) - } catch (error) { - return fail(error instanceof Error ? error.message : '获取平台授权令牌失败', 502, 'BRIDGE_TOKEN_FAILED') - } - - return ok({ - supplierIdentification, - supplierId: supplier.supplierId, - supplierName: supplier.supplierName, - userId - }) -}) - +export { supplierTokenController as default } from '../controllers/supplier.controller.js' diff --git a/supplied-service/src/functions/upload.ts b/supplied-service/src/functions/upload.ts index 6ab828b..d64505d 100644 --- a/supplied-service/src/functions/upload.ts +++ b/supplied-service/src/functions/upload.ts @@ -1,12 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { ok } from '../utils/http.js' -import { withErrorBoundary } from '../utils/handler.js' - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['POST']) - if (methodResponse) return methodResponse - - return ok({ message: '上传接口待实现' }) -}) - +export { uploadController as default } from '../controllers/basic.controller.js' diff --git a/supplied-service/src/functions/user-info.ts b/supplied-service/src/functions/user-info.ts index c43fa5a..3b57ebc 100644 --- a/supplied-service/src/functions/user-info.ts +++ b/supplied-service/src/functions/user-info.ts @@ -1,12 +1 @@ -import type { Context } from '../utils/handler.js' -import { guardMethod } from '../utils/method.js' -import { ok } from '../utils/http.js' -import { withErrorBoundary } from '../utils/handler.js' - -export default withErrorBoundary(async (request: Request, _context: Context) => { - const methodResponse = guardMethod(request, ['GET']) - if (methodResponse) return methodResponse - - return ok({ message: '用户信息接口待实现' }) -}) - +export { userInfoController as default } from '../controllers/basic.controller.js' diff --git a/supplied-service/src/server.ts b/supplied-service/src/server.ts index 7b07025..81e5ebf 100644 --- a/supplied-service/src/server.ts +++ b/supplied-service/src/server.ts @@ -3,48 +3,54 @@ import Koa from 'koa' import Router from '@koa/router' import bodyParser from 'koa-bodyparser' import type { FunctionHandler } from './utils/handler.js' -import cartAdd from './functions/cart-add.js' -import login from './functions/login.js' -import orderCancelMain from './functions/order-cancel-main.js' -import orderCancelPaidMain from './functions/order-cancel-paid-main.js' -import orderCancelSub from './functions/order-cancel-sub.js' -import orderCreate from './functions/order-create.js' -import orderList from './functions/order-list.js' -import orderPay from './functions/order-pay.js' -import orderPushEvent from './functions/order-push-event.js' -import productDetail from './functions/product-detail.js' -import productList from './functions/product-list.js' -import supplierDebugTokens from './functions/supplier-debug-tokens.js' -import supplierInfo from './functions/supplier-info.js' -import supplierOrderInfo from './functions/supplier-order-info.js' -import supplierPoints from './functions/supplier-points.js' -import supplierToken from './functions/supplier-token.js' -import upload from './functions/upload.js' -import userInfo from './functions/user-info.js' +import { logger } from './utils/logger.js' +import { + cartAddController, + loginController, + uploadController, + userInfoController +} from './controllers/basic.controller.js' +import { + orderCancelMainController, + orderCancelPaidMainController, + orderCancelSubController, + orderCreateController, + orderListController, + orderPayController, + orderPushEventController, + supplierOrderInfoController +} from './controllers/order.controller.js' +import { productDetailController, productListController } from './controllers/product.controller.js' +import { + supplierDebugTokensController, + supplierInfoController, + supplierPointsController, + supplierTokenController +} from './controllers/supplier.controller.js' const app = new Koa() const router = new Router() const port = Number(process.env.PORT || 8888) const handlers: Record = { - 'cart-add': cartAdd, - login, - 'order-cancel-main': orderCancelMain, - 'order-cancel-paid-main': orderCancelPaidMain, - 'order-cancel-sub': orderCancelSub, - 'order-create': orderCreate, - 'order-list': orderList, - 'order-pay': orderPay, - 'order-push-event': orderPushEvent, - 'product-detail': productDetail, - 'product-list': productList, - 'supplier-debug-tokens': supplierDebugTokens, - 'supplier-info': supplierInfo, - 'supplier-order-info': supplierOrderInfo, - 'supplier-points': supplierPoints, - 'supplier-token': supplierToken, - upload, - 'user-info': userInfo + 'cart-add': cartAddController, + login: loginController, + 'order-cancel-main': orderCancelMainController, + 'order-cancel-paid-main': orderCancelPaidMainController, + 'order-cancel-sub': orderCancelSubController, + 'order-create': orderCreateController, + 'order-list': orderListController, + 'order-pay': orderPayController, + 'order-push-event': orderPushEventController, + 'product-detail': productDetailController, + 'product-list': productListController, + 'supplier-debug-tokens': supplierDebugTokensController, + 'supplier-info': supplierInfoController, + 'supplier-order-info': supplierOrderInfoController, + 'supplier-points': supplierPointsController, + 'supplier-token': supplierTokenController, + upload: uploadController, + 'user-info': userInfoController } const corsHeaders = { @@ -75,6 +81,7 @@ const writeResponse = async (ctx: Koa.Context, response: Response) => { } const dispatch = (name: string) => async (ctx: Koa.Context) => { + logger.info('dispatch api request', { name, method: ctx.method, path: ctx.path }) const handler = handlers[name] if (!handler) { ctx.status = 404 @@ -103,5 +110,5 @@ app.use(router.routes()) app.use(router.allowedMethods()) app.listen(port, () => { - console.log(`supplied-service Koa server running at http://localhost:${port}`) + logger.info(`Koa server running at http://localhost:${port}`) }) diff --git a/supplied-service/src/services/basic.service.ts b/supplied-service/src/services/basic.service.ts new file mode 100644 index 0000000..1683255 --- /dev/null +++ b/supplied-service/src/services/basic.service.ts @@ -0,0 +1,24 @@ +import { logger } from '../utils/logger.js' +import { serviceOk } from '../types/service-result.js' + +export const basicService = { + login() { + logger.info('login placeholder called') + return serviceOk({ message: '登录接口待实现' }) + }, + + addCart() { + logger.info('cart add placeholder called') + return serviceOk({ message: '加入购物车接口待实现' }) + }, + + upload() { + logger.info('upload placeholder called') + return serviceOk({ message: '上传接口待实现' }) + }, + + userInfo() { + logger.info('user info placeholder called') + return serviceOk({ message: '用户信息接口待实现' }) + } +} diff --git a/supplied-service/src/services/order-action.service.ts b/supplied-service/src/services/order-action.service.ts new file mode 100644 index 0000000..a1a03a5 --- /dev/null +++ b/supplied-service/src/services/order-action.service.ts @@ -0,0 +1,255 @@ +import { ORDER_EVENT_TYPE, ORDER_SPLIT_STATE, ORDER_STATE } from '../constants/order.js' +import { orderDao } from '../dao/order.dao.js' +import { bridgeFetchWithAuth } from '../utils/bridge-auth.js' +import { logger } from '../utils/logger.js' +import { serviceFail, serviceOk } from '../types/service-result.js' + +type BridgeOrderResponse = { + resultCode?: number + message?: string +} + +type CancelMainInput = { + supplierId: string + userId: string + orderId: string + mainOrderId: string +} + +type CancelSubInput = { + supplierId: string + userId: string + orderId: string + mainOrderId: string +} + +type PayOrderInput = { + supplierId: string + orderId: string + totalPrice: number + wPayOrAPay: number + pointsPrice: number +} + +export const orderActionService = { + async cancelPendingMain(input: CancelMainInput) { + const { supplierId, userId, mainOrderId } = input + const orderId = input.orderId || mainOrderId + logger.info('cancel pending main order', { supplierId, userId, mainOrderId }) + + if (!supplierId || !userId || !mainOrderId) { + return serviceFail('供应商、用户或订单信息不能为空') + } + + const order = await orderDao.findMainOrder(supplierId, userId, mainOrderId) + if (!order) { + return serviceFail('主单不存在或已取消', 404, 'MAIN_ORDER_NOT_FOUND') + } + + if (order.orderState !== ORDER_STATE.PENDING_PAY) { + return serviceFail('已支付主单不能通过未支付取消接口取消', 400, 'MAIN_ORDER_ALREADY_PAID') + } + + await orderDao.transaction(async tx => { + await tx.supplierOrderMain.update({ + where: { orderId: mainOrderId }, + data: { orderState: ORDER_STATE.CANCELLED, updateTime: new Date() } + }) + await tx.supplierOrderSub.updateMany({ + where: { mainOrderId, supplierId, userId, deleted: 0 }, + data: { orderState: ORDER_STATE.CANCELLED, updateTime: new Date() } + }) + }) + + const { response, payload } = await bridgeFetchWithAuth(supplierId, '/pointOrder/pushOrderInfoMsg', { + orderType: ORDER_EVENT_TYPE.CANCEL_MAIN, + orderId, + mainOrderId, + userId, + time: Date.now() + }) + + if (!response.ok || payload.resultCode !== 0) { + return serviceFail(payload.message || '取消主单推送失败', 502, 'BRIDGE_CANCEL_MAIN_FAILED') + } + + return serviceOk({ supplierId, orderType: ORDER_EVENT_TYPE.CANCEL_MAIN, orderId, mainOrderId, userId }) + }, + + async cancelPaidMain(input: CancelMainInput) { + const { supplierId, userId, mainOrderId } = input + const orderId = input.orderId || mainOrderId + logger.info('cancel paid unsplit main order', { supplierId, userId, mainOrderId }) + + if (!supplierId || !userId || !mainOrderId) { + return serviceFail('供应商、用户或订单信息不能为空') + } + + const order = await orderDao.findMainOrder(supplierId, userId, mainOrderId) + if (!order) { + return serviceFail('主单不存在或已取消', 404, 'MAIN_ORDER_NOT_FOUND') + } + + if (order.orderState === ORDER_STATE.PENDING_PAY) { + return serviceFail('未支付主单请使用未支付取消接口', 400, 'MAIN_ORDER_NOT_PAID') + } + + if (order.orderState !== ORDER_STATE.FINISH_PAY || order.isSplit === ORDER_SPLIT_STATE.SPLIT) { + return serviceFail('该主单已拆单,不能按已支付未拆单整单取消', 400, 'MAIN_ORDER_ALREADY_SPLIT') + } + + await orderDao.transaction(async tx => { + await tx.supplierOrderMain.update({ + where: { orderId: mainOrderId }, + data: { orderState: ORDER_STATE.CANCELLED, updateTime: new Date() } + }) + await tx.supplierOrderSub.updateMany({ + where: { mainOrderId, supplierId, userId, deleted: 0 }, + data: { orderState: ORDER_STATE.CANCELLED, updateTime: new Date() } + }) + }) + + const { response, payload } = await bridgeFetchWithAuth(supplierId, '/pointOrder/pushOrderInfoMsg', { + orderType: ORDER_EVENT_TYPE.CANCEL_PAID_NOT_SPLIT_MAIN, + orderId, + mainOrderId, + userId, + time: Date.now() + }) + + if (!response.ok || payload.resultCode !== 0) { + return serviceFail(payload.message || '取消已支付未拆单整单推送失败', 502, 'BRIDGE_CANCEL_PAID_MAIN_FAILED') + } + + return serviceOk({ supplierId, orderType: ORDER_EVENT_TYPE.CANCEL_PAID_NOT_SPLIT_MAIN, orderId, mainOrderId, userId }) + }, + + async cancelSub(input: CancelSubInput) { + const { supplierId, userId, orderId } = input + logger.info('cancel sub order', { supplierId, userId, orderId }) + + if (!supplierId || !userId || !orderId) { + return serviceFail('供应商、用户或子单信息不能为空') + } + + const subOrder = await orderDao.findSubOrder(supplierId, userId, orderId) + if (!subOrder) { + return serviceFail('子单不存在或已取消', 404, 'SUB_ORDER_NOT_FOUND') + } + + const mainOrderId = input.mainOrderId || subOrder.mainOrderId + + await orderDao.transaction(async tx => { + await tx.supplierOrderSub.update({ + where: { orderId }, + data: { orderState: ORDER_STATE.CANCELLED, updateTime: new Date() } + }) + }) + + const { response, payload } = await bridgeFetchWithAuth(supplierId, '/pointOrder/pushOrderInfoMsg', { + orderType: ORDER_EVENT_TYPE.CANCEL_SUB, + orderId, + mainOrderId, + userId, + time: Date.now() + }) + + if (!response.ok || payload.resultCode !== 0) { + return serviceFail(payload.message || '取消子单推送失败', 502, 'BRIDGE_CANCEL_SUB_FAILED') + } + + return serviceOk({ supplierId, orderType: ORDER_EVENT_TYPE.CANCEL_SUB, orderId, mainOrderId, userId }) + }, + + async payOrder(input: PayOrderInput) { + const { supplierId, orderId, totalPrice, wPayOrAPay, pointsPrice } = input + logger.info('pay order', { supplierId, orderId, totalPrice, wPayOrAPay, pointsPrice }) + + if (!supplierId || !orderId) { + return serviceFail('供应商或订单信息不能为空') + } + + if (totalPrice <= 0) { + return serviceFail('支付金额必须大于0') + } + + const order = await orderDao.findPayingMainOrder(orderId) + if (!order || order.deleted === 1) { + return serviceFail('待支付订单不存在', 404, 'ORDER_NOT_FOUND') + } + + if (order.orderState !== ORDER_STATE.PENDING_PAY) { + logger.warn('order is not payable', { supplierId, orderId, orderState: order.orderState }) + return serviceFail('当前订单状态不可支付,请刷新订单状态', 400, 'ORDER_NOT_PAYABLE') + } + + await orderDao.transaction(async tx => { + await tx.supplierOrderMain.update({ + where: { orderId }, + data: { + orderState: ORDER_STATE.FINISH_PAY, + isSplit: ORDER_SPLIT_STATE.NOT_SPLIT, + totalPrice, + wPayOrAPay, + pointsPrice, + updateTime: new Date() + } + }) + await tx.supplierOrderSub.updateMany({ + where: { mainOrderId: orderId, deleted: 0 }, + data: { orderState: ORDER_STATE.FINISH_PAY, updateTime: new Date() } + }) + }) + + const confirm = await bridgeFetchWithAuth(supplierId, '/pointOrder/confirmPurchase', { + orderId, + totalPrice, + wPayOrAPay, + pointsPrice + }) + if (!confirm.response.ok || confirm.payload.resultCode !== 0) { + return serviceFail(confirm.payload.message || '确认购买推送失败', 502, 'BRIDGE_CONFIRM_PURCHASE_FAILED') + } + + const purchaseResult = await bridgeFetchWithAuth(supplierId, '/pointOrder/purchaseResult', { + orderId, + totalPrice, + wPayOrAPay, + pointsPrice, + purchaseResult: true, + message: '支付成功' + }) + if (!purchaseResult.response.ok || purchaseResult.payload.resultCode !== 0) { + return serviceFail(purchaseResult.payload.message || '支付结果通知推送失败', 502, 'BRIDGE_PURCHASE_RESULT_FAILED') + } + + const subOrders = await orderDao.findSubOrderIdsByMain(orderId) + const subOrderIds = subOrders.map(item => item.orderId).filter(Boolean).join(',') + + if (subOrderIds) { + await orderDao.transaction(async tx => { + await tx.supplierOrderMain.update({ + where: { orderId }, + data: { isSplit: ORDER_SPLIT_STATE.SPLIT, splitTime: new Date(), updateTime: new Date() } + }) + await tx.supplierOrderSub.updateMany({ + where: { mainOrderId: orderId, deleted: 0 }, + data: { orderState: ORDER_STATE.IN_PROGRESS, updateTime: new Date() } + }) + }) + + const split = await bridgeFetchWithAuth(supplierId, '/pointOrder/pushOrderInfoMsg', { + orderType: ORDER_EVENT_TYPE.SPLIT_SUB_ORDER, + orderId: subOrderIds, + mainOrderId: orderId, + userId: order.userId, + time: Date.now() + }) + if (!split.response.ok || split.payload.resultCode !== 0) { + return serviceFail(split.payload.message || '推送拆单信息失败', 502, 'BRIDGE_SPLIT_ORDER_FAILED') + } + } + + return serviceOk({ orderId, subOrderIds, totalPrice, wPayOrAPay, pointsPrice }) + } +} diff --git a/supplied-service/src/services/order-create.service.ts b/supplied-service/src/services/order-create.service.ts new file mode 100644 index 0000000..f64ec89 --- /dev/null +++ b/supplied-service/src/services/order-create.service.ts @@ -0,0 +1,256 @@ +import { ORDER_SPLIT_STATE, ORDER_STATE } from '../constants/order.js' +import { orderDao } from '../dao/order.dao.js' +import { bridgeFetchWithAuth } from '../utils/bridge-auth.js' +import { logger } from '../utils/logger.js' +import { serviceFail, serviceOk } from '../types/service-result.js' + +export type CheckoutGoods = { + id?: string | number + sku?: string | number | Record + count?: number + number?: number + price?: string | number + name?: string + image?: string + imageUrl?: string + picInfo?: string[] + taxRate?: number +} + +type OrderCreateInput = { + supplierId: string + userId: string + orderNo: string + orderNumberId?: string + amount?: number + goods: CheckoutGoods[] +} + +type BridgeOrderResponse = { + resultCode?: number + message?: string +} + +type OrderProductSnapshot = { + sku: string + number: number + price: number + name: string + taxRate: number + picInfo: string[] + fileId: string | null + filePath: string | null +} + +const nowText = () => { + const now = new Date() + const pad = (value: number) => String(value).padStart(2, '0') + + return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}` +} + +const createOrderNumberId = () => { + const now = new Date() + const pad = (value: number, size = 2) => String(value).padStart(size, '0') + + return `TR${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}${pad(now.getMilliseconds(), 3)}` +} + +const toYuan = (centAmount?: number) => Number((Number(centAmount || 0) / 100).toFixed(2)) +const toMoney = (value: number) => Number(value.toFixed(2)) +const createSubOrderId = (orderId: string, index: number) => `${orderId}_${String(index + 1).padStart(3, '0')}` +const createSubOrderNumberId = (orderNumberId: string, index: number) => `${orderNumberId}_${String(index + 1).padStart(3, '0')}` + +const toOrderProducts = (goods: CheckoutGoods[]) => goods.map((item, index) => { + const image = item.image || item.imageUrl || item.picInfo?.[0] || '' + const sku = typeof item.sku === 'object' && item.sku + ? [item.id, item.sku.color, item.sku.size].filter(Boolean).join('-') + : item.sku + + return { + sku: String(sku || item.id || `SKU_${index + 1}`), + number: Number(item.count || item.number || 1), + price: Number(item.price || 0), + name: String(item.name || ''), + taxRate: Number(item.taxRate || 0.13), + picInfo: item.picInfo?.length ? item.picInfo : image ? [image] : [], + fileId: image || null, + filePath: image || null + } +}) + +const productSubtotal = (item: OrderProductSnapshot) => toMoney(Number(item.price || 0) * Number(item.number || 0)) + +export const orderCreateService = { + async createOrder(input: OrderCreateInput) { + const supplierId = input.supplierId + const userId = input.userId + const orderId = input.orderNo + const amount = toYuan(input.amount) + const goods = Array.isArray(input.goods) ? input.goods : [] + const orderProInfos = toOrderProducts(goods) + logger.info('create pending order', { supplierId, userId, orderId, goodsCount: goods.length, amount }) + + if (!supplierId || !userId || !orderId) { + return serviceFail('供应商、用户或订单信息不能为空') + } + + if (amount <= 0) { + return serviceFail('订单金额必须大于0') + } + + if (!orderProInfos.length) { + return serviceFail('订单商品不能为空') + } + + const orderNumberId = String(input.orderNumberId || createOrderNumberId()) + const orderTime = nowText() + const now = new Date() + const subOrders = orderProInfos.map((item, index) => { + const subTotal = productSubtotal(item) + + return { + orderId: createSubOrderId(orderId, index), + orderNumberId: createSubOrderNumberId(orderNumberId, index), + product: item, + totalPrice: subTotal, + pointsPrice: subTotal + } + }) + const orderInfo = { + orderId, + orderNumberId, + supplierId, + aplName: '积分商城', + createTime: orderTime, + updateTime: orderTime, + deliveryTime: null, + orderFinishTime: null, + completeTime: null, + deliveryStatus: '0', + freightCost: 0, + wPayOrAPayFreightCost: 0, + refund: 0, + wPayOrAPayRefund: 0, + logisticsOrderId: null, + crrgBsnNm: null, + orderState: ORDER_STATE.PENDING_PAY, + totalPrice: amount, + wPayOrAPay: 0, + pointsPrice: amount, + invoiceCode: null, + orderProInfos + } + + await orderDao.transaction(async tx => { + const existingSubOrders = await tx.supplierOrderSub.findMany({ + where: { mainOrderId: orderId }, + select: { orderId: true } + }) + const existingSubOrderIds = existingSubOrders.map(item => item.orderId) + + if (existingSubOrderIds.length) { + await tx.supplierOrderProduct.deleteMany({ where: { subOrderId: { in: existingSubOrderIds } } }) + await tx.supplierOrderSub.deleteMany({ where: { mainOrderId: orderId } }) + } + + await tx.supplierOrderMain.upsert({ + where: { orderId }, + update: { + orderNumberId, + supplierId, + userId, + aplName: orderInfo.aplName, + updateTime: now, + deliveryStatus: orderInfo.deliveryStatus, + freightCost: orderInfo.freightCost, + wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost, + refund: orderInfo.refund, + wPayOrAPayRefund: orderInfo.wPayOrAPayRefund, + orderState: ORDER_STATE.PENDING_PAY, + isSplit: ORDER_SPLIT_STATE.NOT_SPLIT, + splitTime: null, + totalPrice: orderInfo.totalPrice, + wPayOrAPay: orderInfo.wPayOrAPay, + pointsPrice: orderInfo.pointsPrice, + invoiceCode: orderInfo.invoiceCode, + deleted: 0 + }, + create: { + orderId, + orderNumberId, + supplierId, + userId, + aplName: orderInfo.aplName, + createTime: now, + updateTime: now, + deliveryStatus: orderInfo.deliveryStatus, + freightCost: orderInfo.freightCost, + wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost, + refund: orderInfo.refund, + wPayOrAPayRefund: orderInfo.wPayOrAPayRefund, + orderState: ORDER_STATE.PENDING_PAY, + isSplit: ORDER_SPLIT_STATE.NOT_SPLIT, + totalPrice: orderInfo.totalPrice, + wPayOrAPay: orderInfo.wPayOrAPay, + pointsPrice: orderInfo.pointsPrice, + invoiceCode: orderInfo.invoiceCode + } + }) + + await tx.supplierOrderSub.createMany({ + data: subOrders.map(subOrder => ({ + orderId: subOrder.orderId, + orderNumberId: subOrder.orderNumberId, + mainOrderId: orderId, + supplierId, + userId, + aplName: orderInfo.aplName, + createTime: now, + updateTime: now, + deliveryStatus: orderInfo.deliveryStatus, + freightCost: orderInfo.freightCost, + wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost, + refund: orderInfo.refund, + wPayOrAPayRefund: orderInfo.wPayOrAPayRefund, + orderState: ORDER_STATE.PENDING_PAY, + totalPrice: subOrder.totalPrice, + wPayOrAPay: 0, + pointsPrice: subOrder.pointsPrice, + invoiceCode: orderInfo.invoiceCode + })) + }) + + await tx.supplierOrderProduct.createMany({ + data: subOrders.map(subOrder => ({ + subOrderId: subOrder.orderId, + sku: subOrder.product.sku, + number: subOrder.product.number, + price: subOrder.product.price, + name: subOrder.product.name, + taxRate: subOrder.product.taxRate, + productImgUrl: subOrder.product.picInfo[0] || null, + fileId: subOrder.product.fileId, + filePath: subOrder.product.filePath, + picInfo: subOrder.product.picInfo + })) + }) + }) + + const { response, payload } = await bridgeFetchWithAuth(supplierId, '/pointOrder/pendingOrderInfo', { + userId, + orderInfo + }) + + if (!response.ok || payload.resultCode !== 0) { + return serviceFail(payload.message || '提交待支付订单失败', 502, 'BRIDGE_PENDING_ORDER_FAILED') + } + + return serviceOk({ + orderId, + orderNumberId, + subOrderIds: subOrders.map(item => item.orderId), + orderInfo + }) + } +} diff --git a/supplied-service/src/services/order-list.service.ts b/supplied-service/src/services/order-list.service.ts new file mode 100644 index 0000000..399f157 --- /dev/null +++ b/supplied-service/src/services/order-list.service.ts @@ -0,0 +1,145 @@ +import { ORDER_SPLIT_STATE, ORDER_STATE } from '../constants/order.js' +import { orderDao } from '../dao/order.dao.js' +import { logger } from '../utils/logger.js' +import { serviceFail, serviceOk } from '../types/service-result.js' + +type OrderStatusView = { + text: string + statusClass: string + actionText: string + buttonClass: string +} + +const statusMap: Record = { + [ORDER_STATE.PENDING_PAY]: { text: '待付款', statusClass: 'pending', actionText: '去支付', buttonClass: 'primary-action' }, + [ORDER_STATE.FINISH_PAY]: { text: '待发货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' }, + [ORDER_STATE.IN_PROGRESS]: { text: '待收货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' }, + [ORDER_STATE.CANCELLED]: { text: '已取消', statusClass: 'muted', actionText: '', buttonClass: 'dark-action' }, + [ORDER_STATE.COMPLETED]: { text: '已完成', statusClass: 'done', actionText: '再次购买', buttonClass: 'dark-action' }, + [ORDER_STATE.EXCHANGE]: { text: '换货', statusClass: 'muted', actionText: '查看订单', buttonClass: 'dark-action' }, + [ORDER_STATE.REFUNDED]: { text: '退货', statusClass: 'muted', actionText: '查看订单', buttonClass: 'dark-action' } +} + +const toYuanText = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => { + if (value && typeof value === 'object' && 'toFixed' in value) { + return value.toFixed(2) + } + + return Number(value || 0).toFixed(2) +} + +const firstImage = (picInfo: unknown) => { + if (Array.isArray(picInfo)) { + return String(picInfo[0] || '') + } + + return '' +} + +const toDateTimeText = (date?: Date | null) => { + if (!date) return '' + const pad = (value: number) => String(value).padStart(2, '0') + + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +} + +const sumProductsCount = (subOrders: Array<{ products: Array<{ number: number | null }> }>) => { + return subOrders.reduce((total, subOrder) => { + return total + subOrder.products.reduce((subTotal, product) => subTotal + Number(product.number || 0), 0) + }, 0) +} + +export const orderListService = { + async listOrders(supplierId: string, userId: string) { + logger.info('order list query', { supplierId, userId }) + + if (!supplierId || !userId) { + return serviceFail('供应商或用户信息不能为空') + } + + const mainOrders = await orderDao.findMainOrdersWithSubs(supplierId, userId) + const orders = mainOrders.map(mainOrder => { + const mainStatus = statusMap[mainOrder.orderState] || statusMap[ORDER_STATE.FINISH_PAY] + const subOrders = mainOrder.subOrders.map(subOrder => { + const status = statusMap[subOrder.orderState] || mainStatus + const products = subOrder.products.map(product => ({ + id: product.id, + sku: product.sku, + name: product.name, + number: product.number, + price: toYuanText(product.price), + image: product.productImgUrl || firstImage(product.picInfo) + })) + + return { + id: subOrder.orderId, + orderId: subOrder.orderId, + orderNumberId: subOrder.orderNumberId || '', + mainOrderId: subOrder.mainOrderId, + supplierId: subOrder.supplierId, + createTime: toDateTimeText(subOrder.createTime || subOrder.createAt), + status: status.text, + statusClass: status.statusClass, + actionText: status.actionText, + buttonClass: status.buttonClass, + orderState: subOrder.orderState, + amount: toYuanText(subOrder.totalPrice), + count: products.reduce((total, item) => total + Number(item.number || 0), 0), + products + } + }) + const products = subOrders.flatMap(subOrder => subOrder.products) + const isMainOrderItem = mainOrder.orderState === ORDER_STATE.PENDING_PAY || + (mainOrder.orderState === ORDER_STATE.CANCELLED && mainOrder.isSplit !== ORDER_SPLIT_STATE.SPLIT) + const orderItems = isMainOrderItem + ? [{ + id: mainOrder.orderId, + viewId: `main-${mainOrder.orderId}`, + displayType: 'main', + orderId: mainOrder.orderId, + mainOrderId: mainOrder.orderId, + orderNumberId: mainOrder.orderNumberId || '', + mainOrderNumberId: mainOrder.orderNumberId || '', + supplierId: mainOrder.supplierId, + createTime: toDateTimeText(mainOrder.createTime || mainOrder.createAt), + status: mainStatus.text, + statusClass: mainStatus.statusClass, + actionText: mainStatus.actionText, + buttonClass: mainStatus.buttonClass, + orderState: mainOrder.orderState, + amount: toYuanText(mainOrder.totalPrice), + count: sumProductsCount(subOrders), + products, + subOrders + }] + : subOrders.map(subOrder => ({ + ...subOrder, + viewId: `sub-${subOrder.orderId}`, + displayType: 'sub', + mainOrderNumberId: mainOrder.orderNumberId || '' + })) + + return { + id: mainOrder.orderId, + mainOrderId: mainOrder.orderId, + mainOrderNumberId: mainOrder.orderNumberId || '', + supplierId: mainOrder.supplierId, + createTime: toDateTimeText(mainOrder.createTime || mainOrder.createAt), + status: mainStatus.text, + statusClass: mainStatus.statusClass, + orderState: mainOrder.orderState, + isSplit: mainOrder.isSplit, + splitTime: mainOrder.splitTime, + canCancel: mainOrder.orderState === ORDER_STATE.PENDING_PAY, + products, + amount: toYuanText(mainOrder.totalPrice), + count: sumProductsCount(subOrders), + orderItems, + subOrders + } + }) + + logger.info('order list result', { supplierId, userId, count: orders.length }) + return serviceOk({ orders, orderItems: orders.flatMap(order => order.orderItems) }) + } +} diff --git a/supplied-service/src/services/order-push-event.service.ts b/supplied-service/src/services/order-push-event.service.ts new file mode 100644 index 0000000..066cff3 --- /dev/null +++ b/supplied-service/src/services/order-push-event.service.ts @@ -0,0 +1,290 @@ +import { + ORDER_EVENT_TYPE, + ORDER_OPERATE_TYPE, + ORDER_PUSH_STATUS, + ORDER_STATE +} from '../constants/order.js' +import { orderDao } from '../dao/order.dao.js' +import { bridgeFetchWithAuth } from '../utils/bridge-auth.js' +import { logger } from '../utils/logger.js' +import { serviceFail, serviceOk } from '../types/service-result.js' + +export type OrderPushEventInput = { + supplierId?: string + userId?: string + orderType?: number + orderId?: string + mainOrderId?: string + subOrderId?: string + time?: number + amount?: number + reason?: string + settlementId?: string + invoiceBizId?: string + invoiceCode?: string + invoiceIds?: string + orderIds?: string + fileId?: string + filePath?: string +} + +type BridgeOrderResponse = { + resultCode?: number + message?: string +} + +const PUSH_API_PATH = '/pointOrder/pushOrderInfoMsg' +const supportedOrderTypes = new Set([ + ORDER_EVENT_TYPE.RETURN_ORDER, + ORDER_EVENT_TYPE.EXCHANGE_ORDER, + ORDER_EVENT_TYPE.CONFIRM_RECEIVE, + ORDER_EVENT_TYPE.INVOICE_INFO +]) + +const splitIds = (value?: string) => String(value || '') + .split(',') + .map(item => item.trim()) + .filter(Boolean) + +const eventState = (orderType: number) => { + if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER) return ORDER_STATE.REFUNDED + if (orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) return ORDER_STATE.EXCHANGE + if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE) return ORDER_STATE.COMPLETED + return null +} + +const operateType = (orderType: number) => { + if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER) return ORDER_OPERATE_TYPE.REFUND + if (orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) return ORDER_OPERATE_TYPE.EXCHANGE + if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE) return ORDER_OPERATE_TYPE.COMPLETE + if (orderType === ORDER_EVENT_TYPE.INVOICE_INFO) return ORDER_OPERATE_TYPE.INVOICE_INFO + return orderType +} + +const buildBridgeBody = (body: OrderPushEventInput) => ({ + supplierId: String(body.supplierId || '').trim(), + orderType: Number(body.orderType), + orderId: String(body.orderId || '').trim(), + mainOrderId: String(body.mainOrderId || '').trim(), + userId: String(body.userId || '').trim(), + time: Number(body.time || Date.now()) +}) + +export const orderPushEventService = { + async pushEvent(body: OrderPushEventInput) { + const supplierId = String(body.supplierId || '').trim() + const userId = String(body.userId || '').trim() + const orderType = Number(body.orderType) + const orderId = String(body.orderId || '').trim() + const mainOrderId = String(body.mainOrderId || '').trim() + logger.info('push order event', { supplierId, userId, orderType, orderId, mainOrderId }) + + if (!supplierId || !userId || !orderType || !orderId) { + return serviceFail('供应商、用户或订单事件信息不能为空') + } + + if (!supportedOrderTypes.has(orderType)) { + return serviceFail('暂不支持该订单事件类型', 400, 'ORDER_EVENT_NOT_SUPPORTED') + } + + const bridgeBody = buildBridgeBody(body) + const now = new Date() + let pushLogId = 0 + let affectedSubOrderIds: string[] = [] + + await orderDao.transaction(async tx => { + const mainOrder = mainOrderId + ? await tx.supplierOrderMain.findFirst({ where: { orderId: mainOrderId, supplierId, deleted: 0 } }) + : null + + const explicitSubIds = splitIds(body.subOrderId || '') + const orderIds = splitIds(orderId) + const subOrdersByOrderId = await tx.supplierOrderSub.findMany({ + where: { + supplierId, + deleted: 0, + OR: [ + { orderId: { in: orderIds } }, + ...(explicitSubIds.length ? [{ orderId: { in: explicitSubIds } }] : []), + ...(mainOrderId ? [{ mainOrderId }] : []) + ] + }, + orderBy: { createAt: 'asc' } + }) + + const directlyMatched = subOrdersByOrderId.filter(item => orderIds.includes(item.orderId) || explicitSubIds.includes(item.orderId)) + const mainOrderSubs = mainOrderId ? subOrdersByOrderId.filter(item => item.mainOrderId === mainOrderId) : [] + const targetSubOrders = directlyMatched.length ? directlyMatched : mainOrderSubs.length === 1 ? mainOrderSubs : [] + affectedSubOrderIds = targetSubOrders.map(item => item.orderId) + + const nextState = eventState(orderType) + if (nextState !== null && targetSubOrders.length) { + await tx.supplierOrderSub.updateMany({ + where: { orderId: { in: affectedSubOrderIds }, supplierId, deleted: 0 }, + data: { + orderState: nextState, + orderFinishTime: orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE ? now : undefined, + completeTime: orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE ? now : undefined, + updateTime: now + } + }) + + await tx.supplierOrderOperateLog.createMany({ + data: targetSubOrders.map(subOrder => ({ + supplierId, + userId, + mainOrderId: subOrder.mainOrderId, + subOrderId: subOrder.orderId, + operateType: operateType(orderType), + beforeState: subOrder.orderState, + afterState: nextState, + remark: body.reason || null, + operator: 'system' + })) + }) + } + + if (orderType === ORDER_EVENT_TYPE.RETURN_ORDER || orderType === ORDER_EVENT_TYPE.EXCHANGE_ORDER) { + await tx.supplierRefundExchange.upsert({ + where: { serviceOrderId: orderId }, + update: { + supplierId, + userId, + mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null, + subOrderId: targetSubOrders[0]?.orderId || null, + serviceType: orderType, + serviceState: 1, + amount: Number(body.amount || targetSubOrders[0]?.totalPrice || 0), + reason: body.reason || null, + requestBody: body, + deleted: 0 + }, + create: { + serviceOrderId: orderId, + supplierId, + userId, + mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null, + subOrderId: targetSubOrders[0]?.orderId || null, + serviceType: orderType, + serviceState: 1, + amount: Number(body.amount || targetSubOrders[0]?.totalPrice || 0), + reason: body.reason || null, + requestBody: body + } + }) + } + + if (orderType === ORDER_EVENT_TYPE.INVOICE_INFO) { + const invoiceBizId = String(body.invoiceBizId || body.invoiceCode || orderId).trim() + await tx.supplierInvoiceInfo.upsert({ + where: { invoiceBizId }, + update: { + supplierId, + userId, + mainOrderId: mainOrderId || null, + subOrderId: targetSubOrders[0]?.orderId || null, + settlementId: body.settlementId || null, + invoiceCode: body.invoiceCode || null, + invoiceIds: body.invoiceIds || orderId, + orderIds: body.orderIds || mainOrderId || targetSubOrders[0]?.orderId || null, + invoiceStatus: 2, + fileId: body.fileId || null, + filePath: body.filePath || null, + requestBody: body, + deleted: 0 + }, + create: { + invoiceBizId, + supplierId, + userId, + mainOrderId: mainOrderId || null, + subOrderId: targetSubOrders[0]?.orderId || null, + settlementId: body.settlementId || null, + invoiceCode: body.invoiceCode || null, + invoiceIds: body.invoiceIds || orderId, + orderIds: body.orderIds || mainOrderId || targetSubOrders[0]?.orderId || null, + invoiceStatus: 2, + fileId: body.fileId || null, + filePath: body.filePath || null, + requestBody: body + } + }) + + if (body.invoiceCode && mainOrder) { + await tx.supplierOrderMain.update({ + where: { orderId: mainOrder.orderId }, + data: { invoiceCode: body.invoiceCode, updateTime: now } + }) + } + } + + if (orderType === ORDER_EVENT_TYPE.CONFIRM_RECEIVE && mainOrderId) { + const unfinishedCount = await tx.supplierOrderSub.count({ + where: { + mainOrderId, + deleted: 0, + orderState: { notIn: [ORDER_STATE.COMPLETED, ORDER_STATE.CANCELLED, ORDER_STATE.REFUNDED] } + } + }) + if (unfinishedCount === 0) { + await tx.supplierOrderMain.update({ + where: { orderId: mainOrderId }, + data: { orderState: ORDER_STATE.COMPLETED, completeTime: now, updateTime: now } + }) + } + } + + const pushLog = await tx.supplierOrderPushLog.create({ + data: { + supplierId, + userId, + mainOrderId: mainOrderId || targetSubOrders[0]?.mainOrderId || null, + orderId, + orderType, + apiPath: PUSH_API_PATH, + requestBody: bridgeBody, + pushStatus: ORDER_PUSH_STATUS.PENDING + } + }) + pushLogId = pushLog.id + }) + + let bridgeResponse: Response | null = null + let bridgePayload: BridgeOrderResponse = {} + try { + const result = await bridgeFetchWithAuth(supplierId, PUSH_API_PATH, bridgeBody) + bridgeResponse = result.response + bridgePayload = result.payload + } catch (error) { + const message = error instanceof Error ? error.message : '订单事件推送失败' + await orderDao.updatePushLog(pushLogId, { + pushStatus: ORDER_PUSH_STATUS.FAILED, + errorMessage: message, + lastPushTime: new Date() + }) + logger.warn('push order event bridge failed', { supplierId, orderId, message }) + return serviceOk({ localSaved: true, pushSuccess: false, message, affectedSubOrderIds }) + } + + const pushSuccess = bridgeResponse.ok && bridgePayload.resultCode === 0 + + await orderDao.updatePushLog(pushLogId, { + responseBody: bridgePayload, + httpStatus: bridgeResponse.status, + resultCode: bridgePayload.resultCode, + pushStatus: pushSuccess ? ORDER_PUSH_STATUS.SUCCESS : ORDER_PUSH_STATUS.FAILED, + errorMessage: pushSuccess ? null : bridgePayload.message || '订单事件推送失败', + lastPushTime: new Date() + }) + + return serviceOk({ + localSaved: true, + pushSuccess, + orderType, + orderId, + mainOrderId, + affectedSubOrderIds, + message: pushSuccess ? '订单事件已保存并推送成功' : bridgePayload.message || '订单事件已保存,推送失败' + }) + } +} diff --git a/supplied-service/src/services/product.service.ts b/supplied-service/src/services/product.service.ts new file mode 100644 index 0000000..04916ec --- /dev/null +++ b/supplied-service/src/services/product.service.ts @@ -0,0 +1,68 @@ +import { productDao, type ProductRecord } from '../dao/product.dao.js' +import { logger } from '../utils/logger.js' +import { serviceFail, serviceOk } from '../types/service-result.js' + +const categories = [ + { id: 'recommend', name: '推荐' }, + { id: 'homeLiving', name: '居家生活' }, + { id: 'digitalAppliance', name: '数码家电' }, + { id: 'beautyCare', name: '美妆个护' }, + { id: 'foodDrink', name: '食品饮料' }, + { id: 'motherBaby', name: '母婴玩具' }, + { id: 'fashionBag', name: '服饰鞋包' }, + { id: 'sportsOutdoor', name: '运动户外' } +] + +const toProductItem = (product: ProductRecord) => { + const picInfo = Array.isArray(product.picInfo) ? product.picInfo : [] + const image = product.imageUrl || String(picInfo[0] || '') + const price = product.price.toFixed(2) + + return { + id: product.id, + sku: product.sku, + number: product.number, + price, + name: product.name, + taxRate: Number(product.taxRate), + picInfo, + desc: product.description || '', + detail: product.detail || product.description || '', + originPrice: product.originPrice ? product.originPrice.toFixed(2) : price, + image, + imageUrl: image, + categoryId: product.categoryId || '', + category: product.category || '', + tag: product.tag || '', + sales: product.sales || '', + stock: product.stock + } +} + +export const productService = { + async listProducts(keyword: string, categoryId: string) { + logger.info('product list query', { keyword, categoryId }) + const products = await productDao.findEnabledProducts(keyword, categoryId) + + return serviceOk({ + categories, + products: products.map(toProductItem) + }) + }, + + async getProductDetail(id: string, sku: string) { + logger.info('product detail query', { id, sku }) + + if (!id && !sku) { + return serviceFail('商品参数不能为空') + } + + const product = await productDao.findEnabledProduct(id, sku) + + if (!product) { + return serviceFail('商品不存在或已下架', 404, 'PRODUCT_NOT_FOUND') + } + + return serviceOk(toProductItem(product)) + } +} diff --git a/supplied-service/src/services/supplier-open-order.service.ts b/supplied-service/src/services/supplier-open-order.service.ts new file mode 100644 index 0000000..d09ff3c --- /dev/null +++ b/supplied-service/src/services/supplier-open-order.service.ts @@ -0,0 +1,108 @@ +import { supplierDao } from '../dao/supplier.dao.js' +import { orderDao } from '../dao/order.dao.js' +import { logger } from '../utils/logger.js' +import { sm4CbcDecrypt, verifySupplierOpenSignature } from '../utils/supplier-open-crypto.js' + +export type SupplierOrderQueryRequest = { + supplierId?: string + token?: string + orderType?: number + orderId?: string + timestamp?: number + AplParmObjct?: string +} + +const parseBody = (body: SupplierOrderQueryRequest) => { + if (!body.AplParmObjct) return body + + const keyHex = process.env.SUPPLIER_OPEN_SM4_KEY_HEX || process.env.SM4CBC_KEY_HEX || '' + const ivHex = process.env.SUPPLIER_OPEN_SM4_IV_HEX || process.env.SM4CBC_IV_HEX || '' + if (!keyHex || !ivHex) { + throw new Error('供应商开放接口SM4配置缺失') + } + + return JSON.parse(sm4CbcDecrypt(body.AplParmObjct, keyHex, ivHex)) as SupplierOrderQueryRequest +} + +const toText = (date?: Date | null) => { + if (!date) return null + const pad = (value: number) => String(value).padStart(2, '0') + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +} + +const toMoney = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => Number(value || 0).toFixed(2) + +const productSku = (product: { sku: string; subOrderId: string; name: string }) => { + if (product.sku && product.sku !== '[object Object]') { + return product.sku + } + + return `${product.subOrderId}-${product.name || 'SKU'}` +} + +export const supplierOpenOrderService = { + parseBody, + + async queryOrderInfo(body: SupplierOrderQueryRequest) { + const supplierId = String(body.supplierId || '').trim() + const token = String(body.token || '').trim() + const orderIds = String(body.orderId || '') + .split(',') + .map(item => item.trim()) + .filter(Boolean) + const timestamp = Number(body.timestamp || 0) + + logger.info('supplier open order query', { supplierId, orderCount: orderIds.length, timestamp }) + + if (!supplierId || !token || !timestamp || !orderIds.length) { + return { resultCode: 400, message: '供应商、订单或签名信息不能为空', orderInfo: [] } + } + + const supplier = await supplierDao.findActiveBySupplierId(supplierId) + if (!supplier) { + return { resultCode: 404, message: '供应商信息不存在', orderInfo: [] } + } + + if (!verifySupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp, token)) { + return { resultCode: 401, message: '签名无效或已过期', orderInfo: [] } + } + + const orders = await orderDao.findOpenApiSubOrders(supplierId, orderIds) + const orderInfo = orders.map(order => ({ + orderId: order.orderId, + orderNumberId: order.orderNumberId, + supplierId: order.supplierId, + token: order.token, + aplName: order.aplName, + createTime: toText(order.createTime), + updateTime: toText(order.updateTime), + deliveryTime: toText(order.deliveryTime), + orderFinishTime: toText(order.orderFinishTime), + completeTime: toText(order.completeTime), + deliveryStatus: order.deliveryStatus, + freightCost: toMoney(order.freightCost), + wPayOrAPayFreightCost: toMoney(order.wPayOrAPayFreightCost), + refund: toMoney(order.refund), + wPayOrAPayRefund: toMoney(order.wPayOrAPayRefund), + logisticsOrderId: order.logisticsOrderId, + crrgBsnNm: order.crrgBsnNm, + orderState: order.orderState, + totalPrice: toMoney(order.totalPrice), + wPayOrAPay: toMoney(order.wPayOrAPay), + pointsPrice: toMoney(order.pointsPrice), + invoiceCode: order.invoiceCode, + orderProInfos: order.products.map(product => ({ + sku: productSku(product), + number: product.number, + price: toMoney(product.price), + name: product.name, + taxRate: Number(product.taxRate || 0), + picInfo: Array.isArray(product.picInfo) ? product.picInfo : product.productImgUrl ? [product.productImgUrl] : [], + fileId: product.fileId, + filePath: product.filePath + })) + })) + + return { resultCode: 0, message: '成功', orderInfo } + } +} diff --git a/supplied-service/src/services/supplier.service.ts b/supplied-service/src/services/supplier.service.ts new file mode 100644 index 0000000..e2d00b3 --- /dev/null +++ b/supplied-service/src/services/supplier.service.ts @@ -0,0 +1,145 @@ +import { supplierDao } from '../dao/supplier.dao.js' +import { bridgeFetchWithAuth, findSupplierByIdentification, getBridgeApiBase, getBridgeToken, warmBridgeToken } from '../utils/bridge-auth.js' +import { sha256 } from '../utils/crypto.js' +import { logger } from '../utils/logger.js' +import { createSupplierOpenSignature } from '../utils/supplier-open-crypto.js' +import { serviceFail, serviceOk } from '../types/service-result.js' + +type BridgePointsResponse = { + resultCode?: number + message?: string + userPoints?: number | string +} + +export const supplierService = { + async getSupplierInfo(supplierIdentification: string) { + logger.info('supplier info query', { supplierIdentification }) + + if (!supplierIdentification) { + return serviceFail('供应商唯一标识不能为空') + } + + const supplier = await supplierDao.findActiveByIdentification(supplierIdentification) + + if (!supplier) { + return serviceFail('供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED') + } + + return serviceOk({ + id: supplier.id, + supplierIdentification: supplier.supplierIdentification, + supplierId: supplier.supplierId, + supplierName: supplier.supplierName + }) + }, + + async warmSupplierToken(userId: string, supplierIdentification: string) { + logger.info('supplier token warmup', { userId, supplierIdentification }) + + if (!userId || !supplierIdentification) { + return serviceFail('用户信息和供应商唯一标识不能为空') + } + + const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error) + + if (supplier instanceof Error) { + return serviceFail(supplier.message || '供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED') + } + + try { + await warmBridgeToken(supplier) + } catch (error) { + return serviceFail(error instanceof Error ? error.message : '获取平台授权令牌失败', 502, 'BRIDGE_TOKEN_FAILED') + } + + return serviceOk({ + supplierIdentification, + supplierId: supplier.supplierId, + supplierName: supplier.supplierName, + userId + }) + }, + + async getUserPoints(supplierId: string, userId: string) { + logger.info('supplier points query', { supplierId, userId }) + + if (!supplierId || !userId) { + return serviceFail('供应商或用户信息不能为空') + } + + const { response, payload: pointsPayload } = await bridgeFetchWithAuth( + supplierId, + '/pointOrder/queryPoints', + { userId } + ) + + if (!response.ok || pointsPayload.resultCode !== 0) { + return serviceFail(pointsPayload.message || '查询用户积分失败', 502, 'BRIDGE_POINTS_FAILED') + } + + return serviceOk({ + userPoints: Number(pointsPayload.userPoints || 0) + }) + }, + + async getDebugTokens(supplierIdentification: string, forceRefreshZhghToken: boolean) { + logger.info('supplier debug tokens query', { supplierIdentification, forceRefreshZhghToken }) + + if (!supplierIdentification) { + return serviceFail('供应商唯一标识不能为空') + } + + const supplier = await findSupplierByIdentification(supplierIdentification).catch(error => error) + + if (supplier instanceof Error) { + return serviceFail(supplier.message || '供应商信息未配置或已停用', 404, 'SUPPLIER_NOT_CONFIGURED') + } + + const timestamp = Date.now() + const orderType = 2 + const orderId = 'SUB_ORDER_ID_1,SUB_ORDER_ID_2' + const mainOrderId = 'MAIN_ORDER_ID' + const userId = 'USER_ID' + const supplierOpenToken = createSupplierOpenSignature(supplier.supplierId, supplier.clientId, timestamp) + const zhghBridgeToken = await getBridgeToken(supplier.supplierId, forceRefreshZhghToken) + + return serviceOk({ + supplierOpen: { + name: 'supplierOpenToken', + usage: '调用供应商开放接口 /supplierOpenApi/pointOrder/queryOrderInfo', + supplierId: supplier.supplierId, + clientId: supplier.clientId, + timestamp, + token: supplierOpenToken, + orderType, + orderId, + queryOrderInfoBody: { + supplierId: supplier.supplierId, + token: supplierOpenToken, + orderType, + orderId, + timestamp + } + }, + zhghBridge: { + name: 'zhghBridgeToken', + usage: '调用 zhgh /open/pointOrder/* 桥接接口', + apiBase: getBridgeApiBase(), + supplierId: supplier.supplierId, + clientId: supplier.clientId, + timestamp, + sendSha256: sha256(`${supplier.clientId}${supplier.clientSecret}${timestamp}`), + token: zhghBridgeToken, + splitOrderBody: { + supplierId: supplier.supplierId, + token: zhghBridgeToken, + orderType, + orderId, + mainOrderId, + userId, + time: timestamp + } + } + }) + } +} diff --git a/supplied-service/src/types/service-result.ts b/supplied-service/src/types/service-result.ts new file mode 100644 index 0000000..541413a --- /dev/null +++ b/supplied-service/src/types/service-result.ts @@ -0,0 +1,16 @@ +export type ServiceError = { + message: string + status?: number + code?: string +} + +export type ServiceResult = + | { success: true; data: T } + | { success: false; error: ServiceError } + +export const serviceOk = (data: T): ServiceResult => ({ success: true, data }) + +export const serviceFail = (message: string, status?: number, code?: string): ServiceResult => ({ + success: false, + error: { message, status, code } +}) diff --git a/supplied-service/src/utils/bridge-auth.ts b/supplied-service/src/utils/bridge-auth.ts index 3a922b7..cf80274 100644 --- a/supplied-service/src/utils/bridge-auth.ts +++ b/supplied-service/src/utils/bridge-auth.ts @@ -1,6 +1,6 @@ import type { SupplierInfo } from '@prisma/client' +import { supplierDao } from '../dao/supplier.dao.js' import { createBridgeSignature } from './crypto.js' -import { prisma } from './prisma.js' import { getRedis } from './redis.js' type BridgeTokenResponse = { @@ -33,12 +33,7 @@ const TOKEN_EXPIRE_BUFFER_MS = 60_000 export const getBridgeApiBase = () => normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '') export const findSupplierForBridge = async (supplierId: string) => { - const supplier = await prisma.supplierInfo.findFirst({ - where: { - supplierId, - deleted: 0 - } - }) + const supplier = await supplierDao.findActiveBySupplierId(supplierId) if (!supplier) { throw new Error('供应商信息未配置或已停用') @@ -47,13 +42,9 @@ export const findSupplierForBridge = async (supplierId: string) => { } export const findSupplierByIdentification = async (supplierIdentification: string) => { - const supplier = await prisma.supplierInfo.findUnique({ - where: { - supplierIdentification: supplierIdentification.toLowerCase() - } - }) + const supplier = await supplierDao.findActiveByIdentification(supplierIdentification.toLowerCase()) - if (!supplier || supplier.deleted === 1) { + if (!supplier) { throw new Error('供应商信息未配置或已停用') } return supplier diff --git a/supplied-service/src/utils/handler.ts b/supplied-service/src/utils/handler.ts index ab7ca06..642fa8f 100644 --- a/supplied-service/src/utils/handler.ts +++ b/supplied-service/src/utils/handler.ts @@ -1,4 +1,5 @@ -import { fail } from './http.js' +import { fail } from './http.js' +import { logger } from './logger.js' export type Context = Record @@ -9,9 +10,8 @@ export const withErrorBoundary = (handler: FunctionHandler): FunctionHandler => try { return await handler(request, context) } catch (error) { - console.error(error) + logger.error('unhandled request error', error) return fail('服务异常,请稍后再试', 500, 'INTERNAL_SERVER_ERROR') } } } - diff --git a/supplied-service/src/utils/logger.ts b/supplied-service/src/utils/logger.ts new file mode 100644 index 0000000..2223a08 --- /dev/null +++ b/supplied-service/src/utils/logger.ts @@ -0,0 +1,23 @@ +type LogLevel = 'info' | 'warn' | 'error' + +const write = (level: LogLevel, message: string, meta?: unknown) => { + const line = `[supplied-service] ${new Date().toISOString()} ${level.toUpperCase()} ${message}` + + if (level === 'error') { + console.error(line, meta || '') + return + } + + if (level === 'warn') { + console.warn(line, meta || '') + return + } + + console.log(line, meta || '') +} + +export const logger = { + info: (message: string, meta?: unknown) => write('info', message, meta), + warn: (message: string, meta?: unknown) => write('warn', message, meta), + error: (message: string, meta?: unknown) => write('error', message, meta) +}