═══════════════════════════════════════════════════════════════════════════════ MANUAL TESTING GUIDE — Sales PWA Billing Chain (Stages A–G) PWA: https://srv1111289.hstgr.cloud/sales/ SW: 360sales-v16 Custodian: https://srv1111289.hstgr.cloud/finance/custodian/ (v23) Default login: harish / pramod Test dataset: sales/test_billing_data.sql (4 jobs · 5 offers · 3 invoices · 3 challans · 3 receipts) Cleanup: sales/test_billing_data_cleanup.sql ═══════════════════════════════════════════════════════════════════════════════ CONTENTS A. Pre-flight B. Stage A — Quotation / Offer C. Stage B — PO receipt + document upload D. Stage C/D — Proforma Invoice E. Stage E — Tax Invoice (create / issue / preview / pay) F. Stage F — Delivery Challan G. Stage G — Custodian settlement loop H. Negative tests I. Company-specific tests (360DLM) J. Tear-down ╔══════════════════════════════════════════════════════════════════════════════╗ ║ A. PRE-FLIGHT ║ ╚══════════════════════════════════════════════════════════════════════════════╝ A1. Load test dataset (if not already loaded): docker exec -i postgres psql -U lmadmin -d lm360 \ < /var/www/360lm/sales/test_billing_data.sql Report should show: jobs=4, offers=5, offer_lines=17, invoices=3, invoice_lines=9, challans=3, challan_lines=7, invoice_receipts=3 A2. Verify companies are configured: docker exec postgres psql -U lmadmin -d lm360 \ -c "SELECT short_code, name, gstin, state_code FROM sales.companies;" Expect 2 rows: pms (03) and 360dlm (04). A3. Log into /sales/ as harish. Dashboard loads. "Billed ₹" KPI shows a value (not "—") — confirms invoice_kpi view works. A4. Verify SW version: Open DevTools → Application → Service Workers. Active worker contains: 360sales-v16. ╔══════════════════════════════════════════════════════════════════════════════╗ ║ B. STAGE A — QUOTATION / OFFER ║ ╚══════════════════════════════════════════════════════════════════════════════╝ B1. DRAFT OFFER — verify new fields visible All Jobs → bt-job-a (BT-AsianPaints-DealerMeet). Offers tab. Click ✏️ Edit on the Draft offer (bt-off-1). Confirm: ✅ "Billing from" dropdown shows PMS + 360DLM options (from DB) ✅ "PO / Work Order" section has 3 fields: Number, Date, Amount ✅ Line items show HSN/SAC input per line ✅ SVC/MAT toggle button per line (blue=service, amber=material) ✅ Company state code correctly determines GST type (IGST vs CGST+SGST) B2. HSN/SAC DATALIST Click into the HSN/SAC input on any line. Type "99" → browser shows suggestions: 998361, 998362, 998399. Type "49" → shows 4911. Type "39" → shows 3926. Verify you can type a custom code not in the list — it saves freely. B3. SVC/MAT TOGGLE Click the SVC button on a service line → becomes MAT (amber, ₹). Click again → reverts to SVC (blue). Verify the toggle state persists after save (re-open edit and check). B4. PO FIELDS — save and verify Fill: PO Number = "B4-TEST-PO", PO Date = today, PO Amount = 100000. Save → toast "Offer saved". Re-open edit → fields retain values. DB check: SELECT po_number, po_date, po_amount, accepted_at FROM sales.offers WHERE offer_id = 'bt-off-1'; ✅ po_number = 'B4-TEST-PO' (you set it), accepted_at = NULL (still draft). B5. ACCEPTED OFFER WITH PO All Offers (📄 nav) → find bt-off-3 status = Accepted. ✅ Accepted badge with ₹8,50,000 agreed. ✅ Green PO reference "PO/VERVE/2025-26/047" visible below. Edit → accepted_at field NOT editable in form (trigger-only). DB check: SELECT accepted_at FROM sales.offers WHERE offer_id='bt-off-3'; ✅ Not null (trigger stamped it on the UPDATE from the test setup). B6. AMOUNT DRIFT FIX — verify rate×qty = stored amount DB check: SELECT element, rate, qty, amount FROM sales.offer_lines WHERE offer_id='bt-off-5' ORDER BY sr_no; ✅ amount = rate × qty for every line (GENERATED ALWAYS column). ✅ Vinyl line: rate=180, qty=250, amount=45000. B7. COMPANY DROPDOWN ON OFFER FORM Open a new standalone offer (Offers → New Offer). ✅ "Billing from" select shows both PMS and 360DLM (populated from DB). Switch to "360 Degree Logical Marketing". Enter client state 27 (Maharashtra) — GST auto-sets to IGST. (360DLM is Chandigarh state 04; 04 ≠ 27 → interstate → IGST ✓.) Cancel — don't save. ╔══════════════════════════════════════════════════════════════════════════════╗ ║ C. STAGE B — PO DOCUMENT UPLOAD ║ ╚══════════════════════════════════════════════════════════════════════════════╝ C1. Open bt-job-b (Confirmed) → Offers tab → bt-off-3 (Accepted). ✅ "📎 Upload Doc" button visible. Click it → file picker dialog opens. ✅ Accepting only image/PDF/doc/xlsx. Cancel (don't upload — just confirm the picker opens). C2. (Optional if you have a file) Upload a PDF named "PO_VERVE.pdf". ✅ After upload: document link appears under the offer card with type "po" badge. ✅ Document is retrievable (click the link to verify it loads). ╔══════════════════════════════════════════════════════════════════════════════╗ ║ D. STAGE C/D — PROFORMA INVOICE ║ ╚══════════════════════════════════════════════════════════════════════════════╝ D1. Open bt-job-b → Offers → bt-off-3 (Accepted). Click "📋 Proforma". Advance % modal: change 50% → 25% → Generate. D2. Proforma Invoice page opens. Verify all of the following: ✅ "PROFORMA INVOICE" title with PI badge. ✅ Company: "Perfect Marketing Solutions", GSTIN 03AEBPN2166H2ZT. ✅ Bill To: Verve Marketing Solutions, GSTIN 27AAACV5678G1Z3. ✅ PO strip (green banner): "PO/VERVE/2025-26/047 dated 01 June 2026". ✅ Line items table has COLUMN "HSN/SAC" with codes filled: 998361 (BTL service), 4911 (flex material), 3926 (standees), 998399 (photography). ✅ Sub-Total → Agency Fee 10% → Total (before GST) → IGST 18% → Grand Total. ✅ Advance Required 25% green box shown. ✅ Balance Due 75% shown. ✅ Bank details: HDFC + ICICI. ✅ WhatsApp share button (client has phone 9820098765). ✅ Print/PDF button. Click Print/PDF → browser print dialog opens. Close. D3. Share via WhatsApp: Click "💬 WhatsApp". ✅ Opens wa.me link with PI message pre-filled. Close tab. ╔══════════════════════════════════════════════════════════════════════════════╗ ║ E. STAGE E — TAX INVOICE ║ ╚══════════════════════════════════════════════════════════════════════════════╝ E1. CREATE FROM JOB (draft already exists) Open bt-job-c (In Execution, MktVibes Store). Invoices tab → badge "1", draft invoice PMS/26-27/TEST-01. ✅ Row shows: draft status tag · ₹4,15,360 grand total. ✅ "📤 Issue" and "✏️ Edit" buttons. E2. VIEW DRAFT INVOICE Click "👁 View". Verify printed document: ✅ "TAX INVOICE" and "ORIGINAL FOR RECIPIENT" labels. ✅ Invoice No: PMS/26-27/TEST-01. ✅ Company: Perfect Marketing Solutions, GSTIN 03AEBPN2166H2ZT. ✅ Bill To: Marketing vibes — shows "Unregistered" (no GSTIN). ✅ Place of Supply: state 07 (Delhi). ✅ IGST @ 18% (company Punjab 03 ≠ client Delhi 07 → interstate). ✅ PO strip: "PO/MV/2026/0012 dated 02 June 2026 for ₹3,78,000". ✅ Lines: 998361 (Service), 4911 (Goods), 998399 (Service) — 3 rows. ✅ "Amount in words": includes "Four Lakh Fifteen Thousand..." (₹4,15,360). ✅ Bank details section not empty. ✅ Signature row with "Authorised Signatory" + company name. E3. CREATE NEW INVOICE FROM SCRATCH Click "Create Invoice" button. ✅ Invoice form opens pre-filled from most recent accepted offer (bt-off-4). ✅ Lines copied from offer: 3 lines with HSN/SAC pre-populated. ✅ PO fields pre-filled from offer (PO/MV/2026/0012). ✅ Company select visible. ✅ Agency fee 10%, GST 18%, IGST. ✅ Live totals calculate as you edit rate/qty. Edit line 1 rate from 180000 → 190000. Totals update immediately. Reset to 180000. Save. ✅ Toast "Invoice saved". ✅ New invoice appears in the tab list as draft. E4. ISSUE INVOICE Click "📤 Issue" on bt-inv-1 → confirm. ✅ Status tag → purple "Issued". ✅ Job status (bt-job-c) → "Billed" (check All Jobs filter). ✅ Payment panel appears below the card: Invoice ₹4,15,360 · Received ₹0 · Balance ₹4,15,360 · 0% bar. ✅ "+ Record Payment" button visible. E5. PART PAYMENT Click "+ Record Payment". Modal: Amount = 207680 Mode = Bank Transfer (NEFT/RTGS) Ref = UTR-E5-TEST Date = today Save. ✅ Toast. ✅ Received: ₹2,07,680 · Balance: ₹2,07,680 · ~50% bar. ✅ Receipt row: "today · bank_transfer · UTR-E5-TEST · ₹2,07,680". ✅ Invoice still "Issued" (partial, not yet fully paid). DB check: SELECT status, paid_at FROM sales.invoices WHERE invoice_id='bt-inv-1'; ✅ status = 'issued', paid_at = NULL. E6. FULL SETTLEMENT → AUTO-PAID Record second payment: Amount = 207680 (remaining) Mode = UPI Save. ✅ Invoice tag flips to GREEN "Paid" immediately. ✅ Bar = 100%. ✅ No more "Record Payment" button (balance = 0). DB check: SELECT status, paid_at FROM sales.invoices WHERE invoice_id='bt-inv-1'; ✅ status = 'paid', paid_at IS NOT NULL (auto-stamped by trigger). ╔══════════════════════════════════════════════════════════════════════════════╗ ║ F. STAGE F — DELIVERY CHALLAN ║ ╚══════════════════════════════════════════════════════════════════════════════╝ F1. VIEW EXISTING CHALLAN bt-job-c → Challans tab → badge "1". bt-dc-1 shows: ✅ DC-PMS/26-27/TEST-01 · open status · 🚚 PB10AB1234 · Raju Singh. ✅ "Against: PMS/26-27/TEST-01" (linked invoice). ✅ "3 items" count. Click "👁 View". Challan document verifies: ✅ "DELIVERY CHALLAN" header (NOT "Tax Invoice"). ✅ Consignee: Marketing vibes, address, NO GSTIN (unregistered). ✅ Dispatch Details: vehicle, driver, from Mohali, to Lajpat Nagar. ✅ Purpose: "Supply of Goods/Services". ✅ Items table: 3 rows with HSN/SAC codes + unit (nos). ✅ NO rate or amount columns (dispatch doc, not billing). ✅ Two signature boxes: Authorised by + Receiver's Signature. ✅ "GST Rule 55 compliant challan" note. ✅ "This is not a tax invoice." note. ✅ Print button only (no WhatsApp/Email since no billing amounts). Close. F2. CREATE CHALLAN Click "📦 Create Challan". ✅ Form opens with: Consignee pre-filled from job customer. Delivery To pre-filled from job site address. Invoice link dropdown lists bt-inv-1. Company select populated. Date = today. Challan No = empty (auto-generated on save). Fill: Consignee: "F2 Test Site" Vehicle No: PB99ZZ0001 Driver: Ramu Dispatch From: Mohali Store Purpose: Job Work Item 1: "Vinyl Graphics Roll" / 3926 / 50 / sqft Item 2: "Sample Standee" / 3926 / 5 / nos Link to invoice: select PMS/26-27/TEST-01. Save. ✅ Toast "Challan saved". ✅ New challan appears in list: DC-PMS/YY-YY/NNN format. (Increment from existing test seed sequence.) F3. MARK DELIVERED Click "✅ Mark Delivered" on your new challan → confirm. ✅ Status changes to green "delivered". ✅ "Mark Delivered" button disappears. DB check: SELECT status, delivered_at FROM sales.challans WHERE consignee_name='F2 Test Site'; ✅ status=delivered, delivered_at IS NOT NULL. F4. DELIVERED CHALLAN (seed) Open bt-job-b (Confirmed) → Challans tab. ✅ bt-dc-2 shows green "delivered". ✅ "Against: PMS/26-27/TEST-02" (linked to bt-inv-3 paid). View → document shows delivered state cleanly. F5. STANDALONE CHALLAN (no invoice) Open bt-job-d (Billed) → Challans tab. ✅ bt-dc-3: DC-360DLM/26-27/TEST-01 · open · 360dlm company. ✅ No "Against" invoice line (standalone — invoice not yet raised). View → company header shows "360 Degree Logical Marketing". ╔══════════════════════════════════════════════════════════════════════════════╗ ║ G. STAGE G — CUSTODIAN SETTLEMENT LOOP ║ ╚══════════════════════════════════════════════════════════════════════════════╝ G1. VERIFY SEED PAYMENT STATE bt-job-d → Invoices tab → bt-inv-2 (360DLM, Issued). ✅ Payment panel shows: Invoice ₹2,14,170 · Received ₹1,07,085 · Balance ₹1,07,085 · ~50%. 1 receipt: bank_transfer, UTR202600441. G2. CUSTODIAN RECEIPT → INVOICE LINK Open /finance/custodian/. Login as harish (or shambhu). Transfer → Role: Receiver. Category: Party. Search "Park Hospital" → select "Park Hospital Pvt Ltd". ✅ Invoice dropdown appears: "Link to Sales Invoice". ✅ Options include: "360DLM/26-27/TEST-01 · Park Hospital Pvt Ltd · ₹1,07,085 due". Select that invoice. ✅ Amount auto-fills: 107085. ✅ Balance label: "Balance due: ₹1,07,085 of ₹2,14,170 total". Mode = Bank Transfer. Ref = UTR-G2-FINAL. Date = today. Save. ✅ Toast (custodian receipt saved). G3. VERIFY SALES INVOICE AUTO-PAID Return to /sales/ → bt-job-d → Invoices tab. ✅ bt-inv-2 status now "Paid" (green). ✅ Payment panel: 100% · 2 receipts listed (seed + new). DB check: SELECT status, paid_at FROM sales.invoices WHERE invoice_id='bt-inv-2'; ✅ status=paid, paid_at IS NOT NULL. G4. RESET FOR RETEST (optional) If you want to retest G2: docker exec postgres psql -U lmadmin -d lm360 -c " DELETE FROM sales.invoice_receipts WHERE receipt_id LIKE 'rcpt-%' AND created_by='harish'; UPDATE sales.invoices SET status='issued', paid_at=NULL WHERE invoice_id='bt-inv-2';" ╔══════════════════════════════════════════════════════════════════════════════╗ ║ H. NEGATIVE TESTS ║ ╚══════════════════════════════════════════════════════════════════════════════╝ H1. Empty Invoice save → ❌ Toast "Client name required". H2. Invoice with no line items → ❌ Toast "Add at least one line item". H3. Empty Challan save → ❌ Toast "Consignee name required". H4. Challan with no items → ❌ Toast "Add at least one item". H5. Payment with amount 0 → ❌ Toast "Enter valid amount". H6. Pop-up blocked → Toast "Pop-up blocked — allow pop-ups". (Preview / Print requires pop-ups. Chrome blocks by default on first load.) H7. Custodian: select party receiver, clear payee → invoice dropdown hides. H8. Job status: a job can be manually set to "Billed" without any invoice (via the status modal). The invoice tab shows "No invoices yet" cleanly. ╔══════════════════════════════════════════════════════════════════════════════╗ ║ I. COMPANY-SPECIFIC (360DLM) ║ ╚══════════════════════════════════════════════════════════════════════════════╝ I1. VIEW 360DLM INVOICE bt-job-d → Invoices tab → bt-inv-2 (360DLM company). View invoice. Verify: ✅ Company header: "360 Degree Logical Marketing". ✅ GSTIN: 04AJPPN8866P1Z3. ✅ Invoice No starts: "360DLM/". ✅ GST: IGST (client Park Hospital is state 06 Haryana ≠ company state 04 Chandigarh). ✅ Bank details from 360DLM's bank record (if configured). If 360DLM has no bank, section says "Bank details not configured — update via Admin → Companies". I2. NEW OFFER AS 360DLM Offers → New Offer. Switch "Billing from" → 360 Degree Logical Marketing. Enter client state = 04 (same as company — Chandigarh to Chandigarh). ✅ GST badge switches to "CGST+SGST" (intrastate). Enter client state = 27 (Maharashtra). ✅ GST badge switches back to "IGST". Cancel. I3. SENDER PROFILE PER COMPANY Open Clients screen (👥 icon in Offers header). ✅ Sender profile shows current PMS sender. Open settings (⚙️). Note: this is PMS sender (stored as lm360-sales-sender-pms). Switch company to 360DLM on offer form (even without saving). Reload sender settings → a SEPARATE profile for 360DLM (may be empty). This confirms per-company sender isolation. ╔══════════════════════════════════════════════════════════════════════════════╗ ║ J. TEAR-DOWN ║ ╚══════════════════════════════════════════════════════════════════════════════╝ J1. Remove challans and payments created during testing: - Soft-actions via UI: there is no delete UI for challans yet (status=cancelled is the soft path) — mark created challan as cancelled if the status modal is available, else skip. - Payments from E5/E6 and G2 — no delete UI; use DB: docker exec postgres psql -U lmadmin -d lm360 -c " DELETE FROM sales.invoice_receipts WHERE receipt_id LIKE 'rcpt-%' AND created_by IN ('harish','shambhu');" docker exec postgres psql -U lmadmin -d lm360 -c " UPDATE sales.invoices SET status='issued', paid_at=NULL WHERE invoice_id='bt-inv-1' AND status='paid';" J2. Wipe full seed dataset: docker exec -i postgres psql -U lmadmin -d lm360 \ < /var/www/360lm/sales/test_billing_data_cleanup.sql Final report should show 0 in every row. ═══════════════════════════════════════════════════════════════════════════════ TEST PASS CRITERIA - All sections B–I complete with no failures. - Negative tests in H behave as described. - Stage G loop: custodian receipt → invoice auto-paid in one action. - 360DLM company in all documents matches DB data (not hardcoded). - Tear-down in J returns DB to clean state. ═══════════════════════════════════════════════════════════════════════════════