Просмотр исходного кода

Add invoice date, due date, and payment status controls to work orders (#99)

* workorder dates

* service details

* added set paid button in sidebar

* cleanup

* lint
Bernt Christian Egeland 6 месяцев назад
Родитель
Сommit
c306e209ed
66 измененных файлов с 771 добавлено и 509 удалено
  1. 1 14
      messages/de/dashboard.json
  2. 12 0
      messages/de/service.json
  3. 1 14
      messages/en/dashboard.json
  4. 12 0
      messages/en/service.json
  5. 1 14
      messages/es/dashboard.json
  6. 12 0
      messages/es/service.json
  7. 1 14
      messages/fr/dashboard.json
  8. 12 0
      messages/fr/service.json
  9. 1 14
      messages/it/dashboard.json
  10. 12 0
      messages/it/service.json
  11. 1 14
      messages/nb/dashboard.json
  12. 12 0
      messages/nb/service.json
  13. 1 14
      messages/nl/dashboard.json
  14. 12 0
      messages/nl/service.json
  15. 1 14
      messages/pl/dashboard.json
  16. 12 0
      messages/pl/service.json
  17. 1 14
      messages/pt-BR/dashboard.json
  18. 12 0
      messages/pt-BR/service.json
  19. 1 14
      messages/ru/dashboard.json
  20. 12 0
      messages/ru/service.json
  21. 1 14
      messages/tr/dashboard.json
  22. 12 0
      messages/tr/service.json
  23. 18 0
      prisma/migrations/20260327055410_invoice_due_date/migration.sql
  24. 2 0
      prisma/schema.prisma
  25. 3 0
      src/__tests__/features/public/invoice-view.test.tsx
  26. 2 1
      src/app/(authenticated)/billing/billing-client.tsx
  27. 2 1
      src/app/(authenticated)/dashboard-client.tsx
  28. 2 2
      src/app/(authenticated)/settings/data/data-settings.tsx
  29. 1 0
      src/app/(authenticated)/vehicles/[id]/service-records-table.tsx
  30. 9 0
      src/app/(authenticated)/vehicles/[id]/service/[serviceId]/page.tsx
  31. 1 0
      src/app/(authenticated)/vehicles/[id]/vehicle-detail-client.tsx
  32. 2 1
      src/app/(authenticated)/work-orders/work-orders-client.tsx
  33. 1 1
      src/app/(public)/portal/[orgId]/dashboard/page.tsx
  34. 1 1
      src/app/(public)/portal/[orgId]/invoices/page.tsx
  35. 1 1
      src/app/(public)/portal/[orgId]/vehicles/[vehicleId]/page.tsx
  36. 32 11
      src/app/(public)/share/invoice/[orgId]/[token]/invoice-view.tsx
  37. 1 1
      src/app/api/protected/backup/import/route.ts
  38. 2 1
      src/features/ai/Actions/aiActions.ts
  39. 9 4
      src/features/billing/Actions/billingActions.ts
  40. 1 0
      src/features/billing/Actions/recurringInvoiceActions.ts
  41. 5 4
      src/features/calendar/Actions/calendarActions.ts
  42. 6 3
      src/features/portal/Actions/portalActions.ts
  43. 1 0
      src/features/quotes/Actions/quoteActions.ts
  44. 23 17
      src/features/reports/Actions/reportActions.ts
  45. 1 0
      src/features/settings/Components/InvoiceLayoutPreviewRenderer.tsx
  46. 1 0
      src/features/vehicles/Actions/createDraftServiceRecord.ts
  47. 3 3
      src/features/vehicles/Actions/dashboardActions.ts
  48. 8 8
      src/features/vehicles/Actions/predictedMaintenanceActions.ts
  49. 14 10
      src/features/vehicles/Actions/serviceActions.ts
  50. 1 1
      src/features/vehicles/Actions/vehicleActions.ts
  51. 0 152
      src/features/vehicles/Components/CreatePartDialog.tsx
  52. 7 5
      src/features/vehicles/Components/invoice-pdf/InvoicePDF.tsx
  53. 10 2
      src/features/vehicles/Components/invoice-pdf/Notes.tsx
  54. 3 0
      src/features/vehicles/Components/invoice-pdf/types.ts
  55. 1 0
      src/features/vehicles/Components/service-detail/types.ts
  56. 7 83
      src/features/vehicles/Components/service-edit/BasicInfoSection.tsx
  57. 183 0
      src/features/vehicles/Components/service-edit/InvoiceDetailsSection.tsx
  58. 2 0
      src/features/vehicles/Components/service-edit/form-types.ts
  59. 12 4
      src/features/vehicles/Components/service-page/DetailsRightColumn.tsx
  60. 230 31
      src/features/vehicles/Components/service-page/ServicePageClient.tsx
  61. 1 0
      src/features/vehicles/Components/service-page/service-page-types.ts
  62. 10 3
      src/features/vehicles/Components/service-page/useServiceActions.ts
  63. 4 3
      src/features/vehicles/Components/service-page/useServiceFormState.ts
  64. 2 0
      src/features/vehicles/Schema/serviceSchema.ts
  65. 2 1
      src/lib/ai.ts
  66. 1 0
      src/lib/cron/recurring-invoices.ts

+ 1 - 14
messages/de/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Teil konnte nicht hinzugefügt werden",
     "inStock": "Auf Lager: {quantity}",
     "quantity": "Menge",
-    "addToJob": "{qty} zum Auftrag hinzufügen",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "{qty} zum Auftrag hinzufügen"
   },
   "recentActivity": "Letzte Aktivitäten",
   "noRecentActivity": "Keine aktuellen Aktivitäten",

+ 12 - 0
messages/de/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "Dies löscht diesen Serviceeintrag und alle zugehörigen Daten dauerhaft. Dies kann nicht rückgängig gemacht werden.",
     "deleted": "Serviceeintrag gelöscht",
     "failedDelete": "Löschen fehlgeschlagen",
+    "datesExpiredTitle": "Rechnungsdaten abgelaufen",
+    "datesExpiredDescription": "Das Rechnungsdatum oder Fälligkeitsdatum ist überschritten. Möchten Sie die Daten vor dem Fortfahren aktualisieren?",
+    "datesExpiredUpdate": "Daten aktualisieren und fortfahren",
+    "datesExpiredProceed": "Ohne Änderungen fortfahren",
+    "datesExpiredSetToday": "Auf heute setzen",
     "customFieldsInvalid": "Bitte füllen Sie alle erforderlichen benutzerdefinierten Felder aus",
     "customFieldsSaveFailed": "Benutzerdefinierte Felder konnten nicht gespeichert werden"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Grundlegende Informationen",
+    "invoiceDetails": "Rechnungsdetails",
+    "customerInfo": "Kunde & Fahrzeug",
+    "paid": "Bezahlt",
+    "markPaid": "Als bezahlt markieren",
+    "markUnpaid": "Als unbezahlt markieren",
     "vehicle": "Fahrzeug",
     "open": "Öffnen",
     "customer": "Kunde",
@@ -40,6 +50,8 @@
     "status": "Status",
     "technician": "Techniker",
     "invoiceNumber": "Rechnungsnummer",
+    "invoiceDate": "Rechnungsdatum",
+    "invoiceDueDate": "Fälligkeitsdatum",
     "typeOptions": {
       "maintenance": "Wartung",
       "repair": "Reparatur",

+ 1 - 14
messages/en/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Failed to add part",
     "inStock": "In stock: {quantity}",
     "quantity": "Quantity",
-    "addToJob": "Add {qty} to job",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "Add {qty} to job"
   },
   "recentActivity": "Recent Activity",
   "noRecentActivity": "No recent activity",

+ 12 - 0
messages/en/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "This will permanently delete this service record and all associated data. This cannot be undone.",
     "deleted": "Service record deleted",
     "failedDelete": "Failed to delete",
+    "datesExpiredTitle": "Invoice Dates Expired",
+    "datesExpiredDescription": "The invoice date or due date has passed. Would you like to update them before proceeding?",
+    "datesExpiredUpdate": "Update dates and proceed",
+    "datesExpiredProceed": "Proceed without changes",
+    "datesExpiredSetToday": "Set to today",
     "customFieldsInvalid": "Please fill in all required custom fields",
     "customFieldsSaveFailed": "Failed to save custom fields"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Basic Information",
+    "invoiceDetails": "Invoice Details",
+    "customerInfo": "Customer & Vehicle",
+    "paid": "Paid",
+    "markPaid": "Mark as Paid",
+    "markUnpaid": "Mark as Unpaid",
     "vehicle": "Vehicle",
     "open": "Open",
     "customer": "Customer",
@@ -40,6 +50,8 @@
     "status": "Status",
     "technician": "Technician",
     "invoiceNumber": "Invoice Number",
+    "invoiceDate": "Invoice Date",
+    "invoiceDueDate": "Due Date",
     "typeOptions": {
       "maintenance": "Maintenance",
       "repair": "Repair",

+ 1 - 14
messages/es/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Error al añadir la pieza",
     "inStock": "En stock: {quantity}",
     "quantity": "Cantidad",
-    "addToJob": "Añadir {qty} al trabajo",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "Añadir {qty} al trabajo"
   },
   "recentActivity": "Actividad reciente",
   "noRecentActivity": "Sin actividad reciente",

+ 12 - 0
messages/es/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "Esto eliminará permanentemente este registro de servicio y todos los datos asociados. Esto no se puede deshacer.",
     "deleted": "Registro de servicio eliminado",
     "failedDelete": "Error al eliminar",
+    "datesExpiredTitle": "Fechas de factura expiradas",
+    "datesExpiredDescription": "La fecha de la factura o la fecha de vencimiento ha pasado. ¿Desea actualizarlas antes de continuar?",
+    "datesExpiredUpdate": "Actualizar fechas y continuar",
+    "datesExpiredProceed": "Continuar sin cambios",
+    "datesExpiredSetToday": "Establecer a hoy",
     "customFieldsInvalid": "Por favor complete todos los campos personalizados obligatorios",
     "customFieldsSaveFailed": "Error al guardar los campos personalizados"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Información Básica",
+    "invoiceDetails": "Detalles de factura",
+    "customerInfo": "Cliente y vehículo",
+    "paid": "Pagado",
+    "markPaid": "Marcar como pagado",
+    "markUnpaid": "Marcar como no pagado",
     "vehicle": "Vehículo",
     "open": "Abierto",
     "customer": "Cliente",
@@ -40,6 +50,8 @@
     "status": "Estado",
     "technician": "Técnico",
     "invoiceNumber": "Número de Factura",
+    "invoiceDate": "Fecha de factura",
+    "invoiceDueDate": "Fecha de vencimiento",
     "typeOptions": {
       "maintenance": "Mantenimiento",
       "repair": "Reparación",

+ 1 - 14
messages/fr/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Échec de l'ajout de la pièce",
     "inStock": "En stock : {quantity}",
     "quantity": "Quantité",
-    "addToJob": "Ajouter {qty} au travail",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "Ajouter {qty} au travail"
   },
   "recentActivity": "Activité récente",
   "noRecentActivity": "Aucune activité récente",

+ 12 - 0
messages/fr/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "Cela supprimera définitivement ce dossier de service et toutes les données associées. Cette action ne peut pas être annulée.",
     "deleted": "Dossier de service supprimé",
     "failedDelete": "Impossible de supprimer",
+    "datesExpiredTitle": "Dates de facture expirées",
+    "datesExpiredDescription": "La date de facturation ou la date d'échéance est dépassée. Souhaitez-vous les mettre à jour avant de continuer ?",
+    "datesExpiredUpdate": "Mettre à jour les dates et continuer",
+    "datesExpiredProceed": "Continuer sans modifications",
+    "datesExpiredSetToday": "Définir à aujourd'hui",
     "customFieldsInvalid": "Veuillez remplir tous les champs personnalisés obligatoires",
     "customFieldsSaveFailed": "Échec de l'enregistrement des champs personnalisés"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Informations de Base",
+    "invoiceDetails": "Détails de la facture",
+    "customerInfo": "Client et véhicule",
+    "paid": "Payé",
+    "markPaid": "Marquer comme payé",
+    "markUnpaid": "Marquer comme non payé",
     "vehicle": "Véhicule",
     "open": "Ouvert",
     "customer": "Client",
@@ -40,6 +50,8 @@
     "status": "Statut",
     "technician": "Technicien",
     "invoiceNumber": "Numéro de Facture",
+    "invoiceDate": "Date de facture",
+    "invoiceDueDate": "Date d'échéance",
     "typeOptions": {
       "maintenance": "Maintenance",
       "repair": "Réparation",

+ 1 - 14
messages/it/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Impossibile aggiungere il ricambio",
     "inStock": "Disponibile: {quantity}",
     "quantity": "Quantità",
-    "addToJob": "Aggiungi {qty} al lavoro",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "Aggiungi {qty} al lavoro"
   },
   "recentActivity": "Attività recente",
   "noRecentActivity": "Nessuna attività recente",

