@voyant-travel/mice 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,24 +1,11 @@
1
- import type { LinkableDefinition, Module } from "@voyant-travel/core";
1
+ import type { Module } from "@voyant-travel/core";
2
2
  import type { HonoModule } from "@voyant-travel/hono/module";
3
3
  /**
4
4
  * The MICE spine is operator-local (niche) — registered in the deployment, NOT
5
5
  * the framework standard set. The allotment primitives it links to (room
6
6
  * blocks, function spaces) are standard and package-owned. See RFC voyant#1489.
7
7
  */
8
- export declare const programLinkable: LinkableDefinition;
9
- export declare const sessionLinkable: LinkableDefinition;
10
- export declare const delegateLinkable: LinkableDefinition;
11
- export declare const roomingAssignmentLinkable: LinkableDefinition;
12
- export declare const rfpLinkable: LinkableDefinition;
13
- export declare const bidLinkable: LinkableDefinition;
14
- export declare const miceLinkable: {
15
- program: LinkableDefinition;
16
- session: LinkableDefinition;
17
- delegate: LinkableDefinition;
18
- roomingAssignment: LinkableDefinition;
19
- rfp: LinkableDefinition;
20
- bid: LinkableDefinition;
21
- };
8
+ export { bidLinkable, delegateLinkable, miceLinkable, programLinkable, rfpLinkable, roomingAssignmentLinkable, sessionLinkable, } from "./linkables.js";
22
9
  export declare const miceModule: Module;
23
10
  export declare const miceHonoModule: HonoModule;
24
11
  export declare const miceHonoModules: readonly [HonoModule];
@@ -26,6 +13,7 @@ export type { MiceAdminRoutes } from "./routes.js";
26
13
  export { miceAdminRoutes } from "./routes.js";
27
14
  export * from "./schema.js";
28
15
  export * from "./service.js";
16
+ export * from "./service-commercials.js";
29
17
  export * from "./service-delegates.js";
30
18
  export * from "./service-rfp.js";
31
19
  export * from "./service-rooming.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAI5D;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,kBAK7B,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,kBAK7B,CAAA;AAED,eAAO,MAAM,gBAAgB,EAAE,kBAK9B,CAAA;AAED,eAAO,MAAM,yBAAyB,EAAE,kBAKvC,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,kBAKzB,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,kBAKzB,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;CAOxB,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,MAMxB,CAAA;AAED,eAAO,MAAM,cAAc,EAAE,UAG5B,CAAA;AAED,eAAO,MAAM,eAAe,uBAA4B,CAAA;AAExD,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,sBAAsB,CAAA;AACpC,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA;AACnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAK5D;;;;GAIG;AACH,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,WAAW,EACX,yBAAyB,EACzB,eAAe,GAChB,MAAM,gBAAgB,CAAA;AAEvB,eAAO,MAAM,UAAU,EAAE,MAMxB,CAAA;AAED,eAAO,MAAM,cAAc,EAAE,UAG5B,CAAA;AAED,eAAO,MAAM,eAAe,uBAA4B,CAAA;AAExD,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,0BAA0B,CAAA;AACxC,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,sBAAsB,CAAA;AACpC,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA;AACnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA"}
package/dist/index.js CHANGED
@@ -1,53 +1,11 @@
1
+ import { miceLinkable } from "./linkables.js";
1
2
  import { miceAdminRoutes } from "./routes.js";
2
3
  /**
3
4
  * The MICE spine is operator-local (niche) — registered in the deployment, NOT
4
5
  * the framework standard set. The allotment primitives it links to (room
5
6
  * blocks, function spaces) are standard and package-owned. See RFC voyant#1489.
6
7
  */
