Skip to content

Data Model & System Design Notes — Laundry Management System

Document Information

Field Value
Project Laundry Management System
Version 1.0
Language English
Tech Stack ASP.NET Core + Angular + PostgreSQL (central) / SQLite (offline branches)
Document Type Technical Design Notes

Table of Contents

  1. Entity Overview
  2. Core Entities & Relationships
  3. Chart of Accounts & General Ledger
  4. Accounting Rules Engine
  5. Accounting Flows by Transaction Type
  6. UUID Strategy & Offline Sync
  7. Reporting API Contract Pattern
  8. Carpet Settlement Models
  9. Tailor Payout Models
  10. Pricing Strategy Configuration
  11. Soft Delete & Audit Trail
  12. Technology Library References

1. Entity Overview

The system is divided into four layers of entities:

Layer Purpose Examples
Master Data Configuration, rarely changes Users, Branches, Chart of Accounts, Currencies, Price Lists, Garment Types, Service Types, Classifications, Carpet Types
Operational Data Day-to-day business transactions Invoices, InvoiceItems, CarpetReceipts, CarpetReceiptItems, CarpetInvoices, TailoringOrders, TailoringTasks, InventoryTransactions
Financial Data Accounting records (never directly edited) JournalEntries, JournalEntryLines (General Ledger), Payments
Supporting Data Tracking, logging CustomerCredits, BarcodeTags, ShiftSessions, AuditLogs, SyncBatches

2. Core Entities & Relationships

2.1 Master Data Entities

branches (id uuid PK, code, name, mode [online|offline], default_currency_id, status)
users (id uuid PK, username, password_hash, role, branch_id FK, status)
roles (id uuid PK, name)
role_permissions (role_id FK, module, action, is_allowed)
customers (id uuid PK, name, phone, classification_id FK, branch_id FK, credit_limit, status, is_transient)
customer_classifications (id uuid PK, name, default_price_list_id FK, default_discount_pct, allow_deferred_payment, is_default)
garment_types (id uuid PK, code, name_en, name_ar, category, status)
service_types (id uuid PK, code, name_en, name_ar, status)
garment_service_links (garment_type_id FK, service_type_id FK) — which services apply to which garments
price_lists (id uuid PK, name, currency_id FK, status)
price_list_entries (id uuid PK, price_list_id FK, garment_type_id FK, service_type_id FK, base_price numeric(18,4), effective_from, effective_to)
currencies (id uuid PK, code, name, symbol, decimal_places)
currency_rates (id uuid PK, from_currency_id FK, to_currency_id FK, rate numeric(18,6), effective_date)
fiscal_years (id uuid PK, name, start_date, end_date, is_closed)
tax_settings (id uuid PK, is_enabled bool, tax_name, tax_rate numeric(8,4), is_inclusive bool, tax_account_code)
carpet_types (id uuid PK, code, name_en, name_ar)
carpet_companies (id uuid PK, name, contact, phone, address, settlement_model [accrual|cash|prepayment], status)
carpet_company_prices (id uuid PK, company_id FK, carpet_type_id FK, price_per_sqm numeric(18,4), price_per_piece numeric(18,4), effective_date)
tailors (id uuid PK, name, phone, payout_model [periodic|peritem|salary_commission], base_salary numeric(18,4), status)
inventory_items (id uuid PK, code, name_en, name_ar, category, uom, selling_price numeric(18,4), cost_price numeric(18,4), current_quantity numeric(18,4), reorder_level numeric(18,4), branch_id FK, status)

2.2 Operational Entities

invoices (id uuid PK, invoice_number, customer_id FK nullable, branch_id FK, date, due_date, operational_status [draft|confirmed|in_progress|ready|delivered], financial_status [unpaid|partially_paid|paid|deferred], subtotal numeric(18,4), discount_amount numeric(18,4), tax_amount numeric(18,4), grand_total numeric(18,4), paid_amount numeric(18,4), remaining_amount numeric(18,4), notes, created_by_user_id FK, currency_id FK)

