@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,117 @@
|
|
|
1
|
+
import { router } from "@porulle/core";
|
|
2
|
+
import { z } from "@hono/zod-openapi";
|
|
3
|
+
export function buildModifierRoutes(service, ctx) {
|
|
4
|
+
const r = router("POS Restaurant Modifiers", "/pos/restaurant/modifier-groups", ctx);
|
|
5
|
+
r.post("/")
|
|
6
|
+
.summary("Create modifier group")
|
|
7
|
+
.permission("pos-restaurant:admin")
|
|
8
|
+
.input(z.object({
|
|
9
|
+
name: z.string().min(1).max(200),
|
|
10
|
+
entityId: z.string().uuid().optional(),
|
|
11
|
+
itemGroup: z.string().optional(),
|
|
12
|
+
isRequired: z.boolean().optional(),
|
|
13
|
+
minSelect: z.number().int().min(0).optional(),
|
|
14
|
+
maxSelect: z.number().int().min(1).optional(),
|
|
15
|
+
sortOrder: z.number().int().optional(),
|
|
16
|
+
}))
|
|
17
|
+
.handler(async ({ input, orgId }) => {
|
|
18
|
+
const body = input;
|
|
19
|
+
const result = await service.createGroup(orgId, body);
|
|
20
|
+
if (!result.ok)
|
|
21
|
+
throw new Error(result.error);
|
|
22
|
+
return result.value;
|
|
23
|
+
});
|
|
24
|
+
r.get("/")
|
|
25
|
+
.summary("List modifier groups")
|
|
26
|
+
.permission("pos:operate")
|
|
27
|
+
.query(z.object({ entityId: z.string().uuid().optional() }))
|
|
28
|
+
.handler(async ({ query, orgId }) => {
|
|
29
|
+
const q = query;
|
|
30
|
+
const result = await service.listGroups(orgId, q.entityId);
|
|
31
|
+
if (!result.ok)
|
|
32
|
+
throw new Error(result.error);
|
|
33
|
+
return result.value;
|
|
34
|
+
});
|
|
35
|
+
r.get("/{id}")
|
|
36
|
+
.summary("Get modifier group with options")
|
|
37
|
+
.permission("pos:operate")
|
|
38
|
+
.handler(async ({ params, orgId }) => {
|
|
39
|
+
const result = await service.getGroupWithOptions(orgId, params.id);
|
|
40
|
+
if (!result.ok)
|
|
41
|
+
throw new Error(result.error);
|
|
42
|
+
return result.value;
|
|
43
|
+
});
|
|
44
|
+
r.patch("/{id}")
|
|
45
|
+
.summary("Update modifier group")
|
|
46
|
+
.permission("pos-restaurant:admin")
|
|
47
|
+
.input(z.object({
|
|
48
|
+
name: z.string().min(1).max(200).optional(),
|
|
49
|
+
isRequired: z.boolean().optional(),
|
|
50
|
+
minSelect: z.number().int().min(0).optional(),
|
|
51
|
+
maxSelect: z.number().int().min(1).optional(),
|
|
52
|
+
sortOrder: z.number().int().optional(),
|
|
53
|
+
}))
|
|
54
|
+
.handler(async ({ params, input, orgId }) => {
|
|
55
|
+
const body = input;
|
|
56
|
+
const result = await service.updateGroup(orgId, params.id, body);
|
|
57
|
+
if (!result.ok)
|
|
58
|
+
throw new Error(result.error);
|
|
59
|
+
return result.value;
|
|
60
|
+
});
|
|
61
|
+
r.delete("/{id}")
|
|
62
|
+
.summary("Delete modifier group")
|
|
63
|
+
.permission("pos-restaurant:admin")
|
|
64
|
+
.handler(async ({ params, orgId }) => {
|
|
65
|
+
const result = await service.deleteGroup(orgId, params.id);
|
|
66
|
+
if (!result.ok)
|
|
67
|
+
throw new Error(result.error);
|
|
68
|
+
return result.value;
|
|
69
|
+
});
|
|
70
|
+
r.post("/{id}/options")
|
|
71
|
+
.summary("Add modifier option")
|
|
72
|
+
.permission("pos-restaurant:admin")
|
|
73
|
+
.input(z.object({
|
|
74
|
+
name: z.string().min(1).max(200),
|
|
75
|
+
priceAdjustment: z.number().int().optional(),
|
|
76
|
+
isDefault: z.boolean().optional(),
|
|
77
|
+
sortOrder: z.number().int().optional(),
|
|
78
|
+
}))
|
|
79
|
+
.handler(async ({ params, input }) => {
|
|
80
|
+
const body = input;
|
|
81
|
+
const result = await service.addOption(params.id, body);
|
|
82
|
+
if (!result.ok)
|
|
83
|
+
throw new Error(result.error);
|
|
84
|
+
return result.value;
|
|
85
|
+
});
|
|
86
|
+
return r.routes();
|
|
87
|
+
}
|
|
88
|
+
export function buildModifierOptionRoutes(service, ctx) {
|
|
89
|
+
const r = router("POS Restaurant Modifier Options", "/pos/restaurant/modifier-options", ctx);
|
|
90
|
+
r.patch("/{id}")
|
|
91
|
+
.summary("Update modifier option")
|
|
92
|
+
.permission("pos-restaurant:admin")
|
|
93
|
+
.input(z.object({
|
|
94
|
+
name: z.string().min(1).max(200).optional(),
|
|
95
|
+
priceAdjustment: z.number().int().optional(),
|
|
96
|
+
isDefault: z.boolean().optional(),
|
|
97
|
+
isAvailable: z.boolean().optional(),
|
|
98
|
+
sortOrder: z.number().int().optional(),
|
|
99
|
+
}))
|
|
100
|
+
.handler(async ({ params, input }) => {
|
|
101
|
+
const body = input;
|
|
102
|
+
const result = await service.updateOption(params.id, body);
|
|
103
|
+
if (!result.ok)
|
|
104
|
+
throw new Error(result.error);
|
|
105
|
+
return result.value;
|
|
106
|
+
});
|
|
107
|
+
r.delete("/{id}")
|
|
108
|
+
.summary("Delete modifier option")
|
|
109
|
+
.permission("pos-restaurant:admin")
|
|
110
|
+
.handler(async ({ params }) => {
|
|
111
|
+
const result = await service.deleteOption(params.id);
|
|
112
|
+
if (!result.ok)
|
|
113
|
+
throw new Error(result.error);
|
|
114
|
+
return result.value;
|
|
115
|
+
});
|
|
116
|
+
return r.routes();
|
|
117
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operational routes: checklists, alerts, recipes, combos, menu availability,
|
|
3
|
+
* analytics, customer favorites.
|
|
4
|
+
*/
|
|
5
|
+
import type { ChecklistService } from "../services/checklist-service.js";
|
|
6
|
+
import type { AlertService } from "../services/alert-service.js";
|
|
7
|
+
import type { RecipeService } from "../services/recipe-service.js";
|
|
8
|
+
import type { RestaurantAnalyticsService } from "../services/analytics-service.js";
|
|
9
|
+
import type { PluginRouteRegistration } from "@porulle/core";
|
|
10
|
+
export declare function buildChecklistRoutes(service: ChecklistService, ctx: {
|
|
11
|
+
services?: Record<string, unknown>;
|
|
12
|
+
database?: {
|
|
13
|
+
db: unknown;
|
|
14
|
+
};
|
|
15
|
+
}): PluginRouteRegistration[];
|
|
16
|
+
export declare function buildAlertRoutes(service: AlertService, ctx: {
|
|
17
|
+
services?: Record<string, unknown>;
|
|
18
|
+
database?: {
|
|
19
|
+
db: unknown;
|
|
20
|
+
};
|
|
21
|
+
}): PluginRouteRegistration[];
|
|
22
|
+
export declare function buildRecipeRoutes(service: RecipeService, ctx: {
|
|
23
|
+
services?: Record<string, unknown>;
|
|
24
|
+
database?: {
|
|
25
|
+
db: unknown;
|
|
26
|
+
};
|
|
27
|
+
}): PluginRouteRegistration[];
|
|
28
|
+
export declare function buildAnalyticsRoutes(service: RestaurantAnalyticsService, ctx: {
|
|
29
|
+
services?: Record<string, unknown>;
|
|
30
|
+
database?: {
|
|
31
|
+
db: unknown;
|
|
32
|
+
};
|
|
33
|
+
}): PluginRouteRegistration[];
|
|
34
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../src/routes/operations.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE7D,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CA+D3B;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CA+D3B;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CA2C3B;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,0BAA0B,EACnC,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CA2E3B"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operational routes: checklists, alerts, recipes, combos, menu availability,
|
|
3
|
+
* analytics, customer favorites.
|
|
4
|
+
*/
|
|
5
|
+
import { router } from "@porulle/core";
|
|
6
|
+
import { z } from "@hono/zod-openapi";
|
|
7
|
+
export function buildChecklistRoutes(service, ctx) {
|
|
8
|
+
const r = router("POS Restaurant Checklists", "/pos/restaurant/checklists", ctx);
|
|
9
|
+
r.post("/")
|
|
10
|
+
.summary("Create checklist")
|
|
11
|
+
.permission("pos-restaurant:admin")
|
|
12
|
+
.input(z.object({
|
|
13
|
+
name: z.string().min(1),
|
|
14
|
+
type: z.enum(["pre_billing", "shift_open", "shift_close"]),
|
|
15
|
+
items: z.array(z.object({ label: z.string().min(1), isRequired: z.boolean().optional() })).min(1),
|
|
16
|
+
}))
|
|
17
|
+
.handler(async ({ input, orgId }) => {
|
|
18
|
+
const body = input;
|
|
19
|
+
const result = await service.createChecklist(orgId, body);
|
|
20
|
+
if (!result.ok)
|
|
21
|
+
throw new Error(result.error);
|
|
22
|
+
return result.value;
|
|
23
|
+
});
|
|
24
|
+
r.get("/")
|
|
25
|
+
.summary("List checklists")
|
|
26
|
+
.permission("pos:operate")
|
|
27
|
+
.query(z.object({ type: z.enum(["pre_billing", "shift_open", "shift_close"]).optional() }))
|
|
28
|
+
.handler(async ({ query, orgId }) => {
|
|
29
|
+
const q = query;
|
|
30
|
+
const result = await service.listChecklists(orgId, q.type);
|
|
31
|
+
if (!result.ok)
|
|
32
|
+
throw new Error(result.error);
|
|
33
|
+
return result.value;
|
|
34
|
+
});
|
|
35
|
+
r.get("/{id}")
|
|
36
|
+
.summary("Get checklist with items")
|
|
37
|
+
.permission("pos:operate")
|
|
38
|
+
.handler(async ({ params, orgId }) => {
|
|
39
|
+
const result = await service.getChecklistWithItems(params.id, orgId);
|
|
40
|
+
if (!result.ok)
|
|
41
|
+
throw new Error(result.error);
|
|
42
|
+
return result.value;
|
|
43
|
+
});
|
|
44
|
+
r.post("/{id}/complete")
|
|
45
|
+
.summary("Complete a checklist")
|
|
46
|
+
.permission("pos:operate")
|
|
47
|
+
.input(z.object({
|
|
48
|
+
referenceType: z.enum(["transaction", "shift"]),
|
|
49
|
+
referenceId: z.string().uuid(),
|
|
50
|
+
completedItems: z.array(z.object({
|
|
51
|
+
itemId: z.string().uuid(),
|
|
52
|
+
checked: z.boolean(),
|
|
53
|
+
note: z.string().optional(),
|
|
54
|
+
})),
|
|
55
|
+
}))
|
|
56
|
+
.handler(async ({ params, input, actor }) => {
|
|
57
|
+
const body = input;
|
|
58
|
+
const result = await service.completeChecklist({
|
|
59
|
+
checklistId: params.id,
|
|
60
|
+
...body,
|
|
61
|
+
operatorId: actor.userId,
|
|
62
|
+
});
|
|
63
|
+
if (!result.ok)
|
|
64
|
+
throw new Error(result.error);
|
|
65
|
+
return result.value;
|
|
66
|
+
});
|
|
67
|
+
return r.routes();
|
|
68
|
+
}
|
|
69
|
+
export function buildAlertRoutes(service, ctx) {
|
|
70
|
+
const r = router("POS Restaurant Alerts", "/pos/restaurant/alerts", ctx);
|
|
71
|
+
r.get("/")
|
|
72
|
+
.summary("List active alerts")
|
|
73
|
+
.permission("pos:operate")
|
|
74
|
+
.query(z.object({
|
|
75
|
+
type: z.string().optional(),
|
|
76
|
+
unresolvedOnly: z.string().optional(),
|
|
77
|
+
}))
|
|
78
|
+
.handler(async ({ query, orgId }) => {
|
|
79
|
+
const q = query;
|
|
80
|
+
const result = await service.listAlerts(orgId, {
|
|
81
|
+
type: q.type,
|
|
82
|
+
unresolvedOnly: q.unresolvedOnly === "true",
|
|
83
|
+
});
|
|
84
|
+
if (!result.ok)
|
|
85
|
+
throw new Error(result.error);
|
|
86
|
+
return result.value;
|
|
87
|
+
});
|
|
88
|
+
r.post("/detect")
|
|
89
|
+
.summary("Run alert detection scan")
|
|
90
|
+
.permission("pos-restaurant:admin")
|
|
91
|
+
.handler(async ({ orgId }) => {
|
|
92
|
+
const result = await service.runAllDetections(orgId);
|
|
93
|
+
if (!result.ok)
|
|
94
|
+
throw new Error(result.error);
|
|
95
|
+
return result.value;
|
|
96
|
+
});
|
|
97
|
+
r.post("/{id}/resolve")
|
|
98
|
+
.summary("Resolve an alert")
|
|
99
|
+
.permission("pos:operate")
|
|
100
|
+
.handler(async ({ params, actor }) => {
|
|
101
|
+
const result = await service.resolveAlert(params.id, actor.userId);
|
|
102
|
+
if (!result.ok)
|
|
103
|
+
throw new Error(result.error);
|
|
104
|
+
return result.value;
|
|
105
|
+
});
|
|
106
|
+
r.post("/config")
|
|
107
|
+
.summary("Set alert threshold")
|
|
108
|
+
.permission("pos-restaurant:admin")
|
|
109
|
+
.input(z.object({
|
|
110
|
+
alertType: z.string().min(1),
|
|
111
|
+
thresholdMinutes: z.number().int().min(1),
|
|
112
|
+
notifyRoles: z.array(z.string()).optional(),
|
|
113
|
+
}))
|
|
114
|
+
.handler(async ({ input, orgId }) => {
|
|
115
|
+
const body = input;
|
|
116
|
+
const result = await service.setThreshold(orgId, body.alertType, body.thresholdMinutes, body.notifyRoles);
|
|
117
|
+
if (!result.ok)
|
|
118
|
+
throw new Error(result.error);
|
|
119
|
+
return result.value;
|
|
120
|
+
});
|
|
121
|
+
r.get("/config")
|
|
122
|
+
.summary("Get alert configuration")
|
|
123
|
+
.permission("pos-restaurant:admin")
|
|
124
|
+
.handler(async ({ orgId }) => {
|
|
125
|
+
const result = await service.getConfig(orgId);
|
|
126
|
+
if (!result.ok)
|
|
127
|
+
throw new Error(result.error);
|
|
128
|
+
return result.value;
|
|
129
|
+
});
|
|
130
|
+
return r.routes();
|
|
131
|
+
}
|
|
132
|
+
export function buildRecipeRoutes(service, ctx) {
|
|
133
|
+
const r = router("POS Restaurant Recipes", "/pos/restaurant/recipes", ctx);
|
|
134
|
+
r.post("/")
|
|
135
|
+
.summary("Create recipe (BOM)")
|
|
136
|
+
.permission("pos-restaurant:admin")
|
|
137
|
+
.input(z.object({
|
|
138
|
+
entityId: z.string().uuid(),
|
|
139
|
+
name: z.string().min(1),
|
|
140
|
+
yieldQuantity: z.number().int().min(1).optional(),
|
|
141
|
+
ingredients: z.array(z.object({
|
|
142
|
+
ingredientName: z.string().min(1),
|
|
143
|
+
quantity: z.number().int().positive(),
|
|
144
|
+
unit: z.string().min(1),
|
|
145
|
+
costPerUnit: z.number().int().min(0),
|
|
146
|
+
})).min(1),
|
|
147
|
+
}))
|
|
148
|
+
.handler(async ({ input, orgId }) => {
|
|
149
|
+
const body = input;
|
|
150
|
+
const result = await service.createRecipe(orgId, body);
|
|
151
|
+
if (!result.ok)
|
|
152
|
+
throw new Error(result.error);
|
|
153
|
+
return result.value;
|
|
154
|
+
});
|
|
155
|
+
r.get("/")
|
|
156
|
+
.summary("List recipes")
|
|
157
|
+
.permission("pos-restaurant:admin")
|
|
158
|
+
.handler(async ({ orgId }) => {
|
|
159
|
+
const result = await service.listRecipes(orgId);
|
|
160
|
+
if (!result.ok)
|
|
161
|
+
throw new Error(result.error);
|
|
162
|
+
return result.value;
|
|
163
|
+
});
|
|
164
|
+
r.get("/{id}")
|
|
165
|
+
.summary("Get recipe with ingredients")
|
|
166
|
+
.permission("pos-restaurant:admin")
|
|
167
|
+
.handler(async ({ params }) => {
|
|
168
|
+
const result = await service.getRecipeWithIngredients(params.id);
|
|
169
|
+
if (!result.ok)
|
|
170
|
+
throw new Error(result.error);
|
|
171
|
+
return result.value;
|
|
172
|
+
});
|
|
173
|
+
return r.routes();
|
|
174
|
+
}
|
|
175
|
+
export function buildAnalyticsRoutes(service, ctx) {
|
|
176
|
+
const r = router("POS Restaurant Analytics", "/pos/restaurant/analytics", ctx);
|
|
177
|
+
r.post("/daily-pnl")
|
|
178
|
+
.summary("Create daily P&L")
|
|
179
|
+
.permission("pos-restaurant:admin")
|
|
180
|
+
.input(z.object({
|
|
181
|
+
date: z.string(),
|
|
182
|
+
grossSales: z.number().int(),
|
|
183
|
+
netSales: z.number().int(),
|
|
184
|
+
costOfGoods: z.number().int(),
|
|
185
|
+
directExpenses: z.number().int(),
|
|
186
|
+
indirectExpenses: z.number().int(),
|
|
187
|
+
employeeCosts: z.number().int(),
|
|
188
|
+
transactionCount: z.number().int(),
|
|
189
|
+
expenses: z.array(z.object({
|
|
190
|
+
category: z.enum(["cogs", "direct", "indirect", "employee"]),
|
|
191
|
+
name: z.string(),
|
|
192
|
+
amount: z.number().int(),
|
|
193
|
+
percentage: z.number().int().optional(),
|
|
194
|
+
})).optional(),
|
|
195
|
+
}))
|
|
196
|
+
.handler(async ({ input, orgId }) => {
|
|
197
|
+
const body = input;
|
|
198
|
+
const result = await service.createDailyPnl(orgId, {
|
|
199
|
+
...body,
|
|
200
|
+
date: new Date(body.date),
|
|
201
|
+
});
|
|
202
|
+
if (!result.ok)
|
|
203
|
+
throw new Error(result.error);
|
|
204
|
+
return result.value;
|
|
205
|
+
});
|
|
206
|
+
r.get("/daily-pnl")
|
|
207
|
+
.summary("List daily P&L records")
|
|
208
|
+
.permission("pos-restaurant:admin")
|
|
209
|
+
.handler(async ({ orgId }) => {
|
|
210
|
+
const result = await service.listDailyPnl(orgId);
|
|
211
|
+
if (!result.ok)
|
|
212
|
+
throw new Error(result.error);
|
|
213
|
+
return result.value;
|
|
214
|
+
});
|
|
215
|
+
r.get("/station-performance/{id}")
|
|
216
|
+
.summary("Station performance metrics")
|
|
217
|
+
.permission("pos-restaurant:admin")
|
|
218
|
+
.query(z.object({ from: z.string(), to: z.string() }))
|
|
219
|
+
.handler(async ({ params, query, orgId }) => {
|
|
220
|
+
const q = query;
|
|
221
|
+
const result = await service.getStationPerformance(orgId, params.id, new Date(q.from), new Date(q.to));
|
|
222
|
+
if (!result.ok)
|
|
223
|
+
throw new Error(result.error);
|
|
224
|
+
return result.value;
|
|
225
|
+
});
|
|
226
|
+
r.get("/course-performance")
|
|
227
|
+
.summary("Course-wise performance")
|
|
228
|
+
.permission("pos-restaurant:admin")
|
|
229
|
+
.query(z.object({ from: z.string(), to: z.string() }))
|
|
230
|
+
.handler(async ({ query, orgId }) => {
|
|
231
|
+
const q = query;
|
|
232
|
+
const result = await service.getCoursePerformance(orgId, new Date(q.from), new Date(q.to));
|
|
233
|
+
if (!result.ok)
|
|
234
|
+
throw new Error(result.error);
|
|
235
|
+
return result.value;
|
|
236
|
+
});
|
|
237
|
+
r.get("/operator-performance")
|
|
238
|
+
.summary("Staff/captain performance")
|
|
239
|
+
.permission("pos-restaurant:admin")
|
|
240
|
+
.query(z.object({ from: z.string(), to: z.string() }))
|
|
241
|
+
.handler(async ({ query, orgId }) => {
|
|
242
|
+
const q = query;
|
|
243
|
+
const result = await service.getOperatorPerformance(orgId, new Date(q.from), new Date(q.to));
|
|
244
|
+
if (!result.ok)
|
|
245
|
+
throw new Error(result.error);
|
|
246
|
+
return result.value;
|
|
247
|
+
});
|
|
248
|
+
return r.routes();
|
|
249
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TableService } from "../services/table-service.js";
|
|
2
|
+
import type { PluginRouteRegistration } from "@porulle/core";
|
|
3
|
+
export declare function buildTableRoutes(service: TableService, ctx: {
|
|
4
|
+
services?: Record<string, unknown>;
|
|
5
|
+
database?: {
|
|
6
|
+
db: unknown;
|
|
7
|
+
};
|
|
8
|
+
}): PluginRouteRegistration[];
|
|
9
|
+
//# sourceMappingURL=tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/routes/tables.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE7D,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CA4G3B"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { router } from "@porulle/core";
|
|
2
|
+
import { z } from "@hono/zod-openapi";
|
|
3
|
+
export function buildTableRoutes(service, ctx) {
|
|
4
|
+
const r = router("POS Restaurant Tables", "/pos/restaurant/tables", ctx);
|
|
5
|
+
r.post("/")
|
|
6
|
+
.summary("Create table")
|
|
7
|
+
.permission("pos-restaurant:admin")
|
|
8
|
+
.input(z.object({
|
|
9
|
+
number: z.string().min(1).max(50),
|
|
10
|
+
zone: z.string().min(1).max(100),
|
|
11
|
+
capacity: z.number().int().min(1).max(100).optional(),
|
|
12
|
+
minimumSeats: z.number().int().min(1).optional(),
|
|
13
|
+
shape: z.enum(["rectangle", "square", "circle"]).optional(),
|
|
14
|
+
isTakeaway: z.boolean().optional(),
|
|
15
|
+
layoutX: z.number().int().optional(),
|
|
16
|
+
layoutY: z.number().int().optional(),
|
|
17
|
+
}))
|
|
18
|
+
.handler(async ({ input, orgId }) => {
|
|
19
|
+
const body = input;
|
|
20
|
+
const result = await service.create(orgId, body);
|
|
21
|
+
if (!result.ok)
|
|
22
|
+
throw new Error(result.error);
|
|
23
|
+
return result.value;
|
|
24
|
+
});
|
|
25
|
+
r.get("/zones")
|
|
26
|
+
.summary("List zones with table counts")
|
|
27
|
+
.permission("pos:operate")
|
|
28
|
+
.handler(async ({ orgId }) => {
|
|
29
|
+
const result = await service.listZones(orgId);
|
|
30
|
+
if (!result.ok)
|
|
31
|
+
throw new Error(result.error);
|
|
32
|
+
return result.value;
|
|
33
|
+
});
|
|
34
|
+
r.get("/")
|
|
35
|
+
.summary("List tables")
|
|
36
|
+
.permission("pos:operate")
|
|
37
|
+
.query(z.object({ zone: z.string().optional() }))
|
|
38
|
+
.handler(async ({ query, orgId }) => {
|
|
39
|
+
const q = query;
|
|
40
|
+
const result = await service.list(orgId, q.zone);
|
|
41
|
+
if (!result.ok)
|
|
42
|
+
throw new Error(result.error);
|
|
43
|
+
return result.value;
|
|
44
|
+
});
|
|
45
|
+
r.patch("/{id}")
|
|
46
|
+
.summary("Update table")
|
|
47
|
+
.permission("pos-restaurant:admin")
|
|
48
|
+
.input(z.object({
|
|
49
|
+
number: z.string().min(1).max(50).optional(),
|
|
50
|
+
zone: z.string().min(1).max(100).optional(),
|
|
51
|
+
capacity: z.number().int().min(1).optional(),
|
|
52
|
+
shape: z.enum(["rectangle", "square", "circle"]).optional(),
|
|
53
|
+
assignedOperatorId: z.string().nullable().optional(),
|
|
54
|
+
}))
|
|
55
|
+
.handler(async ({ params, input, orgId }) => {
|
|
56
|
+
const body = input;
|
|
57
|
+
const result = await service.update(orgId, params.id, body);
|
|
58
|
+
if (!result.ok)
|
|
59
|
+
throw new Error(result.error);
|
|
60
|
+
return result.value;
|
|
61
|
+
});
|
|
62
|
+
r.post("/{id}/assign")
|
|
63
|
+
.summary("Assign table to transaction")
|
|
64
|
+
.permission("pos:operate")
|
|
65
|
+
.input(z.object({ transactionId: z.string().uuid() }))
|
|
66
|
+
.handler(async ({ params, input, orgId }) => {
|
|
67
|
+
const body = input;
|
|
68
|
+
const result = await service.assignToTransaction(orgId, params.id, body.transactionId);
|
|
69
|
+
if (!result.ok)
|
|
70
|
+
throw new Error(result.error);
|
|
71
|
+
return result.value;
|
|
72
|
+
});
|
|
73
|
+
r.post("/{id}/clear")
|
|
74
|
+
.summary("Clear table")
|
|
75
|
+
.permission("pos:operate")
|
|
76
|
+
.handler(async ({ params, orgId }) => {
|
|
77
|
+
const result = await service.clear(orgId, params.id);
|
|
78
|
+
if (!result.ok)
|
|
79
|
+
throw new Error(result.error);
|
|
80
|
+
return result.value;
|
|
81
|
+
});
|
|
82
|
+
r.post("/{id}/transfer")
|
|
83
|
+
.summary("Transfer to another table")
|
|
84
|
+
.permission("pos:operate")
|
|
85
|
+
.input(z.object({ toTableId: z.string().uuid() }))
|
|
86
|
+
.handler(async ({ params, input, orgId }) => {
|
|
87
|
+
const body = input;
|
|
88
|
+
const result = await service.transfer(orgId, params.id, body.toTableId);
|
|
89
|
+
if (!result.ok)
|
|
90
|
+
throw new Error(result.error);
|
|
91
|
+
return result.value;
|
|
92
|
+
});
|
|
93
|
+
r.patch("/{id}/layout")
|
|
94
|
+
.summary("Update floor plan layout")
|
|
95
|
+
.permission("pos-restaurant:admin")
|
|
96
|
+
.input(z.object({
|
|
97
|
+
layoutX: z.number().int().optional(),
|
|
98
|
+
layoutY: z.number().int().optional(),
|
|
99
|
+
layoutWidth: z.number().int().optional(),
|
|
100
|
+
layoutHeight: z.number().int().optional(),
|
|
101
|
+
}))
|
|
102
|
+
.handler(async ({ params, input, orgId }) => {
|
|
103
|
+
const body = input;
|
|
104
|
+
const result = await service.updateLayout(orgId, params.id, body);
|
|
105
|
+
if (!result.ok)
|
|
106
|
+
throw new Error(result.error);
|
|
107
|
+
return result.value;
|
|
108
|
+
});
|
|
109
|
+
return r.routes();
|
|
110
|
+
}
|