+ 12 - 0
messages/it/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "Questo eliminerà permanentemente questo registro di servizio e tutti i dati associati. Non può essere annullato.",
     "deleted": "Registro di servizio eliminato",
     "failedDelete": "Impossibile eliminare",
+    "datesExpiredTitle": "Date della fattura scadute",
+    "datesExpiredDescription": "La data della fattura o la data di scadenza è passata. Vuoi aggiornare le date prima di procedere?",
+    "datesExpiredUpdate": "Aggiorna le date e procedi",
+    "datesExpiredProceed": "Procedi senza modifiche",
+    "datesExpiredSetToday": "Imposta a oggi",
     "customFieldsInvalid": "Compila tutti i campi personalizzati obbligatori",
     "customFieldsSaveFailed": "Impossibile salvare i campi personalizzati"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Informazioni di Base",
+    "invoiceDetails": "Dettagli fattura",
+    "customerInfo": "Cliente e veicolo",
+    "paid": "Pagato",
+    "markPaid": "Segna come pagato",
+    "markUnpaid": "Segna come non pagato",
     "vehicle": "Veicolo",
     "open": "Apri",
     "customer": "Cliente",
@@ -40,6 +50,8 @@
     "status": "Stato",
     "technician": "Tecnico",
     "invoiceNumber": "Numero Fattura",
+    "invoiceDate": "Data fattura",
+    "invoiceDueDate": "Data di scadenza",
     "typeOptions": {
       "maintenance": "Manutenzione",
       "repair": "Riparazione",

+ 1 - 14
messages/nb/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Kunne ikke legge til del",
     "inStock": "På lager: {quantity}",
     "quantity": "Antall",
-    "addToJob": "Legg til {qty} i jobb",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "Legg til {qty} i jobb"
   },
   "recentActivity": "Nylig aktivitet",
   "noRecentActivity": "Ingen nylig aktivitet",

+ 12 - 0
messages/nb/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "Dette vil permanent slette denne servicepotten og all tilknyttet data. Dette kan ikke angres.",
     "deleted": "Servicepost slettet",
     "failedDelete": "Kunne ikke slette",
+    "datesExpiredTitle": "Fakturadatoer utløpt",
+    "datesExpiredDescription": "Fakturadatoen eller forfallsdatoen har passert. Vil du oppdatere dem før du fortsetter?",
+    "datesExpiredUpdate": "Oppdater datoer og fortsett",
+    "datesExpiredProceed": "Fortsett uten endringer",
+    "datesExpiredSetToday": "Sett til i dag",
     "customFieldsInvalid": "Vennligst fyll ut alle obligatoriske egendefinerte felt",
     "customFieldsSaveFailed": "Kunne ikke lagre egendefinerte felt"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Grunnleggende informasjon",
+    "invoiceDetails": "Fakturadetaljer",
+    "customerInfo": "Kunde og kjøretøy",
+    "paid": "Betalt",
+    "markPaid": "Merk som betalt",
+    "markUnpaid": "Merk som ubetalt",
     "vehicle": "Kjøretøy",
     "open": "Åpen",
     "customer": "Kunde",
@@ -40,6 +50,8 @@
     "status": "Status",
     "technician": "Tekniker",
     "invoiceNumber": "Fakturanummer",
+    "invoiceDate": "Fakturadato",
+    "invoiceDueDate": "Forfallsdato",
     "typeOptions": {
       "maintenance": "Vedlikehold",
       "repair": "Reparasjon",

+ 1 - 14
messages/nl/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Onderdeel toevoegen mislukt",
     "inStock": "Op voorraad: {quantity}",
     "quantity": "Aantal",
-    "addToJob": "{qty} toevoegen aan klus",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "{qty} toevoegen aan klus"
   },
   "recentActivity": "Recente activiteit",
   "noRecentActivity": "Geen recente activiteit",

+ 12 - 0
messages/nl/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "Dit zal dit servicerecord permanent verwijderen en alle bijbehorende gegevens. Dit kan niet ongedaan worden gemaakt.",
     "deleted": "Servicerecord verwijderd",
     "failedDelete": "Verwijderen mislukt",
+    "datesExpiredTitle": "Factuurdatums verlopen",
+    "datesExpiredDescription": "De factuurdatum of vervaldatum is verstreken. Wilt u de datums bijwerken voordat u verdergaat?",
+    "datesExpiredUpdate": "Datums bijwerken en doorgaan",
+    "datesExpiredProceed": "Doorgaan zonder wijzigingen",
+    "datesExpiredSetToday": "Instellen op vandaag",
     "customFieldsInvalid": "Vul alle verplichte aangepaste velden in",
     "customFieldsSaveFailed": "Kan aangepaste velden niet opslaan"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Basisinformatie",
+    "invoiceDetails": "Factuurgegevens",
+    "customerInfo": "Klant en voertuig",
+    "paid": "Betaald",
+    "markPaid": "Markeer als betaald",
+    "markUnpaid": "Markeer als onbetaald",
     "vehicle": "Voertuig",
     "open": "Open",
     "customer": "Klant",
@@ -40,6 +50,8 @@
     "status": "Status",
     "technician": "Technicus",
     "invoiceNumber": "Factuurnummer",
+    "invoiceDate": "Factuurdatum",
+    "invoiceDueDate": "Vervaldatum",
     "typeOptions": {
       "maintenance": "Onderhoud",
       "repair": "Reparatie",

+ 1 - 14
messages/pl/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Nie udało się dodać części",
     "inStock": "Na stanie: {quantity}",
     "quantity": "Ilość",
-    "addToJob": "Dodaj {qty} do zlecenia",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "Dodaj {qty} do zlecenia"
   },
   "recentActivity": "Ostatnia aktywność",
   "noRecentActivity": "Brak ostatniej aktywności",

+ 12 - 0
messages/pl/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "Spowoduje to trwałe usunięcie tego rekordu serwisu i wszystkich związanych danych. Nie można tego cofnąć.",
     "deleted": "Rekord serwisu usunięty",
     "failedDelete": "Nie udało się usunąć",
+    "datesExpiredTitle": "Daty faktury wygasły",
+    "datesExpiredDescription": "Data faktury lub termin płatności już minął. Czy chcesz zaktualizować daty przed kontynuowaniem?",
+    "datesExpiredUpdate": "Zaktualizuj daty i kontynuuj",
+    "datesExpiredProceed": "Kontynuuj bez zmian",
+    "datesExpiredSetToday": "Ustaw na dziś",
     "customFieldsInvalid": "Proszę wypełnić wszystkie wymagane pola niestandardowe",
     "customFieldsSaveFailed": "Nie udało się zapisać pól niestandardowych"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Informacje Podstawowe",
+    "invoiceDetails": "Szczegóły faktury",
+    "customerInfo": "Klient i pojazd",
+    "paid": "Opłacone",
+    "markPaid": "Oznacz jako opłacone",
+    "markUnpaid": "Oznacz jako nieopłacone",
     "vehicle": "Pojazd",
     "open": "Otwarte",
     "customer": "Klient",
@@ -40,6 +50,8 @@
     "status": "Status",
     "technician": "Technik",
     "invoiceNumber": "Numer Faktury",
+    "invoiceDate": "Data faktury",
+    "invoiceDueDate": "Termin płatności",
     "typeOptions": {
       "maintenance": "Konserwacja",
       "repair": "Naprawa",

+ 1 - 14
messages/pt-BR/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Falha ao adicionar peça",
     "inStock": "Em estoque: {quantity}",
     "quantity": "Quantidade",
-    "addToJob": "Adicionar {qty} ao serviço",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "Adicionar {qty} ao serviço"
   },
   "recentActivity": "Atividade recente",
   "noRecentActivity": "Nenhuma atividade recente",

+ 12 - 0
messages/pt-BR/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "Isto vai deletar permanentemente este registro de serviço e todos os dados associados. Isto não pode ser desfeito.",
     "deleted": "Registro de serviço deletado",
     "failedDelete": "Falha ao deletar",
+    "datesExpiredTitle": "Datas da fatura expiradas",
+    "datesExpiredDescription": "A data da fatura ou a data de vencimento já passou. Deseja atualizar as datas antes de prosseguir?",
+    "datesExpiredUpdate": "Atualizar datas e prosseguir",
+    "datesExpiredProceed": "Prosseguir sem alterações",
+    "datesExpiredSetToday": "Definir para hoje",
     "customFieldsInvalid": "Preencha todos os campos personalizados obrigatórios",
     "customFieldsSaveFailed": "Falha ao salvar campos personalizados"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Informações Básicas",
+    "invoiceDetails": "Detalhes da fatura",
+    "customerInfo": "Cliente e veículo",
+    "paid": "Pago",
+    "markPaid": "Marcar como pago",
+    "markUnpaid": "Marcar como não pago",
     "vehicle": "Veículo",
     "open": "Abrir",
     "customer": "Cliente",
@@ -40,6 +50,8 @@
     "status": "Status",
     "technician": "Técnico",
     "invoiceNumber": "Número da Fatura",
+    "invoiceDate": "Data da fatura",
+    "invoiceDueDate": "Data de vencimento",
     "typeOptions": {
       "maintenance": "Manutenção",
       "repair": "Reparo",

+ 1 - 14
messages/ru/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Не удалось добавить запчасть",
     "inStock": "В наличии: {quantity}",
     "quantity": "Количество",
-    "addToJob": "Добавить {qty} к заказу",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "Добавить {qty} к заказу"
   },
   "recentActivity": "Последняя активность",
   "noRecentActivity": "Нет недавней активности",

+ 12 - 0
messages/ru/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "Это безвозвратно удалит запись обслуживания и все связанные данные. Это действие необратимо.",
     "deleted": "Запись обслуживания удалена",
     "failedDelete": "Не удалось удалить",
+    "datesExpiredTitle": "Даты счёта истекли",
+    "datesExpiredDescription": "Дата счёта или срок оплаты истёк. Хотите обновить даты перед продолжением?",
+    "datesExpiredUpdate": "Обновить даты и продолжить",
+    "datesExpiredProceed": "Продолжить без изменений",
+    "datesExpiredSetToday": "Установить на сегодня",
     "customFieldsInvalid": "Заполните все обязательные пользовательские поля",
     "customFieldsSaveFailed": "Не удалось сохранить пользовательские поля"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Основная информация",
+    "invoiceDetails": "Детали счёта",
+    "customerInfo": "Клиент и транспорт",
+    "paid": "Оплачено",
+    "markPaid": "Отметить как оплачено",
+    "markUnpaid": "Отметить как неоплачено",
     "vehicle": "Транспортное средство",
     "open": "Открыть",
     "customer": "Клиент",
@@ -40,6 +50,8 @@
     "status": "Статус",
     "technician": "Механик",
     "invoiceNumber": "Номер счёта",
+    "invoiceDate": "Дата счёта",
+    "invoiceDueDate": "Срок оплаты",
     "typeOptions": {
       "maintenance": "Техобслуживание",
       "repair": "Ремонт",

+ 1 - 14
messages/tr/dashboard.json

@@ -11,20 +11,7 @@
     "partAddFailed": "Parça eklenemedi",
     "inStock": "Stokta: {quantity}",
     "quantity": "Miktar",
-    "addToJob": "{qty} işe ekle",
-    "createPart": {
-      "title": "Part Not Found",
-      "description": "No part found for barcode \"{barcode}\". Create a new one?",
-      "name": "Part Name",
-      "namePlaceholder": "e.g. Oil Filter",
-      "partNumber": "Part Number",
-      "partNumberPlaceholder": "e.g. OF-1234",
-      "cost": "Cost",
-      "sellPrice": "Sell Price",
-      "cancel": "Cancel",
-      "create": "Create & Add",
-      "failed": "Failed to create part"
-    }
+    "addToJob": "{qty} işe ekle"
   },
   "recentActivity": "Son aktivite",
   "noRecentActivity": "Son aktivite yok",

+ 12 - 0
messages/tr/service.json

@@ -7,6 +7,11 @@
     "deleteDescription": "Bu, bu servis kaydını ve ilişkili tüm verileri kalıcı olarak silecektir. Bu işlem geri alınamaz.",
     "deleted": "Servis kaydı silindi",
     "failedDelete": "Silinemedi",
+    "datesExpiredTitle": "Fatura tarihleri geçmiş",
+    "datesExpiredDescription": "Fatura tarihi veya vade tarihi geçmiş. Devam etmeden önce tarihleri güncellemek ister misiniz?",
+    "datesExpiredUpdate": "Tarihleri güncelle ve devam et",
+    "datesExpiredProceed": "Değişiklik yapmadan devam et",
+    "datesExpiredSetToday": "Bugüne ayarla",
     "customFieldsInvalid": "Lütfen tüm zorunlu özel alanları doldurun",
     "customFieldsSaveFailed": "Özel alanlar kaydedilemedi"
   },