invoice_items (id uuid PK, invoice_id FK, garment_type_id FK, service_type_id FK, quantity int, unit_price numeric(18,4), line_total numeric(18,4), discount_amount numeric(18,4), is_tailoring bool, tailor_id FK nullable, tailor_cost numeric(18,4), line_status [active|damaged|lost], compensation_amount numeric(18,4))

barcode_tags (id uuid PK, tag_code, invoice_id FK, invoice_item_id FK, garment_type_id FK, printed_at, scanned_at_delivery)

carpet_receipts (id uuid PK, receipt_number, customer_id FK, branch_id FK, date, advance_amount numeric(18,4), advance_method, notes, status [received|assigned_to_company|returned|ready|delivered], external_company_id FK nullable)

carpet_receipt_items (id uuid PK, receipt_id FK, carpet_type_id FK, quantity int, estimated_size, notes_damages)

carpet_final_invoices (id uuid PK, invoice_number, linked_receipt_id FK UNIQUE, customer_id FK, branch_id FK, date, operational_status, financial_status, subtotal numeric(18,4), discount_amount numeric(18,4), tax_amount numeric(18,4), grand_total numeric(18,4), advance_deduction numeric(18,4), paid_amount numeric(18,4), remaining_amount numeric(18,4), notes)

carpet_final_invoice_items (id uuid PK, final_invoice_id FK, carpet_type_id FK, pricing_strategy [per_sqm|per_piece|cost_plus], actual_area_sqm numeric(18,4) nullable, quantity int, unit_rate numeric(18,4), company_cost numeric(18,4), markup_pct numeric(8,4), line_total numeric(18,4))

tailoring_orders (id uuid PK, order_number, customer_id FK, linked_invoice_id FK nullable, branch_id FK, date, operational_status, financial_status)

tailoring_tasks (id uuid PK, order_id FK, service_description, garment_type_id FK, quantity int, unit_price numeric(18,4), assigned_tailor_id FK, tailor_cost numeric(18,4), task_status [pending|in_progress|completed])

inventory_transactions (id uuid PK, item_id FK, branch_id FK, transaction_type [sale|sale_cancellation|stock_receipt|adjustment], quantity_change numeric(18,4), reference_type, reference_id, date)

2.3 Financial Entities

chart_of_accounts (id uuid PK, account_code, account_name_en, account_name_ar, account_type [asset|liability|equity|income|expense], parent_id FK nullable, is_active, is_contra)

journal_entries (id uuid PK, entry_number, date, reference_type, reference_id, narration, branch_id FK, fiscal_year_id FK, is_manual bool, created_by_user_id FK, status [draft|posted|reversed])

journal_entry_lines (id uuid PK, journal_entry_id FK, account_code FK, debit numeric(18,4), credit numeric(18,4), branch_id FK, customer_id FK nullable, supplier_id FK nullable, tailor_id FK nullable, currency_id FK, narration)

payments (id uuid PK, payment_number, invoice_id FK nullable, carpet_final_invoice_id FK nullable, customer_id FK, branch_id FK, date, amount numeric(18,4), method [cash|card|bank_transfer|customer_credit|other], reference_no, notes, shift_id FK, status)

supplier_payments (id uuid PK, payment_number, supplier_type [carpet_company|tailor], supplier_id, branch_id FK, date, amount numeric(18,4), method, reference_no, notes)

customer_credits (id uuid PK, customer_id FK, branch_id FK, amount numeric(18,4), source_type, source_id, date, notes, remaining_amount numeric(18,4))

2.4 Supporting Entities

shift_sessions (id uuid PK, user_id FK, branch_id FK, open_time, close_time, opening_float numeric(18,4), expected_cash numeric(18,4), actual_cash numeric(18,4), discrepancy numeric(18,4), notes)

audit_logs (id uuid PK, entity_type, entity_id, action, user_id FK, timestamp, old_value jsonb, new_value jsonb, branch_id FK)

