@porulle/plugin-pos-restaurant 0.1.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.
- package/README.md +61 -0
- package/dist/hooks/modifier-validation.d.ts +21 -0
- package/dist/hooks/modifier-validation.d.ts.map +1 -0
- package/dist/hooks/modifier-validation.js +40 -0
- package/dist/hooks/table-lifecycle.d.ts +15 -0
- package/dist/hooks/table-lifecycle.d.ts.map +1 -0
- package/dist/hooks/table-lifecycle.js +40 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +169 -0
- package/dist/routes/kds.d.ts +9 -0
- package/dist/routes/kds.d.ts.map +1 -0
- package/dist/routes/kds.js +112 -0
- package/dist/routes/modifiers.d.ts +15 -0
- package/dist/routes/modifiers.d.ts.map +1 -0
- package/dist/routes/modifiers.js +117 -0
- package/dist/routes/operations.d.ts +34 -0
- package/dist/routes/operations.d.ts.map +1 -0
- package/dist/routes/operations.js +249 -0
- package/dist/routes/tables.d.ts +9 -0
- package/dist/routes/tables.d.ts.map +1 -0
- package/dist/routes/tables.js +110 -0
- package/dist/schema.d.ts +3831 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +418 -0
- package/dist/services/alert-service.d.ts +56 -0
- package/dist/services/alert-service.d.ts.map +1 -0
- package/dist/services/alert-service.js +173 -0
- package/dist/services/analytics-service.d.ts +60 -0
- package/dist/services/analytics-service.d.ts.map +1 -0
- package/dist/services/analytics-service.js +178 -0
- package/dist/services/checklist-service.d.ts +66 -0
- package/dist/services/checklist-service.d.ts.map +1 -0
- package/dist/services/checklist-service.js +106 -0
- package/dist/services/kds-service.d.ts +77 -0
- package/dist/services/kds-service.d.ts.map +1 -0
- package/dist/services/kds-service.js +246 -0
- package/dist/services/modifier-service.d.ts +74 -0
- package/dist/services/modifier-service.d.ts.map +1 -0
- package/dist/services/modifier-service.js +180 -0
- package/dist/services/recipe-deduction-service.d.ts +64 -0
- package/dist/services/recipe-deduction-service.d.ts.map +1 -0
- package/dist/services/recipe-deduction-service.js +144 -0
- package/dist/services/recipe-service.d.ts +43 -0
- package/dist/services/recipe-service.d.ts.map +1 -0
- package/dist/services/recipe-service.js +85 -0
- package/dist/services/table-service.d.ts +61 -0
- package/dist/services/table-service.d.ts.map +1 -0
- package/dist/services/table-service.js +206 -0
- package/dist/types.d.ts +35 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/package.json +61 -0
- package/src/hooks/modifier-validation.ts +60 -0
- package/src/hooks/table-lifecycle.ts +50 -0
- package/src/index.ts +218 -0
- package/src/routes/kds.ts +121 -0
- package/src/routes/modifiers.ts +129 -0
- package/src/routes/operations.ts +276 -0
- package/src/routes/tables.ts +117 -0
- package/src/schema.ts +462 -0
- package/src/services/alert-service.ts +234 -0
- package/src/services/analytics-service.ts +242 -0
- package/src/services/checklist-service.ts +139 -0
- package/src/services/kds-service.ts +340 -0
- package/src/services/modifier-service.ts +251 -0
- package/src/services/recipe-deduction-service.ts +221 -0
- package/src/services/recipe-service.ts +115 -0
- package/src/services/table-service.ts +260 -0
- package/src/types.ts +63 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecipeDeductionService -- resolves recipe ingredient inventory deductions
|
|
3
|
+
* when a POS transaction completes.
|
|
4
|
+
*
|
|
5
|
+
* For each sold item:
|
|
6
|
+
* 1. Look up the entity's active recipe
|
|
7
|
+
* 2. For each ingredient with an entityId, calculate deduction quantity
|
|
8
|
+
* 3. Deduct via inventory service (preferred) or raw SQL (fallback)
|
|
9
|
+
*
|
|
10
|
+
* When the ServiceRegistry is provided (normal plugin boot), deductions
|
|
11
|
+
* go through kernel.services.inventory.adjust() which creates proper
|
|
12
|
+
* movements, fires hooks, and respects audit trail.
|
|
13
|
+
*
|
|
14
|
+
* When the ServiceRegistry is not available (standalone tests, scripts),
|
|
15
|
+
* falls back to raw SQL with the same atomic guard.
|
|
16
|
+
*/
|
|
17
|
+
import { eq, and, sql } from "@porulle/core/drizzle";
|
|
18
|
+
import { Ok } from "@porulle/core";
|
|
19
|
+
import { posRecipes, posRecipeIngredients } from "../schema.js";
|
|
20
|
+
export class RecipeDeductionService {
|
|
21
|
+
db;
|
|
22
|
+
inventorySvc;
|
|
23
|
+
constructor(db, services) {
|
|
24
|
+
this.db = db;
|
|
25
|
+
// Extract inventory service if available
|
|
26
|
+
if (services?.inventory && typeof services.inventory.adjust === "function") {
|
|
27
|
+
this.inventorySvc = services.inventory;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve all recipe ingredient deductions for a list of sold items.
|
|
32
|
+
* Pure query -- no mutations. Returns a flat list to pass to applyDeductions().
|
|
33
|
+
*/
|
|
34
|
+
async resolveDeductions(orgId, items) {
|
|
35
|
+
const deductions = [];
|
|
36
|
+
for (const item of items) {
|
|
37
|
+
const recipes = await this.db
|
|
38
|
+
.select()
|
|
39
|
+
.from(posRecipes)
|
|
40
|
+
.where(and(eq(posRecipes.organizationId, orgId), eq(posRecipes.entityId, item.entityId), eq(posRecipes.isActive, true)));
|
|
41
|
+
if (recipes.length === 0)
|
|
42
|
+
continue;
|
|
43
|
+
const recipe = recipes[0];
|
|
44
|
+
const ingredients = await this.db
|
|
45
|
+
.select()
|
|
46
|
+
.from(posRecipeIngredients)
|
|
47
|
+
.where(eq(posRecipeIngredients.recipeId, recipe.id));
|
|
48
|
+
for (const ing of ingredients) {
|
|
49
|
+
if (ing.entityId == null)
|
|
50
|
+
continue;
|
|
51
|
+
const deductQty = Math.ceil((ing.quantity * item.quantity) / recipe.yieldQuantity);
|
|
52
|
+
deductions.push({
|
|
53
|
+
entityId: ing.entityId,
|
|
54
|
+
variantId: ing.variantId ?? null,
|
|
55
|
+
quantity: deductQty,
|
|
56
|
+
unit: ing.unit,
|
|
57
|
+
itemName: ing.ingredientName,
|
|
58
|
+
reason: `Recipe: ${deductQty}${ing.unit} ${ing.ingredientName} for ${item.quantity}x ${recipe.name}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return Ok(deductions);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Apply deductions to inventory.
|
|
66
|
+
*
|
|
67
|
+
* Strategy 1 (preferred): Use inventory.adjust() via ServiceRegistry.
|
|
68
|
+
* - Creates proper inventory_movements with audit trail
|
|
69
|
+
* - Fires hooks (e.g., low-stock alerts)
|
|
70
|
+
* - Respects the inventory service's stock guard logic
|
|
71
|
+
*
|
|
72
|
+
* Strategy 2 (fallback): Raw SQL with atomic WHERE guard.
|
|
73
|
+
* - Used when services are not available (standalone scripts, tests)
|
|
74
|
+
* - Same oversell protection via UPDATE ... WHERE quantity_on_hand >= N
|
|
75
|
+
*/
|
|
76
|
+
async applyDeductions(tx, deductions, warehouseId, referenceType, referenceId, performedBy) {
|
|
77
|
+
if (this.inventorySvc) {
|
|
78
|
+
return this.applyViaService(deductions, warehouseId, performedBy);
|
|
79
|
+
}
|
|
80
|
+
return this.applyViaRawSQL(tx, deductions, warehouseId, referenceType, referenceId, performedBy);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Strategy 1: Deduct via kernel.services.inventory.adjust().
|
|
84
|
+
* Each deduction becomes a negative adjustment with a reason.
|
|
85
|
+
*/
|
|
86
|
+
async applyViaService(deductions, warehouseId, performedBy) {
|
|
87
|
+
const inventory = this.inventorySvc;
|
|
88
|
+
const systemActor = {
|
|
89
|
+
type: "system",
|
|
90
|
+
userId: performedBy,
|
|
91
|
+
email: null,
|
|
92
|
+
name: "Recipe Deduction",
|
|
93
|
+
vendorId: null,
|
|
94
|
+
organizationId: null,
|
|
95
|
+
role: "system",
|
|
96
|
+
permissions: ["inventory:adjust"],
|
|
97
|
+
};
|
|
98
|
+
let applied = 0;
|
|
99
|
+
for (const d of deductions) {
|
|
100
|
+
const result = await inventory.adjust({
|
|
101
|
+
entityId: d.entityId,
|
|
102
|
+
warehouseId,
|
|
103
|
+
adjustment: -d.quantity,
|
|
104
|
+
reason: d.reason,
|
|
105
|
+
}, systemActor);
|
|
106
|
+
if (result.ok) {
|
|
107
|
+
applied++;
|
|
108
|
+
}
|
|
109
|
+
// If adjust fails (insufficient stock), it returns ok: false
|
|
110
|
+
// and the deduction is skipped -- same behavior as raw SQL guard
|
|
111
|
+
}
|
|
112
|
+
return Ok(applied);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Strategy 2: Raw SQL fallback with atomic oversell guard.
|
|
116
|
+
* Used when ServiceRegistry is not available.
|
|
117
|
+
*/
|
|
118
|
+
async applyViaRawSQL(tx, deductions, warehouseId, referenceType, referenceId, performedBy) {
|
|
119
|
+
let applied = 0;
|
|
120
|
+
// PluginDb (PgDatabase) has .execute() — no cast needed
|
|
121
|
+
const exec = tx;
|
|
122
|
+
for (const d of deductions) {
|
|
123
|
+
const variantClause = d.variantId != null
|
|
124
|
+
? sql `variant_id = ${d.variantId}`
|
|
125
|
+
: sql `variant_id IS NULL`;
|
|
126
|
+
// Atomic guard: only deduct if sufficient stock
|
|
127
|
+
const updateResult = await exec.execute(sql `UPDATE inventory_levels
|
|
128
|
+
SET quantity_on_hand = quantity_on_hand - ${d.quantity}, updated_at = NOW()
|
|
129
|
+
WHERE entity_id = ${d.entityId} AND warehouse_id = ${warehouseId} AND ${variantClause}
|
|
130
|
+
AND quantity_on_hand >= ${d.quantity}
|
|
131
|
+
RETURNING quantity_on_hand`);
|
|
132
|
+
const rows = Array.isArray(updateResult) ? updateResult : updateResult.rows;
|
|
133
|
+
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})`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
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})`);
|
|
140
|
+
applied++;
|
|
141
|
+
}
|
|
142
|
+
return Ok(applied);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecipeService — Recipe/BOM management and COGS calculation.
|
|
3
|
+
*
|
|
4
|
+
* URY: Recipe mapping using Bill of Materials (BOM). Links menu items
|
|
5
|
+
* to raw ingredients for COGS calculation in Daily P&L.
|
|
6
|
+
* URY uses ERPNext's BOM doctype + Item Price for buying cost.
|
|
7
|
+
*
|
|
8
|
+
* Our implementation: pos_recipes + pos_recipe_ingredients tables.
|
|
9
|
+
* COGS = sum(ingredient.quantity * ingredient.costPerUnit) / yieldQuantity.
|
|
10
|
+
*/
|
|
11
|
+
import type { PluginResult } from "@porulle/core";
|
|
12
|
+
import { posRecipes, posRecipeIngredients } from "../schema.js";
|
|
13
|
+
import type { Db } from "../types.js";
|
|
14
|
+
export declare class RecipeService {
|
|
15
|
+
private db;
|
|
16
|
+
constructor(db: Db);
|
|
17
|
+
createRecipe(orgId: string, input: {
|
|
18
|
+
entityId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
yieldQuantity?: number;
|
|
21
|
+
ingredients: Array<{
|
|
22
|
+
ingredientName: string;
|
|
23
|
+
quantity: number;
|
|
24
|
+
unit: string;
|
|
25
|
+
costPerUnit: number;
|
|
26
|
+
entityId?: string;
|
|
27
|
+
variantId?: string;
|
|
28
|
+
}>;
|
|
29
|
+
}): Promise<PluginResult<{
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
costPerUnit: number;
|
|
33
|
+
}>>;
|
|
34
|
+
getRecipeWithIngredients(recipeId: string): Promise<PluginResult<{
|
|
35
|
+
recipe: typeof posRecipes.$inferSelect;
|
|
36
|
+
ingredients: Array<typeof posRecipeIngredients.$inferSelect>;
|
|
37
|
+
totalCost: number;
|
|
38
|
+
costPerUnit: number;
|
|
39
|
+
}>>;
|
|
40
|
+
calculateCOGS(orgId: string, entityId: string, quantity: number): Promise<number>;
|
|
41
|
+
listRecipes(orgId: string): Promise<PluginResult<Array<typeof posRecipes.$inferSelect>>>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=recipe-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipe-service.d.ts","sourceRoot":"","sources":["../../src/services/recipe-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC,qBAAa,aAAa;IACZ,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAEpB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;QACvC,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,KAAK,CAAC;YACjB,cAAc,EAAE,MAAM,CAAC;YACvB,QAAQ,EAAE,MAAM,CAAC;YACjB,IAAI,EAAE,MAAM,CAAC;YACb,WAAW,EAAE,MAAM,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB,CAAC,CAAC;KACJ,GAAG,OAAO,CAAC,YAAY,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAkCtE,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;QACrE,MAAM,EAAE,OAAO,UAAU,CAAC,YAAY,CAAC;QACvC,WAAW,EAAE,KAAK,CAAC,OAAO,oBAAoB,CAAC,YAAY,CAAC,CAAC;QAC7D,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;IAiBG,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBjF,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC;CAO/F"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecipeService — Recipe/BOM management and COGS calculation.
|
|
3
|
+
*
|
|
4
|
+
* URY: Recipe mapping using Bill of Materials (BOM). Links menu items
|
|
5
|
+
* to raw ingredients for COGS calculation in Daily P&L.
|
|
6
|
+
* URY uses ERPNext's BOM doctype + Item Price for buying cost.
|
|
7
|
+
*
|
|
8
|
+
* Our implementation: pos_recipes + pos_recipe_ingredients tables.
|
|
9
|
+
* COGS = sum(ingredient.quantity * ingredient.costPerUnit) / yieldQuantity.
|
|
10
|
+
*/
|
|
11
|
+
import { eq, and } from "@porulle/core/drizzle";
|
|
12
|
+
import { Ok, Err } from "@porulle/core";
|
|
13
|
+
import { posRecipes, posRecipeIngredients } from "../schema.js";
|
|
14
|
+
export class RecipeService {
|
|
15
|
+
db;
|
|
16
|
+
constructor(db) {
|
|
17
|
+
this.db = db;
|
|
18
|
+
}
|
|
19
|
+
async createRecipe(orgId, input) {
|
|
20
|
+
const rows = await this.db
|
|
21
|
+
.insert(posRecipes)
|
|
22
|
+
.values({
|
|
23
|
+
organizationId: orgId,
|
|
24
|
+
entityId: input.entityId,
|
|
25
|
+
name: input.name,
|
|
26
|
+
yieldQuantity: input.yieldQuantity ?? 1,
|
|
27
|
+
})
|
|
28
|
+
.returning();
|
|
29
|
+
const recipe = rows[0];
|
|
30
|
+
let totalCost = 0;
|
|
31
|
+
for (let i = 0; i < input.ingredients.length; i++) {
|
|
32
|
+
const ing = input.ingredients[i];
|
|
33
|
+
await this.db.insert(posRecipeIngredients).values({
|
|
34
|
+
recipeId: recipe.id,
|
|
35
|
+
ingredientName: ing.ingredientName,
|
|
36
|
+
quantity: ing.quantity,
|
|
37
|
+
unit: ing.unit,
|
|
38
|
+
costPerUnit: ing.costPerUnit,
|
|
39
|
+
entityId: ing.entityId,
|
|
40
|
+
variantId: ing.variantId,
|
|
41
|
+
sortOrder: i,
|
|
42
|
+
});
|
|
43
|
+
totalCost += ing.quantity * ing.costPerUnit;
|
|
44
|
+
}
|
|
45
|
+
const costPerUnit = Math.round(totalCost / (input.yieldQuantity ?? 1));
|
|
46
|
+
return Ok({ id: recipe.id, name: recipe.name, costPerUnit });
|
|
47
|
+
}
|
|
48
|
+
async getRecipeWithIngredients(recipeId) {
|
|
49
|
+
const recipes = await this.db.select().from(posRecipes).where(eq(posRecipes.id, recipeId));
|
|
50
|
+
if (recipes.length === 0)
|
|
51
|
+
return Err("Recipe not found");
|
|
52
|
+
const recipe = recipes[0];
|
|
53
|
+
const ingredients = await this.db
|
|
54
|
+
.select()
|
|
55
|
+
.from(posRecipeIngredients)
|
|
56
|
+
.where(eq(posRecipeIngredients.recipeId, recipeId))
|
|
57
|
+
.orderBy(posRecipeIngredients.sortOrder);
|
|
58
|
+
const totalCost = ingredients.reduce((sum, i) => sum + i.quantity * i.costPerUnit, 0);
|
|
59
|
+
const costPerUnit = Math.round(totalCost / recipe.yieldQuantity);
|
|
60
|
+
return Ok({ recipe, ingredients, totalCost, costPerUnit });
|
|
61
|
+
}
|
|
62
|
+
async calculateCOGS(orgId, entityId, quantity) {
|
|
63
|
+
const recipes = await this.db
|
|
64
|
+
.select()
|
|
65
|
+
.from(posRecipes)
|
|
66
|
+
.where(and(eq(posRecipes.organizationId, orgId), eq(posRecipes.entityId, entityId), eq(posRecipes.isActive, true)));
|
|
67
|
+
if (recipes.length === 0)
|
|
68
|
+
return 0;
|
|
69
|
+
const recipe = recipes[0];
|
|
70
|
+
const ingredients = await this.db
|
|
71
|
+
.select()
|
|
72
|
+
.from(posRecipeIngredients)
|
|
73
|
+
.where(eq(posRecipeIngredients.recipeId, recipe.id));
|
|
74
|
+
const costPerYield = ingredients.reduce((sum, i) => sum + i.quantity * i.costPerUnit, 0);
|
|
75
|
+
const costPerUnit = costPerYield / recipe.yieldQuantity;
|
|
76
|
+
return Math.round(costPerUnit * quantity);
|
|
77
|
+
}
|
|
78
|
+
async listRecipes(orgId) {
|
|
79
|
+
const rows = await this.db
|
|
80
|
+
.select()
|
|
81
|
+
.from(posRecipes)
|
|
82
|
+
.where(eq(posRecipes.organizationId, orgId));
|
|
83
|
+
return Ok(rows);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TableService — Table CRUD, status management, assignment, and transfer.
|
|
3
|
+
*
|
|
4
|
+
* Informed by URY's table management patterns:
|
|
5
|
+
* - URY Table: room-based (restaurant_room), binary occupied flag, floor plan layout,
|
|
6
|
+
* shape (Circle/Square/Rectangle), is_take_away
|
|
7
|
+
* - table_transfer(): validates same room, target not occupied, updates KOT links
|
|
8
|
+
* - captain_transfer(): reassigns waiter, validates room access in multi-cashier mode
|
|
9
|
+
* - restrict_existing_order(): prevents double-booking (no 2 draft invoices on 1 table)
|
|
10
|
+
*
|
|
11
|
+
* Improvements over URY:
|
|
12
|
+
* - 4-state status machine (available -> occupied -> bill_requested -> cleaning)
|
|
13
|
+
* instead of binary occupied flag
|
|
14
|
+
* - Multi-table assignments (large party across 2+ tables) via pos_table_assignments
|
|
15
|
+
* - Server section assignment (assignedOperatorId) as first-class field
|
|
16
|
+
* - Zone-based grouping instead of room doctype FK
|
|
17
|
+
*/
|
|
18
|
+
import type { PluginResult } from "@porulle/core";
|
|
19
|
+
import type { Db, Table, TableAssignment, TableStatus } from "../types.js";
|
|
20
|
+
export declare class TableService {
|
|
21
|
+
private db;
|
|
22
|
+
constructor(db: Db);
|
|
23
|
+
create(orgId: string, input: {
|
|
24
|
+
number: string;
|
|
25
|
+
zone: string;
|
|
26
|
+
capacity?: number;
|
|
27
|
+
minimumSeats?: number;
|
|
28
|
+
shape?: "rectangle" | "square" | "circle";
|
|
29
|
+
isTakeaway?: boolean;
|
|
30
|
+
layoutX?: number;
|
|
31
|
+
layoutY?: number;
|
|
32
|
+
}): Promise<PluginResult<Table>>;
|
|
33
|
+
list(orgId: string, zone?: string): Promise<PluginResult<Table[]>>;
|
|
34
|
+
getById(orgId: string, id: string): Promise<PluginResult<Table>>;
|
|
35
|
+
update(orgId: string, id: string, input: {
|
|
36
|
+
number?: string;
|
|
37
|
+
zone?: string;
|
|
38
|
+
capacity?: number;
|
|
39
|
+
shape?: "rectangle" | "square" | "circle";
|
|
40
|
+
assignedOperatorId?: string | null;
|
|
41
|
+
metadata?: Record<string, unknown>;
|
|
42
|
+
}): Promise<PluginResult<Table>>;
|
|
43
|
+
setStatus(orgId: string, id: string, newStatus: TableStatus): Promise<PluginResult<Table>>;
|
|
44
|
+
assignToTransaction(orgId: string, tableId: string, transactionId: string): Promise<PluginResult<TableAssignment>>;
|
|
45
|
+
clear(orgId: string, tableId: string): Promise<PluginResult<Table>>;
|
|
46
|
+
transfer(orgId: string, fromTableId: string, toTableId: string): Promise<PluginResult<{
|
|
47
|
+
from: Table;
|
|
48
|
+
to: Table;
|
|
49
|
+
}>>;
|
|
50
|
+
updateLayout(orgId: string, id: string, layout: {
|
|
51
|
+
layoutX?: number;
|
|
52
|
+
layoutY?: number;
|
|
53
|
+
layoutWidth?: number;
|
|
54
|
+
layoutHeight?: number;
|
|
55
|
+
}): Promise<PluginResult<Table>>;
|
|
56
|
+
listZones(orgId: string): Promise<PluginResult<Array<{
|
|
57
|
+
zone: string;
|
|
58
|
+
count: number;
|
|
59
|
+
}>>>;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=table-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-service.d.ts","sourceRoot":"","sources":["../../src/services/table-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAS3E,qBAAa,YAAY;IACX,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAIpB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;QACjC,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;QAC1C,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IA2B1B,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC;IAalE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAUhE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;QAC1C,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAa1B,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAuB1F,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;IAgClH,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAsBnE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;QAAE,IAAI,EAAE,KAAK,CAAC;QAAC,EAAE,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC;IAqClH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;QACpD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAa1B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CAY9F"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TableService — Table CRUD, status management, assignment, and transfer.
|
|
3
|
+
*
|
|
4
|
+
* Informed by URY's table management patterns:
|
|
5
|
+
* - URY Table: room-based (restaurant_room), binary occupied flag, floor plan layout,
|
|
6
|
+
* shape (Circle/Square/Rectangle), is_take_away
|
|
7
|
+
* - table_transfer(): validates same room, target not occupied, updates KOT links
|
|
8
|
+
* - captain_transfer(): reassigns waiter, validates room access in multi-cashier mode
|
|
9
|
+
* - restrict_existing_order(): prevents double-booking (no 2 draft invoices on 1 table)
|
|
10
|
+
*
|
|
11
|
+
* Improvements over URY:
|
|
12
|
+
* - 4-state status machine (available -> occupied -> bill_requested -> cleaning)
|
|
13
|
+
* instead of binary occupied flag
|
|
14
|
+
* - Multi-table assignments (large party across 2+ tables) via pos_table_assignments
|
|
15
|
+
* - Server section assignment (assignedOperatorId) as first-class field
|
|
16
|
+
* - Zone-based grouping instead of room doctype FK
|
|
17
|
+
*/
|
|
18
|
+
import { eq, and, sql } from "@porulle/core/drizzle";
|
|
19
|
+
import { Ok, Err } from "@porulle/core";
|
|
20
|
+
import { posTables, posTableAssignments } from "../schema.js";
|
|
21
|
+
const VALID_TRANSITIONS = {
|
|
22
|
+
available: ["occupied"],
|
|
23
|
+
occupied: ["bill_requested", "available"],
|
|
24
|
+
bill_requested: ["cleaning", "available"],
|
|
25
|
+
cleaning: ["available"],
|
|
26
|
+
};
|
|
27
|
+
export class TableService {
|
|
28
|
+
db;
|
|
29
|
+
constructor(db) {
|
|
30
|
+
this.db = db;
|
|
31
|
+
}
|
|
32
|
+
// ─── CRUD ──────────────────────────────────────────────────────────
|
|
33
|
+
async create(orgId, input) {
|
|
34
|
+
// Check for duplicate number in org
|
|
35
|
+
const existing = await this.db
|
|
36
|
+
.select()
|
|
37
|
+
.from(posTables)
|
|
38
|
+
.where(and(eq(posTables.organizationId, orgId), eq(posTables.number, input.number)));
|
|
39
|
+
if (existing.length > 0)
|
|
40
|
+
return Err(`Table '${input.number}' already exists`);
|
|
41
|
+
const rows = await this.db
|
|
42
|
+
.insert(posTables)
|
|
43
|
+
.values({
|
|
44
|
+
organizationId: orgId,
|
|
45
|
+
number: input.number,
|
|
46
|
+
zone: input.zone,
|
|
47
|
+
capacity: input.capacity ?? 4,
|
|
48
|
+
minimumSeats: input.minimumSeats ?? 1,
|
|
49
|
+
shape: input.shape ?? "rectangle",
|
|
50
|
+
isTakeaway: input.isTakeaway ?? false,
|
|
51
|
+
layoutX: input.layoutX ?? 0,
|
|
52
|
+
layoutY: input.layoutY ?? 0,
|
|
53
|
+
})
|
|
54
|
+
.returning();
|
|
55
|
+
return Ok(rows[0]);
|
|
56
|
+
}
|
|
57
|
+
async list(orgId, zone) {
|
|
58
|
+
const conditions = [eq(posTables.organizationId, orgId)];
|
|
59
|
+
if (zone)
|
|
60
|
+
conditions.push(eq(posTables.zone, zone));
|
|
61
|
+
const rows = await this.db
|
|
62
|
+
.select()
|
|
63
|
+
.from(posTables)
|
|
64
|
+
.where(and(...conditions))
|
|
65
|
+
.orderBy(posTables.number);
|
|
66
|
+
return Ok(rows);
|
|
67
|
+
}
|
|
68
|
+
async getById(orgId, id) {
|
|
69
|
+
const rows = await this.db
|
|
70
|
+
.select()
|
|
71
|
+
.from(posTables)
|
|
72
|
+
.where(and(eq(posTables.id, id), eq(posTables.organizationId, orgId)));
|
|
73
|
+
if (rows.length === 0)
|
|
74
|
+
return Err("Table not found");
|
|
75
|
+
return Ok(rows[0]);
|
|
76
|
+
}
|
|
77
|
+
async update(orgId, id, input) {
|
|
78
|
+
const rows = await this.db
|
|
79
|
+
.update(posTables)
|
|
80
|
+
.set({ ...input, updatedAt: new Date() })
|
|
81
|
+
.where(and(eq(posTables.id, id), eq(posTables.organizationId, orgId)))
|
|
82
|
+
.returning();
|
|
83
|
+
if (rows.length === 0)
|
|
84
|
+
return Err("Table not found");
|
|
85
|
+
return Ok(rows[0]);
|
|
86
|
+
}
|
|
87
|
+
// ─── Status Management ─────────────────────────────────────────────
|
|
88
|
+
async setStatus(orgId, id, newStatus) {
|
|
89
|
+
const table = await this.getById(orgId, id);
|
|
90
|
+
if (!table.ok)
|
|
91
|
+
return table;
|
|
92
|
+
const current = table.value.status;
|
|
93
|
+
const allowed = VALID_TRANSITIONS[current];
|
|
94
|
+
if (!allowed?.includes(newStatus)) {
|
|
95
|
+
return Err(`Cannot transition table from '${current}' to '${newStatus}'`);
|
|
96
|
+
}
|
|
97
|
+
const rows = await this.db
|
|
98
|
+
.update(posTables)
|
|
99
|
+
.set({ status: newStatus, updatedAt: new Date() })
|
|
100
|
+
.where(eq(posTables.id, id))
|
|
101
|
+
.returning();
|
|
102
|
+
return Ok(rows[0]);
|
|
103
|
+
}
|
|
104
|
+
// ─── Assignment ────────────────────────────────────────────────────
|
|
105
|
+
// Links a table to a POS transaction. Sets table status to "occupied".
|
|
106
|
+
// URY equivalent: sync_order() setting occupied=1 + latest_invoice_time.
|
|
107
|
+
async assignToTransaction(orgId, tableId, transactionId) {
|
|
108
|
+
// Lock the table row to prevent concurrent double-seating
|
|
109
|
+
const locked = await this.db
|
|
110
|
+
.select()
|
|
111
|
+
.from(posTables)
|
|
112
|
+
.where(and(eq(posTables.id, tableId), eq(posTables.organizationId, orgId)))
|
|
113
|
+
.for("update");
|
|
114
|
+
if (locked.length === 0)
|
|
115
|
+
return Err("Table not found");
|
|
116
|
+
const table = locked[0];
|
|
117
|
+
if (table.status !== "available") {
|
|
118
|
+
return Err(`Table '${table.number}' is not available (current: ${table.status})`);
|
|
119
|
+
}
|
|
120
|
+
// Set table to occupied (under lock — no race condition)
|
|
121
|
+
await this.db
|
|
122
|
+
.update(posTables)
|
|
123
|
+
.set({ status: "occupied", updatedAt: new Date() })
|
|
124
|
+
.where(eq(posTables.id, tableId));
|
|
125
|
+
// Create assignment
|
|
126
|
+
const rows = await this.db
|
|
127
|
+
.insert(posTableAssignments)
|
|
128
|
+
.values({ tableId, transactionId, seatedAt: new Date() })
|
|
129
|
+
.returning();
|
|
130
|
+
return Ok(rows[0]);
|
|
131
|
+
}
|
|
132
|
+
// ─── Clear ─────────────────────────────────────────────────────────
|
|
133
|
+
// Clears a table: removes assignments and sets status to "available".
|
|
134
|
+
async clear(orgId, tableId) {
|
|
135
|
+
// Delete assignments
|
|
136
|
+
await this.db
|
|
137
|
+
.delete(posTableAssignments)
|
|
138
|
+
.where(eq(posTableAssignments.tableId, tableId));
|
|
139
|
+
// Set available
|
|
140
|
+
const rows = await this.db
|
|
141
|
+
.update(posTables)
|
|
142
|
+
.set({ status: "available", updatedAt: new Date() })
|
|
143
|
+
.where(and(eq(posTables.id, tableId), eq(posTables.organizationId, orgId)))
|
|
144
|
+
.returning();
|
|
145
|
+
if (rows.length === 0)
|
|
146
|
+
return Err("Table not found");
|
|
147
|
+
return Ok(rows[0]);
|
|
148
|
+
}
|
|
149
|
+
// ─── Transfer ──────────────────────────────────────────────────────
|
|
150
|
+
// Moves a transaction from one table to another.
|
|
151
|
+
// URY equivalent: table_transfer() — validates same room, target not occupied.
|
|
152
|
+
// We validate same zone (our equivalent of URY's room).
|
|
153
|
+
async transfer(orgId, fromTableId, toTableId) {
|
|
154
|
+
const fromTable = await this.getById(orgId, fromTableId);
|
|
155
|
+
if (!fromTable.ok)
|
|
156
|
+
return fromTable;
|
|
157
|
+
const toTable = await this.getById(orgId, toTableId);
|
|
158
|
+
if (!toTable.ok)
|
|
159
|
+
return toTable;
|
|
160
|
+
// Same zone required (URY: same room)
|
|
161
|
+
if (fromTable.value.zone !== toTable.value.zone) {
|
|
162
|
+
return Err(`Cannot transfer between different zones ('${fromTable.value.zone}' -> '${toTable.value.zone}')`);
|
|
163
|
+
}
|
|
164
|
+
// Target must be available
|
|
165
|
+
if (toTable.value.status !== "available") {
|
|
166
|
+
return Err(`Target table '${toTable.value.number}' is not available`);
|
|
167
|
+
}
|
|
168
|
+
// Move assignments
|
|
169
|
+
await this.db
|
|
170
|
+
.update(posTableAssignments)
|
|
171
|
+
.set({ tableId: toTableId })
|
|
172
|
+
.where(eq(posTableAssignments.tableId, fromTableId));
|
|
173
|
+
// Update statuses
|
|
174
|
+
await this.db.update(posTables).set({ status: "available", updatedAt: new Date() }).where(eq(posTables.id, fromTableId));
|
|
175
|
+
await this.db.update(posTables).set({ status: "occupied", updatedAt: new Date() }).where(eq(posTables.id, toTableId));
|
|
176
|
+
const updatedFrom = (await this.getById(orgId, fromTableId)).ok ? (await this.getById(orgId, fromTableId)) : fromTable;
|
|
177
|
+
const updatedTo = (await this.getById(orgId, toTableId)).ok ? (await this.getById(orgId, toTableId)) : toTable;
|
|
178
|
+
if (!updatedFrom.ok || !updatedTo.ok)
|
|
179
|
+
return Err("Transfer failed");
|
|
180
|
+
return Ok({ from: updatedFrom.value, to: updatedTo.value });
|
|
181
|
+
}
|
|
182
|
+
// ─── Layout ────────────────────────────────────────────────────────
|
|
183
|
+
// Updates floor plan position. URY equivalent: updateTableLayout() in table-api.ts.
|
|
184
|
+
async updateLayout(orgId, id, layout) {
|
|
185
|
+
const rows = await this.db
|
|
186
|
+
.update(posTables)
|
|
187
|
+
.set({ ...layout, updatedAt: new Date() })
|
|
188
|
+
.where(and(eq(posTables.id, id), eq(posTables.organizationId, orgId)))
|
|
189
|
+
.returning();
|
|
190
|
+
if (rows.length === 0)
|
|
191
|
+
return Err("Table not found");
|
|
192
|
+
return Ok(rows[0]);
|
|
193
|
+
}
|
|
194
|
+
// ─── Zones ─────────────────────────────────────────────────────────
|
|
195
|
+
async listZones(orgId) {
|
|
196
|
+
const rows = await this.db
|
|
197
|
+
.select({
|
|
198
|
+
zone: posTables.zone,
|
|
199
|
+
count: sql `COUNT(*)`.as("count"),
|
|
200
|
+
})
|
|
201
|
+
.from(posTables)
|
|
202
|
+
.where(eq(posTables.organizationId, orgId))
|
|
203
|
+
.groupBy(posTables.zone);
|
|
204
|
+
return Ok(rows.map((r) => ({ zone: r.zone, count: Number(r.count) })));
|
|
205
|
+
}
|
|
206
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type { PluginDb as Db } from "@porulle/core";
|
|
2
|
+
import type { posModifierGroups, posModifierOptions, posTables, posTableAssignments, kdsStations, kdsStationItemGroups, kdsTickets, kdsTicketItems } from "./schema.js";
|
|
3
|
+
export type ModifierGroup = typeof posModifierGroups.$inferSelect;
|
|
4
|
+
export type ModifierGroupInsert = typeof posModifierGroups.$inferInsert;
|
|
5
|
+
export type ModifierOption = typeof posModifierOptions.$inferSelect;
|
|
6
|
+
export type ModifierOptionInsert = typeof posModifierOptions.$inferInsert;
|
|
7
|
+
export type Table = typeof posTables.$inferSelect;
|
|
8
|
+
export type TableInsert = typeof posTables.$inferInsert;
|
|
9
|
+
export type TableAssignment = typeof posTableAssignments.$inferSelect;
|
|
10
|
+
export type TableAssignmentInsert = typeof posTableAssignments.$inferInsert;
|
|
11
|
+
export type TableStatus = "available" | "occupied" | "bill_requested" | "cleaning";
|
|
12
|
+
export type TableShape = "rectangle" | "square" | "circle";
|
|
13
|
+
export type KDSStation = typeof kdsStations.$inferSelect;
|
|
14
|
+
export type KDSStationInsert = typeof kdsStations.$inferInsert;
|
|
15
|
+
export type KDSStationItemGroup = typeof kdsStationItemGroups.$inferSelect;
|
|
16
|
+
export type KDSTicket = typeof kdsTickets.$inferSelect;
|
|
17
|
+
export type KDSTicketInsert = typeof kdsTickets.$inferInsert;
|
|
18
|
+
export type KDSTicketItem = typeof kdsTicketItems.$inferSelect;
|
|
19
|
+
export type KDSTicketItemInsert = typeof kdsTicketItems.$inferInsert;
|
|
20
|
+
export type TicketType = "new_order" | "modified" | "cancelled" | "partially_cancelled";
|
|
21
|
+
export type TicketStatus = "pending" | "preparing" | "ready" | "served";
|
|
22
|
+
export type TicketItemStatus = "pending" | "preparing" | "done";
|
|
23
|
+
export type OrderType = "dine_in" | "takeaway" | "delivery";
|
|
24
|
+
export interface POSRestaurantPluginOptions {
|
|
25
|
+
/** Enable kitchen display system. Default: true */
|
|
26
|
+
enableKDS?: boolean;
|
|
27
|
+
/** Enable tip collection on payments. Default: true */
|
|
28
|
+
enableTips?: boolean;
|
|
29
|
+
/** Enable item modifiers. Default: true */
|
|
30
|
+
enableModifiers?: boolean;
|
|
31
|
+
/** Minutes before KDS ticket turns red. Default: 15 */
|
|
32
|
+
kdsAlertMinutes?: number;
|
|
33
|
+
}
|
|
34
|
+
export declare const DEFAULT_RESTAURANT_OPTIONS: Required<POSRestaurantPluginOptions>;
|
|
35
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,mBAAmB,EACnB,WAAW,EACX,oBAAoB,EACpB,UAAU,EACV,cAAc,EACf,MAAM,aAAa,CAAC;AAIrB,MAAM,MAAM,aAAa,GAAG,OAAO,iBAAiB,CAAC,YAAY,CAAC;AAClE,MAAM,MAAM,mBAAmB,GAAG,OAAO,iBAAiB,CAAC,YAAY,CAAC;AACxE,MAAM,MAAM,cAAc,GAAG,OAAO,kBAAkB,CAAC,YAAY,CAAC;AACpE,MAAM,MAAM,oBAAoB,GAAG,OAAO,kBAAkB,CAAC,YAAY,CAAC;AAI1E,MAAM,MAAM,KAAK,GAAG,OAAO,SAAS,CAAC,YAAY,CAAC;AAClD,MAAM,MAAM,WAAW,GAAG,OAAO,SAAS,CAAC,YAAY,CAAC;AACxD,MAAM,MAAM,eAAe,GAAG,OAAO,mBAAmB,CAAC,YAAY,CAAC;AACtE,MAAM,MAAM,qBAAqB,GAAG,OAAO,mBAAmB,CAAC,YAAY,CAAC;AAE5E,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,gBAAgB,GAAG,UAAU,CAAC;AACnF,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAI3D,MAAM,MAAM,UAAU,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC;AACzD,MAAM,MAAM,gBAAgB,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC;AAC/D,MAAM,MAAM,mBAAmB,GAAG,OAAO,oBAAoB,CAAC,YAAY,CAAC;AAC3E,MAAM,MAAM,SAAS,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC;AACvD,MAAM,MAAM,eAAe,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC;AAC7D,MAAM,MAAM,aAAa,GAAG,OAAO,cAAc,CAAC,YAAY,CAAC;AAC/D,MAAM,MAAM,mBAAmB,GAAG,OAAO,cAAc,CAAC,YAAY,CAAC;AAErE,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,qBAAqB,CAAC;AACxF,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AACxE,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,CAAC;AAChE,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;AAI5D,MAAM,WAAW,0BAA0B;IACzC,mDAAmD;IACnD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,0BAA0B,CAK3E,CAAC"}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@porulle/plugin-pos-restaurant",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"bun": "./src/index.ts",
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./src/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"./schema": {
|
|
13
|
+
"bun": "./src/schema.ts",
|
|
14
|
+
"import": "./dist/schema.js",
|
|
15
|
+
"require": "./dist/schema.js",
|
|
16
|
+
"types": "./src/schema.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "rm -rf dist tsconfig.build.tsbuildinfo && tsc -p tsconfig.build.json",
|
|
21
|
+
"check-types": "tsc --noEmit",
|
|
22
|
+
"lint": "eslint . --max-warnings 1000",
|
|
23
|
+
"test": "vitest run"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@hono/zod-openapi": "^1.2.2",
|
|
27
|
+
"@porulle/core": "workspace:*",
|
|
28
|
+
"@porulle/plugin-pos": "workspace:*",
|
|
29
|
+
"hono": "^4.12.5"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@repo/eslint-config": "*",
|
|
33
|
+
"@repo/typescript-config": "*",
|
|
34
|
+
"@types/node": "^24.5.2",
|
|
35
|
+
"eslint": "^9.39.1",
|
|
36
|
+
"typescript": "5.9.2",
|
|
37
|
+
"vitest": "^3.2.4"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"src",
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"zod": ">=4.0.0"
|
|
49
|
+
},
|
|
50
|
+
"description": "Restaurant layer on top of @porulle/plugin-pos: tables, floor zones, optional modifiers and KDS, checklists, alerts, recipes (food BOM), and analytics.",
|
|
51
|
+
"homepage": "https://porulle-docs.vercel.app",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/asyncdotengineering/porulle/issues"
|
|
54
|
+
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git+https://github.com/asyncdotengineering/porulle.git",
|
|
58
|
+
"directory": "packages/plugins/plugin-pos-restaurant"
|
|
59
|
+
},
|
|
60
|
+
"author": "Porulle contributors"
|
|
61
|
+
}
|