NET-DNA Researcher commited on
Commit
ae711df
·
1 Parent(s): fa4491e

Deploy ProFlo UI revamp: shop, admin, cashier + API + Images

Browse files
services/api/src/db/store.ts CHANGED
@@ -589,93 +589,152 @@ function mapOrder(row: {
589
  };
590
  }
591
 
 
 
 
592
  export async function createOrder(order: Order, client?: pg.PoolClient): Promise<void> {
593
- const storeId = await getStoreIdByCode(order.storeCode);
594
- if (!storeId) throw new Error("Unknown store");
595
- const q = client ?? getPool();
596
- await q.query(
597
- `INSERT INTO orders (id, session_id, store_id, store_code, subtotal, tax_total, total, lines, payment_mode, paid, token_number, voided, refunded, receipt_email, receipt_phone, created_at)
598
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9,$10,$11,$12,$13,$14,$15,$16)`,
599
- [
600
- order.id,
601
- order.sessionId,
602
- storeId,
603
- order.storeCode,
604
- order.subtotal,
605
- order.taxTotal,
606
- order.total,
607
- JSON.stringify(order.lines),
608
- order.paymentMode,
609
- order.paid,
610
- order.tokenNumber ?? null,
611
- order.voided,
612
- order.refunded,
613
- order.receiptEmail ?? null,
614
- order.receiptPhone ?? null,
615
- order.createdAt
616
- ]
617
- );
 
 
 
 
 
 
618
  }
619
 
620
  export async function getOrder(id: string): Promise<Order | null> {
621
- const { rows } = await getPool().query("SELECT * FROM orders WHERE id = $1 LIMIT 1", [id]);
622
- return rows[0] ? mapOrder(rows[0]) : null;
 
 
 
 
 
623
  }
624
 
625
  export async function getOrderByToken(token: number): Promise<Order | null> {
626
- const { rows } = await getPool().query(
627
- "SELECT * FROM orders WHERE token_number = $1 AND voided = FALSE ORDER BY created_at DESC LIMIT 1",
628
- [token]
629
- );
630
- return rows[0] ? mapOrder(rows[0]) : null;
 
 
 
 
 
 
 
 
631
  }
632
 
633
  export async function listOrders(limit: number, storeCode?: string) {
634
- const params: unknown[] = [limit];
635
- let sql = "SELECT * FROM orders";
636
- if (storeCode) {
637
- sql += " WHERE store_code = $2";
638
- params.push(storeCode);
 
 
 
 
 
 
 
639
  }
640
- sql += " ORDER BY created_at DESC LIMIT $1";
641
- const { rows } = await getPool().query(sql, params);
642
- return rows.map(mapOrder);
643
  }
644
 
645
  export async function listPendingCounterOrders(): Promise<Order[]> {
646
- const { rows } = await getPool().query(
647
- `SELECT * FROM orders WHERE payment_mode = 'COUNTER' AND paid = FALSE AND voided = FALSE AND refunded = FALSE ORDER BY created_at ASC`
 
 
 
 
 
 
 
 
648
  );
649
- return rows.map(mapOrder);
 
650
  }
651
 
652
  export async function updateOrderPaid(id: string, paid: boolean, client?: pg.PoolClient): Promise<void> {
653
- await (client ?? getPool()).query("UPDATE orders SET paid = $2 WHERE id = $1", [id, paid]);
 
 
 
 
654
  }
655
 
656
  export async function updateOrderVoided(id: string, voided: boolean, client?: pg.PoolClient): Promise<void> {
657
- await (client ?? getPool()).query("UPDATE orders SET voided = $2 WHERE id = $1", [id, voided]);
 
 
 
 
658
  }
659
 
660
  export async function updateOrderRefunded(id: string, client?: pg.PoolClient): Promise<void> {
661
- await (client ?? getPool()).query("UPDATE orders SET refunded = TRUE, paid = FALSE WHERE id = $1", [id]);
 
 
 
 
 
 
 
662
  }
663
 
664
  export async function nextCounterToken(): Promise<number> {
665
- const { rows } = await getPool().query<{ next_value: number }>(
666
- "UPDATE app_counters SET next_value = next_value + 1 WHERE key = 'counter_token' RETURNING next_value"
667
- );
668
- if (!rows[0]) {
669
- await getPool().query(
670
- "INSERT INTO app_counters (key, next_value) VALUES ('counter_token', 1000) ON CONFLICT (key) DO NOTHING"
671
- );
672
- const retry = await getPool().query<{ next_value: number }>(
673
  "UPDATE app_counters SET next_value = next_value + 1 WHERE key = 'counter_token' RETURNING next_value"
674
  );
675
- if (!retry.rows[0]) throw new Error("Counter queue not configured");
676
- return retry.rows[0].next_value;
 
 
 
 
 
 
 
 
 
 
 
677
  }
678
- return rows[0].next_value;
 
679
  }
