fix retried submissions double-booking a sale
The server dedupes on client_uuid, but the tablet minted a fresh uuid on every confirm() call — including retries after a timeout/dropped connection, exactly the case the idempotency key exists to guard against. The dedup check never fired on a real retry, so a flaky-Wi-Fi resend could book the same sale twice. Client: generate one uuid per pending cart (a ref, lazily created), reuse it across retries of the same submission, reset it only when the cart is cleared (success or cancel) so the next cart gets its own id. Server: the existing-row dedup check and the insert straddled the db.transaction() boundary, so a genuine UNIQUE-violation race would have surfaced as a raw 500 instead of the idempotent response. Catch that specific violation and fall back to re-reading the row.
This commit is contained in:
parent
32e3af3e23
commit
e52a0469c8
2 changed files with 43 additions and 11 deletions
|
|
@ -22,6 +22,17 @@ export function Sale({ config, onChangeBar }: Props) {
|
|||
const longPressTimer = useRef<number | null>(null);
|
||||
const longPressFired = useRef(false);
|
||||
|
||||
// One idempotency key per pending cart, reused across retries of the same
|
||||
// logical submission (e.g. after a timeout/dropped-connection error) so
|
||||
// the server's client_uuid dedup can actually recognise a resend. Reset
|
||||
// whenever the cart is cleared (submitted successfully or cancelled), so
|
||||
// the *next* cart gets its own fresh id.
|
||||
const cartUuidRef = useRef<string | null>(null);
|
||||
function cartUuid() {
|
||||
if (!cartUuidRef.current) cartUuidRef.current = uuid();
|
||||
return cartUuidRef.current;
|
||||
}
|
||||
|
||||
const drinkById = useMemo(() => {
|
||||
const m = new Map<number, Drink>();
|
||||
drinks.forEach(d => m.set(d.id, d));
|
||||
|
|
@ -72,6 +83,7 @@ export function Sale({ config, onChangeBar }: Props) {
|
|||
function clear() {
|
||||
setLines([]);
|
||||
setPfandReturns(0);
|
||||
cartUuidRef.current = null;
|
||||
}
|
||||
|
||||
function startLongPress(d: Drink) {
|
||||
|
|
@ -115,7 +127,7 @@ export function Sale({ config, onChangeBar }: Props) {
|
|||
const items: CartItem[] = lines.map(l => ({ drink_id: l.drink_id, qty: l.qty }));
|
||||
try {
|
||||
await api.createTransaction({
|
||||
client_uuid: uuid(),
|
||||
client_uuid: cartUuid(),
|
||||
bar_id: bar.id,
|
||||
crew,
|
||||
items,
|
||||
|
|
|
|||
Loading…
Reference in a new issue