179 lines
5.5 KiB
TypeScript
179 lines
5.5 KiB
TypeScript
/** 企微结算账单正文:字段块 / 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],
|
||
]);
|
||
}
|