7
- export const programLinkable = {
8
- module: "mice",
9
- entity: "program",
10
- table: "mice_programs",
11
- idPrefix: "prog",
12
- };
13
- export const sessionLinkable = {
14
- module: "mice",
15
- entity: "session",
16
- table: "mice_program_sessions",
17
- idPrefix: "mpss",
18
- };
19
- export const delegateLinkable = {
20
- module: "mice",
21
- entity: "delegate",
22
- table: "mice_program_delegates",
23
- idPrefix: "mpdl",
24
- };
25
- export const roomingAssignmentLinkable = {
26
- module: "mice",
27
- entity: "roomingAssignment",
28
- table: "mice_rooming_assignments",
29
- idPrefix: "mrma",
30
- };
31
- export const rfpLinkable = {
32
- module: "mice",
33
- entity: "rfp",
34
- table: "mice_rfps",
35
- idPrefix: "mrfp",
36
- };
37
- export const bidLinkable = {
38
- module: "mice",
39
- entity: "bid",
40
- table: "mice_bids",
41
- idPrefix: "mbid",
42
- };
43
- export const miceLinkable = {
44
- program: programLinkable,
45
- session: sessionLinkable,
46
- delegate: delegateLinkable,
47
- roomingAssignment: roomingAssignmentLinkable,
48
- rfp: rfpLinkable,
49
- bid: bidLinkable,
50
- };
8
+ export { bidLinkable, delegateLinkable, miceLinkable, programLinkable, rfpLinkable, roomingAssignmentLinkable, sessionLinkable, } from "./linkables.js";
51
9
  export const miceModule = {
52
10
  name: "mice",
53
11
  // Enrollment, rooming-delegate replace, and session-inclusion set run inside
@@ -63,6 +21,7 @@ export const miceHonoModules = [miceHonoModule];
63
21
  export { miceAdminRoutes } from "./routes.js";
64
22
  export * from "./schema.js";
65
23
  export * from "./service.js";
24
+ export * from "./service-commercials.js";
66
25
  export * from "./service-delegates.js";
67
26
  export * from "./service-rfp.js";
68
27
  export * from "./service-rooming.js";
@@ -0,0 +1,16 @@
1
+ import type { LinkableDefinition } from "@voyant-travel/core";
2
+ export declare const programLinkable: LinkableDefinition;
3
+ export declare const sessionLinkable: LinkableDefinition;
4
+ export declare const delegateLinkable: LinkableDefinition;
5
+ export declare const roomingAssignmentLinkable: LinkableDefinition;
6
+ export declare const rfpLinkable: LinkableDefinition;
7
+ export declare const bidLinkable: LinkableDefinition;
8
+ export declare const miceLinkable: {
9
+ program: LinkableDefinition;
10
+ session: LinkableDefinition;
11
+ delegate: LinkableDefinition;
12
+ roomingAssignment: LinkableDefinition;
13
+ rfp: LinkableDefinition;
14
+ bid: LinkableDefinition;
15
+ };
16
+ //# sourceMappingURL=linkables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkables.d.ts","sourceRoot":"","sources":["../src/linkables.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAE7D,eAAO,MAAM,eAAe,EAAE,kBAK7B,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,kBAK7B,CAAA;AAED,eAAO,MAAM,gBAAgB,EAAE,kBAK9B,CAAA;AAED,eAAO,MAAM,yBAAyB,EAAE,kBAKvC,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,kBAKzB,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,kBAKzB,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;CAOxB,CAAA"}
@@ -0,0 +1,44 @@
1
+ export const programLinkable = {
2
+ module: "mice",
3
+ entity: "program",
4
+ table: "mice_programs",
5
+ idPrefix: "prog",
6
+ };
7
+ export const sessionLinkable = {
8
+ module: "mice",
9
+ entity: "session",
10
+ table: "mice_program_sessions",
11
+ idPrefix: "mpss",
12
+ };
13
+ export const delegateLinkable = {
14
+ module: "mice",
15
+ entity: "delegate",
16
+ table: "mice_program_delegates",
17
+ idPrefix: "mpdl",
18
+ };
19
+ export const roomingAssignmentLinkable = {
20
+ module: "mice",
21
+ entity: "roomingAssignment",
22
+ table: "mice_rooming_assignments",
23
+ idPrefix: "mrma",
24
+ };
25
+ export const rfpLinkable = {
26
+ module: "mice",
27
+ entity: "rfp",
28
+ table: "mice_rfps",
29
+ idPrefix: "mrfp",
30
+ };
31
+ export const bidLinkable = {
32
+ module: "mice",
33
+ entity: "bid",
34
+ table: "mice_bids",
35
+ idPrefix: "mbid",
36
+ };
37
+ export const miceLinkable = {
38
+ program: programLinkable,
39
+ session: sessionLinkable,
40
+ delegate: delegateLinkable,
41
+ roomingAssignment: roomingAssignmentLinkable,
42
+ rfp: rfpLinkable,
43
+ bid: bidLinkable,
44
+ };
package/dist/routes.d.ts CHANGED
@@ -122,6 +122,53 @@ export declare const miceAdminRoutes: import("hono/hono-base").HonoBase<Env, {
122
122
  status: import("hono/utils/http-status").ContentfulStatusCode;
123
123
  };
124
124
  };
