Step-by-Step Guide — Follow each step exactly as written. All values are provided — do not improvise.

TC-BIL-007: Discounts, Adjustments & Void (Detailed)

Module: Billing & Invoicing — Discounts / Void

Primary Test User: treasurer@demo.membervu.com / Treasurer123! (Finance Manager)

Priority: P1

URLs for this test:
Frontend: https://stg-rcme.membervu.com/login
Admin invoices: https://stg-rcme.membervu.com/admin/invoices
API base: https://stg-api.membervu.com/api/billing
Payment gateway is INERT on staging (Paynamics). All payment/allocation states in this TC are created via manual payment recording or direct API calls that create Invoice/Payment records only — never a live charge.

Step 1: Log in and open an OUTSTANDING invoice

StepAction (EXACT clicks/typing)Expected Result (EXACT text/behavior)Test Value
1Go to https://stg-rcme.membervu.com/login; sign in.Dashboard loads.treasurer@demo.membervu.com / Treasurer123!
2Sidebar → FinanceInvoices.Split-view invoice list loads.
3Filter Status = Outstanding. Click a DUES-source invoice (e.g. an RCME-DUES-00xx row) that has no existing payments.Detail panel opens on the right showing amountCents, status, source.Note the exact amountCents shown, e.g. ₱15,000.00

Step 2: Apply a PERCENTAGE discount (BIL7-01)

StepActionExpected ResultTest Value
1Click the invoice's actions menu → Apply Discount (ApplyDiscountModal).Modal opens with a discount-type toggle (FIXED / PERCENTAGE), a value field, a reason field, and a live preview of the resulting net amount.
2Select PERCENTAGE, enter a value, enter a reason.Live preview shows resulting net = amountCents − (amountCents × 10%) exactly.Value 10 (%) · Reason QA BIL-007 percentage discount
3Submit.POST /api/billing/admin/invoices/:invoiceId/discount returns 200. Invoice detail refreshes.
Data assertion: the invoice's balance/amount due must equal the ORIGINAL amount minus EXACTLY 10% (e.g. ₱15,000.00 → ₱13,500.00, not ₱13,499.xx or a rounded-off figure). A visible discount line or badge shows the discount reason and either the original amount or the discount amount — record which of the two the UI actually shows. originalAmountCents must be preserved (recoverable from the invoice detail / audit trail) even after the discount is applied.

Step 3: Apply a FIXED discount on a second invoice (BIL7-02)

StepActionExpected ResultTest Value
1Open a second OUTSTANDING invoice with no existing payments. Apply Discount → select FIXED.Field now expects a peso amount (major units), not a percent.Value 500 (₱500.00)
2Enter reason, submit.200; balance drops by EXACTLY ₱500.00 (50000 cents) — not approximated.Reason QA BIL-007 fixed discount

Step 4: Fully-discount an invoice to ₱0 — still listed (BIL7-03)

StepActionExpected ResultTest Value
1Open a third small-amount OUTSTANDING invoice (or use one of the above after further discounting). Apply a discount that brings the balance to exactly ₱0.00 — FIXED equal to the full amountCents, or PERCENTAGE = 100.Submit succeeds; invoice net amount = 0.FIXED = full invoice amount, or PERCENTAGE 100
2Navigate back to the invoice list (all filters cleared, or filtered to the invoice's known source/period).The ₱0 invoice still appears in the list — it is NOT hidden or filtered out just because its balance is zero.
3Call the list API directly (or inspect network tab).GET /api/billing/invoices/tenant response items[] includes this invoice's id.
Data assertion: count the invoices in the list before and after — the total count must be unchanged (the ₱0 invoice is present both times), and the ₱0 invoice's status is NOT silently flipped to something that would exclude it from "Outstanding" filters if its net is genuinely zero — verify its actual computed status (likely PAID if discount-to-zero is treated as settled) rather than assuming.

Step 5: Discount cap ignores a PENDING allocation (BIL7-04)

This is a money-correctness assertion — do not skip. The point: a payment that hasn't settled (still PENDING/UNDER_REVIEW admin review) must not count as "already paid" for discount-cap purposes.
StepActionExpected ResultTest Value
1Pick a fresh OUTSTANDING invoice, e.g. ₱10,000.00. As a member (or admin recording on the member's behalf), submit a manual bank-transfer payment with proof for ₱6,000.00, but do not approve it yet.Payment record created with status PENDING/UNDER_REVIEW (awaiting approval). Invoice balance is UNCHANGED (still shows ₱10,000.00 outstanding) because the allocation isn't settled.Amount ₱6,000.00
2As Finance Manager, attempt to apply a discount that would bring the invoice's net BELOW ₱6,000.00 (e.g. a FIXED discount of ₱5,000.00, leaving ₱5,000.00 net).Request succeeds (200) — because the pending ₱6,000.00 is NOT counted as "already paid", there's no ₱6,000.00 floor to violate.Discount ₱5,000.00 → net ₱5,000.00
3Reset: pick another fresh invoice, ₱10,000.00. Record and this time approve a manual payment of ₱6,000.00 (status becomes settled/SUCCEEDED).Invoice balance now correctly shows ₱4,000.00 outstanding.
4Attempt the same discount (net below ₱6,000.00, e.g. down to ₱5,000.00).Request is rejected409 CONFLICT — because the resulting net (₱5,000.00) would fall below the SETTLED amount already paid (₱6,000.00). This is R-FIN-001.

Step 6: Void an invoice from each source (BIL7-05 / 06 / 07)

StepActionExpected ResultTest Value
1Open a DUES-source invoice with no payments at all. Actions menu → Void Statement. Enter a reason.Button is visible (source no longer restricts it). POST /api/billing/admin/invoices/:invoiceId/void200. Status becomes VOID.Reason QA BIL-007 void dues
2Repeat on an EVENT-source invoice (created from an event registration) with no payments.Same endpoint succeeds — 200, status VOID. No source-based rejection.Reason QA BIL-007 void event
3Repeat on a manually-created (OTHER/manual) invoice with no payments.Same endpoint succeeds — 200, status VOID.Reason QA BIL-007 void manual

Step 7: Void is blocked on a SUCCEEDED payment, regardless of source (BIL7-08)

StepActionExpected ResultTest Value
1Open the invoice from Step 5.3 (has an approved/SUCCEEDED ₱6,000.00 manual payment).Look for the Void action.
2Observe whether the Void button is present at all in the UI.Known UI/backend drift: the frontend action list hides "Void Statement" whenever the invoice has any payment record (hasPayments, even a rejected/pending one) — stricter than the backend, which only blocks on a SUCCEEDED allocation. Record whether the button is hidden here.
3If the UI exposes it (or via direct API call), attempt POST .../void on this invoice.Backend rejects with 400"Cannot void invoice with existing payments. Please process refunds first." The invoice status remains unchanged (NOT VOID).

CLEANUP