@unifiedcommerce/plugin-pos 0.0.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.
Files changed (77) hide show
  1. package/dist/hooks/checkout-pos.d.ts +29 -0
  2. package/dist/hooks/checkout-pos.d.ts.map +1 -0
  3. package/dist/hooks/checkout-pos.js +69 -0
  4. package/dist/index.d.ts +26 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +109 -0
  7. package/dist/payment-adapter.d.ts +33 -0
  8. package/dist/payment-adapter.d.ts.map +1 -0
  9. package/dist/payment-adapter.js +37 -0
  10. package/dist/routes/lookup.d.ts +9 -0
  11. package/dist/routes/lookup.d.ts.map +1 -0
  12. package/dist/routes/lookup.js +37 -0
  13. package/dist/routes/payments.d.ts +10 -0
  14. package/dist/routes/payments.d.ts.map +1 -0
  15. package/dist/routes/payments.js +62 -0
  16. package/dist/routes/receipts.d.ts +9 -0
  17. package/dist/routes/receipts.d.ts.map +1 -0
  18. package/dist/routes/receipts.js +28 -0
  19. package/dist/routes/returns.d.ts +21 -0
  20. package/dist/routes/returns.d.ts.map +1 -0
  21. package/dist/routes/returns.js +83 -0
  22. package/dist/routes/shifts.d.ts +9 -0
  23. package/dist/routes/shifts.d.ts.map +1 -0
  24. package/dist/routes/shifts.js +91 -0
  25. package/dist/routes/terminals.d.ts +9 -0
  26. package/dist/routes/terminals.d.ts.map +1 -0
  27. package/dist/routes/terminals.js +55 -0
  28. package/dist/routes/transactions.d.ts +19 -0
  29. package/dist/routes/transactions.d.ts.map +1 -0
  30. package/dist/routes/transactions.js +175 -0
  31. package/dist/schema.d.ts +1337 -0
  32. package/dist/schema.d.ts.map +1 -0
  33. package/dist/schema.js +123 -0
  34. package/dist/services/lookup-service.d.ts +38 -0
  35. package/dist/services/lookup-service.d.ts.map +1 -0
  36. package/dist/services/lookup-service.js +104 -0
  37. package/dist/services/payment-service.d.ts +40 -0
  38. package/dist/services/payment-service.d.ts.map +1 -0
  39. package/dist/services/payment-service.js +99 -0
  40. package/dist/services/receipt-service.d.ts +45 -0
  41. package/dist/services/receipt-service.d.ts.map +1 -0
  42. package/dist/services/receipt-service.js +119 -0
  43. package/dist/services/return-service.d.ts +27 -0
  44. package/dist/services/return-service.d.ts.map +1 -0
  45. package/dist/services/return-service.js +51 -0
  46. package/dist/services/shift-service.d.ts +36 -0
  47. package/dist/services/shift-service.d.ts.map +1 -0
  48. package/dist/services/shift-service.js +198 -0
  49. package/dist/services/terminal-service.d.ts +21 -0
  50. package/dist/services/terminal-service.d.ts.map +1 -0
  51. package/dist/services/terminal-service.js +59 -0
  52. package/dist/services/transaction-service.d.ts +30 -0
  53. package/dist/services/transaction-service.d.ts.map +1 -0
  54. package/dist/services/transaction-service.js +202 -0
  55. package/dist/types.d.ts +30 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +5 -0
  58. package/package.json +40 -0
  59. package/src/hooks/checkout-pos.ts +93 -0
  60. package/src/index.ts +131 -0
  61. package/src/payment-adapter.ts +53 -0
  62. package/src/routes/lookup.ts +44 -0
  63. package/src/routes/payments.ts +82 -0
  64. package/src/routes/receipts.ts +35 -0
  65. package/src/routes/returns.ts +116 -0
  66. package/src/routes/shifts.ts +100 -0
  67. package/src/routes/terminals.ts +62 -0
  68. package/src/routes/transactions.ts +192 -0
  69. package/src/schema.ts +136 -0
  70. package/src/services/lookup-service.ts +136 -0
  71. package/src/services/payment-service.ts +133 -0
  72. package/src/services/receipt-service.ts +169 -0
  73. package/src/services/return-service.ts +65 -0
  74. package/src/services/shift-service.ts +260 -0
  75. package/src/services/terminal-service.ts +76 -0
  76. package/src/services/transaction-service.ts +248 -0
  77. package/src/types.ts +49 -0
