Skip to content

Customers, Partners & Suppliers

Party-level endpoints: end customers, B2B partners (agents), suppliers, leads, and the unified "people" surface.


Customers

Handlers: handleSalesCustomers at src/lib/api.ts:6992, handleSalesCustomersById at src/lib/api.ts:7220, handleCustomerSync at src/lib/api.ts:7123, handleSalesCustomerPassengers at src/lib/api.ts:7303, handleSalesCustomerDocuments at src/lib/api.ts:7381, handleSalesCustomerDriveDocuments at src/lib/api.ts:7455, handleSalesCustomerLedger at src/lib/api.ts:7515, handleCustomerPasswordRoute at src/lib/api.ts:8014.

Route Method Permission Purpose
/sales/customers GET (read-only) List customers
/sales/customers POST customers.create Create or revive soft-deleted customer (keyed by passport)
/sales/customers/sync POST customers.edit Sync customer rows with auth users (mirror User → Customer)
/sales/customers/:id GET (read-only) One customer with full profile
/sales/customers/:id PATCH customers.edit Update customer profile
/sales/customers/:id DELETE customers.delete Soft-delete (deletedAt)
/sales/customers/:id/passengers GET, POST customers.edit (POST) Saved passengers linked to the customer (family members)
/sales/customers/:id/passengers/:pid PATCH, DELETE customers.edit Update or remove a saved passenger
/sales/customers/:id/documents GET (read-only) Documents attached to the customer
/sales/customers/:id/drive-documents GET, POST customers.edit (POST) Google-Drive-backed docs
/sales/customers/:id/drive-documents/:docId DELETE customers.edit Remove a drive doc
/sales/customers/:id/ledger GET (read-only) Customer ledger with running balance
/sales/customers/:id/password POST customers.edit Admin resets customer portal password

POST /sales/customers

Cite: src/lib/api.ts:7037.

Input — full personal profile: { title?, firstName, lastName, dob?, gender?, fatherName?, bloodGroup?, passportNo, passportIssuedDate?, passportExpiry?, nationality?, panCard?, aadhaarNumber?, phone?, email?, address?, district?, state?, country?, pinCode?, packagePrice?, currency?, roomType?, source?, sourceAgentId?, agentId?, gstNumber? }.

Special rules:

  • Passport uniqueness — if passportNo matches an existing row:
  • If the existing row is soft-deleted, it is revived (ID reused; fields updated).
  • If active, returns 409 Conflict — "A customer already exists with this passport number."
  • gstNumber is uppercased + trimmed defensively.
  • source defaults to Direct. Partners' clients set source='Through Business Partner' with sourceAgentId pointing to the partner.

GET /sales/customers/:id/ledger

Returns a customer's full ledger — every payment, invoice, booking commitment, on-account receipt allocation — with running balance per row and summary totals.

Per-customer passengers (family members)

Customers can register saved passengers (spouse, children) that get auto-populated when booking. CRUD is at /sales/customers/:id/passengers/[:pid] — all writes require customers.edit. Cite: src/lib/api.ts:7303.


Partners (agents)

Handlers: handleAgents at src/lib/api.ts:12646, handleAgentsById at src/lib/api.ts:12801, handleAgentLedger at src/lib/api.ts:7713.

Route Method Permission Purpose
/agents GET (read-only) List partners with totalBilled / totalPaid / outstanding / totalBookings rollups
/agents POST partners.create Create partner; optionally provisions a Supabase auth user + AGENT role; auto-ensures A/R + A/P GL accounts
/agents/:id PATCH partners.edit Update partner; optionally createLogin=true provisions login; supports re-linking to existing auth user
/agents/:id DELETE partners.delete Soft-delete; rejected (409) if partner has bookings or ledger entries
/agents/:id/ledger GET (read-only) Agent ledger with running balance

POST /agents

Cite: src/lib/api.ts:12704.

Input{ name, company?, email?, phone?, commissionRate?, panCard?, gstNumber?, address?, contactPerson?, createUser?, password? }.

