feat(invoices): auto income entry on 'paid' status
Marking an invoice as paid now creates a finance_entry (type=income) for
the customer with amount = invoice.total, linked via invoiceId. Reverting
status removes the entry. Idempotent: re-saving while already paid keeps
the existing entry (resyncs amount if invoice total changed in the
meantime).
- syncPaymentEntry(tenantId, userId, invoice) helper:
* status === 'paid': create entry if none exists; otherwise update
amount to match current invoice.total.
* status !== 'paid': delete any income entries linked to the invoice.
* Best-effort — failures are swallowed so the invoice mutation always
succeeds even if Appwrite hiccups on the finance write.
* Each create/delete writes an audit row tagged auto: 'invoice_paid' /
'invoice_unpaid' so we can trace later.
- updateInvoiceAction now calls syncPaymentEntry after persisting.
- recomputeTotals (run on every item add/update/delete) also re-syncs
the linked entry's amount when the invoice is currently paid.
- deleteInvoiceAction now cascade-deletes any linked finance_entries in
addition to items.
- /invoices and /invoices/[id] both revalidate /finance after writes.
UI:
- Invoice form shows a hint under the status select explaining the
finance side effect.
- Finance table tags rows with a 'Faturadan' badge when invoiceId is
set, so users can tell auto-generated entries apart from manual ones.
This commit is contained in:
@@ -191,14 +191,18 @@ export function FinanceClient({ entries, customers }: Props) {
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Açıklama",
|
||||
cell: ({ row }) =>
|
||||
row.original.description ? (
|
||||
<span className="text-muted-foreground line-clamp-1 max-w-[260px] text-sm">
|
||||
{row.original.description}
|
||||
cell: ({ row }) => (
|
||||
<div className="flex max-w-[300px] items-center gap-2">
|
||||
<span className="text-muted-foreground line-clamp-1 text-sm">
|
||||
{row.original.description || "—"}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">—</span>
|
||||
),
|
||||
{row.original.invoiceId && (
|
||||
<Badge variant="outline" className="shrink-0 text-[10px]">
|
||||
Faturadan
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
|
||||
@@ -10,6 +10,7 @@ export type FinanceRow = {
|
||||
customerId: string;
|
||||
customerName: string;
|
||||
paymentMethod: PaymentMethod;
|
||||
invoiceId: string;
|
||||
};
|
||||
|
||||
export type Customer = { id: string; name: string };
|
||||
|
||||
@@ -45,6 +45,7 @@ export default async function FinancePage() {
|
||||
customerId: e.customerId ?? "",
|
||||
customerName: e.customerId ? customerMap.get(e.customerId) ?? "" : "",
|
||||
paymentMethod: e.paymentMethod ?? "",
|
||||
invoiceId: e.invoiceId ?? "",
|
||||
}))}
|
||||
customers={customers.map((c) => ({ id: c.$id, name: c.name }))}
|
||||
/>
|
||||
|
||||
@@ -144,6 +144,10 @@ export function InvoiceFormSheet({ open, onOpenChange, invoice, customers }: Pro
|
||||
<SelectItem value="cancelled">İptal</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
“Ödendi” seçildiğinde finans modülüne otomatik gelir kaydı düşer.
|
||||
Durum geri alınırsa kayıt silinir.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
|
||||
Reference in New Issue
Block a user