sync_batches (id uuid PK, source_branch_id FK, target_branch_id FK nullable, export_date, import_date, total_rows, status [pending|imported|validated|rejected], file_path, validation_errors text)

system_settings (key text PK, value text)

3. Chart of Accounts & General Ledger

3.1 Chart of Accounts Structure

The Chart of Accounts (CoA) is a hierarchical tree structure. Each account belongs to one of the five major types:

Type Code Range Examples
Assets (1XXX) 1000-1999 Cash (1000), Bank (1010), Accounts Receivable (1200), Inventory (1300), Prepaid Expenses (1400)
Liabilities (2XXX) 2000-2999 Accounts Payable (2000), Accrued Tailor Payable (2100), Tax Payable (2200), Customer Deposits / Unearned Revenue (2300)
Equity (3XXX) 3000-3999 Owner's Capital (3000), Retained Earnings (3100)
Income (4XXX) 4000-4999 Laundry Income (4000), Carpet Income (4010), Tailoring Income (4020), Product Sales Income (4030)
Expenses (5XXX) 5000-5999 Laundry Supplies (5000), Carpet Cleaning Expense (5010), Tailoring COGS (5020), Product COGS (5030), Salaries (5040), Damage/Loss Expense (5050), Refund Expense (5060)

Rule: Sub-accounts inherit the type of their parent. The hierarchical code reflects the tree (e.g., 1200 = Accounts Receivable, 1201 = AR - Individuals, 1202 = AR - Companies).

3.2 General Ledger Table

The journal_entry_lines table IS the General Ledger. Every row is one side of a double-entry posting.

Core constraints: - For every Journal Entry: SUM(debit) == SUM(credit). Enforced at the application level before posting. - Once posted, a journal entry cannot be modified. Corrections create a reversing entry. - Every line carries branch_id for branch-level reporting. - Every line optionally carries customer_id, supplier_id, or tailor_id for sub-ledger reconciliation.

3.3 Balance Calculation

Customer/Supplier/Tailor balances are NEVER stored in a balance column on those entities. They are always calculated:

-- Customer balance (Accounts Receivable)
SELECT COALESCE(SUM(debit), 0) - COALESCE(SUM(credit), 0) AS balance
FROM journal_entry_lines
WHERE account_code LIKE '120%'  -- All AR sub-accounts
  AND customer_id = 'CUST-UUID';

For performance, a customer_balances materialized table can be maintained, but it is rebuilt from the GL on demand and never treated as the source of truth.


4. Accounting Rules Engine

4.1 Concept

Instead of hard-coding accounting logic for every transaction type, the system uses an Accounting Rules table. Each rule defines:

Field Description
Rule ID UUID
Rule Code Short identifier (e.g., "PAID_LAUNDRY_INVOICE")
Event The business event that triggers this rule
Debit Account Code
Credit Account Code
Condition Optional JSON filter (e.g., only for specific payment method)
Priority Order of execution if multiple rules match

4.2 How It Works

When a business event occurs (e.g., InvoiceConfirmed, PaymentReceived, CarpetReturned), the system:

  1. Looks up all rules matching the event.
  2. For each rule, evaluates the condition.
  3. Generates Journal Entry Lines (Debit X, Credit Y) for the matched accounts.
  4. Wraps everything in a single database transaction with the operational data changes.

4.3 Why a Rules Engine?

  • The shop owner can add a new settlement model for carpet companies by adding a row to accounting_rules, without code changes.
  • The accountant can adjust GL mappings without redeploying the application.
  • Multi-currency expansion: add rules for currency conversion entries.

5. Accounting Flows by Transaction Type

5.1 Paid Laundry Invoice (Cash at Receipt)

# Account Debit Credit
1 Cash / Bank (1000) Grand Total
2 Laundry Income (4000) Grand Total

If tax is enabled: | 3 | Cash (1000) | Tax Amount | | | 4 | Tax Payable (2200) | | Tax Amount |

5.2 Deferred Laundry Invoice

