feat(pos): print customer receipt from the POS page
CI/CD / CI · API (dotnet build + test) (push) Successful in 44s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 32s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m12s
CI/CD / CI · Admin Web (tsc) (push) Successful in 39s
CI/CD / CI · Website (tsc) (push) Successful in 48s
CI/CD / CI · Koja (tsc) (push) Successful in 51s
CI/CD / Deploy · all services (push) Successful in 3m0s
CI/CD / CI · API (dotnet build + test) (push) Successful in 44s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 32s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m12s
CI/CD / CI · Admin Web (tsc) (push) Successful in 39s
CI/CD / CI · Website (tsc) (push) Successful in 48s
CI/CD / CI · Koja (tsc) (push) Successful in 51s
CI/CD / Deploy · all services (push) Successful in 3m0s
POS v2 auto-printed the receipt on full payment but had no manual button. Adds a "چاپ فاکتور" (print receipt) action in the order panel that prints/reprints the active saved order's customer receipt, plus a "print receipt" action on the payment-success toast. Replaces the dead disabled "hold" placeholder button. Backend print endpoint unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
Search, Plus, Minus, Trash2, Send, CreditCard, Pause, SplitSquareHorizontal,
|
||||
Search, Plus, Minus, Trash2, Send, CreditCard, SplitSquareHorizontal,
|
||||
X, WifiOff, ShoppingCart, Users, Coffee, ArrowRight, LayoutGrid, Armchair,
|
||||
Banknote, Check, Delete, ReceiptText, ShoppingBag, Loader2, RotateCcw,
|
||||
BadgePercent, Sparkles, Home, StickyNote,
|
||||
@@ -24,6 +24,7 @@ import { useCartStore } from "@/lib/stores/cart.store";
|
||||
import { apiGet, apiPost, ApiClientError } from "@/lib/api/client";
|
||||
import { submitOrderToApi, orderAmountDue, isLocalOrder } from "@/lib/pos/submit-order";
|
||||
import { requestPosPayment, posDeviceErrorMessage } from "@/lib/api/pos-device";
|
||||
import { printReceipt } from "@/lib/api/print";
|
||||
import { PosCustomerPicker } from "@/components/pos/pos-customer-picker";
|
||||
import { Can } from "@/components/auth/can";
|
||||
import type { Customer, MenuItem, Order, TableBoardItem } from "@/lib/api/types";
|
||||
@@ -107,6 +108,7 @@ export function Pos2Screen() {
|
||||
const hydrateFromOrder = useCartStore((s) => s.hydrateFromOrder);
|
||||
const clearSession = useCartStore((s) => s.clearSession);
|
||||
const activeOrderNo = useCartStore((s) => s.activeOrderDisplayNumber);
|
||||
const activeOrderId = useCartStore((s) => s.activeOrderId);
|
||||
const appliedCoupon = useCartStore((s) => s.appliedCoupon);
|
||||
|
||||
// local view state
|
||||
@@ -285,7 +287,10 @@ export function Pos2Screen() {
|
||||
loyaltyPointsToRedeem: loyaltyRedeem > 0 ? loyaltyRedeem : undefined,
|
||||
});
|
||||
const paid = payments.reduce((s, p) => s + p.amount, 0);
|
||||
notify.success(`پرداخت ${fmt(paid)} تومان ثبت شد`);
|
||||
const paidOrderId = payTarget.id;
|
||||
notify.success(`پرداخت ${fmt(paid)} تومان ثبت شد`, {
|
||||
action: { label: "چاپ فاکتور", onClick: () => void printReceipt(cafeId as string, paidOrderId) },
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ["tables-board", cafeId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders-open", cafeId] });
|
||||
backToBoard();
|
||||
@@ -302,6 +307,27 @@ export function Pos2Screen() {
|
||||
}
|
||||
};
|
||||
|
||||
// Print (or reprint) the customer receipt for the active, already-saved order.
|
||||
const printActiveReceipt = async () => {
|
||||
if (!activeOrderId || isLocalOrder(activeOrderId)) {
|
||||
notify.error("ابتدا سفارش را ثبت کنید");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await printReceipt(cafeId as string, activeOrderId);
|
||||
notify.success("فاکتور برای چاپ ارسال شد");
|
||||
} catch (e) {
|
||||
const code = e instanceof ApiClientError ? e.code : "";
|
||||
notify.error(
|
||||
code === "PRINTER_NOT_CONFIGURED" || code === "KITCHEN_PRINTER_NOT_CONFIGURED"
|
||||
? "پرینتر فاکتور تنظیم نشده است"
|
||||
: code === "PRINTER_CONNECTION_FAILED"
|
||||
? "اتصال به پرینتر برقرار نشد"
|
||||
: "چاپ فاکتور ناموفق بود",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// ── guards ───────────────────────────────────────────────────────────────
|
||||
if (!cafeId) {
|
||||
return (
|
||||
@@ -322,6 +348,8 @@ export function Pos2Screen() {
|
||||
onBump: (id: string, d: number) => { const l = items.find((x) => x.menuItem.id === id); if (l) updateQty(id, l.quantity + d); },
|
||||
onRemove: removeItem, onSend: send, onPay: openPay, onSplit: openPay,
|
||||
onNote: (id: string, notes: string) => setNotes(id, notes),
|
||||
canPrint: !!activeOrderId && !isLocalOrder(activeOrderId),
|
||||
onPrintReceipt: printActiveReceipt,
|
||||
};
|
||||
|
||||
// ── TABLE BOARD ────────────────────────────────────────────────────────────
|
||||
@@ -673,13 +701,14 @@ function Pos2Extras({ cafeId }: { cafeId: string }) {
|
||||
// ── Order ticket ─────────────────────────────────────────────────────────────
|
||||
type TicketLine = { menuItem: MenuItem; quantity: number; notes?: string };
|
||||
function Ticket({
|
||||
cafeId, lines, subtotal, discount, tax, total, count, pendingCount, onBump, onRemove, onNote, onSend, onPay, onSplit,
|
||||
cafeId, lines, subtotal, discount, tax, total, count, pendingCount, onBump, onRemove, onNote, onSend, onPay, onSplit, canPrint, onPrintReceipt,
|
||||
}: {
|
||||
cafeId: string; lines: TicketLine[]; subtotal: number; discount: number; tax: number; total: number;
|
||||
count: number; pendingCount: number;
|
||||
onBump: (id: string, d: number) => void; onRemove: (id: string) => void;
|
||||
onNote: (id: string, notes: string) => void;
|
||||
onSend: () => void; onPay: () => void; onSplit: () => void;
|
||||
canPrint: boolean; onPrintReceipt: () => void;
|
||||
}) {
|
||||
const [noteFor, setNoteFor] = useState<string | null>(null);
|
||||
return (
|
||||
@@ -762,8 +791,10 @@ function Ticket({
|
||||
<CreditCard className="size-5" /> پرداخت
|
||||
</button>
|
||||
</Can>
|
||||
<button type="button" disabled className="flex min-h-[48px] items-center justify-center gap-1.5 rounded-xl bg-muted text-sm font-medium text-muted-foreground opacity-50">
|
||||
<Pause className="size-4" /> نگهداشتن
|
||||
<button type="button" disabled={!canPrint} onClick={onPrintReceipt}
|
||||
className="flex min-h-[48px] items-center justify-center gap-1.5 rounded-xl bg-muted text-sm font-medium text-foreground hover:bg-accent disabled:opacity-40"
|
||||
title="چاپ فاکتور مشتری">
|
||||
<ReceiptText className="size-4" /> چاپ فاکتور
|
||||
</button>
|
||||
<Can permission="HandlePayments">
|
||||
<button type="button" disabled={count === 0} onClick={onSplit}
|
||||
|
||||
Reference in New Issue
Block a user