feat(settlement): v4.0.6 酒厂 T+3 每 3 天出一期并与订单完全比对

含现场提货入账、零应付仍出账、核对补生成,以及线上历史账单修复 SQL。
This commit is contained in:
2026-08-31 17:27:38 +08:00
parent ac94f5f5de
commit f35e1a3ba5
15 changed files with 782 additions and 84 deletions
+54 -1
View File
@@ -33,7 +33,8 @@ export function shanghaiT1DayWindow(anchor = new Date()) {
}
/**
* 酒厂 T+N:出账日 = 今天;纳入窗口为「今天 − lagDays」当天完成的订单
* 酒厂日滞后窗口(旧:每天出账只收 lagDays 前那一天)。
* 现网酒厂已改为「每 periodDays 天出一期」,见 shanghaiWineryPeriodWindow。
*/
export function shanghaiLaggedIssueWindow(anchor = new Date(), lagDays: number) {
const billDate = startOfShanghaiDay(anchor);
@@ -42,6 +43,58 @@ export function shanghaiLaggedIssueWindow(anchor = new Date(), lagDays: number)
return { start, end, billDate };
}
/** 两个北京自然日之间相差天数(b − a;可为负) */
export function shanghaiCalendarDaysBetween(a: Date, b: Date): number {
const sa = startOfShanghaiDay(a).getTime();
const sb = startOfShanghaiDay(b).getTime();
return Math.round((sb - sa) / (24 * 60 * 60 * 1000));
}
/** 是否酒厂出账日:相对 epoch,每 periodDays 天一次(首张 = epoch + periodDays */
export function isWineryIssueDay(anchor: Date, periodDays: number, epochYmd: string): boolean {
const billDate = startOfShanghaiDay(anchor);
const epoch = parseShanghaiYmd(epochYmd);
const diff = shanghaiCalendarDaysBetween(epoch, billDate);
return diff >= periodDays && diff % periodDays === 0;
}
/**
* 酒厂 T+N 期账单窗口:出账日 = anchor 当天;纳入 [出账日 periodDays, 出账日) 内完成的订单。
* 调用方应先用 isWineryIssueDay 判断是否出账日。
*/
export function shanghaiWineryPeriodWindow(anchor = new Date(), periodDays: number) {
const billDate = startOfShanghaiDay(anchor);
const start = addShanghaiDays(billDate, -periodDays);
return { start, end: billDate, billDate };
}
/** 订单完成日 → 所属酒厂出账日(落入下一期出账日) */
export function wineryIssueDateFromCompletedAt(
completedAt: Date,
periodDays: number,
epochYmd: string,
) {
const c = startOfShanghaiDay(completedAt);
const epoch = parseShanghaiYmd(epochYmd);
const daysFromEpoch = shanghaiCalendarDaysBetween(epoch, c);
if (daysFromEpoch < 0) {
return addShanghaiDays(epoch, periodDays);
}
const k = Math.floor(daysFromEpoch / periodDays) + 1;
return addShanghaiDays(epoch, k * periodDays);
}
/** 枚举 [from, to] 内(含)的全部酒厂出账日 */
export function listWineryIssueDays(from: Date, to: Date, periodDays: number, epochYmd: string): Date[] {
const start = startOfShanghaiDay(from);
const end = startOfShanghaiDay(to);
const out: Date[] = [];
for (let d = start; d.getTime() <= end.getTime(); d = addShanghaiDays(d, 1)) {
if (isWineryIssueDay(d, periodDays, epochYmd)) out.push(d);
}
return out;
}
export function shanghaiMonthRange(year: number, month: number): { start: Date; endExclusive: Date } {
const start = new Date(`${year}-${pad2(month)}-01T00:00:00+08:00`);
const endMonth = month === 12 ? 1 : month + 1;