@@ -28,6 +33,11 @@
   },
   "basicInfo": {
     "title": "Temel Bilgiler",
+    "invoiceDetails": "Fatura detayları",
+    "customerInfo": "Müşteri ve araç",
+    "paid": "Ödendi",
+    "markPaid": "Ödendi olarak işaretle",
+    "markUnpaid": "Ödenmedi olarak işaretle",
     "vehicle": "Araç",
     "open": "Aç",
     "customer": "Müşteri",
@@ -40,6 +50,8 @@
     "status": "Durum",
     "technician": "Teknisyen",
     "invoiceNumber": "Fatura Numarası",
+    "invoiceDate": "Fatura tarihi",
+    "invoiceDueDate": "Vade tarihi",
     "typeOptions": {
       "maintenance": "Bakım",
       "repair": "Onarım",

+ 18 - 0
prisma/migrations/20260327055410_invoice_due_date/migration.sql

@@ -0,0 +1,18 @@
+-- AlterTable
+ALTER TABLE "service_records" ADD COLUMN     "invoiceDate" TIMESTAMP(3),
+ADD COLUMN     "invoiceDueDate" TIMESTAMP(3);
+
+-- Backfill startDateTime from serviceDate for all service records where startDateTime is NULL
+UPDATE "service_records"
+SET "startDateTime" = "serviceDate"
+WHERE "startDateTime" IS NULL;
+
+-- Backfill invoiceDate from startDateTime (which is now always set)
+UPDATE "service_records"
+SET "invoiceDate" = COALESCE("startDateTime", "serviceDate")
+WHERE "invoiceDate" IS NULL;
+
+-- Backfill startDateTime for inspections from createdAt where startDateTime is NULL
+UPDATE "inspections"
+SET "startDateTime" = "createdAt"
+WHERE "startDateTime" IS NULL;

+ 2 - 0
prisma/schema.prisma

@@ -197,6 +197,8 @@ model ServiceRecord {
   taxAmount       Float     @default(0)
   totalAmount     Float     @default(0)
   invoiceNumber   String?
+  invoiceDate     DateTime?
+  invoiceDueDate  DateTime?
   discountType    String?
   discountValue   Float     @default(0)
   discountAmount  Float     @default(0)

+ 3 - 0
src/__tests__/features/public/invoice-view.test.tsx

@@ -47,6 +47,9 @@ const BASE_RECORD = {
   type: "service",
   status: "completed",
   serviceDate: new Date("2024-03-10"),
+  startDateTime: new Date("2024-03-10"),
+  invoiceDate: new Date("2024-03-10"),
+  invoiceDueDate: null,
   shopName: null,
   techName: "Alice",
   mileage: 55000,

+ 2 - 1
src/app/(authenticated)/billing/billing-client.tsx

@@ -33,6 +33,7 @@ interface BillingRecord {
   title: string;
   invoiceNumber: string | null;
   serviceDate: Date;
+  startDateTime: Date | null;
   totalAmount: number;
   totalPaid: number;
   status: string;
@@ -325,7 +326,7 @@ export default function BillingClient({
                       {record.vehicle.customer?.name || "\u2014"}
                     </TableCell>
                     <TableCell>
-                      {formatDate(new Date(record.serviceDate))}
+                      {formatDate(new Date(record.startDateTime ?? record.serviceDate))}
                     </TableCell>
                     <TableCell className="text-right">
                       {fmt(record.totalAmount)}

+ 2 - 1
src/app/(authenticated)/dashboard-client.tsx

@@ -56,6 +56,7 @@ interface ServiceItem {
   totalAmount: number;
   cost: number;
   serviceDate: Date;
+  startDateTime: Date | null;
   vehicle: {
     id: string;
     make: string;
@@ -1057,7 +1058,7 @@ export function DashboardClient({
                           }}
                         >
                           <TableCell className="font-mono text-xs">
-                            {formatDate(new Date(s.serviceDate))}
+                            {formatDate(new Date(s.startDateTime ?? s.serviceDate))}
                           </TableCell>
                           <TableCell>
                             <div>

+ 2 - 2
src/app/(authenticated)/settings/data/data-settings.tsx

@@ -434,7 +434,7 @@ export function DataSettings({ contentCounts }: { contentCounts: ContentCounts }
                   alt="LubeLog"
                   width={120}
                   height={30}
-                  className="object-contain"
+                  className="h-auto object-contain"
                   unoptimized
                 />
               </button>
@@ -450,7 +450,7 @@ export function DataSettings({ contentCounts }: { contentCounts: ContentCounts }
                   alt="Invoice Ninja"
                   width={140}
                   height={30}
-                  className="object-contain"
+                  className="h-auto object-contain"
                   unoptimized
                 />
               </button>

+ 1 - 0
src/app/(authenticated)/vehicles/[id]/service-records-table.tsx

@@ -46,6 +46,7 @@ interface ServiceRecordRow {
   cost: number;
   mileage: number | null;
   serviceDate: Date;
+  startDateTime: Date | null;
   shopName: string | null;
   techName: string | null;
   totalAmount: number;

+ 9 - 0
src/app/(authenticated)/vehicles/[id]/service/[serviceId]/page.tsx

@@ -29,6 +29,7 @@ export default async function ServiceDetailPage({
         SETTING_KEYS.DEFAULT_TAX_RATE,
         SETTING_KEYS.TAX_ENABLED,
         SETTING_KEYS.DEFAULT_LABOR_RATE,
+        SETTING_KEYS.INVOICE_DUE_DAYS,
       ]),
       getInventoryPartsList(),
       getTechnicians(),
@@ -64,6 +65,7 @@ export default async function ServiceDetailPage({
     : 0;
   const defaultLaborRate =
     Number(settings[SETTING_KEYS.DEFAULT_LABOR_RATE]) || 0;
+  const defaultDueDays = Number(settings[SETTING_KEYS.INVOICE_DUE_DAYS]) || 0;
   const inventoryParts =
     inventoryResult.success && inventoryResult.data ? inventoryResult.data : [];
   const laborPresets =
@@ -123,6 +125,12 @@ export default async function ServiceDetailPage({
     diagnosticNotes: record.diagnosticNotes || "",
     invoiceNotes: record.invoiceNotes || "",
     invoiceNumber: record.invoiceNumber || "",
+    invoiceDate: (record.invoiceDate ?? record.startDateTime ?? record.serviceDate).toISOString().split("T")[0],
+    invoiceDueDate: record.invoiceDueDate
+      ? record.invoiceDueDate.toISOString().split("T")[0]
+      : defaultDueDays > 0
+        ? new Date((record.invoiceDate ?? record.startDateTime ?? record.serviceDate).getTime() + defaultDueDays * 86400000).toISOString().split("T")[0]
+        : "",
     partItems: record.partItems.map((p) => ({
       partNumber: p.partNumber || "",
       name: p.name,
@@ -189,6 +197,7 @@ export default async function ServiceDetailPage({
         smsEnabled={features?.sms ?? false}
         emailEnabled={features?.smtp ?? false}
         aiEnabled={aiEnabled}
+        defaultDueDays={defaultDueDays}
       />
     </div>
   );

+ 1 - 0
src/app/(authenticated)/vehicles/[id]/vehicle-detail-client.tsx

@@ -101,6 +101,7 @@ interface PaginatedServices {
     cost: number
     mileage: number | null
     serviceDate: Date
+    startDateTime: Date | null
     shopName: string | null
     techName: string | null
     totalAmount: number

+ 2 - 1
src/app/(authenticated)/work-orders/work-orders-client.tsx

@@ -49,6 +49,7 @@ interface WorkOrder {
   totalAmount: number;
   cost: number;
   serviceDate: Date;
+  startDateTime: Date | null;
   techName: string | null;
   invoiceNumber: string | null;
   vehicle: {
@@ -358,7 +359,7 @@ export function WorkOrdersClient({
                       {r.techName || "-"}
                     </TableCell>
                     <TableCell className="font-mono text-xs">
-                      {formatDate(new Date(r.serviceDate))}
+                      {formatDate(new Date(r.startDateTime ?? r.serviceDate))}
                     </TableCell>
                     <TableCell className="text-right font-semibold">
                       {formatCurrency(displayTotal, currencyCode)}

+ 1 - 1
src/app/(public)/portal/[orgId]/dashboard/page.tsx

@@ -105,7 +105,7 @@ export default async function PortalDashboardPage({
                       </p>
                       <p className="text-xs text-muted-foreground">
                         {inv.vehicle?.make} {inv.vehicle?.model} &middot;{" "}
-                        {new Date(inv.serviceDate).toLocaleDateString()}
+                        {new Date(inv.startDateTime ?? inv.serviceDate).toLocaleDateString()}
                       </p>
                     </div>
                     <div className="flex items-center gap-2">

+ 1 - 1
src/app/(public)/portal/[orgId]/invoices/page.tsx

@@ -80,7 +80,7 @@ export default async function PortalInvoicesPage({
                         {inv.vehicle?.make} {inv.vehicle?.model}
                       </TableCell>
                       <TableCell>
-                        {new Date(inv.serviceDate).toLocaleDateString()}
+                        {new Date(inv.startDateTime ?? inv.serviceDate).toLocaleDateString()}
                       </TableCell>
                       <TableCell>${inv.totalAmount.toFixed(2)}</TableCell>
                       <TableCell>

+ 1 - 1
src/app/(public)/portal/[orgId]/vehicles/[vehicleId]/page.tsx

@@ -93,7 +93,7 @@ export default async function PortalVehicleDetailPage({
                           {sr.title}
                         </p>
                         <p className="text-xs text-muted-foreground">
-                          {new Date(sr.serviceDate).toLocaleDateString()}
+                          {new Date(sr.startDateTime ?? sr.serviceDate).toLocaleDateString()}
                         </p>
                       </div>
                       <div className="flex items-center gap-2">

+ 32 - 11
src/app/(public)/share/invoice/[orgId]/[token]/invoice-view.tsx

@@ -26,6 +26,9 @@ interface InvoiceRecord {
   type: string
   status: string
   serviceDate: Date
+  startDateTime: Date | null
+  invoiceDate: Date | null
+  invoiceDueDate: Date | null
   shopName: string | null
   techName: string | null
   mileage: number | null
@@ -243,7 +246,8 @@ export function InvoiceView({
   const invoiceNum = record.invoiceNumber || `INV-${record.id.slice(-8).toUpperCase()}`
   const df = dateFormat || DEFAULT_DATE_FORMAT
   const tz = timezone || undefined
-  const serviceDate = fmtDate(record.serviceDate, df, tz)
+  const effectiveInvoiceDate = record.invoiceDate ?? record.startDateTime ?? record.serviceDate
+  const serviceDate = fmtDate(effectiveInvoiceDate, df, tz)
   const paidFromPayments = record.payments.reduce((sum, p) => sum + p.amount, 0)
   const totalPaid = record.manuallyPaid ? displayTotal : paidFromPayments
   const balanceDue = displayTotal - totalPaid
@@ -993,20 +997,37 @@ export function InvoiceView({
                         <p className="font-medium">{invoiceSettings.orgNumber}</p>
                       </div>
                     )}
-                    {invoiceSettings.paymentTerms && (
-                      <div>
-                        <p className="text-xs text-gray-500 dark:text-gray-400">{t('paymentTerms')}</p>
-                        <p className="font-medium">{invoiceSettings.paymentTerms}</p>
-                      </div>
-                    )}
-                    {invoiceSettings.dueDays > 0 && (
+                    {(() => {
+                      const dueDate = record.invoiceDueDate
+                        ? new Date(record.invoiceDueDate)
+                        : invoiceSettings.dueDays > 0
+                          ? new Date(new Date(effectiveInvoiceDate).getTime() + invoiceSettings.dueDays * 86400000)
+                          : null
+                      const netDays = dueDate
+                        ? Math.ceil((dueDate.getTime() - new Date(effectiveInvoiceDate).getTime()) / 86400000)
+                        : null
+                      return netDays !== null && netDays > 0 ? (
+                        <div>
+                          <p className="text-xs text-gray-500 dark:text-gray-400">{t('paymentTerms')}</p>
+                          <p className="font-medium">Net {netDays} Days</p>
+                        </div>
+                      ) : invoiceSettings.paymentTerms ? (
+                        <div>
+                          <p className="text-xs text-gray-500 dark:text-gray-400">{t('paymentTerms')}</p>
+                          <p className="font-medium">{invoiceSettings.paymentTerms}</p>
+                        </div>
+                      ) : null
+                    })()}
+                    {(record.invoiceDueDate || invoiceSettings.dueDays > 0) && (
                       <div>
                         <p className="text-xs text-gray-500 dark:text-gray-400">{t('dueDate')}</p>
                         <p className="font-medium">
                           {fmtDate(
-                            new Date(
-                              new Date(record.serviceDate).getTime() + invoiceSettings.dueDays * 86400000
-                            ),
+                            record.invoiceDueDate
+                              ? new Date(record.invoiceDueDate)
+                              : new Date(
+                                  new Date(effectiveInvoiceDate).getTime() + invoiceSettings.dueDays * 86400000
+                                ),
                             df,
                             tz,
                           )}

+ 1 - 1
src/app/api/protected/backup/import/route.ts

@@ -253,7 +253,7 @@ export async function POST(request: NextRequest) {
               isActive: t.isActive !== false,
               sortOrder: (t.sortOrder as number) || 0,
               dailyCapacity: (t.dailyCapacity as number) || 480,
-              userId: (t.userId as string) || (t.memberId as string) || null,
+              userId: (t.userId as string) || null, // memberId from old backups ignored — no FK to users
               createdAt: t.createdAt
                 ? new Date(t.createdAt as string)
                 : undefined,

+ 2 - 1
src/features/ai/Actions/aiActions.ts

@@ -75,11 +75,12 @@ export async function aiSummarizeVehicleHistory(vehicleId: string) {
               title: true,
               description: true,
               serviceDate: true,
+              startDateTime: true,
               type: true,
               cost: true,
               mileage: true,
             },
-            orderBy: { serviceDate: "asc" },
+            orderBy: [{ startDateTime: { sort: "asc", nulls: "last" } }, { serviceDate: "asc" }],
           },
         },
       });

+ 9 - 4
src/features/billing/Actions/billingActions.ts

@@ -131,6 +131,7 @@ export async function getBillingHistory(params: {
           title: string | null;
           invoiceNumber: string | null;
           serviceDate: Date;
+          startDateTime: Date | null;
           effective_total: number;
           paid_amount: number;
           manually_paid: boolean;
@@ -148,6 +149,7 @@ export async function getBillingHistory(params: {
           sub.title,
           sub."invoiceNumber",
           sub."serviceDate",
+          sub."startDateTime",
           sub.effective_total,
           sub.paid_amount,
           sub.manually_paid,
@@ -164,6 +166,7 @@ export async function getBillingHistory(params: {
             sr.title,
             sr."invoiceNumber",
             sr."serviceDate",
+            sr."startDateTime",
             sr."manuallyPaid" AS manually_paid,
             CASE WHEN sr."totalAmount" > 0 THEN sr."totalAmount" ELSE sr.cost END AS effective_total,
             CASE WHEN sr."manuallyPaid" = true
@@ -184,7 +187,7 @@ export async function getBillingHistory(params: {
           ${searchCondition}
         ) sub
         WHERE 1=1 ${statusCondition}
-        ORDER BY sub."serviceDate" DESC
+        ORDER BY COALESCE(sub."startDateTime", sub."serviceDate") DESC
         LIMIT ${pageSize} OFFSET ${skip}
       `);
 
@@ -202,7 +205,8 @@ export async function getBillingHistory(params: {
           id: r.id,
           title: r.title || "",
           invoiceNumber: r.invoiceNumber,
-          serviceDate: r.serviceDate,
+          startDateTime: r.startDateTime,
+          serviceDate: r.startDateTime ?? r.serviceDate,
           totalAmount: effectiveTotal,
           totalPaid: paidAmount,
           status: paymentStatus,
@@ -241,7 +245,7 @@ export async function getBillingHistory(params: {
             },
           },
         },
-        orderBy: { serviceDate: "desc" },
+        orderBy: { startDateTime: "desc" },
         skip,
         take: pageSize,
       }),
@@ -262,7 +266,8 @@ export async function getBillingHistory(params: {
           id: r.id,
           title: r.title,
           invoiceNumber: r.invoiceNumber,
-          serviceDate: r.serviceDate,
+          startDateTime: r.startDateTime,
+          serviceDate: r.startDateTime ?? r.serviceDate,
           totalAmount: effectiveTotal,
           totalPaid,
           status: paymentStatus,

+ 1 - 0
src/features/billing/Actions/recurringInvoiceActions.ts

@@ -304,6 +304,7 @@ export async function processRecurringInvoices() {
             status: "completed",
             cost: ri.cost,
             serviceDate: now,
+            startDateTime: now,
             invoiceNotes: ri.invoiceNotes,
             subtotal,
             taxRate: ri.taxRate,

+ 5 - 4
src/features/calendar/Actions/calendarActions.ts

@@ -43,12 +43,13 @@ export async function getCalendarEvents(params: {
       db.serviceRecord.findMany({
         where: {
           vehicle: { organizationId },
-          serviceDate: { gte: start, lte: end },
+          startDateTime: { gte: start, lte: end },
         },
         select: {
           id: true,
           title: true,
           serviceDate: true,
+          startDateTime: true,
           status: true,
           invoiceNumber: true,
           totalAmount: true,
@@ -63,7 +64,7 @@ export async function getCalendarEvents(params: {
             },
           },
         },
-        orderBy: { serviceDate: "asc" },
+        orderBy: [{ startDateTime: { sort: "asc", nulls: "last" } }, { serviceDate: "asc" }],
       }),
       db.reminder.findMany({
         where: {
@@ -119,8 +120,8 @@ export async function getCalendarEvents(params: {
       ...services.map((s) => ({
         id: s.id,
         title: s.title,
-        date: toLocalDateStr(s.serviceDate),
-        time: toTimeStr(s.serviceDate),
+        date: toLocalDateStr(s.startDateTime ?? s.serviceDate),
+        time: s.startDateTime ? toTimeStr(s.startDateTime) : null,
         type: "service" as const,
         status: s.status,
         vehicleId: s.vehicleId,

+ 6 - 3
src/features/portal/Actions/portalActions.ts

@@ -58,10 +58,11 @@ export async function getPortalDashboard() {
             status: true,
             totalAmount: true,
             serviceDate: true,
+            startDateTime: true,
             publicToken: true,
             vehicle: { select: { make: true, model: true } },
           },
-          orderBy: { serviceDate: "desc" },
+          orderBy: [{ startDateTime: { sort: "desc", nulls: "last" } }, { serviceDate: "desc" }],
           take: 5,
         }),
         db.quote.findMany({
@@ -176,9 +177,10 @@ export async function getPortalVehicleDetail(vehicleId: string) {
             status: true,
             totalAmount: true,
             serviceDate: true,
+            startDateTime: true,
             publicToken: true,
           },
-          orderBy: { serviceDate: "desc" },
+          orderBy: [{ startDateTime: { sort: "desc", nulls: "last" } }, { serviceDate: "desc" }],
         },
         inspections: {
           select: {
@@ -218,12 +220,13 @@ export async function getPortalInvoices() {
         status: true,
         totalAmount: true,
         serviceDate: true,
+        startDateTime: true,
         publicToken: true,
         manuallyPaid: true,
         vehicle: { select: { make: true, model: true } },
         payments: { select: { amount: true } },
       },
-      orderBy: { serviceDate: "desc" },
+      orderBy: [{ startDateTime: { sort: "desc", nulls: "last" } }, { serviceDate: "desc" }],
     });
     return invoices;
   });

+ 1 - 0
src/features/quotes/Actions/quoteActions.ts

@@ -343,6 +343,7 @@ export async function convertQuoteToServiceRecord(quoteId: string, vehicleId: st
           discountValue: quote.discountValue,
           discountAmount: quote.discountAmount,
           serviceDate: new Date(),
+          startDateTime: new Date(),
         },
       });
 

+ 23 - 17
src/features/reports/Actions/reportActions.ts

@@ -16,10 +16,11 @@ export async function getRevenueReport(params: {
     const records = await db.serviceRecord.findMany({
       where: {
         vehicle: { organizationId },
-        serviceDate: { gte: start, lte: end },
+        startDateTime: { gte: start, lte: end },
       },
       select: {
         serviceDate: true,
+        startDateTime: true,
         totalAmount: true,
         cost: true,
         type: true,
@@ -27,7 +28,7 @@ export async function getRevenueReport(params: {
         payments: { select: { amount: true } },
         partItems: { select: { unitCost: true, quantity: true } },
       },
-      orderBy: { serviceDate: "asc" },
+      orderBy: [{ startDateTime: { sort: "asc", nulls: "last" } }, { serviceDate: "asc" }],
     });
 
     // Monthly breakdown
@@ -44,7 +45,8 @@ export async function getRevenueReport(params: {
       const total = r.totalAmount > 0 ? r.totalAmount : r.cost;
       const paid = r.manuallyPaid ? total : r.payments.reduce((s, p) => s + p.amount, 0);
       const partsCost = r.partItems.reduce((s, p) => s + (p.unitCost * p.quantity), 0);
-      const month = `${r.serviceDate.getFullYear()}-${String(r.serviceDate.getMonth() + 1).padStart(2, "0")}`;
+      const _date = r.startDateTime ?? r.serviceDate;
+      const month = `${_date.getFullYear()}-${String(_date.getMonth() + 1).padStart(2, "0")}`;
 
       if (!monthly[month]) monthly[month] = { revenue: 0, collected: 0, count: 0, partsCost: 0 };
       monthly[month].revenue += total;
@@ -93,7 +95,7 @@ export async function getServiceReport(params: {
     const records = await db.serviceRecord.findMany({
       where: {
         vehicle: { organizationId },
-        serviceDate: { gte: start, lte: end },
+        startDateTime: { gte: start, lte: end },
       },
       select: {
         type: true,
@@ -137,7 +139,7 @@ export async function getCustomerReport(params: {
         vehicles: {
           select: {
             serviceRecords: {
-              where: { serviceDate: { gte: start, lte: end } },
+              where: { startDateTime: { gte: start, lte: end } },
               select: { totalAmount: true, cost: true },
             },
           },
@@ -180,7 +182,7 @@ export async function getTechnicianReport(params: {
     const records = await db.serviceRecord.findMany({
       where: {
         vehicle: { organizationId },
-        serviceDate: { gte: start, lte: end },
+        startDateTime: { gte: start, lte: end },
         OR: [{ technicianId: { not: null } }, { techName: { not: null } }],
       },
       select: {
@@ -237,7 +239,7 @@ export async function getPartsUsageReport(params: {
       where: {
         serviceRecord: {
           vehicle: { organizationId },
-          serviceDate: { gte: start, lte: end },
+          startDateTime: { gte: start, lte: end },
         },
       },
       select: {
@@ -296,13 +298,14 @@ export async function getJobAnalyticsReport(params: {
     const records = await db.serviceRecord.findMany({
       where: {
         vehicle: { organizationId },
-        serviceDate: { gte: start, lte: end },
+        startDateTime: { gte: start, lte: end },
       },
       select: {
         type: true,
         totalAmount: true,
         cost: true,
         serviceDate: true,
+        startDateTime: true,
         laborItems: { select: { hours: true } },
       },
     });
@@ -334,14 +337,15 @@ export async function getJobAnalyticsReport(params: {
     const dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
     const dayCount = [0, 0, 0, 0, 0, 0, 0];
     for (const r of records) {
-      dayCount[r.serviceDate.getDay()] += 1;
+      dayCount[(r.startDateTime ?? r.serviceDate).getDay()] += 1;
     }
     const dayOfWeek = dayNames.map((day, i) => ({ day, count: dayCount[i] }));
 
     // Monthly trend
     const monthly: Record<string, { count: number; revenue: number }> = {};
     for (const r of records) {
-      const month = `${r.serviceDate.getFullYear()}-${String(r.serviceDate.getMonth() + 1).padStart(2, "0")}`;
+      const _d = r.startDateTime ?? r.serviceDate;
+      const month = `${_d.getFullYear()}-${String(_d.getMonth() + 1).padStart(2, "0")}`;
       if (!monthly[month]) monthly[month] = { count: 0, revenue: 0 };
       monthly[month].count += 1;
       monthly[month].revenue += r.totalAmount > 0 ? r.totalAmount : r.cost;
@@ -378,9 +382,9 @@ export async function getCustomerRetentionReport(params: {
         vehicles: {
           select: {
             serviceRecords: {
-              where: { serviceDate: { gte: start, lte: end } },
-              select: { serviceDate: true, totalAmount: true, cost: true },
-              orderBy: { serviceDate: "asc" },
+              where: { startDateTime: { gte: start, lte: end } },
+              select: { serviceDate: true, startDateTime: true, totalAmount: true, cost: true },
+              orderBy: [{ startDateTime: { sort: "asc", nulls: "last" } }, { serviceDate: "asc" }],
             },
           },
         },
@@ -403,7 +407,7 @@ export async function getCustomerRetentionReport(params: {
       const visits: { date: Date; amount: number }[] = [];
       for (const v of c.vehicles) {
         for (const sr of v.serviceRecords) {
-          visits.push({ date: sr.serviceDate, amount: sr.totalAmount > 0 ? sr.totalAmount : sr.cost });
+          visits.push({ date: sr.startDateTime ?? sr.serviceDate, amount: sr.totalAmount > 0 ? sr.totalAmount : sr.cost });
         }
       }
 
@@ -500,16 +504,17 @@ export async function getTaxReport(params: {
     const records = await db.serviceRecord.findMany({
       where: {
         vehicle: { organizationId },
-        serviceDate: { gte: start, lte: end },
+        startDateTime: { gte: start, lte: end },
       },
       select: {
         serviceDate: true,
+        startDateTime: true,
         subtotal: true,
         taxRate: true,
         taxAmount: true,
         totalAmount: true,
       },
-      orderBy: { serviceDate: "asc" },
+      orderBy: [{ startDateTime: { sort: "asc", nulls: "last" } }, { serviceDate: "asc" }],
     });
 
     const monthly: Record<string, { taxCollected: number; invoiceCount: number; taxableAmount: number }> = {};
@@ -521,7 +526,8 @@ export async function getTaxReport(params: {
     for (const r of records) {
       if (r.taxAmount <= 0) continue;
 
-      const month = `${r.serviceDate.getFullYear()}-${String(r.serviceDate.getMonth() + 1).padStart(2, "0")}`;
+      const _dt = r.startDateTime ?? r.serviceDate;
+      const month = `${_dt.getFullYear()}-${String(_dt.getMonth() + 1).padStart(2, "0")}`;
 
       if (!monthly[month]) monthly[month] = { taxCollected: 0, invoiceCount: 0, taxableAmount: 0 };
       monthly[month].taxCollected += r.taxAmount;

+ 1 - 0
src/features/settings/Components/InvoiceLayoutPreviewRenderer.tsx

@@ -40,6 +40,7 @@ const DUMMY_INVOICE_DATA = {
   description: null,
   type: "Maintenance",
   serviceDate: new Date("2026-03-10"),
+  startDateTime: new Date("2026-03-10"),
   shopName: "Your Workshop",
   techName: "Mike Johnson",
   mileage: 45230,

+ 1 - 0
src/features/vehicles/Actions/createDraftServiceRecord.ts

@@ -138,6 +138,7 @@ export async function createDraftServiceRecord(
           technicianId: resolvedTechId || undefined,
           invoiceNumber,
           serviceDate,
+          invoiceDate: serviceDate,
           startDateTime: defaultStart,
           endDateTime: endDateTime ?? new Date(defaultStart.getTime() + 3600000),
         },

+ 3 - 3
src/features/vehicles/Actions/dashboardActions.ts

@@ -30,7 +30,7 @@ export async function getDashboardStats() {
         where: {
           vehicle: { organizationId },
           status: "completed",
-          serviceDate: { gte: today, lt: tomorrow },
+          startDateTime: { gte: today, lt: tomorrow },
         },
         _sum: { totalAmount: true },
       }),
@@ -52,7 +52,7 @@ export async function getDashboardStats() {
             },
           },
         },
-        orderBy: { serviceDate: "desc" },
+        orderBy: [{ startDateTime: { sort: "desc", nulls: "last" } }, { serviceDate: "desc" }],
         take: 50,
       }),
       db.serviceRecord.findMany({
@@ -72,7 +72,7 @@ export async function getDashboardStats() {
             },
           },
         },
-        orderBy: { serviceDate: "desc" },
+        orderBy: [{ startDateTime: { sort: "desc", nulls: "last" } }, { serviceDate: "desc" }],
         take: 10,
       }),
     ]);

+ 8 - 8
src/features/vehicles/Actions/predictedMaintenanceActions.ts

@@ -49,14 +49,14 @@ export async function getVehiclePredictedMileage(vehicleId: string) {
         mileage: { not: null },
         status: "completed",
       },
-      orderBy: { serviceDate: "asc" },
-      select: { serviceDate: true, mileage: true },
+      orderBy: [{ startDateTime: { sort: "asc", nulls: "last" } }, { serviceDate: "asc" }],
+      select: { serviceDate: true, startDateTime: true, mileage: true },
     });
 
     if (records.length < 2) return null;
 
     const dataPoints = records.map((r) => ({
-      date: new Date(r.serviceDate),
+      date: new Date(r.startDateTime ?? r.serviceDate),
       mileage: r.mileage!,
     }));
 
@@ -139,8 +139,8 @@ export async function getVehiclesDueForService() {
         licensePlate: true,
         serviceRecords: {
           where: { mileage: { not: null }, status: "completed" },
-          orderBy: { serviceDate: "asc" },
-          select: { serviceDate: true, mileage: true },
+          orderBy: [{ startDateTime: { sort: "asc", nulls: "last" } }, { serviceDate: "asc" }],
+          select: { serviceDate: true, startDateTime: true, mileage: true },
         },
       },
     });
@@ -155,8 +155,8 @@ export async function getVehiclesDueForService() {
       const latest = records[records.length - 1];
 
       const totalDays =
-        (new Date(latest.serviceDate).getTime() -
-          new Date(earliest.serviceDate).getTime()) /
+        (new Date(latest.startDateTime ?? latest.serviceDate).getTime() -
+          new Date(earliest.startDateTime ?? earliest.serviceDate).getTime()) /
         (1000 * 60 * 60 * 24);
 
       if (totalDays <= 0) continue;
@@ -167,7 +167,7 @@ export async function getVehiclesDueForService() {
       if (avgPerDay <= 0) continue;
 
       const daysSinceLatest =
-        (Date.now() - new Date(latest.serviceDate).getTime()) /
+        (Date.now() - new Date(latest.startDateTime ?? latest.serviceDate).getTime()) /
         (1000 * 60 * 60 * 24);
       const predictedMileage = Math.round(
         latest.mileage! + daysSinceLatest * avgPerDay

+ 14 - 10
src/features/vehicles/Actions/serviceActions.ts

@@ -23,7 +23,7 @@ export async function getServiceRecords(vehicleId: string) {
       include: {
         _count: { select: { partItems: true, laborItems: true } },
       },
-      orderBy: { serviceDate: "desc" },
+      orderBy: [{ startDateTime: { sort: "desc", nulls: "last" } }, { serviceDate: "desc" }],
     });
   }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.SERVICES }] });
 }
@@ -71,7 +71,7 @@ export async function getServiceRecordsPaginated(
           _count: { select: { partItems: true, laborItems: true, attachments: true } },
           laborItems: { take: 1, select: { description: true } },
         },
-        orderBy: { serviceDate: "desc" },
+        orderBy: [{ startDateTime: { sort: "desc", nulls: "last" } }, { serviceDate: "desc" }],
         skip,
         take: pageSize,
       }),
@@ -96,7 +96,7 @@ export async function getAllServiceRecords() {
         vehicle: { select: { make: true, model: true, year: true } },
         _count: { select: { partItems: true, laborItems: true } },
       },
-      orderBy: { serviceDate: "desc" },
+      orderBy: [{ startDateTime: { sort: "desc", nulls: "last" } }, { serviceDate: "desc" }],
       take: 50,
     });
   }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.SERVICES }] });
@@ -151,7 +151,7 @@ export async function getAllServiceRecordsPaginated(params: {
             },
           },
         },
-        orderBy: { serviceDate: "desc" },
+        orderBy: [{ startDateTime: { sort: "desc", nulls: "last" } }, { serviceDate: "desc" }],
         skip,
         take: pageSize,
       }),
@@ -257,7 +257,7 @@ export async function createServiceRecord(input: unknown) {
       });
     }
 
-    const { partItems, laborItems, attachments, ...recordData } = data;
+    const { partItems, laborItems, attachments, serviceDate, invoiceDate, invoiceDueDate, ...recordData } = data;
 
     const record = await db.$transaction(async (tx) => {
       const created = await tx.serviceRecord.create({
@@ -265,7 +265,9 @@ export async function createServiceRecord(input: unknown) {
           ...recordData,
           shopName,
           invoiceNumber,
-          serviceDate: new Date(recordData.serviceDate),
+          serviceDate: new Date(serviceDate),
+          invoiceDate: invoiceDate ? new Date(invoiceDate) : new Date(serviceDate),
+          invoiceDueDate: invoiceDueDate ? new Date(invoiceDueDate) : undefined,
         },
       });
 
@@ -370,7 +372,7 @@ export async function updateServiceRecord(input: unknown) {
     });
     if (!existing) throw new Error("Service record not found");
 
-    const { id, partItems, laborItems, attachments, ...recordData } = data;
+    const { id, partItems, laborItems, attachments, serviceDate: _sd, invoiceDate: _id, invoiceDueDate: _idd, ...recordData } = data;
 
     // Determine which categories are being replaced and which files were removed
     let removedFileUrls: string[] = [];
@@ -397,7 +399,9 @@ export async function updateServiceRecord(input: unknown) {
           invoiceNotes: recordData.invoiceNotes !== undefined ? (recordData.invoiceNotes || null) : undefined,
           invoiceNumber: recordData.invoiceNumber !== undefined ? (recordData.invoiceNumber || null) : undefined,
           mileage: recordData.mileage !== undefined ? (recordData.mileage ?? null) : undefined,
-          serviceDate: recordData.serviceDate ? new Date(recordData.serviceDate) : undefined,
+          serviceDate: data.serviceDate ? new Date(data.serviceDate) : undefined,
+          invoiceDate: data.invoiceDate ? new Date(data.invoiceDate) : undefined,
+          invoiceDueDate: data.invoiceDueDate ? new Date(data.invoiceDueDate) : undefined,
         },
       });
 
@@ -457,7 +461,7 @@ export async function updateServiceRecord(input: unknown) {
     if (recordData.mileage && recordData.mileage > existing.vehicle.mileage) {
       const latestRecord = await db.serviceRecord.findFirst({
         where: { vehicleId: existing.vehicle.id },
-        orderBy: { serviceDate: 'desc' },
+        orderBy: [{ startDateTime: { sort: 'desc', nulls: 'last' } }, { serviceDate: 'desc' }],
         select: { id: true },
       });
       if (latestRecord?.id === id) {
@@ -633,7 +637,7 @@ export async function getWorkOrders(params: {
             case "techName": return { techName: dir };
             case "totalAmount": return { totalAmount: dir };
             case "serviceDate":
-            default: return { serviceDate: dir };
+            default: return [{ startDateTime: { sort: dir, nulls: dir === "desc" ? "last" : "first" } }, { serviceDate: dir }];
           }
         })(),
         skip,

+ 1 - 1
src/features/vehicles/Actions/vehicleActions.ts

@@ -30,7 +30,7 @@ export async function getVehicle(vehicleId: string) {
       include: {
         customer: { select: { id: true, name: true, company: true, email: true, phone: true } },
         serviceRecords: {
-          orderBy: { serviceDate: "desc" },
+          orderBy: [{ startDateTime: { sort: "desc", nulls: "last" } }, { serviceDate: "desc" }],
           take: 10,
           include: {
             _count: { select: { partItems: true, laborItems: true } },

+ 0 - 152
src/features/vehicles/Components/CreatePartDialog.tsx

@@ -1,152 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import { useTranslations } from "next-intl";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import {
-  Dialog,
-  DialogContent,
-  DialogHeader,
-  DialogTitle,
-} from "@/components/ui/dialog";
-import { Loader2 } from "lucide-react";
-import { createInventoryPart } from "@/features/inventory/Actions/inventoryActions";
-import { toast } from "sonner";
-
-interface CreatePartDialogProps {
-  open: boolean;
-  onOpenChange: (open: boolean) => void;
-  barcode: string;
-  onCreated: (part: {
-    id: string;
-    name: string;
-    partNumber: string | null;
-    sellPrice: number;
-    unitCost: number;
-  }) => void;
-}
-
-export function CreatePartDialog({
-  open,
-  onOpenChange,
-  barcode,
-  onCreated,
-}: CreatePartDialogProps) {
-  const t = useTranslations("dashboard.myJobs.createPart");
-  const [name, setName] = useState("");
-  const [partNumber, setPartNumber] = useState("");
-  const [sellPrice, setSellPrice] = useState("");
-  const [unitCost, setUnitCost] = useState("");
-  const [loading, setLoading] = useState(false);
-
-  const handleOpenChange = (isOpen: boolean) => {
-    if (isOpen) {
-      setName("");
-      setPartNumber("");
-      setSellPrice("");
-      setUnitCost("");
-    }
-    onOpenChange(isOpen);
-  };
-
-  const handleSubmit = async (e: React.FormEvent) => {
-    e.preventDefault();
-    if (!name.trim()) return;
-    setLoading(true);
-
-    const result = await createInventoryPart({
-      name: name.trim(),
-      partNumber: partNumber.trim() || undefined,
-      barcode: barcode || undefined,
-      sellPrice: Number(sellPrice) || 0,
-      unitCost: Number(unitCost) || 0,
-      quantity: 1,
-    });
-
-    setLoading(false);
-
-    if (result.success && result.data) {
-      onCreated({
-        id: result.data.id,
-        name: result.data.name,
-        partNumber: result.data.partNumber ?? null,
-        sellPrice: result.data.sellPrice,
-        unitCost: result.data.unitCost,
-      });
-    } else {
-      toast.error(result.error || t("failed"));
-    }
-  };
-
-  return (
-    <Dialog open={open} onOpenChange={handleOpenChange}>
-      <DialogContent className="max-w-sm">
-        <DialogHeader>
-          <DialogTitle>{t("title")}</DialogTitle>
-        </DialogHeader>
-        <p className="text-sm text-muted-foreground">
-          {t("description", { barcode })}
-        </p>
-        <form onSubmit={handleSubmit} className="space-y-3">
-          <div className="space-y-1.5">
-            <Label htmlFor="part-name">{t("name")}</Label>
-            <Input
-              id="part-name"
-              placeholder={t("namePlaceholder")}
-              value={name}
-              onChange={(e) => setName(e.target.value)}
-              autoFocus
-              required
-            />
-          </div>
-          <div className="space-y-1.5">
-            <Label htmlFor="part-number">{t("partNumber")}</Label>
-            <Input
-              id="part-number"
-              placeholder={t("partNumberPlaceholder")}
-              value={partNumber}
-              onChange={(e) => setPartNumber(e.target.value)}
-            />
-          </div>
-          <div className="grid grid-cols-2 gap-3">
-            <div className="space-y-1.5">
-              <Label htmlFor="part-cost">{t("cost")}</Label>
-              <Input
-                id="part-cost"
-                type="number"
-                min="0"
-                step="0.01"
-                placeholder="0.00"
-                value={unitCost}
-                onChange={(e) => setUnitCost(e.target.value)}
-              />
-            </div>
-            <div className="space-y-1.5">
-              <Label htmlFor="part-price">{t("sellPrice")}</Label>
-              <Input
-                id="part-price"
-                type="number"
-                min="0"
-                step="0.01"
-                placeholder="0.00"
-                value={sellPrice}
-                onChange={(e) => setSellPrice(e.target.value)}
-              />
-            </div>
-          </div>
-          <div className="flex justify-end gap-2 pt-2">
-            <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
-              {t("cancel")}
-            </Button>
-            <Button type="submit" disabled={loading || !name.trim()}>
-              {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
-              {t("create")}
-            </Button>
-          </div>
-        </form>
-      </DialogContent>
-    </Dialog>
-  );
-}

+ 7 - 5
src/features/vehicles/Components/invoice-pdf/InvoicePDF.tsx

@@ -88,13 +88,14 @@ export function InvoicePDF({
   const invoiceNum = data.invoiceNumber || `INV-${data.id.slice(-8).toUpperCase()}`
   const df = invoiceSettings?.dateFormat || DEFAULT_DATE_FORMAT
   const tz = invoiceSettings?.timezone || undefined
-  const serviceDate = formatDateForPdf(data.serviceDate, df, tz)
+  const effectiveInvoiceDate = data.invoiceDate ?? data.startDateTime ?? data.serviceDate
+  const serviceDate = formatDateForPdf(effectiveInvoiceDate, df, tz)
 
-  const dueDays = invoiceSettings?.dueDays || 0
-  const dueDate =
-    dueDays > 0
+  const dueDate = data.invoiceDueDate
+    ? formatDateForPdf(data.invoiceDueDate, df, tz)
+    : (invoiceSettings?.dueDays || 0) > 0
       ? formatDateForPdf(
-          new Date(new Date(data.serviceDate).getTime() + dueDays * 86400000),
+          new Date(new Date(effectiveInvoiceDate).getTime() + (invoiceSettings?.dueDays || 0) * 86400000),
           df,
           tz,
         )
@@ -257,6 +258,7 @@ export function InvoicePDF({
         visibleFields={visibleBankAccountFields}
         primaryColor={primaryColor}
         dueDate={dueDate}
+        invoiceDate={serviceDate}
       />
     ),
 

+ 10 - 2
src/features/vehicles/Components/invoice-pdf/Notes.tsx

@@ -232,6 +232,7 @@ export function BankAccountSection({
   visibleFields,
   primaryColor = '#d97706',
   dueDate,
+  invoiceDate,
 }: {
   invoiceSettings?: InvoiceSettingsProps
   fontFamily: string
@@ -241,6 +242,7 @@ export function BankAccountSection({
   visibleFields?: Set<string> | null
   primaryColor?: string
   dueDate?: string | null
+  invoiceDate?: string | null
 }) {
   const fontBold = getFontBold(fontFamily)
 
@@ -254,7 +256,13 @@ export function BankAccountSection({
 
   const hasBankAccount = showBankAccount && invoiceSettings?.bankAccount
   const hasOrgNumber = showOrgNumber && invoiceSettings?.orgNumber
-  const hasPaymentTerms = !!invoiceSettings?.paymentTerms
+  const netDays = dueDate && invoiceDate
+    ? Math.ceil((new Date(dueDate).getTime() - new Date(invoiceDate).getTime()) / 86400000)
+    : null
+  const paymentTermsText = netDays !== null && netDays > 0
+    ? `Net ${netDays} Days`
+    : invoiceSettings?.paymentTerms || null
+  const hasPaymentTerms = !!paymentTermsText
   const hasDueDate = !!dueDate
 
   if (!hasBankAccount && !hasOrgNumber && !hasPaymentTerms && !hasDueDate) return null
@@ -309,7 +317,7 @@ export function BankAccountSection({
               {labels.paymentTermsLabel || 'Payment Terms'}
             </Text>
             <Text style={{ fontSize: 10, fontFamily: fontBold }}>
-              {invoiceSettings!.paymentTerms}
+              {paymentTermsText}
             </Text>
           </View>
         )}

+ 3 - 0
src/features/vehicles/Components/invoice-pdf/types.ts

@@ -16,6 +16,9 @@ export interface InvoiceData {
   description: string | null
   type: string
   serviceDate: Date
+  startDateTime?: Date | null
+  invoiceDate?: Date | null
+  invoiceDueDate?: Date | null
   shopName: string | null
   techName: string | null
   mileage: number | null

+ 1 - 0
src/features/vehicles/Components/service-detail/types.ts

@@ -25,6 +25,7 @@ export interface ServiceDetail {
   cost: number;
   mileage: number | null;
   serviceDate: Date;
+  startDateTime: Date | null;
   shopName: string | null;
   techName: string | null;
   technicianId: string | null;

+ 7 - 83
src/features/vehicles/Components/service-edit/BasicInfoSection.tsx

@@ -3,13 +3,6 @@
 import { useTranslations } from 'next-intl'
 import { Input } from '@/components/ui/input'
 import { Label } from '@/components/ui/label'
-import {
-  Select,
-  SelectContent,
-  SelectItem,
-  SelectTrigger,
-  SelectValue,
-} from '@/components/ui/select'
 import Link from 'next/link'
 import { ExternalLink } from 'lucide-react'
 import { useServiceType } from '@/components/service-type-context'
@@ -28,10 +21,6 @@ interface BasicInfoSectionProps {
   vehicleName: string
   selectedVehicleId: string
   setSelectedVehicleId: (id: string) => void
-  type: string
-  setType: (type: string) => void
-  status: string
-  setStatus: (status: string) => void
   techName: string
   customer?: CustomerInfo | null
   initialVehicle?: { id: string; make: string; model: string; year: number; licensePlate: string | null } | null
@@ -42,10 +31,6 @@ export function BasicInfoSection({
   vehicleName,
   selectedVehicleId,
   setSelectedVehicleId,
-  type,
-  setType,
-  status,
-  setStatus,
   techName,
   customer,
   initialVehicle,
@@ -55,7 +40,7 @@ export function BasicInfoSection({
 
   return (
     <div className="rounded-lg border p-3 space-y-3">
-      <h3 className="text-sm font-semibold">{t('title')}</h3>
+      <h3 className="text-sm font-semibold">{t('customerInfo')}</h3>
 
       <div className="space-y-1">
         <div className="flex items-center justify-between">
@@ -104,77 +89,16 @@ export function BasicInfoSection({
       )}
 
       <div className="space-y-1">
-        <Label htmlFor="title" className="text-xs">{t('titleLabel')}</Label>
+        <Label htmlFor="mileage" className="text-xs">{serviceType === 'boat' ? t('mileageBoat') : t('mileage')}</Label>
         <Input
-          id="title"
-          name="title"
-          placeholder={t('titlePlaceholder')}
-          defaultValue={initialData.title}
-          maxLength={100}
-          required
+          id="mileage"
+          name="mileage"
+          type="number"
+          placeholder="50000"
+          defaultValue={initialData.mileage ?? ''}
         />
       </div>
 
-      {/* Hidden serviceDate — kept for form submission, visible scheduling is in ScheduleTimesSection */}
-      <input
-        type="hidden"
-        name="serviceDate"
-        value={initialData.serviceDate || new Date().toISOString().split('T')[0]}
-      />
-
-      <div className="grid grid-cols-2 gap-2">
-        <div className="space-y-1">
-          <Label className="text-xs">{t('type')}</Label>
-          <Select value={type} onValueChange={setType}>
-            <SelectTrigger className="w-full">
-              <SelectValue />
-            </SelectTrigger>
-            <SelectContent>
-              <SelectItem value="maintenance">{t('typeOptions.maintenance')}</SelectItem>
-              <SelectItem value="repair">{t('typeOptions.repair')}</SelectItem>
-              <SelectItem value="upgrade">{t('typeOptions.upgrade')}</SelectItem>
-              <SelectItem value="inspection">{t('typeOptions.inspection')}</SelectItem>
-            </SelectContent>
-          </Select>
-        </div>
-        <div className="space-y-1">
-          <Label className="text-xs">{t('status')}</Label>
-          <Select value={status} onValueChange={setStatus}>
-            <SelectTrigger className="w-full">
-              <SelectValue />
-            </SelectTrigger>
-            <SelectContent>
-              <SelectItem value="pending">{t('statusOptions.pending')}</SelectItem>
-              <SelectItem value="in-progress">{t('statusOptions.in_progress')}</SelectItem>
-              <SelectItem value="waiting-parts">{t('statusOptions.waiting_parts')}</SelectItem>
-              <SelectItem value="completed">{t('statusOptions.completed')}</SelectItem>
-            </SelectContent>
-          </Select>
-        </div>
-      </div>
-
-      <div className="grid grid-cols-2 gap-2">
-        <div className="space-y-1">
-          <Label htmlFor="mileage" className="text-xs">{serviceType === 'boat' ? t('mileageBoat') : t('mileage')}</Label>
-          <Input
-            id="mileage"
-            name="mileage"
-            type="number"
-            placeholder="50000"
-            defaultValue={initialData.mileage ?? ''}
-          />
-        </div>
-        <div className="space-y-1">
-          <Label htmlFor="invoiceNumber" className="text-xs">{t('invoiceNumber')}</Label>
-          <Input
-            id="invoiceNumber"
-            name="invoiceNumber"
-            placeholder="2026-1001"
-            defaultValue={initialData.invoiceNumber || ''}
-          />
-        </div>
-      </div>
-
       <input type="hidden" name="techName" value={techName} />
     </div>
   )

+ 183 - 0
src/features/vehicles/Components/service-edit/InvoiceDetailsSection.tsx

@@ -0,0 +1,183 @@
+'use client'
+
+import { useState } from 'react'
+import { useTranslations } from 'next-intl'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { Button } from '@/components/ui/button'
+import { Calendar } from '@/components/ui/calendar'
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from '@/components/ui/select'
+import { CalendarIcon, Check, Loader2 } from 'lucide-react'
+import { cn } from '@/lib/utils'
+import type { InitialData } from './form-types'
+
+interface InvoiceDetailsSectionProps {
+  initialData: InitialData
+  type: string
+  setType: (type: string) => void
+  status: string
+  setStatus: (status: string) => void
+  onDirty?: () => void
+  paymentStatus: string
+  onTogglePaid: () => void
+  paymentLoading?: boolean
+}
+
+export function InvoiceDetailsSection({
+  initialData,
+  type,
+  setType,
+  status,
+  setStatus,
+  onDirty,
+  paymentStatus,
+  onTogglePaid,
+  paymentLoading,
+}: InvoiceDetailsSectionProps) {
+  const t = useTranslations('service.basicInfo')
+  const [invoiceDate, setInvoiceDate] = useState<Date | undefined>(
+    initialData.invoiceDate ? new Date(initialData.invoiceDate + 'T00:00:00') : undefined
+  )
+  const [invoiceDueDate, setInvoiceDueDate] = useState<Date | undefined>(
+    initialData.invoiceDueDate ? new Date(initialData.invoiceDueDate + 'T00:00:00') : undefined
+  )
+
+  const formatDate = (date: Date | undefined) => {
+    if (!date) return ''
+    return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
+  }
+
+  const toISODate = (date: Date | undefined) => {
+    if (!date) return ''
+    return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
+  }
+
+  return (
+    <div className="rounded-lg border p-3 space-y-3">
+      <div className="flex items-center justify-between">
+        <h3 className="text-sm font-semibold">{t('invoiceDetails')}</h3>
+        <Button
+          type="button"
+          variant="outline"
+          size="sm"
+          className={cn('h-7 text-xs', paymentStatus === 'paid' && 'border-green-300 bg-green-50 text-green-700 hover:bg-green-100 hover:text-green-800')}
+          onClick={onTogglePaid}
+          disabled={paymentLoading}
+        >
+          {paymentLoading ? (
+            <Loader2 className="mr-1 h-3 w-3 animate-spin" />
+          ) : (
+            <Check className="mr-1 h-3 w-3" />
+          )}
+          {paymentStatus === 'paid' ? t('paid') : t('markPaid')}
+        </Button>
+      </div>
+
+      <div className="space-y-1">
+        <Label htmlFor="title" className="text-xs">{t('titleLabel')}</Label>
+        <Input
+          id="title"
+          name="title"
+          placeholder={t('titlePlaceholder')}
+          defaultValue={initialData.title}
+          maxLength={100}
+          required
+        />
+      </div>
+
+      {/* Hidden serviceDate — kept for form submission */}
+      <input
+        type="hidden"
+        name="serviceDate"
+        value={initialData.serviceDate || new Date().toISOString().split('T')[0]}
+      />
+
+      <div className="grid grid-cols-2 gap-2">
+        <div className="space-y-1">
+          <Label className="text-xs">{t('type')}</Label>
+          <Select value={type} onValueChange={setType}>
+            <SelectTrigger className="w-full">
+              <SelectValue />
+            </SelectTrigger>
+            <SelectContent>
+              <SelectItem value="maintenance">{t('typeOptions.maintenance')}</SelectItem>
+              <SelectItem value="repair">{t('typeOptions.repair')}</SelectItem>
+              <SelectItem value="upgrade">{t('typeOptions.upgrade')}</SelectItem>
+              <SelectItem value="inspection">{t('typeOptions.inspection')}</SelectItem>
+            </SelectContent>
+          </Select>
+        </div>
+        <div className="space-y-1">
+          <Label className="text-xs">{t('status')}</Label>
+          <Select value={status} onValueChange={setStatus}>
+            <SelectTrigger className="w-full">
+              <SelectValue />
+            </SelectTrigger>
+            <SelectContent>
+              <SelectItem value="pending">{t('statusOptions.pending')}</SelectItem>
+              <SelectItem value="in-progress">{t('statusOptions.in_progress')}</SelectItem>
+              <SelectItem value="waiting-parts">{t('statusOptions.waiting_parts')}</SelectItem>
+              <SelectItem value="completed">{t('statusOptions.completed')}</SelectItem>
+            </SelectContent>
+          </Select>
+        </div>
+      </div>
+
+      <div className="space-y-1">
+        <Label htmlFor="invoiceNumber" className="text-xs">{t('invoiceNumber')}</Label>
+        <Input
+          id="invoiceNumber"
+          name="invoiceNumber"
+          placeholder="2026-1001"
+          defaultValue={initialData.invoiceNumber || ''}
+        />
+      </div>
+
+      <div className="grid grid-cols-2 gap-2">
+        <div className="space-y-1">
+          <Label className="text-xs">{t('invoiceDate')}</Label>
+          <input type="hidden" name="invoiceDate" value={toISODate(invoiceDate)} />
+          <Popover>
+            <PopoverTrigger asChild>
+              <Button
+                variant="outline"
+                className={cn('w-full justify-start text-left font-normal h-9 text-sm', !invoiceDate && 'text-muted-foreground')}
+              >
+                <CalendarIcon className="mr-2 h-3.5 w-3.5" />
+                {invoiceDate ? formatDate(invoiceDate) : t('invoiceDate')}
+              </Button>
+            </PopoverTrigger>
+            <PopoverContent className="w-auto p-0" align="start">
+              <Calendar mode="single" selected={invoiceDate} onSelect={(d) => { setInvoiceDate(d); onDirty?.() }} />
+            </PopoverContent>
+          </Popover>
+        </div>
+        <div className="space-y-1">
+          <Label className="text-xs">{t('invoiceDueDate')}</Label>
+          <input type="hidden" name="invoiceDueDate" value={toISODate(invoiceDueDate)} />
+          <Popover>
+            <PopoverTrigger asChild>
+              <Button
+                variant="outline"
+                className={cn('w-full justify-start text-left font-normal h-9 text-sm', !invoiceDueDate && 'text-muted-foreground')}
+              >
+                <CalendarIcon className="mr-2 h-3.5 w-3.5" />
+                {invoiceDueDate ? formatDate(invoiceDueDate) : t('invoiceDueDate')}
+              </Button>
+            </PopoverTrigger>
+            <PopoverContent className="w-auto p-0" align="start">
+              <Calendar mode="single" selected={invoiceDueDate} onSelect={(d) => { setInvoiceDueDate(d); onDirty?.() }} />
+            </PopoverContent>
+          </Popover>
+        </div>
+      </div>
+    </div>
+  )
+}

+ 2 - 0
src/features/vehicles/Components/service-edit/form-types.ts

@@ -30,6 +30,8 @@ export interface InitialData {
   diagnosticNotes: string
   invoiceNotes: string
   invoiceNumber?: string
+  invoiceDate: string
+  invoiceDueDate: string
   partItems: ServicePartInput[]
   laborItems: ServiceLaborInput[]
   attachments: (ServiceAttachmentInput & { includeInInvoice?: boolean })[]

+ 12 - 4
src/features/vehicles/Components/service-page/DetailsRightColumn.tsx

@@ -1,5 +1,6 @@
 import { useRouter } from 'next/navigation'
 import { SharedLinkCard } from '@/components/shared-link-card'
+import { InvoiceDetailsSection } from '../service-edit/InvoiceDetailsSection'
 import { BasicInfoSection } from '../service-edit/BasicInfoSection'
 import { ScheduleTimesSection } from '../service-edit/ScheduleTimesSection'
 import { TotalsSection } from '../service-edit/TotalsSection'
@@ -54,16 +55,23 @@ export function DetailsRightColumn({
           }}
         />
       )}
+      <InvoiceDetailsSection
+        initialData={formState.initialData}
+        type={formState.type}
+        setType={formState.dirtySetType}
+        status={formState.status}
+        setStatus={formState.dirtySetStatus}
+        onDirty={formState.markDirty}
+        paymentStatus={formState.paymentStatus}
+        onTogglePaid={actions.handleTogglePaid}
+        paymentLoading={actions.paymentLoading}
+      />
       <BasicInfoSection
         initialData={formState.initialData}
         vehicleId={vehicleId}
         vehicleName={formState.vehicleName}
         selectedVehicleId={formState.selectedVehicleId}
         setSelectedVehicleId={formState.dirtySetSelectedVehicleId}
-        type={formState.type}
-        setType={formState.dirtySetType}
-        status={formState.status}
-        setStatus={formState.dirtySetStatus}
         techName={formState.techName}
         customer={record.vehicle.customer}
         initialVehicle={initialVehicle}

+ 230 - 31
src/features/vehicles/Components/service-page/ServicePageClient.tsx

@@ -1,6 +1,7 @@
 'use client'
 
-import { useState, useCallback } from 'react'
+import { useState, useCallback, useMemo, useRef } from 'react'
+import { useRouter } from 'next/navigation'
 import { sendInvoiceEmail } from '@/features/email/Actions/emailActions'
 import { SendEmailDialog } from '@/features/email/Components/SendEmailDialog'
 import { useTranslations } from 'next-intl'
@@ -14,6 +15,19 @@ import { BarcodeScannerDialog } from '@/components/barcode-scanner-dialog'
 import { useHardwareScanner } from '@/hooks/use-hardware-scanner'
 import { lookupPartByBarcode } from '@/features/inventory/Actions/lookupPartByBarcode'
 import { toast } from 'sonner'
+import { Button } from '@/components/ui/button'
+import { Calendar } from '@/components/ui/calendar'
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
+import { Label } from '@/components/ui/label'
+import {
+  Dialog,
+  DialogContent,
+  DialogHeader,
+  DialogTitle,
+  DialogDescription,
+} from '@/components/ui/dialog'
+import { CalendarIcon, AlertTriangle } from 'lucide-react'
+import { updateServiceRecord } from '@/features/vehicles/Actions/serviceActions'
 import { LaborPresetPickerDialog } from '@/features/labor-presets/Components/LaborPresetPickerDialog'
 import type { LaborPresetOption } from './service-page-types'
 import { ServiceImagesManager } from '../service-images-manager'
@@ -25,9 +39,9 @@ import { useServiceFormState } from './useServiceFormState'
 import { useServiceActions } from './useServiceActions'
 import { DetailsLeftColumn } from './DetailsLeftColumn'
 import { DetailsRightColumn } from './DetailsRightColumn'
+import type { ServicePageClientProps } from './service-page-types'
 
 export type { ServicePageClientProps, BoardTechnicianOption } from './service-page-types'
-import type { ServicePageClientProps } from './service-page-types'
 
 export function ServicePageClient({
   record,
@@ -54,10 +68,36 @@ export function ServicePageClient({
   smsEnabled = false,
   emailEnabled = false,
   aiEnabled = false,
+  defaultDueDays = 0,
 }: ServicePageClientProps) {
   const t = useTranslations('service')
+  const router = useRouter()
   const [activeTab, setActiveTab] = useState<ServiceTab>('details')
 
+  // Date check dialog state
+  const [showDateCheck, setShowDateCheck] = useState(false)
+  const dateCheckResolveRef = useRef<((proceed: boolean) => void) | null>(null)
+  const today = new Date(new Date().toISOString().split('T')[0])
+  const suggestedDueDate =
+    defaultDueDays > 0 ? new Date(today.getTime() + defaultDueDays * 86400000) : today
+  const [pendingInvoiceDate, setPendingInvoiceDate] = useState<Date>(today)
+  const [pendingDueDate, setPendingDueDate] = useState<Date>(suggestedDueDate)
+  const [updatingDates, setUpdatingDates] = useState(false)
+
+  const formatDate = (date: Date) =>
+    date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
+  const toISODate = (date: Date) =>
+    `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
+
+  const areDatesExpired = useMemo(() => {
+    const invoiceDateStr = initialData.invoiceDate
+    const dueDateStr = initialData.invoiceDueDate
+    const todayStr = new Date().toISOString().split('T')[0]
+    const invoiceExpired = invoiceDateStr ? invoiceDateStr < todayStr : false
+    const dueExpired = dueDateStr ? dueDateStr < todayStr : false
+    return invoiceExpired || dueExpired
+  }, [initialData.invoiceDate, initialData.invoiceDueDate])
+
   const formState = useServiceFormState({
     vehicleId,
     initialData,
@@ -66,25 +106,51 @@ export function ServicePageClient({
     record,
   })
 
-  const handleBarcodeScan = useCallback(async (barcode: string) => {
-    const result = await lookupPartByBarcode(barcode)
-    if (result.success && result.data) {
-      const part = result.data
-      const price = part.sellPrice > 0 ? part.sellPrice : part.unitCost
-      formState.dirtySetPartItems((prev) => [...prev, {
-        partNumber: part.partNumber || '',
-        name: part.name,
-        quantity: 1,
-        unitPrice: price,
-        total: price,
-        unitCost: part.unitCost,
-        inventoryPartId: part.id,
-      }])
-      toast.success(t('parts.partFound', { name: part.name }))
-    } else {
-      toast.error(t('parts.partNotFound', { barcode }))
-    }
-  }, [formState, t])
+  const checkDates = useCallback(async () => {
+    if (!areDatesExpired || formState.paymentStatus === 'paid') return true
+    // Show current (expired) dates so user sees what's wrong
+    setPendingInvoiceDate(
+      initialData.invoiceDate ? new Date(initialData.invoiceDate + 'T00:00:00') : today
+    )
+    setPendingDueDate(
+      initialData.invoiceDueDate ? new Date(initialData.invoiceDueDate + 'T00:00:00') : today
+    )
+    setShowDateCheck(true)
+    return new Promise<boolean>((resolve) => {
+      dateCheckResolveRef.current = resolve
+    })
+  }, [
+    areDatesExpired,
+    formState.paymentStatus,
+    initialData.invoiceDate,
+    initialData.invoiceDueDate,
+  ])
+
+  const handleBarcodeScan = useCallback(
+    async (barcode: string) => {
+      const result = await lookupPartByBarcode(barcode)
+      if (result.success && result.data) {
+        const part = result.data
+        const price = part.sellPrice > 0 ? part.sellPrice : part.unitCost
+        formState.dirtySetPartItems((prev) => [
+          ...prev,
+          {
+            partNumber: part.partNumber || '',
+            name: part.name,
+            quantity: 1,
+            unitPrice: price,
+            total: price,
+            unitCost: part.unitCost,
+            inventoryPartId: part.id,
+          },
+        ])
+        toast.success(t('parts.partFound', { name: part.name }))
+      } else {
+        toast.error(t('parts.partNotFound', { barcode }))
+      }
+    },
+    [formState, t]
+  )
 
   useHardwareScanner({ onScan: handleBarcodeScan, enabled: activeTab === 'details' })
 
@@ -92,8 +158,10 @@ export function ServicePageClient({
     const newItems = preset.items.map((item) => ({
       description: item.description,
       hours: item.hours,
-      rate: item.rate > 0 ? item.rate : (item.pricingType === 'service' ? 0 : defaultLaborRate),
-      total: item.hours * (item.rate > 0 ? item.rate : (item.pricingType === 'service' ? 0 : defaultLaborRate)),
+      rate: item.rate > 0 ? item.rate : item.pricingType === 'service' ? 0 : defaultLaborRate,
+      total:
+        item.hours *
+        (item.rate > 0 ? item.rate : item.pricingType === 'service' ? 0 : defaultLaborRate),
       pricingType: (item.pricingType as 'hourly' | 'service') || 'hourly',
     }))
     formState.dirtySetLaborItems((prev) => [...prev, ...newItems])
@@ -125,14 +193,32 @@ export function ServicePageClient({
         saving={formState.loading}
         hasUnsavedChanges={formState.hasUnsavedChanges}
         showSaved={formState.showSaved}
-        onDownloadPDF={async () => { if (formState.hasUnsavedChanges) await actions.saveNow(); actions.handleDownloadPDF() }}
+        onDownloadPDF={async () => {
+          if (!(await checkDates())) return
+          if (formState.hasUnsavedChanges) await actions.saveNow()
+          actions.handleDownloadPDF()
+        }}
         onDelete={actions.handleDelete}
-        onShowEmail={async () => { if (formState.hasUnsavedChanges) await actions.saveNow(); actions.setShowEmailDialog(true) }}
-        onShowShare={async () => { if (formState.hasUnsavedChanges) await actions.saveNow(); actions.setShowShareDialog(true) }}
+        onShowEmail={async () => {
+          if (!(await checkDates())) return
+          if (formState.hasUnsavedChanges) await actions.saveNow()
+          actions.setShowEmailDialog(true)
+        }}
+        onShowShare={async () => {
+          if (!(await checkDates())) return
+          if (formState.hasUnsavedChanges) await actions.saveNow()
+          actions.setShowShareDialog(true)
+        }}
       />
 
       {activeTab === 'details' && (
-        <form id="service-record-form" ref={formState.formRef} onSubmit={actions.handleSubmit} onInput={formState.markDirty} className="flex min-h-0 flex-1 flex-col">
+        <form
+          id="service-record-form"
+          ref={formState.formRef}
+          onSubmit={actions.handleSubmit}
+          onInput={formState.markDirty}
+          className="flex min-h-0 flex-1 flex-col"
+        >
           <ServiceDetailContent
             leftColumn={
               <DetailsLeftColumn
@@ -178,10 +264,7 @@ export function ServicePageClient({
 
       {activeTab === 'video' && (
         <div className="flex-1 overflow-y-auto overscroll-contain p-4">
-          <ServiceVideoManager
-            serviceRecordId={record.id}
-            initialVideos={videoAttachments}
-          />
+          <ServiceVideoManager serviceRecordId={record.id} initialVideos={videoAttachments} />
         </div>
       )}
 
@@ -259,6 +342,122 @@ export function ServicePageClient({
           relatedEntityId={record.id}
         />
       )}
+
+      {/* Expired dates check dialog */}
+      <Dialog
+        open={showDateCheck}
+        onOpenChange={(open) => {
+          if (!open) {
+            dateCheckResolveRef.current?.(false)
+            dateCheckResolveRef.current = null
+          }
+          setShowDateCheck(open)
+        }}
+      >
+        <DialogContent className="sm:max-w-sm">
+          <DialogHeader>
+            <DialogTitle className="flex items-center gap-2">
+              <AlertTriangle className="h-5 w-5 text-amber-500" />
+              {t('page.datesExpiredTitle')}
+            </DialogTitle>
+            <DialogDescription>{t('page.datesExpiredDescription')}</DialogDescription>
+          </DialogHeader>
+
+          <div className="space-y-3">
+            <Button
+              variant="secondary"
+              size="sm"
+              className="w-full"
+              onClick={() => {
+                const now = new Date(new Date().toISOString().split('T')[0])
+                setPendingInvoiceDate(now)
+                setPendingDueDate(
+                  defaultDueDays > 0
+                    ? new Date(now.getTime() + defaultDueDays * 86400000)
+                    : new Date(now.getTime() + 14 * 86400000)
+                )
+              }}
+            >
+              {t('page.datesExpiredSetToday')}
+            </Button>
+
+            <div className="space-y-1">
+              <Label className="text-xs">{t('basicInfo.invoiceDate')}</Label>
+              <Popover>
+                <PopoverTrigger asChild>
+                  <Button
+                    variant="outline"
+                    className="w-full justify-start text-left font-normal h-9 text-sm"
+                  >
+                    <CalendarIcon className="mr-2 h-3.5 w-3.5" />
+                    {formatDate(pendingInvoiceDate)}
+                  </Button>
+                </PopoverTrigger>
+                <PopoverContent className="w-auto p-0" align="start">
+                  <Calendar
+                    mode="single"
+                    selected={pendingInvoiceDate}
+                    onSelect={(d) => d && setPendingInvoiceDate(d)}
+                  />
+                </PopoverContent>
+              </Popover>
+            </div>
+
+            <div className="space-y-1">
+              <Label className="text-xs">{t('basicInfo.invoiceDueDate')}</Label>
+              <Popover>
+                <PopoverTrigger asChild>
+                  <Button
+                    variant="outline"
+                    className="w-full justify-start text-left font-normal h-9 text-sm"
+                  >
+                    <CalendarIcon className="mr-2 h-3.5 w-3.5" />
+                    {formatDate(pendingDueDate)}
+                  </Button>
+                </PopoverTrigger>
+                <PopoverContent className="w-auto p-0" align="start">
+                  <Calendar
+                    mode="single"
+                    selected={pendingDueDate}
+                    onSelect={(d) => d && setPendingDueDate(d)}
+                  />
+                </PopoverContent>
+              </Popover>
+            </div>
+          </div>
+
+          <div className="flex flex-col gap-2 pt-2">
+            <Button
+              disabled={updatingDates}
+              onClick={async () => {
+                setUpdatingDates(true)
+                await updateServiceRecord({
+                  id: record.id,
+                  invoiceDate: toISODate(pendingInvoiceDate),
+                  invoiceDueDate: toISODate(pendingDueDate),
+                })
+                setUpdatingDates(false)
+                setShowDateCheck(false)
+                dateCheckResolveRef.current?.(true)
+                dateCheckResolveRef.current = null
+                router.refresh()
+              }}
+            >
+              {t('page.datesExpiredUpdate')}
+            </Button>
+            <Button
+              variant="outline"
+              onClick={() => {
+                setShowDateCheck(false)
+                dateCheckResolveRef.current?.(true)
+                dateCheckResolveRef.current = null
+              }}
+            >
+              {t('page.datesExpiredProceed')}
+            </Button>
+          </div>
+        </DialogContent>
+      </Dialog>
     </div>
   )
 }

+ 1 - 0
src/features/vehicles/Components/service-page/service-page-types.ts

@@ -52,6 +52,7 @@ export interface ServicePageClientProps {
   smsEnabled?: boolean
   emailEnabled?: boolean
   aiEnabled?: boolean
+  defaultDueDays?: number
 }
 
 export type { ServicePartInput, ServiceLaborInput, ServiceDetail, InitialData, InventoryPartOption, LaborPresetOption }

+ 10 - 3
src/features/vehicles/Components/service-page/useServiceActions.ts

@@ -77,10 +77,14 @@ export function useServiceActions({
 
     // Mobile + desktop layouts both render inputs with the same name.
     // formData.get() returns the first (hidden/stale) one, so read the visible input via offsetParent.
+    // For hidden inputs (offsetParent is always null), take the last one in DOM order.
     const getVisible = (name: string) => {
-      const inputs = form.querySelectorAll<HTMLInputElement>(`input[name="${name}"]`)
-      const visible = Array.from(inputs).find(el => el.offsetParent !== null)
-      return visible?.value ?? new FormData(form).get(name) as string
+      const inputs = Array.from(form.querySelectorAll<HTMLInputElement>(`input[name="${name}"]`))
+      const visible = inputs.find(el => el.offsetParent !== null)
+      if (visible) return visible.value
+      // All hidden — take last (active layout renders second)
+      const last = inputs[inputs.length - 1]
+      return last?.value ?? new FormData(form).get(name) as string
     }
     const rawMileage = getVisible('mileage')
     const parsedMileage = rawMileage ? Number(rawMileage) || undefined : undefined
@@ -99,6 +103,8 @@ export function useServiceActions({
       diagnosticNotes: notesRef.current.diagnosticNotes || undefined,
       invoiceNotes: notesRef.current.invoiceNotes || undefined,
       invoiceNumber: getVisible('invoiceNumber') || undefined,
+      invoiceDate: getVisible('invoiceDate') || undefined,
+      invoiceDueDate: getVisible('invoiceDueDate') || undefined,
       partItems: partItems.filter((p) => p.name),
       laborItems: laborItems.filter((l) => l.description),
       subtotal,
@@ -222,6 +228,7 @@ export function useServiceActions({
     const result = await toggleManuallyPaid(record.id)
     setPaymentLoading(false)
     if (result.success) {
+      formState.setLocalManuallyPaid(!!result.data?.manuallyPaid)
       toast.success(result.data?.manuallyPaid ? t('payments.markedPaid') : t('payments.markedUnpaid'))
       router.refresh()
       if (result.data?.manuallyPaid && record.vehicle.customer) {

+ 4 - 3
src/features/vehicles/Components/service-page/useServiceFormState.ts

@@ -110,11 +110,12 @@ export function useServiceFormState({
   const taxAmount = (subtotal - discountAmount) * (taxRate / 100)
   const totalAmount = subtotal - discountAmount + taxAmount
 
+  const [localManuallyPaid, setLocalManuallyPaid] = useState(record.manuallyPaid)
   const displayTotal = totalAmount > 0 ? totalAmount : record.cost
   const paidFromPayments = record.payments?.reduce((sum, p) => sum + p.amount, 0) || 0
-  const totalPaid = record.manuallyPaid ? displayTotal : paidFromPayments
+  const totalPaid = localManuallyPaid ? displayTotal : paidFromPayments
   const balanceDue = displayTotal - totalPaid
-  const paymentStatus = record.manuallyPaid
+  const paymentStatus = localManuallyPaid
     ? 'paid'
     : totalPaid === 0
       ? 'unpaid'
@@ -196,7 +197,7 @@ export function useServiceFormState({
     // Computed
     partsSubtotal, laborSubtotal, subtotal,
     discountAmount, taxAmount, totalAmount,
-    displayTotal, totalPaid, balanceDue, paymentStatus,
+    displayTotal, totalPaid, balanceDue, paymentStatus, setLocalManuallyPaid,
     imageAttachments, vehicleName,
     // Helpers
     updatePart, updateLabor,

+ 2 - 0
src/features/vehicles/Schema/serviceSchema.ts

@@ -54,6 +54,8 @@ export const createServiceSchema = z.object({
   discountValue: z.coerce.number().min(0).default(0),
   discountAmount: z.coerce.number().min(0).default(0),
   invoiceNumber: z.string().optional(),
+  invoiceDate: z.string().optional(),
+  invoiceDueDate: z.string().optional(),
 });
 
 export const updateServiceSchema = createServiceSchema.partial().extend({

+ 2 - 1
src/lib/ai.ts

@@ -163,6 +163,7 @@ export interface ServiceHistoryRecord {
   title: string;
   description: string | null;
   serviceDate: Date | null;
+  startDateTime?: Date | null;
   type: string;
   cost: number;
   mileage: number | null;
@@ -186,7 +187,7 @@ upcomingMaintenance: predict 2-4 likely upcoming needs based on mileage, age, an
   const recordsStr = records
     .map(
       (r) =>
-        `- ${r.serviceDate ? new Date(r.serviceDate).toISOString().slice(0, 10) : "Unknown date"}: ${r.title} (${r.type}) — Cost: ${r.cost}${r.mileage ? `, Mileage: ${r.mileage}` : ""}${r.description ? `\n  Notes: ${r.description}` : ""}`,
+        `- ${r.serviceDate ? new Date(r.startDateTime ?? r.serviceDate).toISOString().slice(0, 10) : "Unknown date"}: ${r.title} (${r.type}) — Cost: ${r.cost}${r.mileage ? `, Mileage: ${r.mileage}` : ""}${r.description ? `\n  Notes: ${r.description}` : ""}`,
     )
     .join("\n");
 

+ 1 - 0
src/lib/cron/recurring-invoices.ts

@@ -92,6 +92,7 @@ export function processRecurringInvoices() {
                 status: 'completed',
                 cost: ri.cost,
                 serviceDate: now,
+                startDateTime: now,
                 invoiceNotes: ri.invoiceNotes,
                 subtotal,
                 taxRate: ri.taxRate,