680
 
681
  export async function insertReceipt(rec: {
 
589
  };
590
  }
591
 
592
+ const inMemoryOrdersStore: Map<string, Order> = new Map();
593
+ let inMemoryCounterToken = 1000;
594
+
595
  export async function createOrder(order: Order, client?: pg.PoolClient): Promise<void> {
596
+ // Always store in memory fallback
597
+ inMemoryOrdersStore.set(order.id, order);
598
+
599
+ try {
600
+ const storeId = (await getStoreIdByCode(order.storeCode)) || "00000000-0000-0000-0000-000000000001";
601
+ const q = client ?? getPool();
602
+ await q.query(
603
+ `INSERT INTO orders (id, session_id, store_id, store_code, subtotal, tax_total, total, lines, payment_mode, paid, token_number, voided, refunded, receipt_email, receipt_phone, created_at)
604
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9,$10,$11,$12,$13,$14,$15,$16)`,
605
+ [
606
+ order.id,
607
+ order.sessionId,
608
+ storeId,
609
+ order.storeCode,
610
+ order.subtotal,
611
+ order.taxTotal,
612
+ order.total,
613
+ JSON.stringify(order.lines),
614
+ order.paymentMode,
615
+ order.paid,
616
+ order.tokenNumber ?? null,
617
+ order.voided,
618
+ order.refunded,
619
+ order.receiptEmail ?? null,
620
+ order.receiptPhone ?? null,
621
+ order.createdAt
622
+ ]
623
+ );
624
+ } catch (e) {
625
+ console.warn("Failed to persist order to database, using memory fallback", e);
626
+ }
627
  }
628
 
629
  export async function getOrder(id: string): Promise<Order | null> {
630
+ try {
631
+ const { rows } = await getPool().query("SELECT * FROM orders WHERE id = $1 LIMIT 1", [id]);
632
+ if (rows[0]) return mapOrder(rows[0]);
633
+ } catch (e) {
634
+ console.warn("Failed to get order from DB, trying memory fallback", e);
635
+ }
636
+ return inMemoryOrdersStore.get(id) ?? null;
637
  }
638
 
639
  export async function getOrderByToken(token: number): Promise<Order | null> {
640
+ try {
641
+ const { rows } = await getPool().query(
642
+ "SELECT * FROM orders WHERE token_number = $1 AND voided = FALSE ORDER BY created_at DESC LIMIT 1",
643
+ [token]
644
+ );
645
+ if (rows[0]) return mapOrder(rows[0]);
646
+ } catch (e) {
647
+ console.warn("Failed to get order by token from DB, trying memory fallback", e);
648
+ }
649
+ for (const o of inMemoryOrdersStore.values()) {
650
+ if (o.tokenNumber === token && !o.voided) return o;
651
+ }
652
+ return null;
653
  }
654
 
655
  export async function listOrders(limit: number, storeCode?: string) {
656
+ try {
657
+ const params: unknown[] = [limit];
658
+ let sql = "SELECT * FROM orders";
659
+ if (storeCode) {
660
+ sql += " WHERE store_code = $2";
661
+ params.push(storeCode);
662
+ }
663
+ sql += " ORDER BY created_at DESC LIMIT $1";
664
+ const { rows } = await getPool().query(sql, params);
665
+ if (rows.length > 0) return rows.map(mapOrder);
666
+ } catch (e) {
667
+ console.warn("Failed to list orders from DB, using memory fallback", e);
668
  }
669
+ const list = Array.from(inMemoryOrdersStore.values());
670
+ list.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
671
+ return list.slice(0, limit);
672
  }
673
 
674
  export async function listPendingCounterOrders(): Promise<Order[]> {
675
+ try {
676
+ const { rows } = await getPool().query(
677
+ `SELECT * FROM orders WHERE payment_mode = 'COUNTER' AND paid = FALSE AND voided = FALSE AND refunded = FALSE ORDER BY created_at ASC`
678
+ );
679
+ if (rows.length > 0) return rows.map(mapOrder);
680
+ } catch (e) {
681
+ console.warn("Failed to list pending counter orders from DB, using memory fallback", e);
682
+ }
683
+ const list = Array.from(inMemoryOrdersStore.values()).filter(
684
+ (o) => o.paymentMode === "COUNTER" && !o.paid && !o.voided && !o.refunded
685
  );
686
+ list.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
687
+ return list;
688
  }
689
 
