总部后端,开发计划-任务列表-来源工单支持链接 总部端-开发计划-任务列表-编辑任务-增加“开发中”状态 账单日期修正
This commit is contained in:
@@ -506,9 +506,9 @@ export class DevPlanService {
|
||||
|
||||
const completed = taskCompletedAtOnStatus(
|
||||
|
||||
existing.status as 'TODO' | 'DEVELOPED' | 'RELEASED',
|
||||
existing.status as DevPlanTaskStatus,
|
||||
|
||||
dto.status as 'TODO' | 'DEVELOPED' | 'RELEASED',
|
||||
dto.status as DevPlanTaskStatus,
|
||||
|
||||
existing.completedAt,
|
||||
|
||||
|
||||
@@ -9,10 +9,21 @@ import {
|
||||
WINERY_SETTLEMENT_RATE,
|
||||
} from '@dukang/shared-types';
|
||||
import {
|
||||
addShanghaiDays,
|
||||
calcLogisticsFeeByBottles,
|
||||
calcRedeemSettleAmount,
|
||||
parseShanghaiYmd,
|
||||
pickPayoutsForWithdrawAmount,
|
||||
previousShanghaiMonth,
|
||||
resolveSettlementRate,
|
||||
shanghaiLaggedIssueWindow,
|
||||
shanghaiMonthLastInstant,
|
||||
shanghaiMonthRange,
|
||||
shanghaiPeriodYmds,
|
||||
shanghaiT1DayWindow,
|
||||
shanghaiYearMonth,
|
||||
shanghaiYmd,
|
||||
startOfShanghaiDay,
|
||||
sumUnbilledPayoutAmount,
|
||||
validateStoreWithdraw,
|
||||
type LogisticsPricingRule,
|
||||
@@ -64,30 +75,8 @@ function paymentProofUrlsInput(urls?: string[]): Prisma.InputJsonValue | typeof
|
||||
return parsed.length ? (parsed as Prisma.InputJsonValue) : Prisma.JsonNull;
|
||||
}
|
||||
|
||||
/** 上海时区自然日 00:00(用本地 Date 构造;服务器需设 Asia/Shanghai 或等价) */
|
||||
function startOfDay(d: Date) {
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/** 核销窗口:昨日 00:00 ≤ t < 今日 00:00;出账日 = 今日 00:00(窗口右端) */
|
||||
function dayWindow(anchor = new Date()) {
|
||||
const end = startOfDay(anchor);
|
||||
const start = new Date(end);
|
||||
start.setDate(start.getDate() - 1);
|
||||
return { start, end, billDate: end };
|
||||
}
|
||||
|
||||
function shanghaiYmd(d: Date): string {
|
||||
return d.toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' });
|
||||
}
|
||||
|
||||
function wineryDayWindow(anchor = new Date(), lagDays = WINERY_SETTLEMENT_LAG_DAYS) {
|
||||
const billDate = startOfDay(anchor);
|
||||
billDate.setDate(billDate.getDate() - lagDays);
|
||||
const start = billDate;
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + 1);
|
||||
return { start, end, billDate: start };
|
||||
function withShanghaiPeriod<T extends { periodStart: Date; periodEnd: Date }>(row: T) {
|
||||
return { ...row, ...shanghaiPeriodYmds(row.periodStart) };
|
||||
}
|
||||
|
||||
function toPartnerBillItemDto(row: {
|
||||
@@ -180,17 +169,11 @@ function getStoreWithdrawDailyLimit(): number {
|
||||
|
||||
/** 工作日 18:00 前未审完视为 FIN-003 超时(Asia/Shanghai 自然日) */
|
||||
function isWithdrawOverdue(appliedAt: Date, now = new Date()): boolean {
|
||||
const day = appliedAt.getDay(); // 0 Sun … 6 Sat
|
||||
const ymd = shanghaiYmd(appliedAt);
|
||||
const noon = new Date(`${ymd}T12:00:00+08:00`);
|
||||
const day = noon.getUTCDay(); // 与北京日历日相同
|
||||
if (day === 0 || day === 6) return false;
|
||||
const deadline = new Date(
|
||||
appliedAt.getFullYear(),
|
||||
appliedAt.getMonth(),
|
||||
appliedAt.getDate(),
|
||||
18,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
const deadline = new Date(`${ymd}T18:00:00+08:00`);
|
||||
return now.getTime() > deadline.getTime();
|
||||
}
|
||||
|
||||
@@ -210,15 +193,21 @@ export class SettlementService implements OnModuleInit {
|
||||
async onModuleInit() {
|
||||
try {
|
||||
const shifted = await this.realignStoreBillIssueDates();
|
||||
if (shifted > 0) this.logger.log(`Store bill dates aligned to issue day: ${shifted}`);
|
||||
if (shifted > 0) this.logger.log(`Store bill dates aligned to Beijing issue day: ${shifted}`);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Store bill date align skipped: ${e instanceof Error ? e.message : e}`);
|
||||
}
|
||||
try {
|
||||
const shifted = await this.realignWineryBillIssueDates();
|
||||
if (shifted > 0) this.logger.log(`Winery bill dates aligned to Beijing issue day: ${shifted}`);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Winery bill date align skipped: ${e instanceof Error ? e.message : e}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 旧数据 billDate=核销自然日(窗口左端);现改为出账日(窗口右端=今天)。
|
||||
* 按 created_at 日历日对比,只把仍早一天的行 +1;从晚到早更新避免 (storeId,billDate) 冲突。
|
||||
* 账单日 = 出账当天(created_at 的北京日历日 00:00+08)。
|
||||
* 纠正:旧窗口左端、UTC DATE() +1 误伤、以及 toISOString 跨日。
|
||||
*/
|
||||
private async realignStoreBillIssueDates(): Promise<number> {
|
||||
const locked = await this.prisma.$queryRaw<Array<{ acquired: number | bigint | null }>>`
|
||||
@@ -226,18 +215,53 @@ export class SettlementService implements OnModuleInit {
|
||||
`;
|
||||
if (!Number(locked[0]?.acquired)) return 0;
|
||||
try {
|
||||
const shifted = await this.prisma.$executeRaw`
|
||||
UPDATE store_bill
|
||||
SET bill_date = DATE_ADD(bill_date, INTERVAL 1 DAY)
|
||||
WHERE DATE(bill_date) < DATE(created_at)
|
||||
ORDER BY bill_date DESC
|
||||
`;
|
||||
return Number(shifted);
|
||||
const bills = await this.prisma.storeBill.findMany({
|
||||
select: { id: true, storeId: true, billDate: true, createdAt: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
let shifted = 0;
|
||||
for (const b of bills) {
|
||||
const issue = startOfShanghaiDay(b.createdAt);
|
||||
if (shanghaiYmd(b.billDate) === shanghaiYmd(issue)) continue;
|
||||
const clash = await this.prisma.storeBill.findUnique({
|
||||
where: { storeId_billDate: { storeId: b.storeId, billDate: issue } },
|
||||
});
|
||||
if (clash && clash.id !== b.id) continue;
|
||||
await this.prisma.storeBill.update({ where: { id: b.id }, data: { billDate: issue } });
|
||||
shifted += 1;
|
||||
}
|
||||
return shifted;
|
||||
} finally {
|
||||
await this.prisma.$queryRaw`SELECT RELEASE_LOCK('store_bill_issue_date_align')`;
|
||||
}
|
||||
}
|
||||
|
||||
/** 酒厂账单日改为出账当天(不再存 T+3 完成日) */
|
||||
private async realignWineryBillIssueDates(): Promise<number> {
|
||||
const locked = await this.prisma.$queryRaw<Array<{ acquired: number | bigint | null }>>`
|
||||
SELECT GET_LOCK('winery_bill_issue_date_align', 5) AS acquired
|
||||
`;
|
||||
if (!Number(locked[0]?.acquired)) return 0;
|
||||
try {
|
||||
const bills = await this.prisma.wineryBill.findMany({
|
||||
select: { id: true, billDate: true, createdAt: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
let shifted = 0;
|
||||
for (const b of bills) {
|
||||
const issue = startOfShanghaiDay(b.createdAt);
|
||||
if (shanghaiYmd(b.billDate) === shanghaiYmd(issue)) continue;
|
||||
const clash = await this.prisma.wineryBill.findUnique({ where: { billDate: issue } });
|
||||
if (clash && clash.id !== b.id) continue;
|
||||
await this.prisma.wineryBill.update({ where: { id: b.id }, data: { billDate: issue } });
|
||||
shifted += 1;
|
||||
}
|
||||
return shifted;
|
||||
} finally {
|
||||
await this.prisma.$queryRaw`SELECT RELEASE_LOCK('winery_bill_issue_date_align')`;
|
||||
}
|
||||
}
|
||||
|
||||
private notifyPartnerBillDigest(
|
||||
period: string,
|
||||
rows: Array<{
|
||||
@@ -424,9 +448,8 @@ export class SettlementService implements OnModuleInit {
|
||||
}
|
||||
|
||||
private async todayWithdrawAppliedAmount(storeId: bigint, now = new Date()) {
|
||||
const start = startOfDay(now);
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + 1);
|
||||
const start = startOfShanghaiDay(now);
|
||||
const end = addShanghaiDays(start, 1);
|
||||
const agg = await this.prisma.storeWithdrawRequest.aggregate({
|
||||
where: {
|
||||
storeId,
|
||||
@@ -897,7 +920,7 @@ export class SettlementService implements OnModuleInit {
|
||||
...lines,
|
||||
'请尽快在 HQ「财务 → 门店账单(手动提现)」处理。',
|
||||
].join('\n'),
|
||||
dedupeKey: `store_withdraw_overdue|${new Date().toISOString().slice(0, 10)}`,
|
||||
dedupeKey: `store_withdraw_overdue|${shanghaiYmd(new Date())}`,
|
||||
dedupeTtlSec: 6 * 3600,
|
||||
});
|
||||
}
|
||||
@@ -1075,9 +1098,9 @@ export class SettlementService implements OnModuleInit {
|
||||
|
||||
// ─── Store bills (daily header) ──────────────────────
|
||||
|
||||
/** 生成昨日核销窗口的门店对账单;billDate = 出账日(今日) */
|
||||
/** 生成昨日核销窗口的门店对账单;billDate = 出账日(北京时间今天) */
|
||||
async generateStoreBillsForDay(anchor = new Date()) {
|
||||
const { start, end, billDate } = dayWindow(anchor);
|
||||
const { start, end, billDate } = shanghaiT1DayWindow(anchor);
|
||||
const payouts = await this.prisma.storePayout.findMany({
|
||||
where: {
|
||||
storeBillId: null,
|
||||
@@ -1275,12 +1298,8 @@ export class SettlementService implements OnModuleInit {
|
||||
if (query.storeId) where.storeId = BigInt(query.storeId);
|
||||
if (query.dateFrom || query.dateTo) {
|
||||
where.appliedAt = {};
|
||||
if (query.dateFrom) where.appliedAt.gte = new Date(query.dateFrom);
|
||||
if (query.dateTo) {
|
||||
const end = new Date(query.dateTo);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
where.appliedAt.lte = end;
|
||||
}
|
||||
if (query.dateFrom) where.appliedAt.gte = parseShanghaiYmd(query.dateFrom);
|
||||
if (query.dateTo) where.appliedAt.lt = addShanghaiDays(parseShanghaiYmd(query.dateTo), 1);
|
||||
}
|
||||
return where;
|
||||
})()
|
||||
@@ -1564,8 +1583,8 @@ export class SettlementService implements OnModuleInit {
|
||||
if (query.storeId) where.storeId = BigInt(query.storeId);
|
||||
if (query.dateFrom || query.dateTo) {
|
||||
where.billDate = {};
|
||||
if (query.dateFrom) where.billDate.gte = startOfDay(new Date(query.dateFrom));
|
||||
if (query.dateTo) where.billDate.lte = startOfDay(new Date(query.dateTo));
|
||||
if (query.dateFrom) where.billDate.gte = parseShanghaiYmd(query.dateFrom);
|
||||
if (query.dateTo) where.billDate.lte = parseShanghaiYmd(query.dateTo);
|
||||
}
|
||||
return where;
|
||||
}
|
||||
@@ -1598,7 +1617,7 @@ export class SettlementService implements OnModuleInit {
|
||||
eventName: 'partner_bill_view',
|
||||
extraJson: { count: bills.length },
|
||||
});
|
||||
return serializeBigInt(bills);
|
||||
return serializeBigInt(bills.map(withShanghaiPeriod));
|
||||
}
|
||||
|
||||
async getPartnerBill(partnerAccountId: bigint, billId: bigint) {
|
||||
@@ -1617,7 +1636,7 @@ export class SettlementService implements OnModuleInit {
|
||||
});
|
||||
const { items, ...header } = bill;
|
||||
return serializeBigInt({
|
||||
...header,
|
||||
...withShanghaiPeriod(header),
|
||||
partnerId: header.partnerAccountId.toString(),
|
||||
...splitPartnerBillItems(items),
|
||||
});
|
||||
@@ -1664,7 +1683,7 @@ export class SettlementService implements OnModuleInit {
|
||||
]);
|
||||
|
||||
const items = rawItems.map((b) => ({
|
||||
...b,
|
||||
...withShanghaiPeriod(b),
|
||||
partner: b.partnerAccount,
|
||||
}));
|
||||
|
||||
@@ -1688,9 +1707,8 @@ export class SettlementService implements OnModuleInit {
|
||||
if (query.status) where.status = query.status as Prisma.EnumPartnerBillStatusFilter['equals'];
|
||||
if (query.partnerId) where.partnerAccountId = BigInt(query.partnerId);
|
||||
if (query.year && query.month) {
|
||||
const periodStart = new Date(query.year, query.month - 1, 1);
|
||||
const periodEnd = new Date(query.year, query.month, 0, 23, 59, 59, 999);
|
||||
where.periodStart = { gte: periodStart, lte: periodEnd };
|
||||
const { start, endExclusive } = shanghaiMonthRange(query.year, query.month);
|
||||
where.periodStart = { gte: start, lt: endExclusive };
|
||||
}
|
||||
return where;
|
||||
}
|
||||
@@ -1767,8 +1785,8 @@ export class SettlementService implements OnModuleInit {
|
||||
|
||||
/** 每月 1 日任务:生成上一自然月账单 */
|
||||
async generatePreviousMonthPartnerBills(anchor = new Date()) {
|
||||
const prev = new Date(anchor.getFullYear(), anchor.getMonth() - 1, 1);
|
||||
return this.generateAllPartnerBills({ year: prev.getFullYear(), month: prev.getMonth() + 1 });
|
||||
const prev = previousShanghaiMonth(anchor);
|
||||
return this.generateAllPartnerBills({ year: prev.year, month: prev.month });
|
||||
}
|
||||
|
||||
async getAdminPartnerBill(id: bigint) {
|
||||
@@ -1779,7 +1797,7 @@ export class SettlementService implements OnModuleInit {
|
||||
if (!bill) throw new NotFoundException('账单不存在');
|
||||
const { items, ...header } = bill;
|
||||
return serializeBigInt({
|
||||
...header,
|
||||
...withShanghaiPeriod(header),
|
||||
partnerId: header.partnerAccountId.toString(),
|
||||
...splitPartnerBillItems(items),
|
||||
});
|
||||
@@ -1791,12 +1809,13 @@ export class SettlementService implements OnModuleInit {
|
||||
) {
|
||||
const partnerAccountId = BigInt(body.partnerId);
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
const periodStart = new Date(body.year, body.month - 1, 1);
|
||||
const periodEnd = new Date(body.year, body.month, 0, 23, 59, 59, 999);
|
||||
const { start: periodStart, endExclusive } = shanghaiMonthRange(body.year, body.month);
|
||||
const periodEnd = shanghaiMonthLastInstant(body.year, body.month);
|
||||
|
||||
const existing = await this.prisma.partnerBill.findUnique({
|
||||
const existing = await this.prisma.partnerBill.findFirst({
|
||||
where: {
|
||||
partnerAccountId_periodStart: { partnerAccountId: primary.id, periodStart },
|
||||
partnerAccountId: primary.id,
|
||||
periodStart: { gte: periodStart, lt: endExclusive },
|
||||
},
|
||||
});
|
||||
if (existing && existing.status !== 'PENDING_REVIEW') {
|
||||
@@ -1876,6 +1895,7 @@ export class SettlementService implements OnModuleInit {
|
||||
orderCommission: round2(orderCommission),
|
||||
redeemCommission: round2(redeemCommission),
|
||||
totalAmount,
|
||||
periodStart,
|
||||
periodEnd,
|
||||
status: 'PENDING_REVIEW',
|
||||
},
|
||||
@@ -2142,15 +2162,15 @@ export class SettlementService implements OnModuleInit {
|
||||
csvEscape(b.billNo),
|
||||
csvEscape(b.partnerAccount.companyName ?? ''),
|
||||
csvEscape(b.partnerAccount.phone ?? ''),
|
||||
b.periodStart.toISOString().slice(0, 10),
|
||||
b.periodEnd.toISOString().slice(0, 10),
|
||||
shanghaiPeriodYmds(b.periodStart).periodStart,
|
||||
shanghaiPeriodYmds(b.periodStart).periodEnd,
|
||||
Number(b.orderCommission),
|
||||
Number(b.redeemCommission),
|
||||
Number(b.totalAmount),
|
||||
b.status,
|
||||
b.sentAt ? b.sentAt.toISOString().slice(0, 10) : '',
|
||||
b.confirmedAt ? b.confirmedAt.toISOString().slice(0, 10) : '',
|
||||
b.paidAt ? b.paidAt.toISOString().slice(0, 10) : '',
|
||||
b.sentAt ? shanghaiYmd(b.sentAt) : '',
|
||||
b.confirmedAt ? shanghaiYmd(b.confirmedAt) : '',
|
||||
b.paidAt ? shanghaiYmd(b.paidAt) : '',
|
||||
csvEscape(b.paymentRef ?? ''),
|
||||
csvEscape(b.partnerAccount.bankAccountName ?? ''),
|
||||
csvEscape(b.partnerAccount.bankAccountNo ?? ''),
|
||||
@@ -2182,15 +2202,20 @@ export class SettlementService implements OnModuleInit {
|
||||
// ─── Winery bills ────────────────────────────────────
|
||||
|
||||
async generateWineryBillForDay(anchor = new Date()) {
|
||||
const { start, end, billDate } = wineryDayWindow(anchor);
|
||||
const { start, end, billDate } = shanghaiLaggedIssueWindow(anchor, WINERY_SETTLEMENT_LAG_DAYS);
|
||||
const rate = WINERY_SETTLEMENT_RATE;
|
||||
const issueYmd = shanghaiYmd(billDate);
|
||||
|
||||
const existing = await this.prisma.wineryBill.findUnique({ where: { billDate } });
|
||||
const existing =
|
||||
(await this.prisma.wineryBill.findUnique({ where: { billDate } })) ??
|
||||
(await this.prisma.wineryBill.findFirst({
|
||||
where: { createdAt: { gte: billDate, lt: addShanghaiDays(billDate, 1) } },
|
||||
}));
|
||||
if (existing?.status === 'PAID') {
|
||||
return { billDate: billDate.toISOString().slice(0, 10), skipped: true, reason: '已打款' };
|
||||
return { billDate: issueYmd, skipped: true, reason: '已打款' };
|
||||
}
|
||||
|
||||
// T+3:纳入「账单日」当天完成的同城/跨城订单(完成日 = 今天 − lagDays)
|
||||
// T+3:纳入「今天 − lagDays」当天完成的同城/跨城订单;billDate = 出账日(今天)
|
||||
const orders = await this.prisma.order.findMany({
|
||||
where: {
|
||||
status: 'COMPLETED',
|
||||
@@ -2203,7 +2228,7 @@ export class SettlementService implements OnModuleInit {
|
||||
});
|
||||
|
||||
if (orders.length === 0 && !existing) {
|
||||
return { billDate: billDate.toISOString().slice(0, 10), skipped: true, reason: '无订单' };
|
||||
return { billDate: issueYmd, skipped: true, reason: '无订单' };
|
||||
}
|
||||
|
||||
const orderAmount = round2(orders.reduce((s, o) => s + Number(o.payAmount), 0));
|
||||
@@ -2214,6 +2239,7 @@ export class SettlementService implements OnModuleInit {
|
||||
? await tx.wineryBill.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
billDate,
|
||||
orderCount: orders.length,
|
||||
orderAmount,
|
||||
wineryRate: rate,
|
||||
@@ -2254,7 +2280,7 @@ export class SettlementService implements OnModuleInit {
|
||||
return header;
|
||||
});
|
||||
|
||||
const period = billDate.toISOString().slice(0, 10);
|
||||
const period = issueYmd;
|
||||
const wineryBank = await loadWineryBankConfig(this.prisma);
|
||||
const bankParts = wecomBankParts(wineryBank);
|
||||
const cityNames = joinLimited(
|
||||
@@ -2324,7 +2350,13 @@ export class SettlementService implements OnModuleInit {
|
||||
totalAmount: Number(aggregates._sum.wineryAmount ?? 0),
|
||||
};
|
||||
|
||||
return serializeBigInt({ items, total, page, pageSize, summary });
|
||||
return serializeBigInt({
|
||||
items: items.map((b) => ({ ...b, billDate: shanghaiYmd(b.billDate) })),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
summary,
|
||||
});
|
||||
}
|
||||
|
||||
async getAdminWineryBill(id: bigint) {
|
||||
@@ -2334,7 +2366,7 @@ export class SettlementService implements OnModuleInit {
|
||||
});
|
||||
if (!bill) throw new NotFoundException('酒厂对账单不存在');
|
||||
const wineryBank = await loadWineryBankConfig(this.prisma);
|
||||
return serializeBigInt({ ...bill, wineryBank });
|
||||
return serializeBigInt({ ...bill, billDate: shanghaiYmd(bill.billDate), wineryBank });
|
||||
}
|
||||
|
||||
async confirmWineryBill(id: bigint, dto: { paymentRef?: string } = {}) {
|
||||
@@ -2418,7 +2450,7 @@ export class SettlementService implements OnModuleInit {
|
||||
rows.push(
|
||||
[
|
||||
csvEscape(b.billNo),
|
||||
b.billDate.toISOString().slice(0, 10),
|
||||
shanghaiYmd(b.billDate),
|
||||
'',
|
||||
'',
|
||||
Number(b.orderAmount),
|
||||
@@ -2435,7 +2467,7 @@ export class SettlementService implements OnModuleInit {
|
||||
rows.push(
|
||||
[
|
||||
csvEscape(b.billNo),
|
||||
b.billDate.toISOString().slice(0, 10),
|
||||
shanghaiYmd(b.billDate),
|
||||
csvEscape(item.orderNo),
|
||||
item.deliveryType === 'LOCAL' ? '同城' : item.deliveryType === 'CROSS_CITY' ? '跨城' : item.deliveryType,
|
||||
Number(item.payAmount),
|
||||
@@ -2469,13 +2501,12 @@ export class SettlementService implements OnModuleInit {
|
||||
where.wineryAmount = { gt: 0 };
|
||||
}
|
||||
if (query.year && query.month) {
|
||||
const start = new Date(query.year, query.month - 1, 1);
|
||||
const end = new Date(query.year, query.month, 0, 23, 59, 59, 999);
|
||||
where.billDate = { gte: start, lte: end };
|
||||
const { start, endExclusive } = shanghaiMonthRange(query.year, query.month);
|
||||
where.billDate = { gte: start, lt: endExclusive };
|
||||
} else if (query.dateFrom || query.dateTo) {
|
||||
where.billDate = {};
|
||||
if (query.dateFrom) where.billDate.gte = startOfDay(new Date(query.dateFrom));
|
||||
if (query.dateTo) where.billDate.lte = startOfDay(new Date(query.dateTo));
|
||||
if (query.dateFrom) where.billDate.gte = parseShanghaiYmd(query.dateFrom);
|
||||
if (query.dateTo) where.billDate.lte = parseShanghaiYmd(query.dateTo);
|
||||
}
|
||||
return where;
|
||||
}
|
||||
@@ -2483,10 +2514,10 @@ export class SettlementService implements OnModuleInit {
|
||||
// ─── Logistics bills (按承运商月结) ───────────────────
|
||||
|
||||
async generatePreviousMonthLogisticsBills(anchor = new Date()) {
|
||||
const prev = new Date(anchor.getFullYear(), anchor.getMonth() - 1, 1);
|
||||
const prev = previousShanghaiMonth(anchor);
|
||||
return this.generateAllLogisticsBills({
|
||||
year: prev.getFullYear(),
|
||||
month: prev.getMonth() + 1,
|
||||
year: prev.year,
|
||||
month: prev.month,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2590,12 +2621,13 @@ export class SettlementService implements OnModuleInit {
|
||||
});
|
||||
if (!provider) throw new NotFoundException('仓配承运商不存在');
|
||||
|
||||
const periodStart = new Date(body.year, body.month - 1, 1);
|
||||
const periodEnd = new Date(body.year, body.month, 0, 23, 59, 59, 999);
|
||||
const { start: periodStart, endExclusive } = shanghaiMonthRange(body.year, body.month);
|
||||
const periodEnd = shanghaiMonthLastInstant(body.year, body.month);
|
||||
|
||||
const existing = await this.prisma.logisticsBill.findUnique({
|
||||
const existing = await this.prisma.logisticsBill.findFirst({
|
||||
where: {
|
||||
fulfillmentProviderId_periodStart: { fulfillmentProviderId, periodStart },
|
||||
fulfillmentProviderId,
|
||||
periodStart: { gte: periodStart, lt: endExclusive },
|
||||
},
|
||||
});
|
||||
if (existing?.status === 'PAID') {
|
||||
@@ -2676,6 +2708,7 @@ export class SettlementService implements OnModuleInit {
|
||||
? await tx.logisticsBill.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
periodStart,
|
||||
periodEnd,
|
||||
orderCount,
|
||||
bottleCount,
|
||||
@@ -2814,7 +2847,7 @@ export class SettlementService implements OnModuleInit {
|
||||
]);
|
||||
|
||||
const items = rawItems.map((b) => ({
|
||||
...b,
|
||||
...withShanghaiPeriod(b),
|
||||
providerCode: b.fulfillmentProvider.code,
|
||||
providerName: b.fulfillmentProvider.name,
|
||||
pricingSnapshot: this.fulfillmentProviderService.parsePricingRules(b.pricingSnapshotJson),
|
||||
@@ -2831,10 +2864,11 @@ export class SettlementService implements OnModuleInit {
|
||||
}
|
||||
|
||||
async listLogisticsProviderSummary(query: { year?: number; month?: number }) {
|
||||
const year = query.year ?? new Date().getFullYear();
|
||||
const month = query.month ?? new Date().getMonth() + 1;
|
||||
const periodStart = new Date(year, month - 1, 1);
|
||||
const periodEnd = new Date(year, month, 0, 23, 59, 59, 999);
|
||||
const fallback = shanghaiYearMonth();
|
||||
const year = query.year ?? fallback.year;
|
||||
const month = query.month ?? fallback.month;
|
||||
const { start: periodStart, endExclusive } = shanghaiMonthRange(year, month);
|
||||
const periodEnd = new Date(endExclusive.getTime() - 1);
|
||||
|
||||
const providers = await this.prisma.fulfillmentProvider.findMany({
|
||||
orderBy: { code: 'asc' },
|
||||
@@ -2897,12 +2931,10 @@ export class SettlementService implements OnModuleInit {
|
||||
);
|
||||
}
|
||||
|
||||
const bill = await this.prisma.logisticsBill.findUnique({
|
||||
const bill = await this.prisma.logisticsBill.findFirst({
|
||||
where: {
|
||||
fulfillmentProviderId_periodStart: {
|
||||
fulfillmentProviderId: p.id,
|
||||
periodStart,
|
||||
},
|
||||
fulfillmentProviderId: p.id,
|
||||
periodStart: { gte: periodStart, lt: endExclusive },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2938,7 +2970,7 @@ export class SettlementService implements OnModuleInit {
|
||||
});
|
||||
if (!bill) throw new NotFoundException('物流对账单不存在');
|
||||
return serializeBigInt({
|
||||
...bill,
|
||||
...withShanghaiPeriod(bill),
|
||||
providerCode: bill.fulfillmentProvider.code,
|
||||
providerName: bill.fulfillmentProvider.name,
|
||||
pricingSnapshot: this.fulfillmentProviderService.parsePricingRules(bill.pricingSnapshotJson),
|
||||
@@ -3056,8 +3088,8 @@ export class SettlementService implements OnModuleInit {
|
||||
csvEscape(b.billNo),
|
||||
csvEscape(p.code),
|
||||
csvEscape(p.name),
|
||||
b.periodStart.toISOString().slice(0, 10),
|
||||
b.periodEnd.toISOString().slice(0, 10),
|
||||
shanghaiPeriodYmds(b.periodStart).periodStart,
|
||||
shanghaiPeriodYmds(b.periodStart).periodEnd,
|
||||
b.settlementMethod,
|
||||
'',
|
||||
b.bottleCount,
|
||||
@@ -3075,8 +3107,8 @@ export class SettlementService implements OnModuleInit {
|
||||
csvEscape(b.billNo),
|
||||
csvEscape(p.code),
|
||||
csvEscape(p.name),
|
||||
b.periodStart.toISOString().slice(0, 10),
|
||||
b.periodEnd.toISOString().slice(0, 10),
|
||||
shanghaiPeriodYmds(b.periodStart).periodStart,
|
||||
shanghaiPeriodYmds(b.periodStart).periodEnd,
|
||||
b.settlementMethod,
|
||||
csvEscape(item.orderNo),
|
||||
item.quantity,
|
||||
@@ -3101,9 +3133,8 @@ export class SettlementService implements OnModuleInit {
|
||||
if (query.status === 'UNPAID' || query.status === 'PAID') where.status = query.status;
|
||||
if (query.providerId) where.fulfillmentProviderId = BigInt(query.providerId);
|
||||
if (query.year && query.month) {
|
||||
const periodStart = new Date(query.year, query.month - 1, 1);
|
||||
const periodEnd = new Date(query.year, query.month, 0, 23, 59, 59, 999);
|
||||
where.periodStart = { gte: periodStart, lte: periodEnd };
|
||||
const { start, endExclusive } = shanghaiMonthRange(query.year, query.month);
|
||||
where.periodStart = { gte: start, lt: endExclusive };
|
||||
}
|
||||
return where;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user