@@ -0,0 +1,91 @@
1
+ import { router } from "@unifiedcommerce/core";
2
+ import { z } from "@hono/zod-openapi";
3
+ export function buildShiftRoutes(service, ctx) {
4
+ const r = router("POS Shifts", "/pos/shifts", ctx);
5
+ r.post("/open")
6
+ .summary("Open shift")
7
+ .permission("pos:operate")
8
+ .input(z.object({
9
+ terminalId: z.string().uuid(),
10
+ openingFloat: z.number().int().min(0),
11
+ }))
12
+ .handler(async ({ input, actor, orgId }) => {
13
+ const body = input;
14
+ const result = await service.open(orgId, {
15
+ ...body,
16
+ operatorId: actor.userId,
17
+ });
18
+ if (!result.ok)
19
+ throw new Error(result.error);
20
+ return result.value;
21
+ });
22
+ r.post("/{id}/close")
23
+ .summary("Close shift")
24
+ .permission("pos:operate")
25
+ .input(z.object({
26
+ closingCount: z.number().int().min(0),
27
+ }))
28
+ .handler(async ({ params, input, orgId }) => {
29
+ const body = input;
30
+ const result = await service.close(orgId, params.id, body);
31
+ if (!result.ok)
32
+ throw new Error(result.error);
33
+ return result.value;
34
+ });
35
+ r.get("/current")
36
+ .summary("Get current open shift")
37
+ .permission("pos:operate")
38
+ .handler(async ({ actor, orgId }) => {
39
+ const result = await service.getCurrent(orgId, actor.userId);
40
+ if (!result.ok)
41
+ throw new Error(result.error);
42
+ return result.value;
43
+ });
44
+ r.get("/{id}")
45
+ .summary("Get shift details")
46
+ .permission("pos:operate")
47
+ .handler(async ({ params, orgId }) => {
48
+ const result = await service.getById(orgId, params.id);
49
+ if (!result.ok)
50
+ throw new Error(result.error);
51
+ return result.value;
52
+ });
53
+ r.get("/{id}/report")
54
+ .summary("Z-report")
55
+ .permission("pos:admin")
56
+ .handler(async ({ params, orgId }) => {
57
+ const result = await service.getReport(orgId, params.id);
58
+ if (!result.ok)
59
+ throw new Error(result.error);
60
+ return result.value;
61
+ });
62
+ // ─── Cash Events ───────────────────────────────────────────────────
63
+ r.post("/{id}/cash-events")
64
+ .summary("Record cash event")
65
+ .permission("pos:operate")
66
+ .input(z.object({
67
+ type: z.enum(["drop", "pickup", "paid_in", "paid_out"]),
68
+ amount: z.number().int().positive(),
69
+ reason: z.string().max(500).optional(),
70
+ }))
71
+ .handler(async ({ params, input, actor }) => {
72
+ const body = input;
73
+ const result = await service.addCashEvent(params.id, {
74
+ ...body,
75
+ performedBy: actor.userId,
76
+ });
77
+ if (!result.ok)
78
+ throw new Error(result.error);
79
+ return result.value;
80
+ });
81
+ r.get("/{id}/cash-events")
82
+ .summary("List cash events")
83
+ .permission("pos:operate")
84
+ .handler(async ({ params }) => {
85
+ const result = await service.listCashEvents(params.id);
86
+ if (!result.ok)
87
+ throw new Error(result.error);
88
+ return result.value;
89
+ });
90
+ return r.routes();
91
+ }
@@ -0,0 +1,9 @@
1
+ import type { TerminalService } from "../services/terminal-service";
2
+ import type { PluginRouteRegistration } from "@unifiedcommerce/core";
3
+ export declare function buildTerminalRoutes(service: TerminalService, ctx: {
4
+ services?: Record<string, unknown>;
5
+ database?: {
6
+ db: unknown;
7
+ };
8
+ }): PluginRouteRegistration[];
9
+ //# sourceMappingURL=terminals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminals.d.ts","sourceRoot":"","sources":["../../src/routes/terminals.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,EACxB,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,CAqD3B"}
@@ -0,0 +1,55 @@
1
+ import { router } from "@unifiedcommerce/core";
2
+ import { z } from "@hono/zod-openapi";
3
+ export function buildTerminalRoutes(service, ctx) {
4
+ const r = router("POS Terminals", "/pos/terminals", ctx);
5
+ r.post("/")
6
+ .summary("Register terminal")
7
+ .permission("pos:admin")
8
+ .input(z.object({
9
+ name: z.string().min(1).max(100),
10
+ code: z.string().min(1).max(20),
11
+ type: z.enum(["register", "tablet", "mobile", "kiosk"]).optional(),
12
+ metadata: z.record(z.string(), z.unknown()).optional(),
13
+ }))
14
+ .handler(async ({ input, orgId }) => {
15
+ const body = input;
16
+ const result = await service.create(orgId, body);
17
+ if (!result.ok)
18
+ throw new Error(result.error);
19
+ return result.value;
20
+ });
21
+ r.get("/")
22
+ .summary("List terminals")
23
+ .permission("pos:admin")
24
+ .handler(async ({ orgId }) => {
25
+ const result = await service.list(orgId);
26
+ if (!result.ok)
27
+ throw new Error(result.error);
28
+ return result.value;
29
+ });
30
+ r.patch("/{id}")
31
+ .summary("Update terminal")
32
+ .permission("pos:admin")
33
+ .input(z.object({
34
+ name: z.string().min(1).max(100).optional(),
35
+ isActive: z.boolean().optional(),
36
+ metadata: z.record(z.string(), z.unknown()).optional(),
37
+ }))
38
+ .handler(async ({ params, input, orgId }) => {
39
+ const body = input;
40
+ const result = await service.update(orgId, params.id, body);
41
+ if (!result.ok)
42
+ throw new Error(result.error);
43
+ return result.value;
44
+ });
45
+ r.delete("/{id}")
46
+ .summary("Deactivate terminal")
47
+ .permission("pos:admin")
48
+ .handler(async ({ params, orgId }) => {
49
+ const result = await service.deactivate(orgId, params.id);
50
+ if (!result.ok)
51
+ throw new Error(result.error);
52
+ return result.value;
53
+ });
54
+ return r.routes();
55
+ }
@@ -0,0 +1,19 @@
1
+ import type { TransactionService } from "../services/transaction-service";
2
+ import type { PluginRouteRegistration } from "@unifiedcommerce/core";
3
+ export declare function buildTransactionRoutes(service: TransactionService, cartService: {
4
+ create: (input: {
5
+ currency?: string;
6
+ metadata?: Record<string, unknown>;
7
+ }, actor: unknown) => Promise<{
8
+ ok: boolean;
9
+ value?: {
10
+ id: string;
11
+ };
12
+ }>;
13
+ }, ctx: {
14
+ services?: Record<string, unknown>;
15
+ database?: {
16
+ db: unknown;
17
+ };
18
+ }): PluginRouteRegistration[];
19
+ //# sourceMappingURL=transactions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactions.d.ts","sourceRoot":"","sources":["../../src/routes/transactions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,WAAW,EAAE;IAAE,MAAM,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAA;CAAE,EAC/J,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,CAsL3B"}
@@ -0,0 +1,175 @@
1
+ import { router } from "@unifiedcommerce/core";
2
+ import { z } from "@hono/zod-openapi";
3
+ export function buildTransactionRoutes(service, cartService, ctx) {
4
+ const r = router("POS Transactions", "/pos/transactions", ctx);
5
+ r.post("/")
6
+ .summary("Start transaction")
7
+ .permission("pos:operate")
8
+ .input(z.object({
9
+ shiftId: z.string().uuid(),
10
+ terminalId: z.string().uuid(),
11
+ type: z.enum(["sale", "return", "exchange"]).optional(),
12
+ customerId: z.string().uuid().optional(),
13
+ currency: z.string().min(3).max(3).optional(),
14
+ }))
15
+ .handler(async ({ input, actor, orgId }) => {
16
+ const body = input;
17
+ // Create a cart for this transaction
18
+ const cartResult = await cartService.create({ currency: body.currency ?? "USD", metadata: { posTransaction: true } }, actor);
19
+ if (!cartResult.ok || !cartResult.value) {
20
+ throw new Error("Failed to create cart for POS transaction");
21
+ }
22
+ const result = await service.create(orgId, {
23
+ shiftId: body.shiftId,
24
+ terminalId: body.terminalId,
25
+ operatorId: actor.userId,
26
+ cartId: cartResult.value.id,
27
+ ...(body.type != null ? { type: body.type } : {}),
28
+ ...(body.customerId != null ? { customerId: body.customerId } : {}),
29
+ });
30
+ if (!result.ok)
31
+ throw new Error(result.error);
32
+ return result.value;
33
+ });
34
+ // NOTE: /held must be registered BEFORE /{id} to avoid UUID validation catching "held"
35
+ r.get("/held")
36
+ .summary("List held transactions")
37
+ .permission("pos:operate")
38
+ .query(z.object({
39
+ terminalId: z.string().uuid(),
40
+ }))
41
+ .handler(async ({ query, orgId }) => {
42
+ const q = query;
43
+ const result = await service.listHeld(orgId, q.terminalId);
44
+ if (!result.ok)
45
+ throw new Error(result.error);
46
+ return result.value;
47
+ });
48
+ r.get("/{id}")
49
+ .summary("Get transaction")
50
+ .permission("pos:operate")
51
+ .handler(async ({ params, orgId }) => {
52
+ const result = await service.getById(orgId, params.id);
53
+ if (!result.ok)
54
+ throw new Error(result.error);
55
+ return result.value;
56
+ });
57
+ r.post("/{id}/items")
58
+ .summary("Add item to transaction")
59
+ .permission("pos:operate")
60
+ .input(z.object({
61
+ entityId: z.string().uuid(),
62
+ variantId: z.string().uuid().optional(),
63
+ quantity: z.number().int().min(1).max(9999).optional(),
64
+ notes: z.string().max(500).optional(),
65
+ }))
66
+ .handler(async ({ params, input, actor, services, orgId }) => {
67
+ const body = input;
68
+ const txnResult = await service.getById(orgId, params.id);
69
+ if (!txnResult.ok)
70
+ throw new Error(txnResult.error);
71
+ if (txnResult.value.status !== "open")
72
+ throw new Error("Transaction is not open");
73
+ // Delegate to cart service
74
+ const cart = services.cart;
75
+ const addResult = await cart.addItem({
76
+ cartId: txnResult.value.cartId,
77
+ entityId: body.entityId,
78
+ variantId: body.variantId,
79
+ quantity: body.quantity ?? 1,
80
+ notes: body.notes,
81
+ }, actor);
82
+ if (!addResult.ok)
83
+ throw new Error("Failed to add item");
84
+ return addResult.value;
85
+ });
86
+ r.patch("/{id}/items/{itemId}")
87
+ .summary("Update line item")
88
+ .permission("pos:operate")
89
+ .input(z.object({
90
+ quantity: z.number().int().min(1).max(9999).optional(),
91
+ notes: z.string().max(500).optional(),
92
+ }))
93
+ .handler(async ({ params, input, actor, services, orgId }) => {
94
+ const body = input;
95
+ const txnResult = await service.getById(orgId, params.id);
96
+ if (!txnResult.ok)
97
+ throw new Error(txnResult.error);
98
+ if (txnResult.value.status !== "open")
99
+ throw new Error("Transaction is not open");
100
+ const cart = services.cart;
101
+ if (body.quantity != null) {
102
+ const updateResult = await cart.updateQuantity({
103
+ cartId: txnResult.value.cartId,
104
+ itemId: params.itemId,
105
+ quantity: body.quantity,
106
+ }, actor);
107
+ if (!updateResult.ok)
108
+ throw new Error("Failed to update item");
109
+ return updateResult.value;
110
+ }
111
+ return { updated: true };
112
+ });
113
+ r.delete("/{id}/items/{itemId}")
114
+ .summary("Remove line item")
115
+ .permission("pos:operate")
116
+ .handler(async ({ params, actor, services, orgId }) => {
117
+ const txnResult = await service.getById(orgId, params.id);
118
+ if (!txnResult.ok)
119
+ throw new Error(txnResult.error);
120
+ if (txnResult.value.status !== "open")
121
+ throw new Error("Transaction is not open");
122
+ const cart = services.cart;
123
+ await cart.removeItem(txnResult.value.cartId, params.itemId, actor);
124
+ return { removed: true };
125
+ });
126
+ r.post("/{id}/customer")
127
+ .summary("Associate customer")
128
+ .permission("pos:operate")
129
+ .input(z.object({
130
+ customerId: z.string().uuid(),
131
+ }))
132
+ .handler(async ({ params, input, orgId }) => {
133
+ const body = input;
134
+ const result = await service.setCustomer(orgId, params.id, body.customerId);
135
+ if (!result.ok)
136
+ throw new Error(result.error);
137
+ return result.value;
138
+ });
139
+ r.post("/{id}/hold")
140
+ .summary("Hold transaction")
141
+ .permission("pos:operate")
142
+ .input(z.object({
143
+ label: z.string().min(1).max(200),
144
+ }))
145
+ .handler(async ({ params, input, orgId }) => {
146
+ const body = input;
147
+ const result = await service.hold(orgId, params.id, body.label);
148
+ if (!result.ok)
149
+ throw new Error(result.error);
150
+ return result.value;
151
+ });
152
+ r.post("/{id}/recall")
153
+ .summary("Recall held transaction")
154
+ .permission("pos:operate")
155
+ .handler(async ({ params, orgId }) => {
156
+ const result = await service.recall(orgId, params.id);
157
+ if (!result.ok)
158
+ throw new Error(result.error);
159
+ return result.value;
160
+ });
161
+ r.post("/{id}/void")
162
+ .summary("Void transaction")
163
+ .permission("pos:manage")
164
+ .input(z.object({
165
+ reason: z.string().min(1).max(500),
166
+ }))
167
+ .handler(async ({ params, input, orgId }) => {
168
+ const body = input;
169
+ const result = await service.void(orgId, params.id, body.reason);
170
+ if (!result.ok)
171
+ throw new Error(result.error);
172
+ return result.value;
173
+ });
174
+ return r.routes();
175
+ }