Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a9f27eb38 | |||
| e2f5aa9dd8 | |||
| 6a5c0cb090 | |||
| 448b9983d3 | |||
| 00bb6a2227 | |||
| 1f06530894 | |||
| 93390ccf75 | |||
| a2976cdc8f | |||
| 8f2ebbfef4 | |||
| ee1a3f4b6c | |||
| f2fbf95c53 | |||
| 98d52ff173 | |||
| 43de361e61 | |||
| b1511ecb92 | |||
| 0b548a2764 | |||
| 30f8d649cb | |||
| 40e8a12596 | |||
| d7241d54b1 | |||
| 3d819dc10b | |||
| 5e0691ab35 | |||
| 8984465893 | |||
| b90a4e9f8f | |||
| 3f208d27a9 | |||
| 6973e3d7f5 | |||
| fa69ced448 | |||
| a144a35c3d | |||
| 18c5abea79 | |||
| 012626f6bd | |||
| 5d4ed566de | |||
| 3004a65b35 | |||
| 5fdddae41c | |||
| 7d2e4ef57e | |||
| 22c0b03a47 | |||
| 73fd27bdf6 | |||
| f2d03e2595 | |||
| cd9696bc1c | |||
| 0c28c204b2 | |||
| b5d4f8b27c | |||
| 9a80f0f8d6 | |||
| e7f49a9639 | |||
| f941dcf072 | |||
| e8b585e623 | |||
| 5702266276 | |||
| 3382d36a6c | |||
| 9b8e3f1347 | |||
| a8a37fbc4a | |||
| 1440a59fc8 | |||
| e34416a9b3 | |||
| 11659484c6 | |||
| 6c44e9c56f | |||
| 0fb7ab7abb | |||
| 905e145af3 |
+1
-2
@@ -30,9 +30,8 @@
|
||||
| 3.5.8 | 08-24 | HQ 单账号追加/撤销权限;运营改客服修复;城市门店服务+城市范围 | [`v3.5.8`](./杜康好客-v3.5.8-开发文档.md) |
|
||||
| 3.5.9 | 08-25 | 门店累计核销好客权益;HQ 表格去省略号;序号列 + 列设置 + 拖表头改列宽;用户备注 / 手机号不脱敏 | [`v3.5.9`](./杜康好客-v3.5.9-开发文档.md) |
|
||||
| 3.5.10 | 08-25 | C 端门店详情去掉分享按钮;同城送提示改承运商 HTML;小程序客服接通企微微信客服 | [`v3.5.10`](./杜康好客-v3.5.10-开发文档.md) |
|
||||
| 3.5.11 | 08-26 | 城市三种履约起购;企微通知补提交人/规格物流/核销用户(明文手机+备注);结算任务失败通知 + 提现通过/四账单(城市合伙人、笔数、累计金额) | [`v3.5.11`](./杜康好客-v3.5.11-开发文档.md) |
|
||||
| 3.5.11 | 08-26 | 城市三种履约起购;企微通知补提交人/规格物流/核销用户;结算任务失败通知 + 提现通过/四账单 | [`v3.5.11`](./杜康好客-v3.5.11-开发文档.md) |
|
||||
| 3.5.12 | 08-26 | 发布会订单大屏循环 BGM;HQ 日志/订单状态流转/用户行为时间线展示中文;修复删除门店分类被默认树回种;HQ 侧栏顺序(业务前 11 项,系统设置置底) | [`v3.5.12`](./杜康好客-v3.5.12-开发文档.md) |
|
||||
| 3.5.14 | 08-26 | 修复 C 端提交订单/支付成功日志误标 H5;线上 `order_submit`/`pay_success` 回填为小程序 | [`v3.5.14`](./杜康好客-v3.5.14-开发文档.md) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
2. 携带 `sessionId`(`dukang_session_id`)
|
||||
3. `extraJson` 无密码/令牌;手机号脱敏
|
||||
4. eventName 先登记 taxonomy 再 emit
|
||||
5. C 端后端埋点 `clientApp` 取 JWT / `X-Client-App`(`USER_MINI` 或 `USER_H5`),禁止写死 H5。`order_submit` / `pay_success` 见 v3.5.14。
|
||||
|
||||
## API
|
||||
|
||||
|
||||
@@ -67,13 +67,11 @@
|
||||
| 3.5.10 | [`门店详情去分享按钮 + 同城提示改承运商 HTML + 小程序客服接企微`](./杜康好客-v3.5.10-开发文档.md) | 🔶 开发完成 |
|
||||
| 3.5.11 | [`城市履约起购 + 企微通知字段与结算通知`](./杜康好客-v3.5.11-开发文档.md) | 🔶 开发完成 |
|
||||
| 3.5.12 | [`订单大屏 BGM + HQ 中文展示 + 修复删除门店分类 + HQ 侧栏顺序`](./杜康好客-v3.5.12-开发文档.md) | 🔶 开发完成 |
|
||||
| 3.5.14 | [`提交订单/支付成功日志端回填 USER_MINI`](./杜康好客-v3.5.14-开发文档.md) | 🔶 开发完成 |
|
||||
|
||||
| 日期 | 说明 |
|
||||
|------|------|
|
||||
| 2026-08-26 | v3.5.14:`order_submit`/`pay_success` 埋点改用真实 `clientApp`;线上这两类 `USER_H5` 回填为 `USER_MINI` |
|
||||
| 2026-08-26 | v3.5.12:订单大屏循环 BGM;HQ 日志/订单状态流转/用户行为时间线英文码改中文;修复删除门店分类后被 `ensureDefaults` 回种;HQ 侧栏按业务前 11 项重排、系统设置置底 |
|
||||
| 2026-08-26 | v3.5.11:城市三种履约起购;企微补提交人/订单规格物流收货/核销用户(昵称+明文手机+HQ备注);`alert.settlement` 改名「结算任务失败通知」归入系统监控;补提现通过与四账单(城市/合伙人、笔数、累计金额、收款人账号开户行) |
|
||||
| 2026-08-26 | v3.5.11:城市三种履约起购;企微补提交人/订单规格物流收货/核销用户;`alert.settlement` 改名「结算任务失败通知」归入系统监控;补提现通过与四账单 |
|
||||
| 2026-08-25 | v3.5.10:门店详情去掉分享按钮;同城送提示改承运商 HTML(`GET /catalog/local-deliveries`);小程序客服接通企微(需填 CorpID) |
|
||||
| 2026-08-25 | v3.5.9:门店列表累计核销好客权益;HQ 表格去省略号;主列表序号 + 列设置/列宽存 HQ 账号;用户列表备注(不改昵称)、手机号不脱敏;订单列表状态可多选 |
|
||||
| 2026-08-24 | v3.5.8:HQ 单账号追加/撤销权限;运营改客服;城市门店服务与城市范围;分类删除权限与概览按权限/城市裁剪 |
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
| 2 | 现场提货 | 体验 | 默认数量对齐起购;文案用销售单位 |
|
||||
| 3 | 承运商 | 文案 | 「起送瓶数」标明不影响用户起购 |
|
||||
| 4 | 企微提交人 | 优化 | 门店新建/信息变更/套餐待审显示提交人名称 |
|
||||
| 5 | 成交播报 | 优化 | 支付成功补规格/物流/收货;支付与核销补用户昵称、明文手机、HQ 备注 |
|
||||
| 5 | 成交播报 | 优化 | 支付成功补规格/物流/收货;核销成功补用户 |
|
||||
| 6 | 结算通知 | 需求 | `alert.settlement` 改名为「结算任务失败通知」并归入系统监控;提现通过 + 财务四账单(含收款账户) |
|
||||
|
||||
**不做**:起购迁到承运商;多城按地址匹配起购(仍读当前 ACTIVE 城市);HQ 直改已营业门店不发待审通知。
|
||||
@@ -63,12 +63,11 @@
|
||||
- 商品:`{名称} {规格} {瓶\|箱}×{数量}`
|
||||
- 物流:现场提货 / 同城 / 跨城
|
||||
- 收货:提货=姓名+手机;同城/跨城=姓名+手机+详细地址
|
||||
- 用户:昵称、手机号(明文)、HQ 备注 `hq_remark`(空则 —)
|
||||
- 测试单不推
|
||||
|
||||
### 4.3 核销成功 `redeem.success`
|
||||
|
||||
补 `userName`、`userPhone`(明文)、`userRemark`(HQ 用户备注,空则 —)。
|
||||
补 `userName`、`userPhone`(脱敏,与支付播报一致)。
|
||||
|
||||
---
|
||||
|
||||
@@ -85,7 +84,7 @@
|
||||
| `finance.winery_bill` | 酒厂日账单未 skip | 系统配置酒厂账户 |
|
||||
| `finance.logistics_bill` | 物流月账成功且未 skip | 承运商卡 |
|
||||
|
||||
账单一次摘要:抬头含账期 / 账单笔数 / 累计金额;每笔分块写城市、门店或承运商、合伙人(公司-姓名)、核销/订单笔数、账单金额、收款人 / 账户 / 开户行。缺卡写「未登记」。内部群账号不脱敏。`created=0` / `skipped` 不推。正文过长时截断并提示到 HQ 查看。
|
||||
账单一次摘要(多店多行,最多 15 行);缺卡写「未登记收款账户」;内部群账号不脱敏。`created=0` / `skipped` 不推。
|
||||
|
||||
启动 upsert「结算通知群」(占位 webhook + 禁用)。已有「运营告警」若勾了 `alert.settlement`,会收到任务失败/超时,**不会**自动勾上 5 个新业务事件。
|
||||
|
||||
@@ -96,8 +95,8 @@
|
||||
- [ ] 城市三字段起购;提货默认数量对齐;承运商起送不影响下单
|
||||
- [ ] 合伙人新建/信息变更/套餐:企微可见「企业 · 姓名」
|
||||
- [ ] HQ 新建门店:企微可见 HQ 名称
|
||||
- [ ] 支付成功:规格单位、物流方式、收货信息按提货/配送分支正确;用户昵称、明文手机、备注
|
||||
- [ ] 核销成功:有用户昵称、明文手机、备注
|
||||
- [ ] 支付成功:规格单位、物流方式、收货信息按提货/配送分支正确
|
||||
- [ ] 核销成功:有用户昵称与脱敏手机
|
||||
- [ ] HQ 推送条件展示「结算任务失败通知」,在系统监控组
|
||||
- [ ] 账单任务失败 / 提现超时出现在勾了「结算任务失败通知」的群
|
||||
- [ ] 提现通过、四类账单生成含城市/合伙人、笔数、累计金额、收款人/账号/开户行;无账单不刷屏
|
||||
- [ ] 提现通过、四类账单生成含收款账户;无账单不刷屏
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
# 杜康好客 · v3.5.14 修复文档
|
||||
|
||||
> **2026-08-26** · API / 生产数据
|
||||
> **主题**:C 端「提交订单 / 支付成功」用户日志 `client_app` 误标为 H5,改为真实端(小程序)
|
||||
|
||||
---
|
||||
|
||||
## 1. 版本目标
|
||||
|
||||
1. 修复后端埋点:`order_submit`、`pay_success` 不再写死 `USER_H5`。
|
||||
2. 回填线上 `log_user_analytics`:这两类事件里所有 `USER_H5` 改为 `USER_MINI`。
|
||||
|
||||
**不做**:核销埋点(`benefit_redeem_*`);未登录 `POST /analytics/events` 的 H5 回退;H5 预览(`TARO_ENV=h5`)仍报 `USER_H5`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 根因
|
||||
|
||||
`TradeService.createOrder` / `payOrder`(Mock 支付成功)/ `handlePaySuccess`(微信支付回调)调用 `analyticsService.trackOneSafe` 时把 `clientApp` 写死为 `'USER_H5'`。
|
||||
|
||||
当前生产 C 端是 `apps/mini-user` 小程序,请求头为 `X-Client-App: USER_MINI`(写入 JWT)。同链路的 `pay_fail` 已使用 JWT 的 `clientApp`,故 HQ「用户日志」里提交订单显示「C 端 H5」,支付失败却可能显示「C 端小程序」。
|
||||
|
||||
---
|
||||
|
||||
## 3. 代码规则
|
||||
|
||||
| 写入点 | 事件 | `clientApp` |
|
||||
|--------|------|-------------|
|
||||
| `POST /trade/orders` → `createOrder` | `order_submit` | JWT `user.clientApp`(缺省 `USER_H5`) |
|
||||
| `POST /trade/orders/:id/pay` Mock 成功 | `pay_success` | 入参 `clientApp`(缺省 `USER_H5`) |
|
||||
| 微信支付回调 `handlePaySuccess` | `pay_success` | 该订单已有 `order_submit` / `pay_fail`;没有则默认 `USER_MINI` |
|
||||
|
||||
小程序原生:`USER_MINI`。`mini-user` H5 预览:仍为 `USER_H5`(公众号 JSAPI,见 `apps/mini-user/src/lib/api.ts`)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 线上数据回填
|
||||
|
||||
表:`log_user_analytics`
|
||||
范围:`event_name IN ('order_submit', 'pay_success') AND client_app = 'USER_H5'`
|
||||
动作:`client_app` → `USER_MINI`
|
||||
|
||||
```sql
|
||||
SELECT event_name, client_app, COUNT(*) AS cnt
|
||||
FROM log_user_analytics
|
||||
WHERE event_name IN ('order_submit', 'pay_success')
|
||||
GROUP BY event_name, client_app;
|
||||
|
||||
UPDATE log_user_analytics
|
||||
SET client_app = 'USER_MINI'
|
||||
WHERE event_name IN ('order_submit', 'pay_success')
|
||||
AND client_app = 'USER_H5';
|
||||
```
|
||||
|
||||
回填结果(生产,2026-08-26):
|
||||
|
||||
| 事件 | 回填前 USER_H5 | 回填后 USER_MINI |
|
||||
|------|----------------|------------------|
|
||||
| `order_submit` | 55 | 55 |
|
||||
| `pay_success` | 28 | 28 |
|
||||
|
||||
共更新 **83** 行。回填后这两类事件已无 `USER_H5`。
|
||||
|
||||
不改其它事件名,不改 `common_event`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 关键路径
|
||||
|
||||
| 域 | 路径 |
|
||||
|----|------|
|
||||
| 建单 / 支付埋点 | `server/dukang-api/src/modules/trade/trade.service.ts` |
|
||||
| 建单入参 | `server/dukang-api/src/modules/trade/trade.controller.ts` |
|
||||
| HQ 展示 | `order_submit` → 提交订单;`USER_MINI` → C 端小程序 |
|
||||
|
||||
无 Prisma 迁移。
|
||||
|
||||
---
|
||||
|
||||
## 6. 发版步骤
|
||||
|
||||
| 步骤 | 动作 |
|
||||
|------|------|
|
||||
| 1 | 生产已执行上述 `UPDATE`(历史行) |
|
||||
| 2 | 发 `dukang-api`(新单按 JWT 写端);`--skip-db` |
|
||||
|
||||
---
|
||||
|
||||
## 7. 验收清单
|
||||
|
||||
- [x] 历史 `order_submit` / `pay_success` 在 HQ 不再显示「C 端 H5」(已回填 83 行)
|
||||
- [ ] 小程序提交订单后,HQ 用户日志「提交订单」端为「C 端小程序」(需发 `dukang-api`)
|
||||
- [ ] 小程序支付成功后,「支付成功」端为「C 端小程序」(需发 `dukang-api`)
|
||||
- [ ] `mini-user` H5 预览下单仍为「C 端 H5」
|
||||
- [ ] 浏览类埋点(`home_view` 等)端字段不变
|
||||
@@ -51,8 +51,6 @@ C 端购酒核销 · 门店扫码核销+打款 · 合伙人拓店履约 · WebAd
|
||||
|
||||
**订单大屏(v3.5.12)**:循环 BGM;右上角「播放 BGM」开关(需点击才出声);成交撒花时压低背景音。HQ 日志 / 订单状态流转 / 用户行为时间线展示中文(存储码不变)。删除门店分类后列表不再自动补种默认树(「同步默认分类」才补)。HQ 侧栏:概览→用户→商品→订单→开城→门店→财务→好客权益→配送单→工单→发票,其余运营/工具项随后,**系统设置最后**。
|
||||
|
||||
**用户日志端(v3.5.14)**:`order_submit` / `pay_success` 的 `clientApp` 取 JWT(小程序 `USER_MINI`);微信支付回调沿用该订单已有埋点,缺省小程序。禁止再写死 `USER_H5`。
|
||||
|
||||
## 5. 验收用例(必过)
|
||||
|
||||
**主链路 15 项**:登录、4 SKU、起购、支付+权益、双通道核销、payout、关店不可见、拓店审核、配送完成、退款、T+1/T+30…
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
WECOM_PUSH_CONDITION_LABELS,
|
||||
WECOM_PUSH_DEFAULT_SETTLEMENT_CONDITIONS,
|
||||
WECOM_SETTLEMENT_PUSH_NAME,
|
||||
WECOM_TEMPLATE_PLACEHOLDERS,
|
||||
parseWecomPushConditions,
|
||||
} from './wecom-message-push';
|
||||
|
||||
@@ -34,22 +33,4 @@ describe('wecom-message-push', () => {
|
||||
'finance.store_bill',
|
||||
]);
|
||||
});
|
||||
|
||||
it('支付与核销模板含昵称、明文手机、备注占位符', () => {
|
||||
expect(WECOM_TEMPLATE_PLACEHOLDERS['order.paid']).toEqual(
|
||||
expect.arrayContaining(['userName', 'userPhone', 'userRemark']),
|
||||
);
|
||||
expect(WECOM_TEMPLATE_PLACEHOLDERS['redeem.success']).toEqual(
|
||||
expect.arrayContaining(['userName', 'userPhone', 'userRemark']),
|
||||
);
|
||||
});
|
||||
|
||||
it('结算账单模板含累计金额与明细摘要', () => {
|
||||
expect(WECOM_TEMPLATE_PLACEHOLDERS['finance.store_bill']).toEqual(
|
||||
expect.arrayContaining(['billCount', 'totalAmount', 'billSummary']),
|
||||
);
|
||||
expect(WECOM_TEMPLATE_PLACEHOLDERS['store.withdraw_approved']).toEqual(
|
||||
expect.arrayContaining(['cityName', 'partnerLabel', 'payee', 'accountNo', 'bankBranch']),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -197,9 +197,6 @@ export const WECOM_TEMPLATE_PLACEHOLDERS: Record<WecomTemplateEventKey, string[]
|
||||
'skuSummary',
|
||||
'deliveryLabel',
|
||||
'receiverInfo',
|
||||
'userName',
|
||||
'userPhone',
|
||||
'userRemark',
|
||||
'phoneMasked',
|
||||
'time',
|
||||
'handleUrl',
|
||||
@@ -212,7 +209,6 @@ export const WECOM_TEMPLATE_PLACEHOLDERS: Record<WecomTemplateEventKey, string[]
|
||||
'channel',
|
||||
'userName',
|
||||
'userPhone',
|
||||
'userRemark',
|
||||
'time',
|
||||
'handleUrl',
|
||||
'handleLabel',
|
||||
@@ -259,14 +255,9 @@ export const WECOM_TEMPLATE_PLACEHOLDERS: Record<WecomTemplateEventKey, string[]
|
||||
'storeId',
|
||||
],
|
||||
'store.withdraw_approved': [
|
||||
'cityName',
|
||||
'storeName',
|
||||
'partnerLabel',
|
||||
'withdrawNo',
|
||||
'amount',
|
||||
'payee',
|
||||
'accountNo',
|
||||
'bankBranch',
|
||||
'bankAccount',
|
||||
'time',
|
||||
'handleUrl',
|
||||
@@ -286,7 +277,6 @@ export const WECOM_TEMPLATE_PLACEHOLDERS: Record<WecomTemplateEventKey, string[]
|
||||
'finance.store_bill': [
|
||||
'period',
|
||||
'billCount',
|
||||
'totalAmount',
|
||||
'billSummary',
|
||||
'time',
|
||||
'handleUrl',
|
||||
@@ -295,7 +285,6 @@ export const WECOM_TEMPLATE_PLACEHOLDERS: Record<WecomTemplateEventKey, string[]
|
||||
'finance.partner_bill': [
|
||||
'period',
|
||||
'billCount',
|
||||
'totalAmount',
|
||||
'billSummary',
|
||||
'time',
|
||||
'handleUrl',
|
||||
@@ -303,17 +292,8 @@ export const WECOM_TEMPLATE_PLACEHOLDERS: Record<WecomTemplateEventKey, string[]
|
||||
],
|
||||
'finance.winery_bill': [
|
||||
'period',
|
||||
'billCount',
|
||||
'totalAmount',
|
||||
'cityNames',
|
||||
'partnerNames',
|
||||
'orderCount',
|
||||
'orderAmount',
|
||||
'rate',
|
||||
'amount',
|
||||
'payee',
|
||||
'accountNo',
|
||||
'bankBranch',
|
||||
'orderCount',
|
||||
'bankAccount',
|
||||
'time',
|
||||
'handleUrl',
|
||||
@@ -322,7 +302,6 @@ export const WECOM_TEMPLATE_PLACEHOLDERS: Record<WecomTemplateEventKey, string[]
|
||||
'finance.logistics_bill': [
|
||||
'period',
|
||||
'billCount',
|
||||
'totalAmount',
|
||||
'billSummary',
|
||||
'time',
|
||||
'handleUrl',
|
||||
|
||||
@@ -208,8 +208,8 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
this.logger.log(`seeded wecom push template: ${def.eventKey}`);
|
||||
continue;
|
||||
}
|
||||
const legacyBodies = WECOM_PUSH_TEMPLATE_LEGACY_BODIES[def.eventKey] ?? [];
|
||||
if (legacyBodies.includes(existing.body)) {
|
||||
const legacy = WECOM_PUSH_TEMPLATE_LEGACY_BODIES[def.eventKey];
|
||||
if (legacy && existing.body === legacy) {
|
||||
await this.prisma.wecomPushTemplate.update({
|
||||
where: { eventKey: def.eventKey },
|
||||
data: {
|
||||
@@ -473,10 +473,7 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
skuSummary: '杜康原浆 52度 瓶×2',
|
||||
deliveryLabel: '同城',
|
||||
receiverInfo: '张三 13800008000 河南省郑州市金水区示例路1号',
|
||||
userName: '好客用户',
|
||||
userPhone: '13800008000',
|
||||
userRemark: '大客户',
|
||||
phoneMasked: '13800008000',
|
||||
phoneMasked: '138****8000',
|
||||
},
|
||||
handlePath: '/orders?orderNo=DK202608200001',
|
||||
},
|
||||
@@ -487,8 +484,7 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
storeName: '示例门店',
|
||||
channel: '扫码',
|
||||
userName: '好客用户',
|
||||
userPhone: '13800008000',
|
||||
userRemark: '大客户',
|
||||
userPhone: '138****8000',
|
||||
},
|
||||
handlePath: '/redeem-records?redeemNo=RD202608200001',
|
||||
},
|
||||
@@ -535,14 +531,9 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
},
|
||||
'store.withdraw_approved': {
|
||||
vars: {
|
||||
cityName: '郑州',
|
||||
storeName: '示例门店',
|
||||
partnerLabel: '郑州某某商贸-张三',
|
||||
withdrawNo: 'SW202608200001',
|
||||
amount: '500.00',
|
||||
payee: '张三',
|
||||
accountNo: '6222000011112222',
|
||||
bankBranch: '郑州支行',
|
||||
bankAccount: '张三 / 郑州支行 / 6222000011112222',
|
||||
storeId: '1',
|
||||
},
|
||||
@@ -562,19 +553,7 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
vars: {
|
||||
period: '2026-08-25',
|
||||
billCount: '2',
|
||||
totalAmount: '500.00',
|
||||
billSummary: [
|
||||
'城市:郑州',
|
||||
'门店:示例门店',
|
||||
'合伙人:郑州某某商贸-张三',
|
||||
'核销笔数:12',
|
||||
'核销金额:¥500.00',
|
||||
'结算比例:60%',
|
||||
'账单:¥300.00',
|
||||
'收款人:张三',
|
||||
'收款账户:6222000011112222',
|
||||
'收款开户行:郑州支行',
|
||||
].join('\n'),
|
||||
billSummary: '示例门店 ¥300.00 张三 / 郑州支行 / 6222000011112222',
|
||||
},
|
||||
handlePath: '/finance/store-bills?kind=T1_BILL',
|
||||
},
|
||||
@@ -582,36 +561,15 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
vars: {
|
||||
period: '2026-07',
|
||||
billCount: '1',
|
||||
totalAmount: '120.00',
|
||||
billSummary: [
|
||||
'城市:郑州',
|
||||
'合伙人:郑州某某商贸-张三',
|
||||
'订单笔数:8',
|
||||
'核销笔数:12',
|
||||
'订单佣金:¥100.00',
|
||||
'核销佣金:¥20.00',
|
||||
'账单:¥120.00',
|
||||
'收款人:张三',
|
||||
'收款账户:6222000011112222',
|
||||
'收款开户行:郑州支行',
|
||||
].join('\n'),
|
||||
billSummary: '郑州某某商贸 ¥120.00 张三 / 郑州支行 / 6222000011112222',
|
||||
},
|
||||
handlePath: '/finance/partner-bills',
|
||||
},
|
||||
'finance.winery_bill': {
|
||||
vars: {
|
||||
period: '2026-08-23',
|
||||
billCount: '1',
|
||||
totalAmount: '1990.00',
|
||||
cityNames: '郑州、洛阳',
|
||||
partnerNames: '郑州某某商贸-张三',
|
||||
amount: '1990.00',
|
||||
orderCount: '8',
|
||||
orderAmount: '6633.33',
|
||||
rate: '30%',
|
||||
payee: '杜康酒厂',
|
||||
accountNo: '6222000099998888',
|
||||
bankBranch: '洛阳支行',
|
||||
bankAccount: '杜康酒厂 / 洛阳支行 / 6222000099998888',
|
||||
},
|
||||
handlePath: '/finance/winery-bills',
|
||||
@@ -620,19 +578,7 @@ export class WecomMessagePushService implements OnModuleInit {
|
||||
vars: {
|
||||
period: '2026-07',
|
||||
billCount: '1',
|
||||
totalAmount: '86.00',
|
||||
billSummary: [
|
||||
'城市:郑州',
|
||||
'合伙人:郑州某某商贸-张三',
|
||||
'承运商:小飞侠',
|
||||
'结算方式:充值扣款',
|
||||
'发货笔数:10',
|
||||
'瓶数:60',
|
||||
'账单:¥86.00',
|
||||
'收款人:承运商',
|
||||
'收款账户:6222000033334444',
|
||||
'收款开户行:郑州支行',
|
||||
].join('\n'),
|
||||
billSummary: '小飞侠 ¥86.00 承运商 / 郑州支行 / 6222000033334444',
|
||||
},
|
||||
handlePath: '/finance/logistics-bills',
|
||||
},
|
||||
|
||||
@@ -20,8 +20,7 @@ export const WECOM_PUSH_TEMPLATE_DEFAULTS: WecomTemplateDefault[] = [
|
||||
'商品:{{skuSummary}}',
|
||||
'物流:{{deliveryLabel}}',
|
||||
'收货:{{receiverInfo}}',
|
||||
'用户:{{userName}} {{userPhone}}',
|
||||
'备注:{{userRemark}}',
|
||||
'用户:{{phoneMasked}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
@@ -37,7 +36,6 @@ export const WECOM_PUSH_TEMPLATE_DEFAULTS: WecomTemplateDefault[] = [
|
||||
'门店:{{storeName}}',
|
||||
'渠道:{{channel}}',
|
||||
'用户:{{userName}} {{userPhone}}',
|
||||
'备注:{{userRemark}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
@@ -104,14 +102,10 @@ export const WECOM_PUSH_TEMPLATE_DEFAULTS: WecomTemplateDefault[] = [
|
||||
title: '门店提现已通过',
|
||||
body: [
|
||||
'**门店提现已通过**',
|
||||
'城市:{{cityName}}',
|
||||
'门店:{{storeName}}',
|
||||
'合伙人:{{partnerLabel}}',
|
||||
'提现单号:{{withdrawNo}}',
|
||||
'账单:¥{{amount}}',
|
||||
'收款人:{{payee}}',
|
||||
'收款账户:{{accountNo}}',
|
||||
'收款开户行:{{bankBranch}}',
|
||||
'金额:¥{{amount}}',
|
||||
'收款账户:{{bankAccount}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
@@ -138,12 +132,9 @@ export const WECOM_PUSH_TEMPLATE_DEFAULTS: WecomTemplateDefault[] = [
|
||||
body: [
|
||||
'**门店日账单已生成**',
|
||||
'账期:{{period}}',
|
||||
'账单笔数:{{billCount}}',
|
||||
'累计金额:¥{{totalAmount}}',
|
||||
'时间:{{time}}',
|
||||
'',
|
||||
'笔数:{{billCount}}',
|
||||
'{{billSummary}}',
|
||||
'',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
@@ -154,12 +145,9 @@ export const WECOM_PUSH_TEMPLATE_DEFAULTS: WecomTemplateDefault[] = [
|
||||
body: [
|
||||
'**合伙人月账单已生成**',
|
||||
'账期:{{period}}',
|
||||
'账单笔数:{{billCount}}',
|
||||
'累计金额:¥{{totalAmount}}',
|
||||
'时间:{{time}}',
|
||||
'',
|
||||
'笔数:{{billCount}}',
|
||||
'{{billSummary}}',
|
||||
'',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
@@ -170,20 +158,10 @@ export const WECOM_PUSH_TEMPLATE_DEFAULTS: WecomTemplateDefault[] = [
|
||||
body: [
|
||||
'**酒厂日账单已生成**',
|
||||
'账期:{{period}}',
|
||||
'账单笔数:{{billCount}}',
|
||||
'累计金额:¥{{totalAmount}}',
|
||||
'订单数:{{orderCount}}',
|
||||
'金额:¥{{amount}}',
|
||||
'收款账户:{{bankAccount}}',
|
||||
'时间:{{time}}',
|
||||
'',
|
||||
'城市:{{cityNames}}',
|
||||
'合伙人:{{partnerNames}}',
|
||||
'订单笔数:{{orderCount}}',
|
||||
'订单金额:¥{{orderAmount}}',
|
||||
'结算比例:{{rate}}',
|
||||
'账单:¥{{amount}}',
|
||||
'收款人:{{payee}}',
|
||||
'收款账户:{{accountNo}}',
|
||||
'收款开户行:{{bankBranch}}',
|
||||
'',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
@@ -194,149 +172,62 @@ export const WECOM_PUSH_TEMPLATE_DEFAULTS: WecomTemplateDefault[] = [
|
||||
body: [
|
||||
'**物流月对账已生成**',
|
||||
'账期:{{period}}',
|
||||
'账单笔数:{{billCount}}',
|
||||
'累计金额:¥{{totalAmount}}',
|
||||
'时间:{{time}}',
|
||||
'',
|
||||
'笔数:{{billCount}}',
|
||||
'{{billSummary}}',
|
||||
'',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
handleLabel: '去处理',
|
||||
},
|
||||
];
|
||||
|
||||
/** 上一版默认正文;ensureTemplates 仅在库中仍为此文案时升级,不覆盖 HQ 手改 */
|
||||
export const WECOM_PUSH_TEMPLATE_LEGACY_BODIES: Partial<Record<WecomTemplateEventKey, string[]>> = {
|
||||
/** v3.5.3 默认正文;ensureTemplates 仅在库中仍为此文案时升级,不覆盖 HQ 手改 */
|
||||
export const WECOM_PUSH_TEMPLATE_LEGACY_BODIES: Partial<Record<WecomTemplateEventKey, string>> = {
|
||||
'order.paid': [
|
||||
[
|
||||
'**订单支付成功**',
|
||||
'订单号:{{orderNo}}',
|
||||
'实付:¥{{payAmount}}',
|
||||
'城市:{{cityName}}',
|
||||
'商品:{{skuSummary}}',
|
||||
'用户:{{phoneMasked}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
[
|
||||
'**订单支付成功**',
|
||||
'订单号:{{orderNo}}',
|
||||
'实付:¥{{payAmount}}',
|
||||
'城市:{{cityName}}',
|
||||
'商品:{{skuSummary}}',
|
||||
'物流:{{deliveryLabel}}',
|
||||
'收货:{{receiverInfo}}',
|
||||
'用户:{{phoneMasked}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
],
|
||||
'**订单支付成功**',
|
||||
'订单号:{{orderNo}}',
|
||||
'实付:¥{{payAmount}}',
|
||||
'城市:{{cityName}}',
|
||||
'商品:{{skuSummary}}',
|
||||
'用户:{{phoneMasked}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
'redeem.success': [
|
||||
[
|
||||
'**门店核销成功**',
|
||||
'核销单号:{{redeemNo}}',
|
||||
'金额:¥{{amount}}',
|
||||
'门店:{{storeName}}',
|
||||
'渠道:{{channel}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
[
|
||||
'**门店核销成功**',
|
||||
'核销单号:{{redeemNo}}',
|
||||
'金额:¥{{amount}}',
|
||||
'门店:{{storeName}}',
|
||||
'渠道:{{channel}}',
|
||||
'用户:{{userName}} {{userPhone}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
],
|
||||
'**门店核销成功**',
|
||||
'核销单号:{{redeemNo}}',
|
||||
'金额:¥{{amount}}',
|
||||
'门店:{{storeName}}',
|
||||
'渠道:{{channel}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
'store.audit_pending': [
|
||||
[
|
||||
'**门店审核待处理 · {{action}}**',
|
||||
'门店:{{storeName}}',
|
||||
'城市:{{cityName}}',
|
||||
'合伙人:{{partnerLabel}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
],
|
||||
'**门店审核待处理 · {{action}}**',
|
||||
'门店:{{storeName}}',
|
||||
'城市:{{cityName}}',
|
||||
'合伙人:{{partnerLabel}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
'store.package_audit_pending': [
|
||||
[
|
||||
'**套餐变更待审核**',
|
||||
'门店:{{storeName}}',
|
||||
'城市:{{cityName}}',
|
||||
'提交端:{{submitter}}',
|
||||
'套餐条数:{{packageCount}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
],
|
||||
'**套餐变更待审核**',
|
||||
'门店:{{storeName}}',
|
||||
'城市:{{cityName}}',
|
||||
'提交端:{{submitter}}',
|
||||
'套餐条数:{{packageCount}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
'store.info_change_pending': [
|
||||
[
|
||||
'**门店信息变更待审**',
|
||||
'门店:{{storeName}}',
|
||||
'城市:{{cityName}}',
|
||||
'提交端:{{submitter}}',
|
||||
'变更字段:{{changedFields}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
],
|
||||
'store.withdraw_approved': [
|
||||
[
|
||||
'**门店提现已通过**',
|
||||
'门店:{{storeName}}',
|
||||
'提现单号:{{withdrawNo}}',
|
||||
'金额:¥{{amount}}',
|
||||
'收款账户:{{bankAccount}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
],
|
||||
'finance.store_bill': [
|
||||
[
|
||||
'**门店日账单已生成**',
|
||||
'账期:{{period}}',
|
||||
'笔数:{{billCount}}',
|
||||
'{{billSummary}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
],
|
||||
'finance.partner_bill': [
|
||||
[
|
||||
'**合伙人月账单已生成**',
|
||||
'账期:{{period}}',
|
||||
'笔数:{{billCount}}',
|
||||
'{{billSummary}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
],
|
||||
'finance.winery_bill': [
|
||||
[
|
||||
'**酒厂日账单已生成**',
|
||||
'账期:{{period}}',
|
||||
'订单数:{{orderCount}}',
|
||||
'金额:¥{{amount}}',
|
||||
'收款账户:{{bankAccount}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
],
|
||||
'finance.logistics_bill': [
|
||||
[
|
||||
'**物流月对账已生成**',
|
||||
'账期:{{period}}',
|
||||
'笔数:{{billCount}}',
|
||||
'{{billSummary}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
],
|
||||
'**门店信息变更待审**',
|
||||
'门店:{{storeName}}',
|
||||
'城市:{{cityName}}',
|
||||
'提交端:{{submitter}}',
|
||||
'变更字段:{{changedFields}}',
|
||||
'时间:{{time}}',
|
||||
'[{{handleLabel}}]({{handleUrl}})',
|
||||
].join('\n'),
|
||||
};
|
||||
|
||||
export function getDefaultTemplate(eventKey: string): WecomTemplateDefault | undefined {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
calcRedeemSettleAmount,
|
||||
generateRedeemNo,
|
||||
generateRedeemPendingNo,
|
||||
maskContactPhone,
|
||||
REDEEM_WEAKNET_FAIL_THRESHOLD,
|
||||
resolveSettlementRate,
|
||||
validateRedeemAmount,
|
||||
@@ -196,7 +197,7 @@ export class RedeemService {
|
||||
const [userRow, storeRow] = await Promise.all([
|
||||
this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { isTest: true, nickname: true, phone: true, hqRemark: true },
|
||||
select: { isTest: true, nickname: true, phone: true },
|
||||
}),
|
||||
this.prisma.store.findUnique({
|
||||
where: { id: account.storeId },
|
||||
@@ -294,8 +295,7 @@ export class RedeemService {
|
||||
storeName: account.store.name || String(account.storeId),
|
||||
channel: channelLabel,
|
||||
userName: userRow?.nickname?.trim() || '—',
|
||||
userPhone: (userRow?.phone || '').trim() || '—',
|
||||
userRemark: (userRow?.hqRemark || '').trim() || '—',
|
||||
userPhone: maskContactPhone(userRow?.phone),
|
||||
},
|
||||
{
|
||||
handlePath: `/redeem-records?redeemNo=${encodeURIComponent(record.redeemNo)}`,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Prisma } from '@prisma/client';
|
||||
import {
|
||||
DEFAULT_STORE_WITHDRAW_DAILY_LIMIT,
|
||||
DEFAULT_XFX_LOGISTICS_PRICING,
|
||||
LOGISTICS_SETTLEMENT_METHOD_LABELS,
|
||||
WINERY_SETTLEMENT_LAG_DAYS,
|
||||
WINERY_SETTLEMENT_RATE,
|
||||
} from '@dukang/shared-types';
|
||||
@@ -29,17 +28,6 @@ import {
|
||||
loadWineryBankConfig,
|
||||
formatWecomBankAccount,
|
||||
} from '../../common/store/store-bank.util';
|
||||
import {
|
||||
capBillBlocks,
|
||||
formatLogisticsBillWecomBlock,
|
||||
formatPartnerBillWecomBlock,
|
||||
formatPartnerDashLabel,
|
||||
formatPercent,
|
||||
formatStoreBillWecomBlock,
|
||||
joinLimited,
|
||||
wecomBankParts,
|
||||
type WecomBankLike,
|
||||
} from './wecom-bill-digest';
|
||||
|
||||
function generateBillNo(prefix: string) {
|
||||
return `${prefix}${Date.now()}${Math.floor(Math.random() * 900 + 100)}`;
|
||||
@@ -75,37 +63,12 @@ function round2(n: number) {
|
||||
return Math.round(n * 100) / 100;
|
||||
}
|
||||
|
||||
function sumAmounts(rows: Array<{ amount: number }>): string {
|
||||
return rows.reduce((s, r) => s + Number(r.amount || 0), 0).toFixed(2);
|
||||
}
|
||||
const BILL_DIGEST_MAX_LINES = 15;
|
||||
|
||||
function storeWecomMeta(
|
||||
store:
|
||||
| {
|
||||
name?: string | null;
|
||||
cityName?: string | null;
|
||||
cityRef?: { name: string } | null;
|
||||
partnerAccount?: {
|
||||
name: string | null;
|
||||
companyName: string | null;
|
||||
parent?: { companyName: string | null } | null;
|
||||
} | null;
|
||||
}
|
||||
| null
|
||||
| undefined,
|
||||
fallbackName: string,
|
||||
) {
|
||||
const cityName = store?.cityRef?.name?.trim() || store?.cityName?.trim() || '—';
|
||||
const acc = store?.partnerAccount;
|
||||
const partnerLabel = formatPartnerDashLabel(
|
||||
acc?.companyName || acc?.parent?.companyName,
|
||||
acc?.name,
|
||||
);
|
||||
return {
|
||||
storeName: store?.name?.trim() || fallbackName,
|
||||
cityName,
|
||||
partnerLabel,
|
||||
};
|
||||
function capBillSummary(lines: string[]): string {
|
||||
if (!lines.length) return '无';
|
||||
if (lines.length <= BILL_DIGEST_MAX_LINES) return lines.join('\n');
|
||||
return `${lines.slice(0, BILL_DIGEST_MAX_LINES).join('\n')}\n…等共 ${lines.length} 笔`;
|
||||
}
|
||||
|
||||
function getStoreWithdrawDailyLimit(): number {
|
||||
@@ -144,25 +107,24 @@ export class SettlementService {
|
||||
private notifyPartnerBillDigest(
|
||||
period: string,
|
||||
rows: Array<{
|
||||
cityName: string;
|
||||
partnerLabel: string;
|
||||
orderCount?: number;
|
||||
redeemCount?: number;
|
||||
orderCommission: number;
|
||||
redeemCommission: number;
|
||||
label: string;
|
||||
amount: number;
|
||||
bank?: WecomBankLike | null;
|
||||
bankAccountName?: string | null;
|
||||
bankBranch?: string | null;
|
||||
bankAccountNo?: string | null;
|
||||
}>,
|
||||
) {
|
||||
if (!rows.length) return;
|
||||
const blocks = rows.map((r) => formatPartnerBillWecomBlock(r));
|
||||
const lines = rows.map(
|
||||
(r) =>
|
||||
`${r.label} ¥${Number(r.amount).toFixed(2)} ${formatWecomBankAccount(r)}`,
|
||||
);
|
||||
void this.wecomPush.dispatchEvent(
|
||||
'finance.partner_bill',
|
||||
{
|
||||
period,
|
||||
billCount: String(rows.length),
|
||||
totalAmount: sumAmounts(rows),
|
||||
billSummary: capBillBlocks(blocks),
|
||||
billSummary: capBillSummary(lines),
|
||||
},
|
||||
{ handlePath: '/finance/partner-bills' },
|
||||
);
|
||||
@@ -171,25 +133,25 @@ export class SettlementService {
|
||||
private notifyLogisticsBillDigest(
|
||||
period: string,
|
||||
rows: Array<{
|
||||
cityNames: string;
|
||||
partnerNames: string;
|
||||
providerName: string;
|
||||
settlementMethod: string;
|
||||
orderCount: number;
|
||||
bottleCount: number;
|
||||
label: string;
|
||||
amount: number;
|
||||
bank?: WecomBankLike | null;
|
||||
bankAccountName?: string | null;
|
||||
bankName?: string | null;
|
||||
bankBranch?: string | null;
|
||||
bankAccountNo?: string | null;
|
||||
}>,
|
||||
) {
|
||||
if (!rows.length) return;
|
||||
const blocks = rows.map((r) => formatLogisticsBillWecomBlock(r));
|
||||
const lines = rows.map(
|
||||
(r) =>
|
||||
`${r.label} ¥${Number(r.amount).toFixed(2)} ${formatWecomBankAccount(r)}`,
|
||||
);
|
||||
void this.wecomPush.dispatchEvent(
|
||||
'finance.logistics_bill',
|
||||
{
|
||||
period,
|
||||
billCount: String(rows.length),
|
||||
totalAmount: sumAmounts(rows),
|
||||
billSummary: capBillBlocks(blocks),
|
||||
billSummary: capBillSummary(lines),
|
||||
},
|
||||
{ handlePath: '/finance/logistics-bills' },
|
||||
);
|
||||
@@ -687,34 +649,16 @@ export class SettlementService {
|
||||
const [store, bank] = await Promise.all([
|
||||
this.prisma.store.findUnique({
|
||||
where: { id: row.storeId },
|
||||
select: {
|
||||
name: true,
|
||||
cityName: true,
|
||||
cityRef: { select: { name: true } },
|
||||
partnerAccount: {
|
||||
select: {
|
||||
name: true,
|
||||
companyName: true,
|
||||
parent: { select: { companyName: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
select: { name: true },
|
||||
}),
|
||||
loadStorePrimaryBank(this.prisma, row.storeId),
|
||||
]);
|
||||
const meta = storeWecomMeta(store, String(row.storeId));
|
||||
const bankParts = wecomBankParts(bank);
|
||||
void this.wecomPush.dispatchEvent(
|
||||
'store.withdraw_approved',
|
||||
{
|
||||
cityName: meta.cityName,
|
||||
storeName: meta.storeName,
|
||||
partnerLabel: meta.partnerLabel,
|
||||
storeName: store?.name || String(row.storeId),
|
||||
withdrawNo: row.withdrawNo,
|
||||
amount: Number(row.amount).toFixed(2),
|
||||
payee: bankParts.payee,
|
||||
accountNo: bankParts.accountNo,
|
||||
bankBranch: bankParts.bankBranch,
|
||||
bankAccount: formatWecomBankAccount(bank),
|
||||
storeId: row.storeId.toString(),
|
||||
},
|
||||
@@ -986,24 +930,7 @@ export class SettlementService {
|
||||
withdrawItem: { is: null },
|
||||
status: 'PENDING',
|
||||
},
|
||||
include: {
|
||||
store: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
cityName: true,
|
||||
settlementRate: true,
|
||||
cityRef: { select: { name: true } },
|
||||
partnerAccount: {
|
||||
select: {
|
||||
name: true,
|
||||
companyName: true,
|
||||
parent: { select: { companyName: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
include: { store: { select: { id: true, name: true, settlementRate: true } } },
|
||||
});
|
||||
|
||||
const byStore = new Map<string, typeof payouts>();
|
||||
@@ -1016,19 +943,10 @@ export class SettlementService {
|
||||
|
||||
let created = 0;
|
||||
let skipped = 0;
|
||||
const createdBills: Array<{
|
||||
storeId: bigint;
|
||||
storeName: string;
|
||||
cityName: string;
|
||||
partnerLabel: string;
|
||||
redeemCount: number;
|
||||
redeemAmount: number;
|
||||
settlementRate: number;
|
||||
amount: number;
|
||||
}> = [];
|
||||
const createdBills: Array<{ storeId: bigint; storeName: string; amount: number }> = [];
|
||||
for (const [storeIdStr, list] of byStore) {
|
||||
const storeId = BigInt(storeIdStr);
|
||||
const meta = storeWecomMeta(list[0]?.store, storeIdStr);
|
||||
const storeName = list[0]?.store?.name || storeIdStr;
|
||||
const existing = await this.prisma.storeBill.findUnique({
|
||||
where: { storeId_billDate: { storeId, billDate } },
|
||||
});
|
||||
@@ -1057,14 +975,7 @@ export class SettlementService {
|
||||
});
|
||||
});
|
||||
created += 1;
|
||||
createdBills.push({
|
||||
storeId,
|
||||
...meta,
|
||||
redeemCount: existing.redeemCount + list.length,
|
||||
redeemAmount: round2(Number(existing.redeemAmount) + redeemAmount),
|
||||
settlementRate: rate,
|
||||
amount: updatedTotal,
|
||||
});
|
||||
createdBills.push({ storeId, storeName, amount: updatedTotal });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1090,14 +1001,7 @@ export class SettlementService {
|
||||
});
|
||||
});
|
||||
created += 1;
|
||||
createdBills.push({
|
||||
storeId,
|
||||
...meta,
|
||||
redeemCount: list.length,
|
||||
redeemAmount,
|
||||
settlementRate: rate,
|
||||
amount: payoutAmount,
|
||||
});
|
||||
createdBills.push({ storeId, storeName, amount: payoutAmount });
|
||||
}
|
||||
|
||||
const period = billDate.toISOString().slice(0, 10);
|
||||
@@ -1106,19 +1010,16 @@ export class SettlementService {
|
||||
this.prisma,
|
||||
createdBills.map((b) => b.storeId),
|
||||
);
|
||||
const blocks = createdBills.map((b) =>
|
||||
formatStoreBillWecomBlock({
|
||||
...b,
|
||||
bank: banks.get(b.storeId.toString()) ?? null,
|
||||
}),
|
||||
const lines = createdBills.map(
|
||||
(b) =>
|
||||
`${b.storeName} ¥${b.amount.toFixed(2)} ${formatWecomBankAccount(banks.get(b.storeId.toString()) ?? null)}`,
|
||||
);
|
||||
void this.wecomPush.dispatchEvent(
|
||||
'finance.store_bill',
|
||||
{
|
||||
period,
|
||||
billCount: String(createdBills.length),
|
||||
totalAmount: sumAmounts(createdBills),
|
||||
billSummary: capBillBlocks(blocks),
|
||||
billSummary: capBillSummary(lines),
|
||||
},
|
||||
{ handlePath: '/finance/store-bills?kind=T1_BILL' },
|
||||
);
|
||||
@@ -1575,19 +1476,15 @@ export class SettlementService {
|
||||
bankAccountName: true,
|
||||
bankBranch: true,
|
||||
bankAccountNo: true,
|
||||
city: { select: { name: true } },
|
||||
},
|
||||
});
|
||||
const results: Array<{ partnerId: string; companyName: string; ok: boolean; message?: string }> = [];
|
||||
const created: Array<{
|
||||
cityName: string;
|
||||
partnerLabel: string;
|
||||
orderCount?: number;
|
||||
redeemCount?: number;
|
||||
orderCommission: number;
|
||||
redeemCommission: number;
|
||||
label: string;
|
||||
amount: number;
|
||||
bank?: WecomBankLike | null;
|
||||
bankAccountName: string | null;
|
||||
bankBranch: string | null;
|
||||
bankAccountNo: string | null;
|
||||
}> = [];
|
||||
for (const p of partners) {
|
||||
try {
|
||||
@@ -1600,19 +1497,14 @@ export class SettlementService {
|
||||
{ notify: false },
|
||||
);
|
||||
results.push({ partnerId: p.id.toString(), companyName: p.companyName ?? '', ok: true });
|
||||
const label =
|
||||
[p.companyName?.trim(), p.name?.trim()].filter(Boolean).join(' · ') || p.id.toString();
|
||||
created.push({
|
||||
cityName: p.city?.name?.trim() || '—',
|
||||
partnerLabel: formatPartnerDashLabel(p.companyName, p.name),
|
||||
orderCount: Number(bill.orderCount ?? 0),
|
||||
redeemCount: Number(bill.redeemCount ?? 0),
|
||||
orderCommission: Number(bill.orderCommission),
|
||||
redeemCommission: Number(bill.redeemCommission),
|
||||
label,
|
||||
amount: Number(bill.totalAmount),
|
||||
bank: {
|
||||
bankAccountName: p.bankAccountName,
|
||||
bankBranch: p.bankBranch,
|
||||
bankAccountNo: p.bankAccountNo,
|
||||
},
|
||||
bankAccountName: p.bankAccountName,
|
||||
bankBranch: p.bankBranch,
|
||||
bankAccountNo: p.bankAccountNo,
|
||||
});
|
||||
} catch (e) {
|
||||
results.push({
|
||||
@@ -1736,37 +1628,24 @@ export class SettlementService {
|
||||
});
|
||||
|
||||
if (opts?.notify !== false) {
|
||||
let cityName = '—';
|
||||
if (primary.cityId) {
|
||||
const city = await this.prisma.commonCity.findUnique({
|
||||
where: { id: primary.cityId },
|
||||
select: { name: true },
|
||||
});
|
||||
cityName = city?.name?.trim() || '—';
|
||||
}
|
||||
this.notifyPartnerBillDigest(`${body.year}-${String(body.month).padStart(2, '0')}`, [
|
||||
{
|
||||
cityName,
|
||||
partnerLabel: formatPartnerDashLabel(primary.companyName, primary.name),
|
||||
orderCount: orders.length,
|
||||
redeemCount: redeems.length,
|
||||
orderCommission: round2(orderCommission),
|
||||
redeemCommission: round2(redeemCommission),
|
||||
amount: totalAmount,
|
||||
bank: {
|
||||
const label =
|
||||
[primary.companyName?.trim(), primary.name?.trim()].filter(Boolean).join(' · ') ||
|
||||
primary.id.toString();
|
||||
this.notifyPartnerBillDigest(
|
||||
`${body.year}-${String(body.month).padStart(2, '0')}`,
|
||||
[
|
||||
{
|
||||
label,
|
||||
amount: totalAmount,
|
||||
bankAccountName: primary.bankAccountName,
|
||||
bankBranch: primary.bankBranch,
|
||||
bankAccountNo: primary.bankAccountNo,
|
||||
},
|
||||
},
|
||||
]);
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return serializeBigInt({
|
||||
...bill,
|
||||
orderCount: orders.length,
|
||||
redeemCount: redeems.length,
|
||||
});
|
||||
return serializeBigInt(bill);
|
||||
}
|
||||
|
||||
/** HQ:发送给合伙人(待审核 → 待合伙人确认) */
|
||||
@@ -2001,7 +1880,6 @@ export class SettlementService {
|
||||
deliveryType: { in: ['LOCAL', 'CROSS_CITY'] },
|
||||
completedAt: { gte: start, lt: end },
|
||||
},
|
||||
include: { city: { select: { name: true } } },
|
||||
orderBy: { completedAt: 'asc' },
|
||||
});
|
||||
|
||||
@@ -2059,27 +1937,12 @@ export class SettlementService {
|
||||
|
||||
const period = billDate.toISOString().slice(0, 10);
|
||||
const wineryBank = await loadWineryBankConfig(this.prisma);
|
||||
const bankParts = wecomBankParts(wineryBank);
|
||||
const cityNames = joinLimited(
|
||||
orders.map((o) => o.city?.name || ''),
|
||||
'城',
|
||||
);
|
||||
const partnerNames = await this.partnerDashLabels(orders.map((o) => o.partnerAccountIdAtPay));
|
||||
void this.wecomPush.dispatchEvent(
|
||||
'finance.winery_bill',
|
||||
{
|
||||
period,
|
||||
billCount: '1',
|
||||
totalAmount: wineryAmount.toFixed(2),
|
||||
cityNames,
|
||||
partnerNames,
|
||||
orderCount: String(orders.length),
|
||||
orderAmount: orderAmount.toFixed(2),
|
||||
rate: formatPercent(rate),
|
||||
amount: wineryAmount.toFixed(2),
|
||||
payee: bankParts.payee,
|
||||
accountNo: bankParts.accountNo,
|
||||
bankBranch: bankParts.bankBranch,
|
||||
orderCount: String(orders.length),
|
||||
bankAccount: formatWecomBankAccount(wineryBank),
|
||||
},
|
||||
{ handlePath: '/finance/winery-bills' },
|
||||
@@ -2308,14 +2171,12 @@ export class SettlementService {
|
||||
billId?: string;
|
||||
}> = [];
|
||||
const created: Array<{
|
||||
cityNames: string;
|
||||
partnerNames: string;
|
||||
providerName: string;
|
||||
settlementMethod: string;
|
||||
orderCount: number;
|
||||
bottleCount: number;
|
||||
label: string;
|
||||
amount: number;
|
||||
bank?: WecomBankLike | null;
|
||||
bankAccountName: string | null;
|
||||
bankName: string | null;
|
||||
bankBranch: string | null;
|
||||
bankAccountNo: string | null;
|
||||
}> = [];
|
||||
|
||||
for (const p of providers) {
|
||||
@@ -2342,8 +2203,15 @@ export class SettlementService {
|
||||
: String(bill.bill.id)
|
||||
: undefined,
|
||||
});
|
||||
if (!bill.skipped && bill.wecom) {
|
||||
created.push(bill.wecom);
|
||||
if (!bill.skipped && bill.bill) {
|
||||
created.push({
|
||||
label: p.name,
|
||||
amount: Number(bill.bill.logisticsAmount ?? 0),
|
||||
bankAccountName: p.bankAccountName,
|
||||
bankName: p.bankName,
|
||||
bankBranch: p.bankBranch,
|
||||
bankAccountNo: p.bankAccountNo,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
results.push({
|
||||
@@ -2369,20 +2237,6 @@ export class SettlementService {
|
||||
};
|
||||
}
|
||||
|
||||
private async partnerDashLabels(ids: Array<bigint | null | undefined>, max = 5): Promise<string> {
|
||||
const uniq = [...new Set(ids.filter((id): id is bigint => id != null))];
|
||||
if (!uniq.length) return '—';
|
||||
const partners = await this.prisma.partnerAccount.findMany({
|
||||
where: { id: { in: uniq } },
|
||||
select: { name: true, companyName: true },
|
||||
});
|
||||
return joinLimited(
|
||||
partners.map((p) => formatPartnerDashLabel(p.companyName, p.name)),
|
||||
'家',
|
||||
max,
|
||||
);
|
||||
}
|
||||
|
||||
async generateLogisticsBill(
|
||||
body: { providerId: string; year: number; month: number },
|
||||
opts?: { notify?: boolean },
|
||||
@@ -2437,8 +2291,6 @@ export class SettlementService {
|
||||
quantity: true,
|
||||
deliveryType: true,
|
||||
payStatus: true,
|
||||
partnerAccountIdAtPay: true,
|
||||
city: { select: { name: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2540,38 +2392,25 @@ export class SettlementService {
|
||||
return header;
|
||||
});
|
||||
|
||||
const wecom = {
|
||||
cityNames: joinLimited(
|
||||
eligible.map((d) => d.order.city?.name || ''),
|
||||
'城',
|
||||
),
|
||||
partnerNames: await this.partnerDashLabels(eligible.map((d) => d.order.partnerAccountIdAtPay)),
|
||||
providerName: provider.name,
|
||||
settlementMethod:
|
||||
LOGISTICS_SETTLEMENT_METHOD_LABELS[
|
||||
provider.settlementMethod as keyof typeof LOGISTICS_SETTLEMENT_METHOD_LABELS
|
||||
] ?? String(provider.settlementMethod),
|
||||
orderCount,
|
||||
bottleCount,
|
||||
amount: Number(bill.logisticsAmount),
|
||||
bank: {
|
||||
bankAccountName: provider.bankAccountName,
|
||||
bankName: provider.bankName,
|
||||
bankBranch: provider.bankBranch,
|
||||
bankAccountNo: provider.bankAccountNo,
|
||||
},
|
||||
};
|
||||
|
||||
if (opts?.notify !== false) {
|
||||
this.notifyLogisticsBillDigest(`${body.year}-${String(body.month).padStart(2, '0')}`, [
|
||||
wecom,
|
||||
]);
|
||||
this.notifyLogisticsBillDigest(
|
||||
`${body.year}-${String(body.month).padStart(2, '0')}`,
|
||||
[
|
||||
{
|
||||
label: provider.name,
|
||||
amount: Number(bill.logisticsAmount),
|
||||
bankAccountName: provider.bankAccountName,
|
||||
bankName: provider.bankName,
|
||||
bankBranch: provider.bankBranch,
|
||||
bankAccountNo: provider.bankAccountNo,
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
skipped: false,
|
||||
bill: serializeBigInt(bill),
|
||||
wecom,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
/** 企微结算账单正文:分字段块 + 字节上限(机器人 markdown 约 4096 字节) */
|
||||
|
||||
export type WecomBankLike = {
|
||||
bankAccountName?: string | null;
|
||||
bankName?: string | null;
|
||||
bankBranch?: string | null;
|
||||
bankAccountNo?: string | null;
|
||||
};
|
||||
|
||||
export function formatYuan(n: number): string {
|
||||
return `¥${Number(n).toFixed(2)}`;
|
||||
}
|
||||
|
||||
export function formatPercent(rate: number): string {
|
||||
const p = Number(rate) * 100;
|
||||
if (!Number.isFinite(p)) return '—';
|
||||
const rounded = Math.round(p * 100) / 100;
|
||||
const text = Number.isInteger(rounded) ? String(rounded) : String(rounded);
|
||||
return `${text}%`;
|
||||
}
|
||||
|
||||
export function formatPartnerDashLabel(
|
||||
company?: string | null,
|
||||
name?: string | null,
|
||||
): string {
|
||||
const person = (name || '').trim() || '—';
|
||||
const org = (company || '').trim();
|
||||
return org ? `${org}-${person}` : person;
|
||||
}
|
||||
|
||||
export function wecomBankParts(bank?: WecomBankLike | null): {
|
||||
payee: string;
|
||||
accountNo: string;
|
||||
bankBranch: string;
|
||||
} {
|
||||
if (!bank) return { payee: '未登记', accountNo: '—', bankBranch: '—' };
|
||||
const payee = bank.bankAccountName?.trim() || '';
|
||||
const accountNo = bank.bankAccountNo?.trim() || '';
|
||||
const bankBranch = bank.bankName?.trim() || bank.bankBranch?.trim() || '';
|
||||
if (!payee && !accountNo) {
|
||||
return { payee: '未登记', accountNo: '—', bankBranch: bankBranch || '—' };
|
||||
}
|
||||
return {
|
||||
payee: payee || '—',
|
||||
accountNo: accountNo || '—',
|
||||
bankBranch: bankBranch || '—',
|
||||
};
|
||||
}
|
||||
|
||||
export function formatWecomFieldBlock(rows: Array<[string, string]>): string {
|
||||
return rows.map(([k, v]) => `${k}:${v}`).join('\n');
|
||||
}
|
||||
|
||||
export function joinLimited(names: string[], unit: string, max = 8): string {
|
||||
const uniq = [...new Set(names.map((n) => n.trim()).filter(Boolean))];
|
||||
if (!uniq.length) return '—';
|
||||
if (uniq.length <= max) return uniq.join('、');
|
||||
return `${uniq.slice(0, max).join('、')} 等${uniq.length}${unit}`;
|
||||
}
|
||||
|
||||
const BILL_DIGEST_MAX_BYTES = 3200;
|
||||
|
||||
export function capBillBlocks(blocks: string[]): string {
|
||||
if (!blocks.length) return '无';
|
||||
const out: string[] = [];
|
||||
let bytes = 0;
|
||||
let shown = 0;
|
||||
for (const block of blocks) {
|
||||
const extra = (shown ? 2 : 0) + Buffer.byteLength(block, 'utf8');
|
||||
if (shown > 0 && bytes + extra > BILL_DIGEST_MAX_BYTES) {
|
||||
out.push(`另有 ${blocks.length - shown} 笔未列出,请到 HQ 查看`);
|
||||
break;
|
||||
}
|
||||
out.push(block);
|
||||
bytes += extra;
|
||||
shown += 1;
|
||||
}
|
||||
return out.join('\n\n');
|
||||
}
|
||||
|
||||
export function formatStoreBillWecomBlock(row: {
|
||||
cityName: string;
|
||||
storeName: string;
|
||||
partnerLabel: string;
|
||||
redeemCount: number;
|
||||
redeemAmount: number;
|
||||
settlementRate: number;
|
||||
amount: number;
|
||||
bank?: WecomBankLike | null;
|
||||
}): string {
|
||||
const bank = wecomBankParts(row.bank);
|
||||
return formatWecomFieldBlock([
|
||||
['城市', row.cityName || '—'],
|
||||
['门店', row.storeName || '—'],
|
||||
['合伙人', row.partnerLabel || '—'],
|
||||
['核销笔数', String(row.redeemCount)],
|
||||
['核销金额', formatYuan(row.redeemAmount)],
|
||||
['结算比例', formatPercent(row.settlementRate)],
|
||||
['账单', formatYuan(row.amount)],
|
||||
['收款人', bank.payee],
|
||||
['收款账户', bank.accountNo],
|
||||
['收款开户行', bank.bankBranch],
|
||||
]);
|
||||
}
|
||||
|
||||
export function formatPartnerBillWecomBlock(row: {
|
||||
cityName: string;
|
||||
partnerLabel: string;
|
||||
orderCount?: number;
|
||||
redeemCount?: number;
|
||||
orderCommission: number;
|
||||
redeemCommission: number;
|
||||
amount: number;
|
||||
bank?: WecomBankLike | null;
|
||||
}): string {
|
||||
const bank = wecomBankParts(row.bank);
|
||||
const rows: Array<[string, string]> = [
|
||||
['城市', row.cityName || '—'],
|
||||
['合伙人', row.partnerLabel || '—'],
|
||||
];
|
||||
if (row.orderCount != null) rows.push(['订单笔数', String(row.orderCount)]);
|
||||
if (row.redeemCount != null) rows.push(['核销笔数', String(row.redeemCount)]);
|
||||
rows.push(
|
||||
['订单佣金', formatYuan(row.orderCommission)],
|
||||
['核销佣金', formatYuan(row.redeemCommission)],
|
||||
['账单', formatYuan(row.amount)],
|
||||
['收款人', bank.payee],
|
||||
['收款账户', bank.accountNo],
|
||||
['收款开户行', bank.bankBranch],
|
||||
);
|
||||
return formatWecomFieldBlock(rows);
|
||||
}
|
||||
|
||||
export function formatLogisticsBillWecomBlock(row: {
|
||||
cityNames: string;
|
||||
partnerNames: string;
|
||||
providerName: string;
|
||||
settlementMethod: string;
|
||||
orderCount: number;
|
||||
bottleCount: number;
|
||||
amount: number;
|
||||
bank?: WecomBankLike | null;
|
||||
}): string {
|
||||
const bank = wecomBankParts(row.bank);
|
||||
return formatWecomFieldBlock([
|
||||
['城市', row.cityNames || '—'],
|
||||
['合伙人', row.partnerNames || '—'],
|
||||
['承运商', row.providerName || '—'],
|
||||
['结算方式', row.settlementMethod || '—'],
|
||||
['发货笔数', String(row.orderCount)],
|
||||
['瓶数', String(row.bottleCount)],
|
||||
['账单', formatYuan(row.amount)],
|
||||
['收款人', bank.payee],
|
||||
['收款账户', bank.accountNo],
|
||||
['收款开户行', bank.bankBranch],
|
||||
]);
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export class TradeController {
|
||||
|
||||
@Post()
|
||||
create(@CurrentUser() user: AuthUser, @Body() body: Record<string, unknown>, @Req() req: Request) {
|
||||
return this.tradeService.createOrder(user.actorId, body as never, req, user.clientApp);
|
||||
return this.tradeService.createOrder(user.actorId, body as never, req);
|
||||
}
|
||||
|
||||
@Get()
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { FreightPayType } from '@prisma/client';
|
||||
import {
|
||||
calcBenefitAmount,
|
||||
generateOrderNo,
|
||||
maskContactPhone,
|
||||
minBottleQtyForDelivery,
|
||||
orderTabToStatuses,
|
||||
toMinSaleQuantity,
|
||||
@@ -209,31 +210,6 @@ export class TradeService {
|
||||
};
|
||||
}
|
||||
|
||||
private resolveTrackedUserClientApp(
|
||||
clientApp?: ClientApp | string | null,
|
||||
fallback: ClientApp = ClientApp.USER_H5,
|
||||
): ClientApp {
|
||||
if (clientApp === ClientApp.USER_MINI || clientApp === ClientApp.USER_H5) {
|
||||
return clientApp;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/** 支付回调无 JWT:沿用该订单已有埋点;没有则默认小程序(当前生产 C 端) */
|
||||
private async clientAppForPaidOrder(userId: bigint, orderId: bigint): Promise<ClientApp> {
|
||||
const row = await this.prisma.logUserAnalytics.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
refType: 'ORDER',
|
||||
refId: orderId,
|
||||
eventName: { in: ['order_submit', 'pay_fail'] },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: { clientApp: true },
|
||||
});
|
||||
return this.resolveTrackedUserClientApp(row?.clientApp, ClientApp.USER_MINI);
|
||||
}
|
||||
|
||||
async createOrder(
|
||||
userId: bigint,
|
||||
body: {
|
||||
@@ -245,7 +221,6 @@ export class TradeService {
|
||||
skuId?: string;
|
||||
},
|
||||
req: Request,
|
||||
clientApp?: ClientApp,
|
||||
) {
|
||||
const preview = await this.preview(userId, body);
|
||||
if (preview.quantityOk === false) {
|
||||
@@ -376,7 +351,7 @@ export class TradeService {
|
||||
);
|
||||
}
|
||||
|
||||
this.analyticsService.trackOneSafe(userId, this.resolveTrackedUserClientApp(clientApp), {
|
||||
this.analyticsService.trackOneSafe(userId, 'USER_H5', {
|
||||
eventName: 'order_submit',
|
||||
refType: 'ORDER',
|
||||
refId: order.id,
|
||||
@@ -505,7 +480,7 @@ export class TradeService {
|
||||
userId,
|
||||
});
|
||||
|
||||
this.analyticsService.trackOneSafe(userId, this.resolveTrackedUserClientApp(clientApp), {
|
||||
this.analyticsService.trackOneSafe(userId, 'USER_H5', {
|
||||
eventName: 'pay_success',
|
||||
refType: 'ORDER',
|
||||
refId: order.id,
|
||||
@@ -521,7 +496,7 @@ export class TradeService {
|
||||
where: { id: orderId },
|
||||
include: {
|
||||
city: { select: { name: true } },
|
||||
user: { select: { phone: true, nickname: true, hqRemark: true } },
|
||||
user: { select: { phone: true } },
|
||||
},
|
||||
});
|
||||
if (!order) return;
|
||||
@@ -542,9 +517,7 @@ export class TradeService {
|
||||
order.deliveryType === 'ON_SITE_PICKUP'
|
||||
? `${order.receiverName} ${order.receiverPhone}`.trim()
|
||||
: `${order.receiverName} ${order.receiverPhone} ${order.receiverAddress}`.trim();
|
||||
const userName = order.user?.nickname?.trim() || '—';
|
||||
const userPhone = (order.user?.phone || '').trim() || '—';
|
||||
const userRemark = (order.user?.hqRemark || '').trim() || '—';
|
||||
const phoneMasked = maskContactPhone(order.user?.phone || order.receiverPhone);
|
||||
void this.wecomPush.dispatchEvent(
|
||||
'order.paid',
|
||||
{
|
||||
@@ -554,10 +527,7 @@ export class TradeService {
|
||||
skuSummary,
|
||||
deliveryLabel,
|
||||
receiverInfo,
|
||||
userName,
|
||||
userPhone,
|
||||
userRemark,
|
||||
phoneMasked: userPhone,
|
||||
phoneMasked,
|
||||
},
|
||||
{ handlePath: `/orders?orderNo=${encodeURIComponent(order.orderNo)}` },
|
||||
);
|
||||
@@ -683,16 +653,12 @@ export class TradeService {
|
||||
orderNo: order.orderNo,
|
||||
userId: order.userId,
|
||||
});
|
||||
this.analyticsService.trackOneSafe(
|
||||
order.userId,
|
||||
await this.clientAppForPaidOrder(order.userId, order.id),
|
||||
{
|
||||
eventName: 'pay_success',
|
||||
refType: 'ORDER',
|
||||
refId: order.id,
|
||||
extraJson: { orderId: order.id.toString(), mode: 'wechat_callback' },
|
||||
},
|
||||
);
|
||||
this.analyticsService.trackOneSafe(order.userId, 'USER_H5', {
|
||||
eventName: 'pay_success',
|
||||
refType: 'ORDER',
|
||||
refId: order.id,
|
||||
extraJson: { orderId: order.id.toString(), mode: 'wechat_callback' },
|
||||
});
|
||||
}
|
||||
|
||||
return { orderId: order.id.toString(), alreadyPaid: false };
|
||||
|
||||
Reference in New Issue
Block a user