Klinik-laboratuvar finance in TR is dönemsel, not job-by-job. Forcing
the lab to mark each finance_entry as paid was unrealistic — labs get a
single 50.000 TL transfer covering twelve jobs and don't want to walk a
list. Reworked the page around connection balances and free-amount
payments.
Data model
- New 'payments' table:
tenantId whose ledger it lives in
counterpartTenantId the other side of the transaction
direction inflow (lab received) | outflow (clinic paid)
amount, currency, paymentDate
method cash | bank | card | check | other
notes, recordedBy
Indexes: (tenantId, counterpartTenantId), paymentDate DESC. Permission:
both teams can read, only owners/admins of the recording side can
update or delete.
Server
- recordPaymentAction: requires owner/admin, verifies an approved
connection exists between (lab, clinic), then writes a single row.
Direction is inferred from the caller's tenant kind so a lab can
never accidentally book an outflow.
- deletePaymentAction: same auth, tenant-scoped delete.
- listPayments(tenantId) + computeBalancesByCounterpart({ kind,
entries, payments }): one pass over both ledgers, returns
[{ counterpartTenantId, invoiced, paid, open, lastPaymentAt }]
sorted by open desc. invoiced pulls from finance_entries (receivable
for lab, debt for clinic); paid pulls from the new payments table.
UI
- /finance now leads with a Bakiye kartı: a row per connected
counterpart showing invoiced, paid, last payment date and the open
amount tinted green (lab alacak) or red (clinic borç), each with an
inline 'Ödeme Al' / 'Ödeme Yap' button.
- RecordPaymentDialog: amount (defaults to the open balance, lump
sums obviously not pre-filled with a specific entry), date,
currency, method (Nakit/Banka/Kart/Çek/Diğer), free-text note.
Posts to recordPaymentAction, refresh on success.
- Stat cards reworked: Toplam Açık Alacak/Borç and Tahsil Edilen /
Ödenen replaced the old pending-totals so the headline numbers
actually reflect the new flow.
Existing finance_entries (job-driven receivables/debts) remain the
single source of truth for 'how much was invoiced'; the new table tracks
'how much was actually collected'. Open balance = invoiced - paid, always
computed live — no individual entry needs to be marked 'paid' anymore.