# Account Debit Credit
1 Accounts Receivable - Customer (120X) Grand Total
2 Laundry Income (4000) Grand Total

When payment is received later: | 3 | Cash (1000) | Paid Amount | | | 4 | Accounts Receivable - Customer (120X) | | Paid Amount |

5.3 Partial Payment

Partial payments debit Cash and credit Accounts Receivable for the paid portion only.

5.4 Invoice Contains Inventory Items

# Account Debit Credit
1 Cash / AR (1000/1200) Full Total
2 Laundry Income (4000) Service Portion
3 Product Sales Income (4030) Product Portion
4 Product COGS (5030) Product Cost
5 Inventory Asset (1300) Product Cost

5.5 Prepayment / Advance (Carpet)

# Account Debit Credit
1 Cash (1000) Advance Amount
2 Customer Deposits - Unearned Revenue (2300) Advance Amount

When final carpet invoice is created: | 3 | Customer Deposits (2300) | Advance Amount | | | 4 | Carpet Income (4010) | | Final Total | | 5 | If Advance > Final: Customer Deposits (2300) | Excess | | | 6 | If Advance > Final: Customer Credit Payable (23XX) | | Excess | | 7 | If Final > Advance: AR - Customer (120X) | Shortfall | | | 8 | If Final > Advance: Carpet Income (4010) | | Shortfall |

5.6 Carpet Company — Accrual Model

When carpet is returned from company: | 1 | Carpet Cleaning Expense (5010) | Company Cost | | | 2 | Accounts Payable - Company X (200X) | | Company Cost |

When payment is made to company: | 3 | Accounts Payable - Company X (200X) | Paid Amount | | | 4 | Cash (1000) | | Paid Amount |

5.7 Carpet Company — Cash Model

When payment is made to company: | 1 | Carpet Cleaning Expense (5010) | Paid Amount | | | 2 | Cash (1000) | | Paid Amount |

5.8 Carpet Company — Prepayment Model

When advance is paid to company: | 1 | Prepaid Expense - Company X (140X) | Advance | | | 2 | Cash (1000) | | Advance |

When carpet is returned: | 3 | Carpet Cleaning Expense (5010) | Company Cost | | | 4 | Prepaid Expense - Company X (140X) | | Company Cost |

5.9 Tailor — Periodic / Per-Item Model

When tailoring task is completed: | 1 | Tailoring COGS (5020) | Tailor Cost | | | 2 | Accrued Tailor Payable - Tailor X (210X) | | Tailor Cost |

When payment is made to tailor: | 3 | Accrued Tailor Payable - Tailor X (210X) | Paid Amount | | | 4 | Cash (1000) | | Paid Amount |

5.10 Tailor — Salary + Commission Model

Monthly salary (auto-generated by Quartz job): | 1 | Salaries Expense (5040) | Base Salary | | | 2 | Accrued Tailor Payable - Tailor X (210X) | | Base Salary |

Commission for completed items: | 3 | Tailoring Bonus Expense (5045) | Commission Amount | | | 4 | Accrued Tailor Payable - Tailor X (210X) | | Commission Amount |

5.11 Damage / Loss Compensation

# Account Debit Credit
1 Damage/Loss Expense (5050) Compensation Value
2 Customer Credit Payable (23XX) Compensation Value

Or if refunded in cash: | 1 | Damage/Loss Expense (5050) | Compensation Value | | | 2 | Cash (1000) | | Compensation Value |

5.12 Refund (Paid Invoice)

# Account Debit Credit
1 Refund Expense / Income Contra (5060) Refund Amount
2 Cash / Customer Credit (1000 / 23XX) Refund Amount

5.13 Invoice Cancellation (Unpaid)

The original entry is REVERSED: | # | Account | Debit | Credit | |---|---------|-------|--------| | 1 | Laundry Income (4000) | Original Amount | | | 2 | Accounts Receivable (120X) | | Original Amount |


6. UUID Strategy & Offline Sync

6.1 Why UUIDs