125
+ } & {
126
+ "/programs/:id/cost-sheet": {
127
+ $get: {
128
+ input: {
129
+ param: {
130
+ id: string;
131
+ };
132
+ };
133
+ output: {
134
+ error: string;
135
+ };
136
+ outputFormat: "json";
137
+ status: 404;
138
+ } | {
139
+ input: {
140
+ param: {
141
+ id: string;
142
+ };
143
+ };
144
+ output: {
145
+ data: {
146
+ programId: string;
147
+ mixedCurrency: boolean;
148
+ byCurrency: {
149
+ currency: string;
150
+ roomBlocks: {
151
+ contractedCostCents: number;
152
+ pickedCostCents: number;
153
+ pickedSellCents: number;
154
+ };
155
+ spaceBlocks: {
156
+ contractedCostCents: number;
157
+ pickedCostCents: number;
158
+ pickedSellCents: number;
159
+ };
160
+ sessionInclusionsCostCents: number;
161
+ costCents: number;
162
+ sellCents: number;
163
+ marginCents: number;
164
+ marginPct: number | null;
165
+ }[];
166
+ };
167
+ };
168
+ outputFormat: "json";
169
+ status: import("hono/utils/http-status").ContentfulStatusCode;
170
+ };
171
+ };
125
172
  } & {
126
173
  "/programs/:id": {
127
174
  $patch: {
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AA0DjE,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCA0MxB,CAAA;AAEJ,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAA"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AA2DjE,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAiNxB,CAAA;AAEJ,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAA"}
package/dist/routes.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { parseJsonBody, parseQuery } from "@voyant-travel/hono";
6
6
  import { Hono } from "hono";
7
7
  import { createProgram, getProgram, listPrograms, updateProgram } from "./service.js";
8
+ import { commercialsService } from "./service-commercials.js";
8
9
  import { createDelegate, enrollDelegate, getDelegate, listDelegates, updateDelegate, } from "./service-delegates.js";
9
10
  import { rfpService } from "./service-rfp.js";
10
11
  import { createRoomingAssignment, getRoomingAssignment, listRoomingAssignments, setRoomingDelegates, updateRoomingAssignment, } from "./service-rooming.js";
@@ -28,6 +29,14 @@ export const miceAdminRoutes = new Hono()
28
29
  if (!program)
29
30
  return c.json({ error: "Program not found" }, 404);
30
31
  return c.json({ data: program });
32
+ })
33
+ // Consolidated commercials — program cost sheet / P&L (Phase 5).
34
+ .get("/programs/:id/cost-sheet", async (c) => {
35
+ const id = c.req.param("id");
36
+ const program = await getProgram(c.get("db"), id);
37
+ if (!program)
38
+ return c.json({ error: "Program not found" }, 404);
39
+ return c.json({ data: await commercialsService.getProgramCostSheet(c.get("db"), id) });
31
40
  })
