feat(billing): manual cadence + fix admin date inputs ignoring date-format setting

Manual billing cadence
  Adds a "Manual (trigger only)" cadence alongside monthly/quarterly. It reuses
  the monthly draft accumulator — invoices and billed hours pile onto one running
  draft — but stores NULL monthly_period_start/end so the scheduler's auto-flush
  never matches. The draft ships only when an admin clicks "Trigger invoice now".
  No migration: billing_cadence is a free-form string column gated by validators.
  - adminCustomers.js: allow 'manual' in billing_cadence validator
  - invoiceService.js: route manual through accumulator; NULL periods + placeholder
    issue/due date in getOrCreateMonthlyDraft
  - customerHoursService.js: manual auto-appends hours to running draft;
    billUnbilledEntries refuses manual (CADENCE_MISMATCH)
  - CustomerDetailPage.tsx: dropdown option, cycle-day hidden for manual,
    NULL-period-safe draft preview + trigger button, manual-specific copy
  - customerAdmin.service.ts: cadence union + nullable periodStart/periodEnd
  - en.json / de.json: manual, triggerConfirmManual, triggerHintManual,
    draftPreview.titleManual
Date-format fixes
  Replace raw <input type="date"> (browser-locale) with LocalizedDateInput so
  these admin surfaces honor the general_date_format setting:
  - ContractEditorPage.tsx (issue / valid-until / event dates)
  - QuoteEditorPage.tsx (event / valid-until dates)
  - EventDetailsPage.tsx (expiry date)
  - HoursSection.tsx (entry date)
This commit is contained in:
Luca
2026-06-02 09:27:10 +02:00
parent d788a6cde5
commit 06b4522dec
11 changed files with 104 additions and 65 deletions
@@ -17,7 +17,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { toast } from 'react-toastify';
import { Clock } from 'lucide-react';
import { Button, Card } from '../common';
import { Button, Card, LocalizedDateInput } from '../common';
import { DecimalInput } from '../common/DecimalInput';
import { parseLocaleDecimal, parseDuration } from '../../utils/parsers';
import { customerAdminService } from '../../services/customerAdmin.service';
@@ -224,8 +224,7 @@ export const HoursSection: React.FC<HoursSectionProps> = ({
<label className="block text-xs text-muted-theme mb-1">
{t('customers.hours.form.date', 'Date')}
</label>
<input type="date" value={entryDate}
onChange={(e) => setEntryDate(e.target.value)} className="input w-full" />
<LocalizedDateInput value={entryDate} onChange={setEntryDate} />
</div>
<div>
<label className="block text-xs text-muted-theme mb-1">