All primary keys are UUIDs generated client-side (C# Guid.NewGuid()). This prevents ID collisions when merging data from offline branches into the central server.

6.2 UUID Generation Rule

  • UUIDs are generated in the application layer (ASP.NET Core), not by the database.
  • The frontend never generates UUIDs — it sends POST requests without IDs, and the backend generates the UUID.
  • This ensures a single point of UUID creation.

6.3 Offline Branch Data Model

Offline branches use SQLite with an identical table structure. SQLite supports: - TEXT for UUIDs (stored as 36-character strings). - NUMERIC for decimal amounts. - SQLite triggers or application logic enforce the same SUM(debit) == SUM(credit) rule.

6.4 Sync Batch Structure

Each row in the offline database has sync_status = 'local' | 'pending' | 'synced'.

Export Package (JSON format):

{
  "batch_id": "uuid",
  "source_branch_id": "uuid",
  "source_branch_code": "CAIRO-01",
  "export_date": "2026-05-09T18:00:00Z",
  "tables": {
    "journal_entry_lines": [
      { "id": "uuid", "journal_entry_id": "uuid", ... }
    ],
    "invoices": [ ... ],
    "payments": [ ... ],
    "customers": [ ... ],
    ...
  }
}

6.5 Import and Merge Logic

1. Load export JSON file.
2. Validate branch_id matches expected source.
3. Validate SUM(debit) == SUM(credit) within the batch.
4. Validate all account_code references exist in central CoA.
5. For each table, iterate rows:
   - If row.id does NOT exist centrally → INSERT.
   - If row.id exists centrally (re-import) → UPSERT (update if source newer).
6. Record the sync_batch with status 'imported' or 'validated'.
7. Update source rows (if accessible) to sync_status = 'synced'.

6.6 Conflict Resolution

  • Customer phone collision: If a customer with the same phone number already exists centrally, the import process flags it. The Admin chooses:
  • Link (associate offline customer to existing central customer).
  • Keep separate (the import creates a new customer entry).
  • Invoice number collision: Invoice numbers are prefixed with branch code, so this should not happen. If it does, the import is rejected.
  • Duplicate sync: Since all keys are UUIDs, re-importing the same file is safe (upsert by id).

7. Reporting API Contract Pattern

7.1 Standard Response Format

Every report API endpoint returns this envelope:

{
  "meta": {
    "reportName": "string",
    "generatedAt": "ISO8601",
    "branchId": "uuid | 'all'",
    "currency": "EGP",
    "filters": {
      "dateFrom": "YYYY-MM-DD",
      "dateTo": "YYYY-MM-DD"
    }
  },
  "summary": {
    "totalRevenue": 12500.00,
    "totalInvoices": 45,
    ...
  },
  "details": [
    { /* pre-calculated row */ }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 50,
    "totalCount": 450
  }
}

7.2 Key Principles

Principle Rule
All calculations in SQL Running balances, SUM, AVG, grouping, filtering — all done in the database.
Frontend never calculates money The balance, total, remaining fields are pre-computed. Frontend only formats and renders.
Pagination for large datasets The backend paginates. Frontend uses lazy loading on PrimeNG tables.
Summary endpoint separate Dashboards call /report/daily-sales/summary (aggregates only). Drill-down calls /report/daily-sales/details (full rows).
Authorization at the API level The backend returns only data the requesting user is allowed to see (branch filter, role filter).
Date range limits Reports default to 30 days. Maximum 1 year without Admin override.

7.3 Report API Endpoints (Reference)

GET /api/reports/daily-sales          ?branchId=&dateFrom=&dateTo=
GET /api/reports/shift-closing        ?shiftId=
GET /api/reports/customer-statement   ?customerId=&dateFrom=&dateTo=
GET /api/reports/general-ledger       ?accountCode=&dateFrom=&dateTo=&page=&pageSize=
GET /api/reports/trial-balance        ?dateFrom=&dateTo=
GET /api/reports/income-statement     ?branchId=&dateFrom=&dateTo=
GET /api/reports/balance-sheet        ?date=
GET /api/reports/tailor-payout        ?tailorId=&dateFrom=&dateTo=
GET /api/reports/company-payables     ?companyId=&dateFrom=&dateTo=
GET /api/reports/inventory-levels     ?branchId=&category=
GET /api/reports/best-selling         ?dateFrom=&dateTo=&limit=
GET /api/reports/corporate-summary    ?customerId=&year=&month=
GET /api/reports/damage-report        ?dateFrom=&dateTo=
GET /api/reports/discounts-overrides  ?dateFrom=&dateTo=&userId=
GET /api/reports/branch-comparison    ?dateFrom=&dateTo=
GET /api/reports/audit-log            ?entityType=&entityId=&userId=&dateFrom=&dateTo=

8. Carpet Settlement Models (Configurable Per Company)

Model When Carpet Returns From Company When You Pay The Company
Accrual Debit Expense, Credit A/P Debit A/P, Credit Cash
Cash Nothing Debit Expense, Credit Cash
Prepayment Debit Expense, Credit Prepaid Debit Prepaid (advance), Credit Cash; Balance auto-calculated

Each carpet_company has a settlement_model field that can be overridden per transaction if needed.


9. Tailor Payout Models (Configurable Per Tailor)

Model When Task Completed When Paid
Periodic Debit Tailoring COGS, Credit Accrued Payable Debit Accrued Payable, Credit Cash
Per-Item Debit Tailoring COGS, Credit Accrued Payable (instant) Debit Accrued Payable, Credit Cash (any time)
Salary + Commission Salary: Quartz job posts monthly debit Salary Expense, credit Accrued Payable. Commission: Same as Periodic for bonus items. Same as others

Each tailor has a payout_model field. The accounting engine reads it and applies the matching rule.


10. Pricing Strategy Configuration

10.1 Carpet Pricing Strategies

Strategy Parameters Price Calculation
Per Square Meter actual_area_sqm, selling_rate_per_sqm actual_area_sqm × selling_rate_per_sqm
Per Piece quantity, fixed_price_per_piece quantity × fixed_price_per_piece
Cost-Plus Markup company_cost, markup_pct company_cost × (1 + markup_pct / 100)

Each carpet_type record specifies which strategies are allowed (allowed_strategies JSON field: ["per_sqm", "per_piece", "cost_plus"]).

10.2 Flexible Price List Architecture

CustomerClassification
PriceList (selected per classification)
PriceListEntry (garment_type + service_type → base_price)
    ▼ (optional override with reason)
InvoiceItem (frozen price at time of invoice confirmation)

Multiple price lists can be active simultaneously. Changing a price list does not affect historical invoices.


11. Soft Delete & Audit Trail

11.1 No Hard Deletes for Financial Data

  • Invoices, payments, journal entries, and receipts are never physically deleted.
  • "Cancellation" creates a status change + a reversing journal entry.
  • Only Draft invoices (unconfirmed, no financial impact) can be removed.

11.2 Audit Log Table

CREATE TABLE audit_logs (
    id UUID PRIMARY KEY,
    entity_type VARCHAR(50) NOT NULL,    -- 'Invoice', 'Payment', 'Customer', etc.
    entity_id UUID NOT NULL,
    action VARCHAR(20) NOT NULL,          -- 'CREATE', 'UPDATE', 'DELETE', 'STATUS_CHANGE'
    user_id UUID NOT NULL,
    timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    old_value JSONB,                      -- NULL for CREATE
    new_value JSONB,                      -- NULL for DELETE
    branch_id UUID NOT NULL
);

11.3 What Triggers an Audit Entry

  • Any API call that modifies Master Data, Operational Data, or Financial Data.
  • Status changes (e.g., Invoice from "Ready" to "Delivered").
  • Price overrides and manual discounts (old price, new price, reason).
  • Permission changes.

12. Technology Library References

12.1 Backend (ASP.NET Core)

Library NuGet Package Purpose
Entity Framework Core Microsoft.EntityFrameworkCore ORM
Npgsql EF Core Provider Npgsql.EntityFrameworkCore.PostgreSQL PostgreSQL driver
Dapper Dapper High-performance raw SQL for reports & ledger queries
MediatR MediatR CQRS pattern (commands for writes, queries for reads)
FluentValidation FluentValidation.AspNetCore Input validation
AutoMapper AutoMapper DTO mapping
Serilog Serilog.AspNetCore Structured logging
Serilog Npgsql Sink Serilog.Sinks.PostgreSQL Log to database
Quartz.NET Quartz.Extensions.Hosting Background jobs (payout generation, nightly sync)
BCrypt.Net BCrypt.Net-Next Password hashing
Microsoft Identity / JWT Microsoft.AspNetCore.Authentication.JwtBearer Auth

12.2 Frontend (Angular)

Library npm Package Purpose
PrimeNG primeng UI components (confirmed by client)
PrimeFlex primeflex CSS utilities
PrimeIcons primeicons Icons
ngx-translate @ngx-translate/core AR/EN translations
SheetJS xlsx Excel export
html2canvas html2canvas Render HTML to canvas
jspdf jspdf PDF generation
jsbarcode jsbarcode Barcode generation
RxJS rxjs Reactive state

12.3 Database

Technology Role
PostgreSQL 16+ Central database
SQLite 3 Offline branch database
Npgsql .NET driver for PostgreSQL
Microsoft.Data.Sqlite .NET driver for SQLite

Appendix A: State Transition Diagrams

A.1 Invoice Operational Status

stateDiagram-v2
    [*] --> Draft
    Draft --> Confirmed : Confirm
    Confirmed --> InProgress : Start Processing
    InProgress --> Ready : All Items Ready
    Ready --> Delivered : Customer Pickup
    Draft --> [*] : Remove
    Confirmed --> [*] : Cancel (if unpaid)
    Ready --> DamagedLost : Mark Damaged/Lost

A.2 Invoice Financial Status

stateDiagram-v2
    [*] --> Unpaid
    Unpaid --> PartiallyPaid : Partial Payment
    PartiallyPaid --> Paid : Remaining Paid
    Unpaid --> Deferred : Deferred Payment Terms
    Deferred --> PartiallyPaid : Partial Payment
    Deferred --> Paid : Full Payment
    Paid --> Refunded : Refund Processed

A.3 Carpet Receipt → Final Invoice

stateDiagram-v2
    [*] --> Received : Receive Carpet (Receipt Created)
    Received --> Assigned : Assign to Company
    Assigned --> Returned : Carpet Returned (Measure Area)
    Returned --> FinalInvoice : Create Final Invoice (1-to-1 linked)
    FinalInvoice --> Deducted : Advance Auto-Deducted
    Deducted --> PaidRemainder : Advance < Final → Pay Remainder
    Deducted --> CreditCreated : Advance > Final → Customer Credit
    PaidRemainder --> [*]
    CreditCreated --> [*]

Appendix B: Quick Reference — Accounting Account Codes

Code Account Name Type
1000 Cash Asset
1010 Bank Asset
1200 Accounts Receivable Asset
1300 Inventory Asset
1400 Prepaid Expenses Asset
2000 Accounts Payable Liability
2100 Accrued Tailor Payable Liability
2200 Tax Payable Liability
2300 Customer Deposits / Unearned Revenue Liability
3000 Owner's Capital Equity
3100 Retained Earnings Equity
4000 Laundry Income Income
4010 Carpet Income Income
4020 Tailoring Income Income
4030 Product Sales Income Income
5000 Laundry Supplies Expense Expense
5010 Carpet Cleaning Expense Expense
5020 Tailoring COGS Expense
5030 Product COGS Expense
5040 Salaries Expense Expense
5045 Tailoring Bonus Expense Expense
5050 Damage/Loss Expense Expense
5060 Refund Expense / Income Contra Expense