32
41
  .patch("/programs/:id", async (c) => {
33
42
  const body = await parseJsonBody(c, updateProgramSchema);
@@ -0,0 +1,42 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ /**
3
+ * Consolidated commercials — a program P&L / cost sheet computed on the fly from
4
+ * the program's committed inventory (room blocks, space blocks, session
5
+ * inclusions). No new spine tables (RFC §7 Phase 5): a read model over what
6
+ * Phases 1–4 persist.
7
+ *
8
+ * Each source carries its own currency, so totals are GROUPED BY CURRENCY — no
9
+ * FX conversion is assumed. `mixedCurrency` flags programs spanning more than
10
+ * one. Amounts whose source row has no currency fall back to the program
11
+ * currency, or `UNSPECIFIED` when the program has none either.
12
+ *
13
+ * Per category: contracted exposure (held × net) and the actualized figures on
14
+ * picked-up inventory (cost + sell); margin is on the actualized numbers.
15
+ */
16
+ export interface CostSheetCategory {
17
+ contractedCostCents: number;
18
+ pickedCostCents: number;
19
+ pickedSellCents: number;
20
+ }
21
+ export interface CostSheetCurrencyTotals {
22
+ currency: string;
23
+ roomBlocks: CostSheetCategory;
24
+ spaceBlocks: CostSheetCategory;
25
+ sessionInclusionsCostCents: number;
26
+ costCents: number;
27
+ sellCents: number;
28
+ marginCents: number;
29
+ /** Margin as a % of sell, or null when there is no realized revenue yet. */
30
+ marginPct: number | null;
31
+ }
32
+ export interface ProgramCostSheet {
33
+ programId: string;
34
+ mixedCurrency: boolean;
35
+ /** One entry per currency present, sorted by currency code. */
36
+ byCurrency: CostSheetCurrencyTotals[];
37
+ }
38
+ export declare function getProgramCostSheet(db: PostgresJsDatabase, programId: string): Promise<ProgramCostSheet>;
39
+ export declare const commercialsService: {
40
+ getProgramCostSheet: typeof getProgramCostSheet;
41
+ };
42
+ //# sourceMappingURL=service-commercials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-commercials.d.ts","sourceRoot":"","sources":["../src/service-commercials.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAKjE;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,iBAAiB;IAChC,mBAAmB,EAAE,MAAM,CAAA;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,iBAAiB,CAAA;IAC7B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,0BAA0B,EAAE,MAAM,CAAA;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;IACtB,+DAA+D;IAC/D,UAAU,EAAE,uBAAuB,EAAE,CAAA;CACtC;AAgBD,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,gBAAgB,CAAC,CAoI3B;AAED,eAAO,MAAM,kBAAkB;;CAA0B,CAAA"}
@@ -0,0 +1,125 @@
1
+ import { roomBlockNights, roomBlocks } from "@voyant-travel/accommodations/schema";
2
+ import { spaceBlockSlots, spaceBlocks } from "@voyant-travel/operations";
3
+ import { eq, inArray } from "drizzle-orm";
4
+ import { programs } from "./schema.js";
5
+ import { programSessions, sessionInclusions } from "./schema-sessions.js";
6
+ const UNSPECIFIED = "UNSPECIFIED";
7
+ const emptyCategory = () => ({
8
+ contractedCostCents: 0,
9
+ pickedCostCents: 0,
10
+ pickedSellCents: 0,
11
+ });
12
+ export async function getProgramCostSheet(db, programId) {
13
+ const [program] = await db
14
+ .select({ currency: programs.currency })
15
+ .from(programs)
16
+ .where(eq(programs.id, programId))
17
+ .limit(1);
18
+ const fallbackCurrency = program?.currency ?? UNSPECIFIED;
19
+ const buckets = new Map();
20
+ const bucketFor = (currency) => {
21
+ let b = buckets.get(currency);
22
+ if (!b) {
23
+ b = {
24
+ roomBlocks: emptyCategory(),
25
+ spaceBlocks: emptyCategory(),
26
+ sessionInclusionsCostCents: 0,
27
+ };
28
+ buckets.set(currency, b);
29
+ }
30
+ return b;
31
+ };
32
+ // ── Room blocks (accommodations) — currency is required on the block. ──
33
+ const rBlocks = await db
34
+ .select({
35
+ id: roomBlocks.id,
36
+ net: roomBlocks.netRateCents,
37
+ sell: roomBlocks.sellRateCents,
38
+ currency: roomBlocks.currency,
39
+ })
40
+ .from(roomBlocks)
41
+ .where(eq(roomBlocks.programId, programId));
42
+ if (rBlocks.length) {
43
+ const byId = new Map(rBlocks.map((b) => [b.id, b]));
44
+ const nights = await db
45
+ .select()
46
+ .from(roomBlockNights)
47
+ .where(inArray(roomBlockNights.blockId, rBlocks.map((b) => b.id)));
48
+ for (const n of nights) {
49
+ const block = byId.get(n.blockId);
50
+ if (!block)
51
+ continue;
52
+ const cat = bucketFor(block.currency ?? fallbackCurrency).roomBlocks;
53
+ const net = n.netRateCentsOverride ?? block.net ?? 0;
54
+ const sell = n.sellRateCentsOverride ?? block.sell ?? 0;
55
+ cat.contractedCostCents += n.roomsHeld * net;
56
+ cat.pickedCostCents += n.roomsPickedUp * net;
57
+ cat.pickedSellCents += n.roomsPickedUp * sell;
58
+ }
59
+ }
60
+ // ── Space blocks (operations) — currency nullable. ──
61
+ const sBlocks = await db
62
+ .select({
63
+ id: spaceBlocks.id,
64
+ net: spaceBlocks.netRateCents,
65
+ sell: spaceBlocks.sellRateCents,
66
+ currency: spaceBlocks.currency,
67
+ })
68
+ .from(spaceBlocks)
69
+ .where(eq(spaceBlocks.programId, programId));
70
+ if (sBlocks.length) {
71
+ const byId = new Map(sBlocks.map((b) => [b.id, b]));
72
+ const slots = await db
73
+ .select()
74
+ .from(spaceBlockSlots)
75
+ .where(inArray(spaceBlockSlots.blockId, sBlocks.map((b) => b.id)));
76
+ for (const s of slots) {
77
+ const block = byId.get(s.blockId);
78
+ if (!block)
79
+ continue;
80
+ const cat = bucketFor(block.currency ?? fallbackCurrency).spaceBlocks;
81
+ const net = s.netRateCentsOverride ?? block.net ?? 0;
82
+ const sell = s.sellRateCentsOverride ?? block.sell ?? 0;
83
+ cat.contractedCostCents += s.unitsHeld * net;
84
+ cat.pickedCostCents += s.unitsPickedUp * net;
85
+ cat.pickedSellCents += s.unitsPickedUp * sell;
86
+ }
87
+ }
88
+ // ── Session inclusions (mice) — currency nullable, cost only. ──
89
+ const sessions = await db
90
+ .select({ id: programSessions.id })
91
+ .from(programSessions)
92
+ .where(eq(programSessions.programId, programId));
93
+ if (sessions.length) {
94
+ const inclusions = await db
95
+ .select({
96
+ qty: sessionInclusions.quantity,
97
+ cost: sessionInclusions.costAmountCents,
98
+ currency: sessionInclusions.currency,
99
+ })
100
+ .from(sessionInclusions)
101
+ .where(inArray(sessionInclusions.sessionId, sessions.map((s) => s.id)));
102
+ for (const i of inclusions) {
103
+ bucketFor(i.currency ?? fallbackCurrency).sessionInclusionsCostCents += i.qty * (i.cost ?? 0);
104
+ }
105
+ }
106
+ const byCurrency = [...buckets.entries()]
107
+ .sort(([a], [b]) => a.localeCompare(b))
108
+ .map(([currency, b]) => {
109
+ const costCents = b.roomBlocks.pickedCostCents + b.spaceBlocks.pickedCostCents + b.sessionInclusionsCostCents;
110
+ const sellCents = b.roomBlocks.pickedSellCents + b.spaceBlocks.pickedSellCents;
111
+ const marginCents = sellCents - costCents;
112
+ return {
113
+ currency,
114
+ roomBlocks: b.roomBlocks,
115
+ spaceBlocks: b.spaceBlocks,
116
+ sessionInclusionsCostCents: b.sessionInclusionsCostCents,
117
+ costCents,
118
+ sellCents,
119
+ marginCents,
120
+ marginPct: sellCents > 0 ? Math.round((marginCents / sellCents) * 1000) / 10 : null,
121
+ };
122
+ });
123
+ return { programId, mixedCurrency: byCurrency.length > 1, byCurrency };
124
+ }
125
+ export const commercialsService = { getProgramCostSheet };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyant-travel/mice",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -9,6 +9,11 @@
9
9
  "import": "./dist/index.js",
10
10
  "default": "./dist/index.js"
11
11
  },
12
+ "./linkables": {
13
+ "types": "./dist/linkables.d.ts",
14
+ "import": "./dist/linkables.js",
15
+ "default": "./dist/linkables.js"
16
+ },
12
17
  "./schema": {
13
18
  "types": "./dist/schema.d.ts",
14
19
  "import": "./dist/schema.js",
@@ -34,9 +39,11 @@
34
39
  "drizzle-orm": "^0.45.2",
35
40
  "hono": "^4.12.10",
36
41
  "zod": "^4.3.6",
42
+ "@voyant-travel/accommodations": "^0.108.1",
37
43
  "@voyant-travel/core": "^0.111.0",
38
44
  "@voyant-travel/db": "^0.109.4",
39
- "@voyant-travel/hono": "^0.116.1"
45
+ "@voyant-travel/operations": "^0.5.1",
46
+ "@voyant-travel/hono": "^0.116.2"
40
47
  },
41
48
  "devDependencies": {
42
49
  "drizzle-kit": "^0.31.10",
@@ -58,7 +65,11 @@
58
65
  "directory": "packages/mice"
59
66
  },
60
67
  "voyant": {
61
- "schema": "./schema"
68
+ "schema": "./schema",
69
+ "requiresSchemas": [
70
+ "@voyant-travel/accommodations",
71
+ "@voyant-travel/operations"
72
+ ]
62
73
  },
63
74
  "scripts": {
64
75
  "typecheck": "tsc --noEmit",