Files
dukang/packages/domain/src/shanghai-date.test.ts
T
jacy f35e1a3ba5 feat(settlement): v4.0.6 酒厂 T+3 每 3 天出一期并与订单完全比对
含现场提货入账、零应付仍出账、核对补生成,以及线上历史账单修复 SQL。
2026-08-31 17:27:38 +08:00

148 lines
5.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
addShanghaiDays,
isWineryIssueDay,
listWineryIssueDays,
previousShanghaiMonth,
shanghaiLaggedIssueWindow,
shanghaiMonthLastInstant,
shanghaiMonthRange,
shanghaiPeriodYmds,
shanghaiT1DayWindow,
shanghaiWineryPeriodWindow,
shanghaiYmd,
startOfShanghaiDay,
wineryIssueDateFromCompletedAt,
} from './shanghai-date';
describe('shanghaiYmd / startOfShanghaiDay', () => {
it('UTC 16:00 已是北京次日 00:00,账单日取北京日历日', () => {
const utc = new Date('2026-08-30T16:00:00.000Z');
expect(shanghaiYmd(utc)).toBe('2026-08-31');
expect(startOfShanghaiDay(utc).toISOString()).toBe('2026-08-30T16:00:00.000Z');
});
it('UTC 15:59 仍是北京当日', () => {
const utc = new Date('2026-08-30T15:59:59.000Z');
expect(shanghaiYmd(utc)).toBe('2026-08-30');
expect(startOfShanghaiDay(utc).toISOString()).toBe('2026-08-29T16:00:00.000Z');
});
it('不依赖服务器本地时区:输入 ISO 即得到北京自然日 00:00', () => {
const noonUtc = new Date('2026-08-30T04:00:00.000Z'); // 北京 12:00
expect(shanghaiYmd(noonUtc)).toBe('2026-08-30');
expect(startOfShanghaiDay(noonUtc).toISOString()).toBe('2026-08-29T16:00:00.000Z');
});
});
describe('shanghaiT1DayWindow', () => {
it('北京 8 月 30 日 08:00 出账:出账日 30 日,窗口为 29 日全天', () => {
const { start, end, billDate } = shanghaiT1DayWindow(new Date('2026-08-30T00:00:00.000Z'));
expect(shanghaiYmd(billDate)).toBe('2026-08-30');
expect(shanghaiYmd(start)).toBe('2026-08-29');
expect(end.toISOString()).toBe(billDate.toISOString());
});
it('北京 8 月 31 日 00:05:出账日仍是 31 日,不是 UTC 的 30 日', () => {
const { billDate } = shanghaiT1DayWindow(new Date('2026-08-30T16:05:00.000Z'));
expect(shanghaiYmd(billDate)).toBe('2026-08-31');
});
});
describe('shanghaiLaggedIssueWindow', () => {
it('旧日滞后:出账日是今天,纳入 3 天前完成的订单', () => {
const { start, end, billDate } = shanghaiLaggedIssueWindow(
new Date('2026-08-31T00:00:00.000Z'),
3,
);
expect(shanghaiYmd(billDate)).toBe('2026-08-31');
expect(shanghaiYmd(start)).toBe('2026-08-28');
expect(shanghaiYmd(end)).toBe('2026-08-29');
});
});
describe('酒厂 T+3 每 3 天一期', () => {
const epoch = '2026-08-01';
const period = 3;
it('出账日:8/4、8/7、8/28;非 8/5、8/29', () => {
expect(isWineryIssueDay(new Date('2026-08-04T00:00:00+08:00'), period, epoch)).toBe(true);
expect(isWineryIssueDay(new Date('2026-08-07T00:00:00+08:00'), period, epoch)).toBe(true);
expect(isWineryIssueDay(new Date('2026-08-28T00:00:00+08:00'), period, epoch)).toBe(true);
expect(isWineryIssueDay(new Date('2026-08-01T00:00:00+08:00'), period, epoch)).toBe(false);
expect(isWineryIssueDay(new Date('2026-08-05T00:00:00+08:00'), period, epoch)).toBe(false);
expect(isWineryIssueDay(new Date('2026-08-29T00:00:00+08:00'), period, epoch)).toBe(false);
});
it('窗口:出账日 8/28 → 完成日 [8/25, 8/28)', () => {
const { start, end, billDate } = shanghaiWineryPeriodWindow(
new Date('2026-08-28T00:00:00+08:00'),
period,
);
expect(shanghaiYmd(billDate)).toBe('2026-08-28');
expect(shanghaiYmd(start)).toBe('2026-08-25');
expect(shanghaiYmd(end)).toBe('2026-08-28');
});
it('完成日归属出账日', () => {
expect(
shanghaiYmd(wineryIssueDateFromCompletedAt(new Date('2026-08-01T04:00:00.000Z'), period, epoch)),
).toBe('2026-08-04');
expect(
shanghaiYmd(wineryIssueDateFromCompletedAt(new Date('2026-08-03T04:00:00.000Z'), period, epoch)),
).toBe('2026-08-04');
expect(
shanghaiYmd(wineryIssueDateFromCompletedAt(new Date('2026-08-04T04:00:00.000Z'), period, epoch)),
).toBe('2026-08-07');
expect(
shanghaiYmd(wineryIssueDateFromCompletedAt(new Date('2026-08-27T04:00:00.000Z'), period, epoch)),
).toBe('2026-08-28');
});
it('listWineryIssueDays 只返回周期日', () => {
const days = listWineryIssueDays(
new Date('2026-08-01T00:00:00+08:00'),
new Date('2026-08-10T00:00:00+08:00'),
period,
epoch,
).map(shanghaiYmd);
expect(days).toEqual(['2026-08-04', '2026-08-07', '2026-08-10']);
});
});
describe('addShanghaiDays', () => {
it('跨月跨年按北京日历加减', () => {
expect(shanghaiYmd(addShanghaiDays(new Date('2026-08-31T16:00:00.000Z'), 1))).toBe(
'2026-09-02',
);
expect(shanghaiYmd(addShanghaiDays(new Date('2026-01-01T00:00:00.000Z'), -1))).toBe(
'2025-12-31',
);
});
});
describe('shanghaiMonthRange', () => {
it('8 月账期为北京 8/1 00:00 至 9/1 00:00(不含)', () => {
const { start, endExclusive } = shanghaiMonthRange(2026, 8);
expect(start.toISOString()).toBe('2026-07-31T16:00:00.000Z');
expect(endExclusive.toISOString()).toBe('2026-08-31T16:00:00.000Z');
expect(shanghaiYmd(shanghaiMonthLastInstant(2026, 8))).toBe('2026-08-31');
});
it('UTC 月末 23:59:59 不会把账期止日滚成下月', () => {
const periodStart = new Date('2026-08-01T00:00:00.000Z');
const ymds = shanghaiPeriodYmds(periodStart);
expect(ymds.periodStart).toBe('2026-08-01');
expect(ymds.periodEnd).toBe('2026-08-31');
});
});
describe('previousShanghaiMonth', () => {
it('北京 9 月 1 日取上月为 8 月', () => {
expect(previousShanghaiMonth(new Date('2026-08-31T16:00:00.000Z'))).toEqual({
year: 2026,
month: 8,
});
});
});