Side-effects:

  • If createUser=true, call invokeAdminUsers({ action: 'create_user', ... }) to provision the auth user, assign AGENT role, and link Agent.userId.
  • If the auth user already exists (email collision), link the existing user and reset the password.
  • Ensure the partner's receivable (A/R) and payable (A/P) sub-ledger accounts exist in the chart of accounts (ensureAgentReceivableAccount, ensureAgentPayableAccount).

Returns{ id, name, tempPassword?, loginError? }.

DELETE /agents/:id

Delete is blocked if history exists

Returns 409 Conflict with a clear message if the partner has any:

  • Bookings (Booking.agentId = :id)
  • Ledger entries on their A/R or A/P GL accounts

Deactivate via PATCH /agents/:id with status='inactive' instead. Cite: src/lib/api.ts:12807.


Suppliers

Handler: handleSuppliers at src/lib/api.ts:17065.

Route Method Permission Purpose
/suppliers GET (read-only) List (supports ?category=, ?includeInactive=true)
/suppliers POST suppliers.create Create supplier; duplicate-detects by category + name (+ airline for ticketing)
/suppliers/:id PATCH suppliers.edit Update supplier
/suppliers/:id DELETE suppliers.delete Soft-delete
/suppliers/:id/ledger GET (read-only) Supplier ledger with running balance; filters SupplierTransaction rows whose journalEntryId is not yet approved
/suppliers/:id/transactions GET (read-only) All transactions
/suppliers/:id/transactions POST suppliers.edit Create supplier transaction (debit / credit / refund); posts journal; optionally deducts TDS
/suppliers/transactions/:txnId PATCH suppliers.edit Edit transaction; auto-reposts journal delta
/suppliers/transactions/:txnId DELETE suppliers.delete Delete transaction; reverses journal
/suppliers/transactions/:txnId/allocate POST suppliers.edit Apply a credit/refund to a specific debit
/suppliers/quota-payments GET inventory.view Supplier payments tied to airline quota blocks

Duplicate detection

POST /suppliers first calls findSupplierDuplicate which checks:

  1. For category='ticketing' with an airlineId: any existing active supplier for that airline.
  2. For all categories: any existing supplier with the same category + normalized name (case-insensitive, whitespace-collapsed).

If a duplicate is found it is returned as 409 Conflict with the existing row's id.

TDS inside supplier flows

Cite: src/lib/api.ts:17650. When a supplier payment qualifies for TDS deduction, the handler calls requirePermission('finance.tds.deduct') inline before recording the deduction and TDS-liability journal.

Airline quota block outstanding guard

Cite: src/lib/api.ts:17200. When creating a supplier credit/refund linked to an AirlineQuotaBlock, the handler computes outstanding = (seats × pricePerSeat) - already-paid and rejects (400) if the new payment would exceed the outstanding amount.


Leads

Handlers: handleLeads at src/lib/api.ts:20023, handleLeadById at src/lib/api.ts:20076, handleLeadConversion at src/lib/api.ts:19897.

Route Method Permission Purpose
/leads or /sales/leads GET (read-only) List leads
/leads or /sales/leads POST leads.edit Create lead
/leads/:id GET (read-only) One lead
/leads/:id PATCH, DELETE leads.edit Update / delete
/leads/:id/convert POST leads.edit Convert lead → customer (+ optional booking)

POST /leads/:id/convert accepts { createBooking?: boolean, bookingPayload? } and when createBooking=true delegates to handleSalesBookings('POST', ...).


People (unified party query)

The "People" frontend page reads from /sales/customers for customers and /agents for partners — there is no /people handler. Dedicated to the People UI:

  • GET /sales/customers for customer list.
  • GET /agents for partner list.
  • GET /users for staff users (see Admin).

Customer portal password management

POST /sales/customers/:id/password — creates or resets the customer portal login. Cite: src/lib/api.ts:8014. Supports two modes depending on body:

  • { action: 'create', password } — provisions a Supabase auth user for the customer (linked via Customer.userId) and assigns the CUSTOMER role.
  • { action: 'reset', password } — resets the existing linked user's password.