@porulle/plugin-pos-restaurant 0.7.0 → 0.9.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"kds-service.d.ts","sourceRoot":"","sources":["../../src/services/kds-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"kds-service.d.ts","sourceRoot":"","sources":["../../src/services/kds-service.ts"],"names":[],"mappings":"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"}
@@ -112,7 +112,7 @@ export class KDSService {
112
112
  .from(kdsTickets)
113
113
  .where(and(eq(kdsTickets.transactionId, input.transactionId), eq(kdsTickets.stationId, station.id)));
114
114
  const ticketType = existingTickets.length > 0 ? "modified" : "new_order";
115
- const ticketNumber = await this.generateTicketNumber(station.id);
115
+ const ticketNumber = await this.generateTicketNumber(orgId, station.id);
116
116
  const ticketRows = await this.db
117
117
  .insert(kdsTickets)
118
118
  .values({
@@ -232,14 +232,14 @@ export class KDSService {
232
232
  return Ok(result);
233
233
  }
234
234
  // ─── Helpers ───────────────────────────────────────────────────────
235
- async generateTicketNumber(stationId) {
235
+ async generateTicketNumber(orgId, stationId) {
236
236
  const today = new Date();
237
237
  const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
238
238
  const startOfDayISO = startOfDay.toISOString();
239
239
  const countRows = await this.db
240
240
  .select({ count: sql `COUNT(*)`.as("count") })
241
241
  .from(kdsTickets)
242
- .where(and(eq(kdsTickets.stationId, stationId), sql `${kdsTickets.createdAt} >= ${startOfDayISO}::timestamptz`));
242
+ .where(and(eq(kdsTickets.organizationId, orgId), eq(kdsTickets.stationId, stationId), sql `${kdsTickets.createdAt} >= ${startOfDayISO}::timestamptz`));
243
243
  const seq = Number(countRows[0]?.count ?? 0) + 1;
244
244
  return `KDS-${String(seq).padStart(4, "0")}`;
245
245
  }
@@ -48,7 +48,8 @@ export declare class RecipeDeductionService {
48
48
  * - Used when services are not available (standalone scripts, tests)
49
49
  * - Same oversell protection via UPDATE ... WHERE quantity_on_hand >= N
50
50
  */
51
- applyDeductions(tx: Db, deductions: DeductionItem[], warehouseId: string, referenceType: string, referenceId: string, performedBy: string): Promise<PluginResult<number>>;
51
+ applyDeductions(tx: Db, deductions: DeductionItem[], warehouseId: string, referenceType: string, referenceId: string, performedBy: string, orgId?: string): Promise<PluginResult<number>>;
52
+ private resolveOrgIdFromWarehouse;
52
53
  /**
53
54
  * Strategy 1: Deduct via kernel.services.inventory.adjust().
54
55
  * Each deduction becomes a negative adjustment with a reason.
@@ -1 +1 @@
1
- {"version":3,"file":"recipe-deduction-service.d.ts","sourceRoot":"","sources":["../../src/services/recipe-deduction-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAUtC,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,sBAAsB;IAI/B,OAAO,CAAC,EAAE;IAHZ,OAAO,CAAC,YAAY,CAAC,CAAoB;gBAG/B,EAAE,EAAE,EAAE,EACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAQpC;;;OAGG;IACG,iBAAiB,CACrB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,GACnD,OAAO,CAAC,YAAY,CAAC,aAAa,EAAE,CAAC,CAAC;IAwCzC;;;;;;;;;;;OAWG;IACG,eAAe,CACnB,EAAE,EAAE,EAAE,EACN,UAAU,EAAE,aAAa,EAAE,EAC3B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAQhC;;;OAGG;YACW,eAAe;IAwC7B;;;OAGG;YACW,cAAc;CA6C7B"}
1
+ {"version":3,"file":"recipe-deduction-service.d.ts","sourceRoot":"","sources":["../../src/services/recipe-deduction-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAUtC,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,sBAAsB;IAI/B,OAAO,CAAC,EAAE;IAHZ,OAAO,CAAC,YAAY,CAAC,CAAoB;gBAG/B,EAAE,EAAE,EAAE,EACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAQpC;;;OAGG;IACG,iBAAiB,CACrB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,GACnD,OAAO,CAAC,YAAY,CAAC,aAAa,EAAE,CAAC,CAAC;IAwCzC;;;;;;;;;;;OAWG;IACG,eAAe,CACnB,EAAE,EAAE,EAAE,EACN,UAAU,EAAE,aAAa,EAAE,EAC3B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAmBlB,yBAAyB;IAUvC;;;OAGG;YACW,eAAe;IAwC7B;;;OAGG;YACW,cAAc;CA+C7B"}
@@ -15,7 +15,7 @@
15
15
  * falls back to raw SQL with the same atomic guard.
16
16
  */
17
17
  import { eq, and, sql } from "@porulle/core/drizzle";
18
- import { Ok } from "@porulle/core";
18
+ import { Ok, Err } from "@porulle/core";
19
19
  import { posRecipes, posRecipeIngredients } from "../schema.js";
20
20
  export class RecipeDeductionService {
21
21
  db;
@@ -73,11 +73,21 @@ export class RecipeDeductionService {
73
73
  * - Used when services are not available (standalone scripts, tests)
74
74
  * - Same oversell protection via UPDATE ... WHERE quantity_on_hand >= N
75
75
  */
76
- async applyDeductions(tx, deductions, warehouseId, referenceType, referenceId, performedBy) {
76
+ async applyDeductions(tx, deductions, warehouseId, referenceType, referenceId, performedBy, orgId) {
77
77
  if (this.inventorySvc) {
78
78
  return this.applyViaService(deductions, warehouseId, performedBy);
79
79
  }
80
- return this.applyViaRawSQL(tx, deductions, warehouseId, referenceType, referenceId, performedBy);
80
+ const resolvedOrgId = orgId ?? await this.resolveOrgIdFromWarehouse(tx, warehouseId);
81
+ if (!resolvedOrgId)
82
+ return Err("Warehouse not found");
83
+ return this.applyViaRawSQL(tx, deductions, warehouseId, referenceType, referenceId, performedBy, resolvedOrgId);
84
+ }
85
+ async resolveOrgIdFromWarehouse(tx, warehouseId) {
86
+ const result = await tx.execute(sql `SELECT organization_id FROM warehouses WHERE id = ${warehouseId} LIMIT 1`);
87
+ const rows = Array.isArray(result)
88
+ ? result
89
+ : result.rows;
90
+ return rows[0]?.organization_id ?? null;
81
91
  }
82
92
  /**
83
93
  * Strategy 1: Deduct via kernel.services.inventory.adjust().
@@ -115,7 +125,7 @@ export class RecipeDeductionService {
115
125
  * Strategy 2: Raw SQL fallback with atomic oversell guard.
116
126
  * Used when ServiceRegistry is not available.
117
127
  */
118
- async applyViaRawSQL(tx, deductions, warehouseId, referenceType, referenceId, performedBy) {
128
+ async applyViaRawSQL(tx, deductions, warehouseId, referenceType, referenceId, performedBy, orgId) {
119
129
  let applied = 0;
120
130
  // PluginDb (PgDatabase) has .execute() — no cast needed
121
131
  const exec = tx;
@@ -127,16 +137,17 @@ export class RecipeDeductionService {
127
137
  const updateResult = await exec.execute(sql `UPDATE inventory_levels
128
138
  SET quantity_on_hand = quantity_on_hand - ${d.quantity}, updated_at = NOW()
129
139
  WHERE entity_id = ${d.entityId} AND warehouse_id = ${warehouseId} AND ${variantClause}
140
+ AND organization_id = ${orgId}
130
141
  AND quantity_on_hand >= ${d.quantity}
131
142
  RETURNING quantity_on_hand`);
132
143
  const rows = Array.isArray(updateResult) ? updateResult : updateResult.rows;
133
144
  if (rows.length === 0) {
134
- await exec.execute(sql `INSERT INTO inventory_movements (entity_id, variant_id, warehouse_id, type, quantity, reference_type, reference_id, reason, performed_by)
135
- VALUES (${d.entityId}, ${d.variantId}, ${warehouseId}, 'sale', ${0}, ${referenceType}, ${referenceId}, ${"SKIPPED: insufficient stock for " + d.reason}, ${performedBy})`);
145
+ await exec.execute(sql `INSERT INTO inventory_movements (entity_id, variant_id, warehouse_id, organization_id, type, quantity, reference_type, reference_id, reason, performed_by)
146
+ VALUES (${d.entityId}, ${d.variantId}, ${warehouseId}, ${orgId}, 'sale', ${0}, ${referenceType}, ${referenceId}, ${"SKIPPED: insufficient stock for " + d.reason}, ${performedBy})`);
136
147
  continue;
137
148
  }
138
- await exec.execute(sql `INSERT INTO inventory_movements (entity_id, variant_id, warehouse_id, type, quantity, reference_type, reference_id, reason, performed_by)
139
- VALUES (${d.entityId}, ${d.variantId}, ${warehouseId}, 'sale', ${-d.quantity}, ${referenceType}, ${referenceId}, ${d.reason}, ${performedBy})`);
149
+ await exec.execute(sql `INSERT INTO inventory_movements (entity_id, variant_id, warehouse_id, organization_id, type, quantity, reference_type, reference_id, reason, performed_by)
150
+ VALUES (${d.entityId}, ${d.variantId}, ${warehouseId}, ${orgId}, 'sale', ${-d.quantity}, ${referenceType}, ${referenceId}, ${d.reason}, ${performedBy})`);
140
151
  applied++;
141
152
  }
142
153
  return Ok(applied);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/plugin-pos-restaurant",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -19,8 +19,8 @@
19
19
  "dependencies": {
20
20
  "@hono/zod-openapi": "^1.2.2",
21
21
  "hono": "^4.12.5",
22
- "@porulle/core": "0.7.0",
23
- "@porulle/plugin-pos": "0.7.0"
22
+ "@porulle/plugin-pos": "0.9.0",
23
+ "@porulle/core": "0.9.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/node": "^24.5.2",
@@ -175,7 +175,7 @@ export class KDSService {
175
175
  ));
176
176
 
177
177
  const ticketType = existingTickets.length > 0 ? "modified" : "new_order";
178
- const ticketNumber = await this.generateTicketNumber(station.id);
178
+ const ticketNumber = await this.generateTicketNumber(orgId, station.id);
179
179
 
180
180
  const ticketRows = await this.db
181
181
  .insert(kdsTickets)
@@ -321,7 +321,7 @@ export class KDSService {
321
321
 
322
322
  // ─── Helpers ───────────────────────────────────────────────────────
323
323
 
324
- private async generateTicketNumber(stationId: string): Promise<string> {
324
+ private async generateTicketNumber(orgId: string, stationId: string): Promise<string> {
325
325
  const today = new Date();
326
326
  const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
327
327
  const startOfDayISO = startOfDay.toISOString();
@@ -330,6 +330,7 @@ export class KDSService {
330
330
  .select({ count: sql<number>`COUNT(*)`.as("count") })
331
331
  .from(kdsTickets)
332
332
  .where(and(
333
+ eq(kdsTickets.organizationId, orgId),
333
334
  eq(kdsTickets.stationId, stationId),
334
335
  sql`${kdsTickets.createdAt} >= ${startOfDayISO}::timestamptz`,
335
336
  ));
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import { eq, and, sql } from "@porulle/core/drizzle";
19
- import { Ok } from "@porulle/core";
19
+ import { Ok, Err } from "@porulle/core";
20
20
  import type { PluginResult } from "@porulle/core";
21
21
  import { posRecipes, posRecipeIngredients } from "../schema.js";
22
22
  import type { Db } from "../types.js";
@@ -117,12 +117,34 @@ export class RecipeDeductionService {
117
117
  referenceType: string,
118
118
  referenceId: string,
119
119
  performedBy: string,
120
+ orgId?: string,
120
121
  ): Promise<PluginResult<number>> {
121
122
  if (this.inventorySvc) {
122
123
  return this.applyViaService(deductions, warehouseId, performedBy);
123
124
  }
124
125
 
125
- return this.applyViaRawSQL(tx, deductions, warehouseId, referenceType, referenceId, performedBy);
126
+ const resolvedOrgId = orgId ?? await this.resolveOrgIdFromWarehouse(tx, warehouseId);
127
+ if (!resolvedOrgId) return Err("Warehouse not found");
128
+
129
+ return this.applyViaRawSQL(
130
+ tx,
131
+ deductions,
132
+ warehouseId,
133
+ referenceType,
134
+ referenceId,
135
+ performedBy,
136
+ resolvedOrgId,
137
+ );
138
+ }
139
+
140
+ private async resolveOrgIdFromWarehouse(tx: Db, warehouseId: string): Promise<string | null> {
141
+ const result = await tx.execute(
142
+ sql`SELECT organization_id FROM warehouses WHERE id = ${warehouseId} LIMIT 1`,
143
+ );
144
+ const rows = Array.isArray(result)
145
+ ? result as Array<{ organization_id: string }>
146
+ : (result as { rows: Array<{ organization_id: string }> }).rows;
147
+ return rows[0]?.organization_id ?? null;
126
148
  }
127
149
 
128
150
  /**
@@ -180,6 +202,7 @@ export class RecipeDeductionService {
180
202
  referenceType: string,
181
203
  referenceId: string,
182
204
  performedBy: string,
205
+ orgId: string,
183
206
  ): Promise<PluginResult<number>> {
184
207
  let applied = 0;
185
208
  // PluginDb (PgDatabase) has .execute() — no cast needed
@@ -195,6 +218,7 @@ export class RecipeDeductionService {
195
218
  sql`UPDATE inventory_levels
196
219
  SET quantity_on_hand = quantity_on_hand - ${d.quantity}, updated_at = NOW()
197
220
  WHERE entity_id = ${d.entityId} AND warehouse_id = ${warehouseId} AND ${variantClause}
221
+ AND organization_id = ${orgId}
198
222
  AND quantity_on_hand >= ${d.quantity}
199
223
  RETURNING quantity_on_hand`,
200
224
  );
@@ -202,15 +226,15 @@ export class RecipeDeductionService {
202
226
  const rows = Array.isArray(updateResult) ? updateResult : (updateResult as { rows: unknown[] }).rows;
203
227
  if (rows.length === 0) {
204
228
  await exec.execute(
205
- sql`INSERT INTO inventory_movements (entity_id, variant_id, warehouse_id, type, quantity, reference_type, reference_id, reason, performed_by)
206
- VALUES (${d.entityId}, ${d.variantId}, ${warehouseId}, 'sale', ${0}, ${referenceType}, ${referenceId}, ${"SKIPPED: insufficient stock for " + d.reason}, ${performedBy})`,
229
+ sql`INSERT INTO inventory_movements (entity_id, variant_id, warehouse_id, organization_id, type, quantity, reference_type, reference_id, reason, performed_by)
230
+ VALUES (${d.entityId}, ${d.variantId}, ${warehouseId}, ${orgId}, 'sale', ${0}, ${referenceType}, ${referenceId}, ${"SKIPPED: insufficient stock for " + d.reason}, ${performedBy})`,
207
231
  );
208
232
  continue;
209
233
  }
210
234
 
211
235
  await exec.execute(
212
- sql`INSERT INTO inventory_movements (entity_id, variant_id, warehouse_id, type, quantity, reference_type, reference_id, reason, performed_by)
213
- VALUES (${d.entityId}, ${d.variantId}, ${warehouseId}, 'sale', ${-d.quantity}, ${referenceType}, ${referenceId}, ${d.reason}, ${performedBy})`,
236
+ sql`INSERT INTO inventory_movements (entity_id, variant_id, warehouse_id, organization_id, type, quantity, reference_type, reference_id, reason, performed_by)
237
+ VALUES (${d.entityId}, ${d.variantId}, ${warehouseId}, ${orgId}, 'sale', ${-d.quantity}, ${referenceType}, ${referenceId}, ${d.reason}, ${performedBy})`,
214
238
  );
215
239
 
216
240
  applied++;