feat(invoices): surface monthly/manual accumulator drafts in the Bills list
Manual/monthly-cadence customers accumulate logged hours into one running
draft invoice (is_monthly_draft, migration 128). That draft gets a real
invoice number and stamps the hours ("Billed: R-2026-0026"), but listInvoices
hid is_monthly_draft rows from the main list — so the invoice looked lost even
though it existed on the customer's monthly-queue card. It also carried status
'scheduled' despite never auto-sending on manual cadence, reading misleadingly
as "Scheduled".
- Bills list now opts into drafts via a new `includeDrafts` query param
(GET /admin/invoices → listInvoices includeMonthlyDrafts). Pickers/sub-lists
that reuse billsService.list leave it off, so they're unaffected.
- Draft rows render a distinct "Draft" badge instead of "Scheduled"
(transformInvoice already exposes isMonthlyDraft).
- The hours "Billed: R-…" chip now links straight to its invoice.
- i18n: bills.status.draft (de "Entwurf", en "Draft").
This commit is contained in:
@@ -47,6 +47,9 @@ export const BillsListPage: React.FC = () => {
|
||||
q: search || undefined,
|
||||
status: statusFilter.length ? statusFilter : undefined,
|
||||
unpaidOnly,
|
||||
// Surface the running monthly/manual accumulator drafts here (they're
|
||||
// badged "Draft"); they're hidden from pickers/sub-lists by default.
|
||||
includeDrafts: true,
|
||||
sort, page, pageSize: 25,
|
||||
}),
|
||||
});
|
||||
@@ -186,14 +189,23 @@ export const BillsListPage: React.FC = () => {
|
||||
{formatMoney(Number(inv.totalAmountMinor) / 100, inv.currency)}
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<span className={`px-2 py-0.5 rounded text-xs font-medium ${
|
||||
inv.status === 'paid' ? 'bg-green-100 text-green-800'
|
||||
: inv.status === 'overdue' ? 'bg-red-100 text-red-800'
|
||||
: inv.status === 'sent' ? 'bg-blue-100 text-blue-800'
|
||||
: inv.status === 'cancelled' ? 'bg-neutral-200 text-neutral-600'
|
||||
: inv.status === 'skipped' ? 'bg-neutral-100 text-neutral-500 italic'
|
||||
: 'bg-amber-100 text-amber-800'
|
||||
}`}>{t(`bills.status.${inv.status}`, inv.status)}</span>
|
||||
{inv.isMonthlyDraft ? (
|
||||
// Running accumulator draft (manual/monthly). It carries
|
||||
// status 'scheduled' but never auto-sends on manual cadence,
|
||||
// so badge it honestly as "Draft" rather than "Scheduled".
|
||||
<span className="px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900/40 dark:text-purple-200">
|
||||
{t('bills.status.draft', 'Draft')}
|
||||
</span>
|
||||
) : (
|
||||
<span className={`px-2 py-0.5 rounded text-xs font-medium ${
|
||||
inv.status === 'paid' ? 'bg-green-100 text-green-800'
|
||||
: inv.status === 'overdue' ? 'bg-red-100 text-red-800'
|
||||
: inv.status === 'sent' ? 'bg-blue-100 text-blue-800'
|
||||
: inv.status === 'cancelled' ? 'bg-neutral-200 text-neutral-600'
|
||||
: inv.status === 'skipped' ? 'bg-neutral-100 text-neutral-500 italic'
|
||||
: 'bg-amber-100 text-amber-800'
|
||||
}`}>{t(`bills.status.${inv.status}`, inv.status)}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user