feat: manage owner usage history records

This commit is contained in:
Wyndham ARR
2026-08-03 16:43:27 +08:00
parent 393b841023
commit 84443d5dba
17 changed files with 798 additions and 102 deletions

View File

@@ -166,6 +166,7 @@ export class ImportSnapshotRepository {
));
this.ownerById = new Map(this.owners.map(owner => [owner.id, owner]));
this.periodBalances = new Map();
this.periodInitialBalances = new Map();
this.periodYears = new Set();
this.idempotentResponses = new Map();
@@ -183,6 +184,7 @@ export class ImportSnapshotRepository {
const rows2025 = usageByRoomAndYear.get(`${room}|2025`) ?? [];
const carryForward = rows2025.length > 0 ? Number(rows2025.at(-1).balance) : 0;
this.periodYears.add(periodKey(owner.id, 2026));
this.periodInitialBalances.set(periodKey(owner.id, 2026), 15 + carryForward);
const used2026 = (usageByRoomAndYear.get(`${room}|2026`) ?? [])
.reduce((sum, record) => sum + Number(record.use), 0);
this.periodBalances.set(periodKey(owner.id, 2026), 15 + carryForward - used2026);
@@ -195,6 +197,7 @@ export class ImportSnapshotRepository {
for (const year of otherYears) {
const rows = usageByRoomAndYear.get(`${room}|${year}`) ?? [];
this.periodYears.add(periodKey(owner.id, year));
this.periodInitialBalances.set(periodKey(owner.id, year), 15);
this.periodBalances.set(
periodKey(owner.id, year),
15 - rows.reduce((sum, record) => sum + Number(record.use), 0)
@@ -252,7 +255,7 @@ export class ImportSnapshotRepository {
status: "ok",
database: "booking_test",
schema: "condon",
migrationVersion: "002_legacy_import_and_bookings:snapshot"
migrationVersion: "003_delete_usage_record:snapshot"
};
}
@@ -303,6 +306,38 @@ export class ImportSnapshotRepository {
return paginate(filtered.map(entry => ({ ...entry.publicRecord })), query);
}
rebuildPeriodBalance(ownerAccountId, periodYear) {
const key = periodKey(ownerAccountId, periodYear);
const entries = this.usageEntries
.filter(entry => entry.publicRecord.ownerAccountId === ownerAccountId && entry.periodYear === periodYear)
.sort((left, right) => {
const leftImported = left.sourceSheet !== null;
const rightImported = right.sourceSheet !== null;
if (leftImported !== rightImported) return leftImported ? -1 : 1;
if (leftImported) return sourceOrder(left, right);
return String(left.publicRecord.createdAt).localeCompare(String(right.publicRecord.createdAt))
|| left.publicRecord.id.localeCompare(right.publicRecord.id);
});
let balance = this.periodInitialBalances.get(key) ?? 15;
for (const entry of entries) {
balance -= entry.publicRecord.use;
entry.publicRecord.balance = balance;
}
this.periodBalances.set(key, balance);
}
async deleteUsageRecord(id) {
const index = this.usageEntries.findIndex(entry => entry.publicRecord.id === id);
if (index < 0) {
throw new ApiError(404, "NOT_FOUND", "The requested business record was not found");
}
const [removed] = this.usageEntries.splice(index, 1);
for (const [key, value] of this.idempotentResponses) {
if (value.response.id === id) this.idempotentResponses.delete(key);
}
this.rebuildPeriodBalance(removed.publicRecord.ownerAccountId, removed.periodYear);
}
async createUsageRecord(input) {
const previous = this.idempotentResponses.get(input.idempotencyKey);
if (previous) {