@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.
- package/dist/hooks/checkout-pos.d.ts +29 -0
- package/dist/hooks/checkout-pos.d.ts.map +1 -0
- package/dist/hooks/checkout-pos.js +69 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/payment-adapter.d.ts +33 -0
- package/dist/payment-adapter.d.ts.map +1 -0
- package/dist/payment-adapter.js +37 -0
- package/dist/routes/lookup.d.ts +9 -0
- package/dist/routes/lookup.d.ts.map +1 -0
- package/dist/routes/lookup.js +37 -0
- package/dist/routes/payments.d.ts +10 -0
- package/dist/routes/payments.d.ts.map +1 -0
- package/dist/routes/payments.js +62 -0
- package/dist/routes/receipts.d.ts +9 -0
- package/dist/routes/receipts.d.ts.map +1 -0
- package/dist/routes/receipts.js +28 -0
- package/dist/routes/returns.d.ts +21 -0
- package/dist/routes/returns.d.ts.map +1 -0
- package/dist/routes/returns.js +83 -0
- package/dist/routes/shifts.d.ts +9 -0
- package/dist/routes/shifts.d.ts.map +1 -0
- package/dist/routes/shifts.js +91 -0
- package/dist/routes/terminals.d.ts +9 -0
- package/dist/routes/terminals.d.ts.map +1 -0
- package/dist/routes/terminals.js +55 -0
- package/dist/routes/transactions.d.ts +19 -0
- package/dist/routes/transactions.d.ts.map +1 -0
- package/dist/routes/transactions.js +175 -0
- package/dist/schema.d.ts +1337 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +123 -0
- package/dist/services/lookup-service.d.ts +38 -0
- package/dist/services/lookup-service.d.ts.map +1 -0
- package/dist/services/lookup-service.js +104 -0
- package/dist/services/payment-service.d.ts +40 -0
- package/dist/services/payment-service.d.ts.map +1 -0
- package/dist/services/payment-service.js +99 -0
- package/dist/services/receipt-service.d.ts +45 -0
- package/dist/services/receipt-service.d.ts.map +1 -0
- package/dist/services/receipt-service.js +119 -0
- package/dist/services/return-service.d.ts +27 -0
- package/dist/services/return-service.d.ts.map +1 -0
- package/dist/services/return-service.js +51 -0
- package/dist/services/shift-service.d.ts +36 -0
- package/dist/services/shift-service.d.ts.map +1 -0
- package/dist/services/shift-service.js +198 -0
- package/dist/services/terminal-service.d.ts +21 -0
- package/dist/services/terminal-service.d.ts.map +1 -0
- package/dist/services/terminal-service.js +59 -0
- package/dist/services/transaction-service.d.ts +30 -0
- package/dist/services/transaction-service.d.ts.map +1 -0
- package/dist/services/transaction-service.js +202 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/package.json +40 -0
- package/src/hooks/checkout-pos.ts +93 -0
- package/src/index.ts +131 -0
- package/src/payment-adapter.ts +53 -0
- package/src/routes/lookup.ts +44 -0
- package/src/routes/payments.ts +82 -0
- package/src/routes/receipts.ts +35 -0
- package/src/routes/returns.ts +116 -0
- package/src/routes/shifts.ts +100 -0
- package/src/routes/terminals.ts +62 -0
- package/src/routes/transactions.ts +192 -0
- package/src/schema.ts +136 -0
- package/src/services/lookup-service.ts +136 -0
- package/src/services/payment-service.ts +133 -0
- package/src/services/receipt-service.ts +169 -0
- package/src/services/return-service.ts +65 -0
- package/src/services/shift-service.ts +260 -0
- package/src/services/terminal-service.ts +76 -0
- package/src/services/transaction-service.ts +248 -0
- package/src/types.ts +49 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import { Ok, Err } from "@unifiedcommerce/core";
|
|
3
|
+
import { posReturnItems } from "../schema";
|
|
4
|
+
export class ReturnService {
|
|
5
|
+
db;
|
|
6
|
+
constructor(db) {
|
|
7
|
+
this.db = db;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Record return items linking back to an original order's line items.
|
|
11
|
+
* Called after a return transaction is created.
|
|
12
|
+
*/
|
|
13
|
+
async addReturnItems(transactionId, items) {
|
|
14
|
+
if (items.length === 0)
|
|
15
|
+
return Err("At least one item is required");
|
|
16
|
+
const values = items.map((item) => ({
|
|
17
|
+
transactionId,
|
|
18
|
+
originalOrderId: item.originalOrderId,
|
|
19
|
+
originalLineItemId: item.originalLineItemId,
|
|
20
|
+
quantity: item.quantity,
|
|
21
|
+
reason: item.reason,
|
|
22
|
+
restockingFee: item.restockingFee ?? 0,
|
|
23
|
+
refundAmount: item.refundAmount,
|
|
24
|
+
}));
|
|
25
|
+
const rows = await this.db
|
|
26
|
+
.insert(posReturnItems)
|
|
27
|
+
.values(values)
|
|
28
|
+
.returning();
|
|
29
|
+
return Ok(rows);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get all return items for a return transaction.
|
|
33
|
+
*/
|
|
34
|
+
async getReturnItems(transactionId) {
|
|
35
|
+
const rows = await this.db
|
|
36
|
+
.select()
|
|
37
|
+
.from(posReturnItems)
|
|
38
|
+
.where(eq(posReturnItems.transactionId, transactionId));
|
|
39
|
+
return Ok(rows);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Calculate total refund amount for a return transaction.
|
|
43
|
+
*/
|
|
44
|
+
async calculateRefundTotal(transactionId) {
|
|
45
|
+
const items = await this.db
|
|
46
|
+
.select()
|
|
47
|
+
.from(posReturnItems)
|
|
48
|
+
.where(eq(posReturnItems.transactionId, transactionId));
|
|
49
|
+
return items.reduce((sum, item) => sum + item.refundAmount, 0);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PluginResult } from "@unifiedcommerce/core";
|
|
2
|
+
import type { Db, Shift, CashEvent } from "../types";
|
|
3
|
+
export declare class ShiftService {
|
|
4
|
+
private db;
|
|
5
|
+
private transaction;
|
|
6
|
+
constructor(db: Db, transaction: (fn: (tx: Db) => Promise<unknown>) => Promise<unknown>);
|
|
7
|
+
open(orgId: string, input: {
|
|
8
|
+
terminalId: string;
|
|
9
|
+
operatorId: string;
|
|
10
|
+
openingFloat: number;
|
|
11
|
+
}): Promise<PluginResult<Shift>>;
|
|
12
|
+
close(orgId: string, shiftId: string, input: {
|
|
13
|
+
closingCount: number;
|
|
14
|
+
}): Promise<PluginResult<Shift>>;
|
|
15
|
+
getCurrent(orgId: string, operatorId: string): Promise<PluginResult<Shift | null>>;
|
|
16
|
+
getById(orgId: string, id: string): Promise<PluginResult<Shift>>;
|
|
17
|
+
addCashEvent(shiftId: string, input: {
|
|
18
|
+
type: "drop" | "pickup" | "paid_in" | "paid_out";
|
|
19
|
+
amount: number;
|
|
20
|
+
reason?: string;
|
|
21
|
+
performedBy: string;
|
|
22
|
+
}): Promise<PluginResult<CashEvent>>;
|
|
23
|
+
listCashEvents(shiftId: string): Promise<PluginResult<CashEvent[]>>;
|
|
24
|
+
getReport(orgId: string, shiftId: string): Promise<PluginResult<{
|
|
25
|
+
shift: Shift;
|
|
26
|
+
cashEvents: CashEvent[];
|
|
27
|
+
paymentMethodTotals: Array<{
|
|
28
|
+
method: string;
|
|
29
|
+
total: number;
|
|
30
|
+
count: number;
|
|
31
|
+
}>;
|
|
32
|
+
transactionCount: number;
|
|
33
|
+
}>>;
|
|
34
|
+
private calculateExpectedCash;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=shift-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shift-service.d.ts","sourceRoot":"","sources":["../../src/services/shift-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErD,qBAAa,YAAY;IAErB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,WAAW;gBADX,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC;IAGvE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;QAC/B,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAyC1B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE;QACjD,YAAY,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAmC1B,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAalF,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAYhE,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE;QACzC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;QACjD,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IA2B9B,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC;IAWnE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;QACpE,KAAK,EAAE,KAAK,CAAC;QACb,UAAU,EAAE,SAAS,EAAE,CAAC;QACxB,mBAAmB,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC7E,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;YAgDW,qBAAqB;CA4CpC"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { eq, and, desc, sql } from "drizzle-orm";
|
|
2
|
+
import { Ok, Err } from "@unifiedcommerce/core";
|
|
3
|
+
import { posShifts, posCashEvents, posPayments, posTransactions } from "../schema";
|
|
4
|
+
export class ShiftService {
|
|
5
|
+
db;
|
|
6
|
+
transaction;
|
|
7
|
+
constructor(db, transaction) {
|
|
8
|
+
this.db = db;
|
|
9
|
+
this.transaction = transaction;
|
|
10
|
+
}
|
|
11
|
+
async open(orgId, input) {
|
|
12
|
+
if (input.openingFloat < 0)
|
|
13
|
+
return Err("Opening float must be non-negative");
|
|
14
|
+
// Check no open shift on this terminal
|
|
15
|
+
const openShifts = await this.db
|
|
16
|
+
.select()
|
|
17
|
+
.from(posShifts)
|
|
18
|
+
.where(and(eq(posShifts.terminalId, input.terminalId), eq(posShifts.status, "open")));
|
|
19
|
+
if (openShifts.length > 0) {
|
|
20
|
+
return Err("Terminal already has an open shift");
|
|
21
|
+
}
|
|
22
|
+
const rows = await this.db
|
|
23
|
+
.insert(posShifts)
|
|
24
|
+
.values({
|
|
25
|
+
organizationId: orgId,
|
|
26
|
+
terminalId: input.terminalId,
|
|
27
|
+
operatorId: input.operatorId,
|
|
28
|
+
openingFloat: input.openingFloat,
|
|
29
|
+
status: "open",
|
|
30
|
+
})
|
|
31
|
+
.returning();
|
|
32
|
+
const shift = rows[0];
|
|
33
|
+
// Record the opening float as a cash event
|
|
34
|
+
await this.db.insert(posCashEvents).values({
|
|
35
|
+
shiftId: shift.id,
|
|
36
|
+
type: "float",
|
|
37
|
+
amount: input.openingFloat,
|
|
38
|
+
performedBy: input.operatorId,
|
|
39
|
+
performedAt: new Date(),
|
|
40
|
+
});
|
|
41
|
+
return Ok(shift);
|
|
42
|
+
}
|
|
43
|
+
async close(orgId, shiftId, input) {
|
|
44
|
+
const result = await this.transaction(async (tx) => {
|
|
45
|
+
const shifts = await tx
|
|
46
|
+
.select()
|
|
47
|
+
.from(posShifts)
|
|
48
|
+
.where(and(eq(posShifts.id, shiftId), eq(posShifts.organizationId, orgId)))
|
|
49
|
+
.for("update");
|
|
50
|
+
if (shifts.length === 0)
|
|
51
|
+
return Err("Shift not found");
|
|
52
|
+
const shift = shifts[0];
|
|
53
|
+
if (shift.status === "closed")
|
|
54
|
+
return Err("Shift is already closed");
|
|
55
|
+
// Calculate expected cash
|
|
56
|
+
const expectedCash = await this.calculateExpectedCash(tx, shiftId, shift.openingFloat);
|
|
57
|
+
const cashVariance = input.closingCount - expectedCash;
|
|
58
|
+
const updated = await tx
|
|
59
|
+
.update(posShifts)
|
|
60
|
+
.set({
|
|
61
|
+
status: "closed",
|
|
62
|
+
closingCount: input.closingCount,
|
|
63
|
+
expectedCash,
|
|
64
|
+
cashVariance,
|
|
65
|
+
closedAt: new Date(),
|
|
66
|
+
updatedAt: new Date(),
|
|
67
|
+
})
|
|
68
|
+
.where(eq(posShifts.id, shiftId))
|
|
69
|
+
.returning();
|
|
70
|
+
return Ok(updated[0]);
|
|
71
|
+
});
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
async getCurrent(orgId, operatorId) {
|
|
75
|
+
const rows = await this.db
|
|
76
|
+
.select()
|
|
77
|
+
.from(posShifts)
|
|
78
|
+
.where(and(eq(posShifts.organizationId, orgId), eq(posShifts.operatorId, operatorId), eq(posShifts.status, "open")));
|
|
79
|
+
return Ok(rows[0] ?? null);
|
|
80
|
+
}
|
|
81
|
+
async getById(orgId, id) {
|
|
82
|
+
const rows = await this.db
|
|
83
|
+
.select()
|
|
84
|
+
.from(posShifts)
|
|
85
|
+
.where(and(eq(posShifts.id, id), eq(posShifts.organizationId, orgId)));
|
|
86
|
+
if (rows.length === 0)
|
|
87
|
+
return Err("Shift not found");
|
|
88
|
+
return Ok(rows[0]);
|
|
89
|
+
}
|
|
90
|
+
// ─── Cash Events ───────────────────────────────────────────────────
|
|
91
|
+
async addCashEvent(shiftId, input) {
|
|
92
|
+
if (input.amount <= 0)
|
|
93
|
+
return Err("Amount must be positive");
|
|
94
|
+
// Verify shift is open
|
|
95
|
+
const shifts = await this.db
|
|
96
|
+
.select()
|
|
97
|
+
.from(posShifts)
|
|
98
|
+
.where(eq(posShifts.id, shiftId));
|
|
99
|
+
if (shifts.length === 0)
|
|
100
|
+
return Err("Shift not found");
|
|
101
|
+
if (shifts[0].status !== "open")
|
|
102
|
+
return Err("Shift is not open");
|
|
103
|
+
const rows = await this.db
|
|
104
|
+
.insert(posCashEvents)
|
|
105
|
+
.values({
|
|
106
|
+
shiftId,
|
|
107
|
+
type: input.type,
|
|
108
|
+
amount: input.amount,
|
|
109
|
+
reason: input.reason,
|
|
110
|
+
performedBy: input.performedBy,
|
|
111
|
+
performedAt: new Date(),
|
|
112
|
+
})
|
|
113
|
+
.returning();
|
|
114
|
+
return Ok(rows[0]);
|
|
115
|
+
}
|
|
116
|
+
async listCashEvents(shiftId) {
|
|
117
|
+
const rows = await this.db
|
|
118
|
+
.select()
|
|
119
|
+
.from(posCashEvents)
|
|
120
|
+
.where(eq(posCashEvents.shiftId, shiftId))
|
|
121
|
+
.orderBy(desc(posCashEvents.performedAt));
|
|
122
|
+
return Ok(rows);
|
|
123
|
+
}
|
|
124
|
+
// ─── Z-Report ──────────────────────────────────────────────────────
|
|
125
|
+
async getReport(orgId, shiftId) {
|
|
126
|
+
const shiftResult = await this.getById(orgId, shiftId);
|
|
127
|
+
if (!shiftResult.ok)
|
|
128
|
+
return shiftResult;
|
|
129
|
+
const shift = shiftResult.value;
|
|
130
|
+
const cashEvents = await this.db
|
|
131
|
+
.select()
|
|
132
|
+
.from(posCashEvents)
|
|
133
|
+
.where(eq(posCashEvents.shiftId, shiftId))
|
|
134
|
+
.orderBy(desc(posCashEvents.performedAt));
|
|
135
|
+
// Payment method totals
|
|
136
|
+
const paymentRows = await this.db
|
|
137
|
+
.select({
|
|
138
|
+
method: posPayments.method,
|
|
139
|
+
total: sql `SUM(${posPayments.amount})`.as("total"),
|
|
140
|
+
count: sql `COUNT(*)`.as("count"),
|
|
141
|
+
})
|
|
142
|
+
.from(posPayments)
|
|
143
|
+
.innerJoin(posTransactions, eq(posPayments.transactionId, posTransactions.id))
|
|
144
|
+
.where(and(eq(posTransactions.shiftId, shiftId), eq(posTransactions.status, "completed")))
|
|
145
|
+
.groupBy(posPayments.method);
|
|
146
|
+
const transactionCountRows = await this.db
|
|
147
|
+
.select({ count: sql `COUNT(*)`.as("count") })
|
|
148
|
+
.from(posTransactions)
|
|
149
|
+
.where(and(eq(posTransactions.shiftId, shiftId), eq(posTransactions.status, "completed")));
|
|
150
|
+
return Ok({
|
|
151
|
+
shift,
|
|
152
|
+
cashEvents,
|
|
153
|
+
paymentMethodTotals: paymentRows.map((r) => ({
|
|
154
|
+
method: r.method,
|
|
155
|
+
total: Number(r.total),
|
|
156
|
+
count: Number(r.count),
|
|
157
|
+
})),
|
|
158
|
+
transactionCount: Number(transactionCountRows[0]?.count ?? 0),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// ─── Helpers ───────────────────────────────────────────────────────
|
|
162
|
+
async calculateExpectedCash(db, shiftId, openingFloat) {
|
|
163
|
+
// Cash payments collected during this shift
|
|
164
|
+
const cashPaymentRows = await db
|
|
165
|
+
.select({
|
|
166
|
+
total: sql `COALESCE(SUM(${posPayments.amount} - ${posPayments.changeGiven}), 0)`.as("total"),
|
|
167
|
+
})
|
|
168
|
+
.from(posPayments)
|
|
169
|
+
.innerJoin(posTransactions, eq(posPayments.transactionId, posTransactions.id))
|
|
170
|
+
.where(and(eq(posTransactions.shiftId, shiftId), eq(posPayments.method, "cash"), eq(posPayments.status, "collected")));
|
|
171
|
+
const cashFromSales = Number(cashPaymentRows[0]?.total ?? 0);
|
|
172
|
+
// Cash events: drops reduce, pickups add to drawer
|
|
173
|
+
const cashEventRows = await db
|
|
174
|
+
.select({
|
|
175
|
+
type: posCashEvents.type,
|
|
176
|
+
total: sql `SUM(${posCashEvents.amount})`.as("total"),
|
|
177
|
+
})
|
|
178
|
+
.from(posCashEvents)
|
|
179
|
+
.where(eq(posCashEvents.shiftId, shiftId))
|
|
180
|
+
.groupBy(posCashEvents.type);
|
|
181
|
+
let cashAdjustment = 0;
|
|
182
|
+
for (const row of cashEventRows) {
|
|
183
|
+
const amount = Number(row.total);
|
|
184
|
+
switch (row.type) {
|
|
185
|
+
case "drop":
|
|
186
|
+
case "paid_out":
|
|
187
|
+
cashAdjustment -= amount;
|
|
188
|
+
break;
|
|
189
|
+
case "pickup":
|
|
190
|
+
case "paid_in":
|
|
191
|
+
cashAdjustment += amount;
|
|
192
|
+
break;
|
|
193
|
+
// float is already accounted for in openingFloat
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return openingFloat + cashFromSales + cashAdjustment;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PluginResult } from "@unifiedcommerce/core";
|
|
2
|
+
import type { Db, Terminal } from "../types";
|
|
3
|
+
export declare class TerminalService {
|
|
4
|
+
private db;
|
|
5
|
+
constructor(db: Db);
|
|
6
|
+
create(orgId: string, input: {
|
|
7
|
+
name: string;
|
|
8
|
+
code: string;
|
|
9
|
+
type?: "register" | "tablet" | "mobile" | "kiosk";
|
|
10
|
+
metadata?: Record<string, unknown>;
|
|
11
|
+
}): Promise<PluginResult<Terminal>>;
|
|
12
|
+
list(orgId: string): Promise<PluginResult<Terminal[]>>;
|
|
13
|
+
getById(orgId: string, id: string): Promise<PluginResult<Terminal>>;
|
|
14
|
+
update(orgId: string, id: string, input: {
|
|
15
|
+
name?: string;
|
|
16
|
+
isActive?: boolean;
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}): Promise<PluginResult<Terminal>>;
|
|
19
|
+
deactivate(orgId: string, id: string): Promise<PluginResult<Terminal>>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=terminal-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-service.d.ts","sourceRoot":"","sources":["../../src/services/terminal-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAkB,MAAM,UAAU,CAAC;AAE7D,qBAAa,eAAe;IACd,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAEpB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;QACjC,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;QAClD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAyB7B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC,CAAC;IAQtD,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAUnE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAW7B,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;CAG7E"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { eq, and } from "drizzle-orm";
|
|
2
|
+
import { Ok, Err } from "@unifiedcommerce/core";
|
|
3
|
+
import { posTerminals } from "../schema";
|
|
4
|
+
export class TerminalService {
|
|
5
|
+
db;
|
|
6
|
+
constructor(db) {
|
|
7
|
+
this.db = db;
|
|
8
|
+
}
|
|
9
|
+
async create(orgId, input) {
|
|
10
|
+
// Check for duplicate code in same org
|
|
11
|
+
const existing = await this.db
|
|
12
|
+
.select()
|
|
13
|
+
.from(posTerminals)
|
|
14
|
+
.where(and(eq(posTerminals.organizationId, orgId), eq(posTerminals.code, input.code)));
|
|
15
|
+
if (existing.length > 0) {
|
|
16
|
+
return Err(`Terminal with code '${input.code}' already exists`);
|
|
17
|
+
}
|
|
18
|
+
const rows = await this.db
|
|
19
|
+
.insert(posTerminals)
|
|
20
|
+
.values({
|
|
21
|
+
organizationId: orgId,
|
|
22
|
+
name: input.name,
|
|
23
|
+
code: input.code,
|
|
24
|
+
type: input.type ?? "register",
|
|
25
|
+
metadata: input.metadata ?? {},
|
|
26
|
+
})
|
|
27
|
+
.returning();
|
|
28
|
+
return Ok(rows[0]);
|
|
29
|
+
}
|
|
30
|
+
async list(orgId) {
|
|
31
|
+
const rows = await this.db
|
|
32
|
+
.select()
|
|
33
|
+
.from(posTerminals)
|
|
34
|
+
.where(eq(posTerminals.organizationId, orgId));
|
|
35
|
+
return Ok(rows);
|
|
36
|
+
}
|
|
37
|
+
async getById(orgId, id) {
|
|
38
|
+
const rows = await this.db
|
|
39
|
+
.select()
|
|
40
|
+
.from(posTerminals)
|
|
41
|
+
.where(and(eq(posTerminals.id, id), eq(posTerminals.organizationId, orgId)));
|
|
42
|
+
if (rows.length === 0)
|
|
43
|
+
return Err("Terminal not found");
|
|
44
|
+
return Ok(rows[0]);
|
|
45
|
+
}
|
|
46
|
+
async update(orgId, id, input) {
|
|
47
|
+
const rows = await this.db
|
|
48
|
+
.update(posTerminals)
|
|
49
|
+
.set({ ...input, updatedAt: new Date() })
|
|
50
|
+
.where(and(eq(posTerminals.id, id), eq(posTerminals.organizationId, orgId)))
|
|
51
|
+
.returning();
|
|
52
|
+
if (rows.length === 0)
|
|
53
|
+
return Err("Terminal not found");
|
|
54
|
+
return Ok(rows[0]);
|
|
55
|
+
}
|
|
56
|
+
async deactivate(orgId, id) {
|
|
57
|
+
return this.update(orgId, id, { isActive: false });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PluginResult } from "@unifiedcommerce/core";
|
|
2
|
+
import type { Db, Transaction } from "../types";
|
|
3
|
+
export declare class TransactionService {
|
|
4
|
+
private db;
|
|
5
|
+
private transaction;
|
|
6
|
+
constructor(db: Db, transaction: (fn: (tx: Db) => Promise<unknown>) => Promise<unknown>);
|
|
7
|
+
create(orgId: string, input: {
|
|
8
|
+
shiftId: string;
|
|
9
|
+
terminalId: string;
|
|
10
|
+
operatorId: string;
|
|
11
|
+
cartId: string;
|
|
12
|
+
type?: "sale" | "return" | "exchange";
|
|
13
|
+
customerId?: string;
|
|
14
|
+
}): Promise<PluginResult<Transaction>>;
|
|
15
|
+
getById(orgId: string, id: string): Promise<PluginResult<Transaction>>;
|
|
16
|
+
hold(orgId: string, id: string, label: string): Promise<PluginResult<Transaction>>;
|
|
17
|
+
recall(orgId: string, id: string): Promise<PluginResult<Transaction>>;
|
|
18
|
+
listHeld(orgId: string, terminalId: string): Promise<PluginResult<Transaction[]>>;
|
|
19
|
+
void(orgId: string, id: string, reason: string): Promise<PluginResult<Transaction>>;
|
|
20
|
+
setCustomer(orgId: string, id: string, customerId: string): Promise<PluginResult<Transaction>>;
|
|
21
|
+
updateTotals(id: string, totals: {
|
|
22
|
+
subtotal: number;
|
|
23
|
+
taxTotal: number;
|
|
24
|
+
total: number;
|
|
25
|
+
discountTotal: number;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
complete(id: string, orderId: string | null): Promise<PluginResult<Transaction>>;
|
|
28
|
+
private generateReceiptNumber;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=transaction-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-service.d.ts","sourceRoot":"","sources":["../../src/services/transaction-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAE,EAAE,EAAE,WAAW,EAAqB,MAAM,UAAU,CAAC;AAEnE,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,WAAW;gBADX,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC;IAGvE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;QACjC,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;QACtC,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IA4BhC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IAUtE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IAclF,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IAcrE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,CAAC;IAcjF,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IAgCnF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IAc9F,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;QACrC,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;KACvB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOX,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YAgExE,qBAAqB;CA0BpC"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { eq, and, desc, sql } from "drizzle-orm";
|
|
2
|
+
import { Ok, Err } from "@unifiedcommerce/core";
|
|
3
|
+
import { posTransactions, posPayments, posShifts } from "../schema";
|
|
4
|
+
export class TransactionService {
|
|
5
|
+
db;
|
|
6
|
+
transaction;
|
|
7
|
+
constructor(db, transaction) {
|
|
8
|
+
this.db = db;
|
|
9
|
+
this.transaction = transaction;
|
|
10
|
+
}
|
|
11
|
+
async create(orgId, input) {
|
|
12
|
+
// Verify shift is open
|
|
13
|
+
const shifts = await this.db
|
|
14
|
+
.select()
|
|
15
|
+
.from(posShifts)
|
|
16
|
+
.where(and(eq(posShifts.id, input.shiftId), eq(posShifts.status, "open")));
|
|
17
|
+
if (shifts.length === 0)
|
|
18
|
+
return Err("Shift is not open");
|
|
19
|
+
const receiptNumber = await this.generateReceiptNumber(input.terminalId);
|
|
20
|
+
const rows = await this.db
|
|
21
|
+
.insert(posTransactions)
|
|
22
|
+
.values({
|
|
23
|
+
organizationId: orgId,
|
|
24
|
+
shiftId: input.shiftId,
|
|
25
|
+
terminalId: input.terminalId,
|
|
26
|
+
operatorId: input.operatorId,
|
|
27
|
+
cartId: input.cartId,
|
|
28
|
+
type: input.type ?? "sale",
|
|
29
|
+
status: "open",
|
|
30
|
+
customerId: input.customerId,
|
|
31
|
+
receiptNumber,
|
|
32
|
+
})
|
|
33
|
+
.returning();
|
|
34
|
+
return Ok(rows[0]);
|
|
35
|
+
}
|
|
36
|
+
async getById(orgId, id) {
|
|
37
|
+
const rows = await this.db
|
|
38
|
+
.select()
|
|
39
|
+
.from(posTransactions)
|
|
40
|
+
.where(and(eq(posTransactions.id, id), eq(posTransactions.organizationId, orgId)));
|
|
41
|
+
if (rows.length === 0)
|
|
42
|
+
return Err("Transaction not found");
|
|
43
|
+
return Ok(rows[0]);
|
|
44
|
+
}
|
|
45
|
+
async hold(orgId, id, label) {
|
|
46
|
+
const txnResult = await this.getById(orgId, id);
|
|
47
|
+
if (!txnResult.ok)
|
|
48
|
+
return txnResult;
|
|
49
|
+
if (txnResult.value.status !== "open")
|
|
50
|
+
return Err("Only open transactions can be held");
|
|
51
|
+
const rows = await this.db
|
|
52
|
+
.update(posTransactions)
|
|
53
|
+
.set({ status: "held", holdLabel: label, updatedAt: new Date() })
|
|
54
|
+
.where(eq(posTransactions.id, id))
|
|
55
|
+
.returning();
|
|
56
|
+
return Ok(rows[0]);
|
|
57
|
+
}
|
|
58
|
+
async recall(orgId, id) {
|
|
59
|
+
const txnResult = await this.getById(orgId, id);
|
|
60
|
+
if (!txnResult.ok)
|
|
61
|
+
return txnResult;
|
|
62
|
+
if (txnResult.value.status !== "held")
|
|
63
|
+
return Err("Only held transactions can be recalled");
|
|
64
|
+
const rows = await this.db
|
|
65
|
+
.update(posTransactions)
|
|
66
|
+
.set({ status: "open", holdLabel: null, updatedAt: new Date() })
|
|
67
|
+
.where(eq(posTransactions.id, id))
|
|
68
|
+
.returning();
|
|
69
|
+
return Ok(rows[0]);
|
|
70
|
+
}
|
|
71
|
+
async listHeld(orgId, terminalId) {
|
|
72
|
+
const rows = await this.db
|
|
73
|
+
.select()
|
|
74
|
+
.from(posTransactions)
|
|
75
|
+
.where(and(eq(posTransactions.organizationId, orgId), eq(posTransactions.terminalId, terminalId), eq(posTransactions.status, "held")))
|
|
76
|
+
.orderBy(desc(posTransactions.createdAt));
|
|
77
|
+
return Ok(rows);
|
|
78
|
+
}
|
|
79
|
+
async void(orgId, id, reason) {
|
|
80
|
+
const result = await this.transaction(async (tx) => {
|
|
81
|
+
const txns = await tx
|
|
82
|
+
.select()
|
|
83
|
+
.from(posTransactions)
|
|
84
|
+
.where(and(eq(posTransactions.id, id), eq(posTransactions.organizationId, orgId)))
|
|
85
|
+
.for("update");
|
|
86
|
+
if (txns.length === 0)
|
|
87
|
+
return Err("Transaction not found");
|
|
88
|
+
const txn = txns[0];
|
|
89
|
+
if (txn.status === "completed")
|
|
90
|
+
return Err("Cannot void a completed transaction");
|
|
91
|
+
if (txn.status === "voided")
|
|
92
|
+
return Err("Transaction is already voided");
|
|
93
|
+
const updated = await tx
|
|
94
|
+
.update(posTransactions)
|
|
95
|
+
.set({ status: "voided", voidReason: reason, updatedAt: new Date() })
|
|
96
|
+
.where(eq(posTransactions.id, id))
|
|
97
|
+
.returning();
|
|
98
|
+
// Increment void count on shift
|
|
99
|
+
await tx
|
|
100
|
+
.update(posShifts)
|
|
101
|
+
.set({ voidsCount: sql `${posShifts.voidsCount} + 1`, updatedAt: new Date() })
|
|
102
|
+
.where(eq(posShifts.id, txn.shiftId));
|
|
103
|
+
return Ok(updated[0]);
|
|
104
|
+
});
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
async setCustomer(orgId, id, customerId) {
|
|
108
|
+
const txnResult = await this.getById(orgId, id);
|
|
109
|
+
if (!txnResult.ok)
|
|
110
|
+
return txnResult;
|
|
111
|
+
if (txnResult.value.status !== "open")
|
|
112
|
+
return Err("Can only set customer on open transactions");
|
|
113
|
+
const rows = await this.db
|
|
114
|
+
.update(posTransactions)
|
|
115
|
+
.set({ customerId, updatedAt: new Date() })
|
|
116
|
+
.where(eq(posTransactions.id, id))
|
|
117
|
+
.returning();
|
|
118
|
+
return Ok(rows[0]);
|
|
119
|
+
}
|
|
120
|
+
async updateTotals(id, totals) {
|
|
121
|
+
await this.db
|
|
122
|
+
.update(posTransactions)
|
|
123
|
+
.set({ ...totals, updatedAt: new Date() })
|
|
124
|
+
.where(eq(posTransactions.id, id));
|
|
125
|
+
}
|
|
126
|
+
async complete(id, orderId) {
|
|
127
|
+
const result = await this.transaction(async (tx) => {
|
|
128
|
+
// Lock and verify transaction is in a completable state
|
|
129
|
+
const existing = await tx
|
|
130
|
+
.select()
|
|
131
|
+
.from(posTransactions)
|
|
132
|
+
.where(eq(posTransactions.id, id))
|
|
133
|
+
.for("update");
|
|
134
|
+
if (existing.length === 0)
|
|
135
|
+
return Err("Transaction not found");
|
|
136
|
+
const current = existing[0];
|
|
137
|
+
if (current.status === "completed")
|
|
138
|
+
return Err("Transaction is already completed");
|
|
139
|
+
if (current.status === "voided")
|
|
140
|
+
return Err("Cannot complete a voided transaction");
|
|
141
|
+
if (current.status !== "open")
|
|
142
|
+
return Err(`Cannot complete transaction in '${current.status}' status`);
|
|
143
|
+
// Sum collected payments to derive transaction total
|
|
144
|
+
const paymentRows = await tx
|
|
145
|
+
.select({
|
|
146
|
+
total: sql `COALESCE(SUM(${posPayments.amount} - ${posPayments.changeGiven}), 0)`.as("total"),
|
|
147
|
+
})
|
|
148
|
+
.from(posPayments)
|
|
149
|
+
.where(and(eq(posPayments.transactionId, id), eq(posPayments.status, "collected")));
|
|
150
|
+
const paymentTotal = Number(paymentRows[0]?.total ?? 0);
|
|
151
|
+
const rows = await tx
|
|
152
|
+
.update(posTransactions)
|
|
153
|
+
.set({
|
|
154
|
+
status: "completed",
|
|
155
|
+
subtotal: paymentTotal,
|
|
156
|
+
total: paymentTotal,
|
|
157
|
+
...(orderId != null ? { orderId } : {}),
|
|
158
|
+
completedAt: new Date(),
|
|
159
|
+
updatedAt: new Date(),
|
|
160
|
+
})
|
|
161
|
+
.where(eq(posTransactions.id, id))
|
|
162
|
+
.returning();
|
|
163
|
+
if (rows.length === 0)
|
|
164
|
+
return Err("Transaction not found");
|
|
165
|
+
const txn = rows[0];
|
|
166
|
+
// Update shift sales counters
|
|
167
|
+
if (txn.type === "sale" && paymentTotal > 0) {
|
|
168
|
+
await tx
|
|
169
|
+
.update(posShifts)
|
|
170
|
+
.set({
|
|
171
|
+
salesCount: sql `${posShifts.salesCount} + 1`,
|
|
172
|
+
salesTotal: sql `${posShifts.salesTotal} + ${paymentTotal}`,
|
|
173
|
+
updatedAt: new Date(),
|
|
174
|
+
})
|
|
175
|
+
.where(eq(posShifts.id, txn.shiftId));
|
|
176
|
+
}
|
|
177
|
+
return Ok(txn);
|
|
178
|
+
});
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
// ─── Receipt Number Generation ─────────────────────────────────────
|
|
182
|
+
// Sequential per terminal per day: {terminal_code}-{sequence}
|
|
183
|
+
async generateReceiptNumber(terminalId) {
|
|
184
|
+
const today = new Date();
|
|
185
|
+
const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
186
|
+
const startOfDayISO = startOfDay.toISOString();
|
|
187
|
+
// Get terminal code
|
|
188
|
+
const { posTerminals } = await import("../schema");
|
|
189
|
+
const terminals = await this.db
|
|
190
|
+
.select({ code: posTerminals.code })
|
|
191
|
+
.from(posTerminals)
|
|
192
|
+
.where(eq(posTerminals.id, terminalId));
|
|
193
|
+
const terminalCode = terminals[0]?.code ?? "POS";
|
|
194
|
+
// Count today's transactions for this terminal
|
|
195
|
+
const countRows = await this.db
|
|
196
|
+
.select({ count: sql `COUNT(*)`.as("count") })
|
|
197
|
+
.from(posTransactions)
|
|
198
|
+
.where(and(eq(posTransactions.terminalId, terminalId), sql `${posTransactions.createdAt} >= ${startOfDayISO}::timestamptz`));
|
|
199
|
+
const seq = Number(countRows[0]?.count ?? 0) + 1;
|
|
200
|
+
return `${terminalCode}-${String(seq).padStart(4, "0")}`;
|
|
201
|
+
}
|
|
202
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type { PluginDb as Db } from "@unifiedcommerce/core";
|
|
2
|
+
import type { posTerminals, posShifts, posCashEvents, posTransactions, posPayments, posReturnItems } from "./schema";
|
|
3
|
+
export type Terminal = typeof posTerminals.$inferSelect;
|
|
4
|
+
export type TerminalInsert = typeof posTerminals.$inferInsert;
|
|
5
|
+
export type Shift = typeof posShifts.$inferSelect;
|
|
6
|
+
export type ShiftInsert = typeof posShifts.$inferInsert;
|
|
7
|
+
export type CashEvent = typeof posCashEvents.$inferSelect;
|
|
8
|
+
export type CashEventInsert = typeof posCashEvents.$inferInsert;
|
|
9
|
+
export type Transaction = typeof posTransactions.$inferSelect;
|
|
10
|
+
export type TransactionInsert = typeof posTransactions.$inferInsert;
|
|
11
|
+
export type Payment = typeof posPayments.$inferSelect;
|
|
12
|
+
export type PaymentInsert = typeof posPayments.$inferInsert;
|
|
13
|
+
export type ReturnItem = typeof posReturnItems.$inferSelect;
|
|
14
|
+
export type ReturnItemInsert = typeof posReturnItems.$inferInsert;
|
|
15
|
+
export type TransactionStatus = "open" | "held" | "completed" | "voided";
|
|
16
|
+
export type TransactionType = "sale" | "return" | "exchange";
|
|
17
|
+
export type PaymentMethod = "cash" | "card" | "gift_card" | "store_credit" | "other";
|
|
18
|
+
export type CashEventType = "float" | "drop" | "pickup" | "paid_in" | "paid_out";
|
|
19
|
+
export type ShiftStatus = "open" | "closed";
|
|
20
|
+
export type TerminalType = "register" | "tablet" | "mobile" | "kiosk";
|
|
21
|
+
export interface POSPluginOptions {
|
|
22
|
+
/** Default currency for new transactions. Default: "USD" */
|
|
23
|
+
defaultCurrency?: string;
|
|
24
|
+
/** Maximum hold duration in hours before auto-void. Default: 24 */
|
|
25
|
+
maxHoldHours?: number;
|
|
26
|
+
/** Require manager override for discounts above this percentage. Default: 20 */
|
|
27
|
+
discountOverrideThreshold?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare const DEFAULT_POS_OPTIONS: Required<POSPluginOptions>;
|
|
30
|
+
//# 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,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,aAAa,EACb,eAAe,EACf,WAAW,EACX,cAAc,EACf,MAAM,UAAU,CAAC;AAElB,MAAM,MAAM,QAAQ,GAAG,OAAO,YAAY,CAAC,YAAY,CAAC;AACxD,MAAM,MAAM,cAAc,GAAG,OAAO,YAAY,CAAC,YAAY,CAAC;AAE9D,MAAM,MAAM,KAAK,GAAG,OAAO,SAAS,CAAC,YAAY,CAAC;AAClD,MAAM,MAAM,WAAW,GAAG,OAAO,SAAS,CAAC,YAAY,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AAC1D,MAAM,MAAM,eAAe,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AAEhE,MAAM,MAAM,WAAW,GAAG,OAAO,eAAe,CAAC,YAAY,CAAC;AAC9D,MAAM,MAAM,iBAAiB,GAAG,OAAO,eAAe,CAAC,YAAY,CAAC;AAEpE,MAAM,MAAM,OAAO,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC;AACtD,MAAM,MAAM,aAAa,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC;AAE5D,MAAM,MAAM,UAAU,GAAG,OAAO,cAAc,CAAC,YAAY,CAAC;AAC5D,MAAM,MAAM,gBAAgB,GAAG,OAAO,cAAc,CAAC,YAAY,CAAC;AAElE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAC7D,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,cAAc,GAAG,OAAO,CAAC;AACrF,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AACjF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,CAAC;AAC5C,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtE,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,gBAAgB,CAI1D,CAAC"}
|