Files
dukang/server/dukang-api/src/modules/settlement/wecom-bill-digest.ts
T
2026-09-08 10:07:34 +08:00

179 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 企微结算账单正文:字段块 / markdown_v2 表格 + 字节上限(机器人约 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');
}
function escapeWecomTableCell(v: string): string {
const text = String(v ?? '')
.replace(/\|/g, '')
.replace(/\r?\n/g, ' ')
.trim();
return text || '—';
}
/** 企微 markdown_v2 键值表:表头为标题,下列为字段/值 */
export function formatWecomKvTable(header: string, rows: Array<[string, string]>): string {
const lines = [`| ${escapeWecomTableCell(header)} | |`, '| :--- | ---: |'];
for (const [k, v] of rows) {
lines.push(`| ${escapeWecomTableCell(k)} | ${escapeWecomTableCell(v)} |`);
}
return lines.join('\n');
}
export function filterNonZeroWecomBills<T extends { amount: number }>(rows: T[]): T[] {
return rows.filter((r) => Number(r.amount) > 0);
}
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: {
partnerLabel: string;
period: string;
orderCount?: number;
orderAmount?: number;
orderCommission: number;
redeemCount?: number;
redeemAmount?: number;
redeemCommission: number;
amount: number;
bank?: WecomBankLike | null;
}): string {
const bank = wecomBankParts(row.bank);
return formatWecomKvTable(row.partnerLabel || '—', [
['账期', row.period || '—'],
['订单数量', String(row.orderCount ?? 0)],
['订单金额', formatYuan(row.orderAmount ?? 0)],
['订单佣金', formatYuan(row.orderCommission)],
['核销单数量', String(row.redeemCount ?? 0)],
['核销金额', formatYuan(row.redeemAmount ?? 0)],
['核销佣金', formatYuan(row.redeemCommission)],
['累计金额', formatYuan(row.amount)],
['收款人', bank.payee],
['收款银行账号', bank.accountNo],
['开户行', bank.bankBranch],
]);
}
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],
]);
}