feat: combo ticket 50/50 split + Best of Creation museum

- Combo tickets (matching multiple museums) split revenue/visits evenly
- Each museum gets its own row tagged with TicketType=combo, ComboWith
- Added Best of Creation (متحف خير الخلق) to museum mapping
- Holy Quraan Museum now shows 3.3M total (was 971K without combo share)
- ComboMuseums column tracks split factor for auditing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-31 13:53:25 +03:00
parent 1f1e0756d0
commit 4f4559023b
4 changed files with 57 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
import { fetchSales } from './erpClient';
import { discoverTableIds, deleteRowsByMonth, deleteAllRows, insertRecords } from './nocodbClient';
import { getMuseumFromProduct, getChannelLabel } from '../config/museumMapping';
import { getMuseumsFromProduct, getChannelLabel } from '../config/museumMapping';
import type { ERPSaleRecord, AggregatedRecord } from '../types';
function generateMonthBoundaries(startYear: number, startMonth: number): Array<[string, string]> {
@@ -43,19 +43,38 @@ export function aggregateTransactions(sales: ERPSaleRecord[]): AggregatedRecord[
const channel = getChannelLabel(sale.OperatingAreaName);
for (const product of sale.Products) {
const museum = getMuseumFromProduct(product.ProductDescription);
const key = `${date}|${museum}|${channel}`;
const { museums, split } = getMuseumsFromProduct(product.ProductDescription);
const isCombo = museums.length > 1;
let entry = map.get(key);
if (!entry) {
entry = { Date: date, MuseumName: museum, Channel: channel, Visits: 0, Tickets: 0, GrossRevenue: 0, NetRevenue: 0 };
map.set(key, entry);
for (const museum of museums) {
const comboWith = isCombo
? museums.filter(m => m !== museum).join(', ')
: '';
const ticketType = isCombo ? 'combo' : 'single';
const key = `${date}|${museum}|${channel}|${ticketType}`;
let entry = map.get(key);
if (!entry) {
entry = {
Date: date,
MuseumName: museum,
Channel: channel,
TicketType: ticketType,
ComboMuseums: museums.length,
ComboWith: comboWith,
Visits: 0,
Tickets: 0,
GrossRevenue: 0,
NetRevenue: 0,
};
map.set(key, entry);
}
entry.Visits += product.PeopleCount * split;
entry.Tickets += product.UnitQuantity * split;
entry.GrossRevenue += product.TotalPrice * split;
entry.NetRevenue += (product.TotalPrice - product.TaxAmount) * split;
}
entry.Visits += product.PeopleCount;
entry.Tickets += product.UnitQuantity;
entry.GrossRevenue += product.TotalPrice;
entry.NetRevenue += product.TotalPrice - product.TaxAmount;
}
}