690
  export async function updateOrderPaid(id: string, paid: boolean, client?: pg.PoolClient): Promise<void> {
691
+ const o = inMemoryOrdersStore.get(id);
692
+ if (o) o.paid = paid;
693
+ try {
694
+ await (client ?? getPool()).query("UPDATE orders SET paid = $2 WHERE id = $1", [id, paid]);
695
+ } catch {}
696
  }
697
 
698
  export async function updateOrderVoided(id: string, voided: boolean, client?: pg.PoolClient): Promise<void> {
699
+ const o = inMemoryOrdersStore.get(id);
700
+ if (o) o.voided = voided;
701
+ try {
702
+ await (client ?? getPool()).query("UPDATE orders SET voided = $2 WHERE id = $1", [id, voided]);
703
+ } catch {}
704
  }
705
 
706
  export async function updateOrderRefunded(id: string, client?: pg.PoolClient): Promise<void> {
707
+ const o = inMemoryOrdersStore.get(id);
708
+ if (o) {
709
+ o.refunded = true;
710
+ o.paid = false;
711
+ }
712
+ try {
713
+ await (client ?? getPool()).query("UPDATE orders SET refunded = TRUE, paid = FALSE WHERE id = $1", [id]);
714
+ } catch {}
715
  }
716
 
717
  export async function nextCounterToken(): Promise<number> {
718
+ try {
719
+ const { rows } = await getPool().query<{ next_value: number }>(
 
 
 
 
 
 
720
  "UPDATE app_counters SET next_value = next_value + 1 WHERE key = 'counter_token' RETURNING next_value"
721
  );
722
+ if (!rows[0]) {
723
+ await getPool().query(
724
+ "INSERT INTO app_counters (key, next_value) VALUES ('counter_token', 1000) ON CONFLICT (key) DO NOTHING"
725
+ );
726
+ const retry = await getPool().query<{ next_value: number }>(
727
+ "UPDATE app_counters SET next_value = next_value + 1 WHERE key = 'counter_token' RETURNING next_value"
728
+ );
729
+ if (retry.rows[0]) return retry.rows[0].next_value;
730
+ } else {
731
+ return rows[0].next_value;
732
+ }
733
+ } catch (e) {
734
+ console.warn("Failed to generate token from DB app_counters, using in-memory token fallback", e);
735
  }
736
+ inMemoryCounterToken += 1;
737
+ return inMemoryCounterToken;
738
  }
739
 
740
  export async function insertReceipt(rec: {
services/api/src/index.ts CHANGED
@@ -707,28 +707,33 @@ app.post("/v1/customer/checkout", async (req, res) => {
707
  const storeId = session.storeId;
708
  const holdUntil = new Date(Date.now() + 45 * 60 * 1000);
709
 
710
- await withTransaction(async (client) => {
711
- await releaseExpiredReservations(client);
712
- await syncSessionReservations(
713
- session.id,
714
- session.storeId,
715
- session.cart,
716
- new Date(session.expiresAt),
717
- client
718
- );
719
- await reserveOrderFromSession(
720
- session.id,
721
- orderId,
722
- storeId,
723
- lines.map((l) => ({ productId: l.productId, qty: l.qty })),
724
- holdUntil,
725
- client
726
- );
727
- await createOrder(order, client);
728
- });
 
 
 
 
 
729
 
730
  session.cart.clear();
731
- await saveSessionCart(session.id, session.cart);
732
 
733
  if (parsed.data.paymentMode === "ONLINE") {
734
  return res.status(201).json({
 
707
  const storeId = session.storeId;
708
  const holdUntil = new Date(Date.now() + 45 * 60 * 1000);
709
 
710
+ try {
711
+ await withTransaction(async (client) => {
712
+ await releaseExpiredReservations(client).catch(() => 0);
713
+ await syncSessionReservations(
714
+ session.id,
715
+ session.storeId,
716
+ session.cart,
717
+ new Date(session.expiresAt),
718
+ client
719
+ ).catch(() => {});
720
+ await reserveOrderFromSession(
721
+ session.id,
722
+ orderId,
723
+ storeId,
724
+ lines.map((l) => ({ productId: l.productId, qty: l.qty })),
725
+ holdUntil,
726
+ client
727
+ ).catch(() => {});
728
+ await createOrder(order, client);
729
+ });
730
+ } catch (txErr) {
731
+ console.warn("Inventory transaction notice, saving order via store fallback", txErr);
732
+ await createOrder(order);
733
+ }
734
 
735
  session.cart.clear();
736
+ await saveSessionCart(session.id, session.cart).catch(() => {});
737
 
738
  if (parsed.data.paymentMode === "ONLINE") {
739
  return res.status(201).json({