Skip to main content

backbone_pos/application/service/
pos_receipt.rs

1//! Assembling the printed receipt (hand-authored, user-owned).
2//!
3//! An `impl PosWriteService` chunk over the vocabulary in [`super::pos_write_service`] (the [`Receipt`]
4//! type and its monospace `render_text` live there). Read-only: the GL is billing's, this is purely the
5//! customer-facing slip.
6//!
7//! Per the module's 4-layer rule this file holds no SQL — the three reads live on
8//! `PosInvoiceRepository` / `PosInvoiceItemRepository` / `PosPaymentRepository`.
9
10use uuid::Uuid;
11
12use super::pos_write_service::{PosError, PosWriteService, Receipt, ReceiptLine, ReceiptTender};
13
14impl PosWriteService {
15    /// Assemble the receipt for a ticket: register + money breakdown (incl. server-computed PPN) +
16    /// lines + tenders + change. Ambient-scope read (ADR-0029) — any ticket in the caller's scope can
17    /// be (re)printed; another unit's reads as plain absence. The GL is billing's; this is the
18    /// customer-facing slip.
19    pub async fn receipt(&self, pos_invoice_id: Uuid) -> Result<Receipt, PosError> {
20        // Read-only, ambient scope: every read rides the request-dedicated connection.
21        let hdr = self.invoices
22            .fetch_receipt_header(&self.rpool(), pos_invoice_id).await?
23            .ok_or(PosError::InvoiceNotFound(pos_invoice_id))?;
24
25        let line_rows = self.items.fetch_receipt_lines(&self.rpool(), pos_invoice_id).await?;
26        let lines = line_rows.into_iter().map(|r| {
27            let desc = r.description.unwrap_or_else(|| r.item_id.to_string());
28            ReceiptLine {
29                description: desc, course: r.course, quantity: r.quantity, unit_price: r.unit_price,
30                discount_amount: r.discount_amount, net_amount: r.net_amount,
31            }
32        }).collect();
33
34        let tender_rows = self.payments.fetch_receipt_tenders(&self.rpool(), pos_invoice_id).await?;
35        let tenders = tender_rows.into_iter().map(|r| ReceiptTender { method: r.method, amount: r.amount }).collect();
36
37        Ok(Receipt {
38            pos_invoice_id,
39            receipt_number: hdr.receipt_number,
40            register_name: hdr.register_name,
41            posting_at: hdr.posting_at,
42            currency: hdr.currency,
43            status: hdr.status,
44            is_invoiced: hdr.is_invoiced,
45            lines,
46            net_total: hdr.net_total,
47            tax_rate: hdr.tax_rate,
48            tax_total: hdr.tax_total,
49            rounding_adjustment: hdr.rounding_adjustment,
50            // The slip's TOTAL is the ROUNDED total — what the customer actually pays.
51            grand_total: hdr.rounded_total,
52            tenders,
53            paid_total: hdr.paid_total,
54            change_due: hdr.change_due,
55        })
56    }
57}