4
0
Эх сурвалжийг харах

Add net profit breakdown to financial and spare parts reports (#128)

* net profit

* revenue layout
Bernt Christian Egeland 5 сар өмнө
parent
commit
29c9340253

+ 19 - 3
messages/de/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "Ausstehend",
     "services": "Dienstleistungen",
     "partsCost": "Teilekosten",
-    "netProfit": "Nettogewinn",
+    "partsNetProfit": "Ersatzteil-Nettogewinn",
+    "partsNetProfitDesc": "Ersatzteilumsatz minus Einkaufskosten",
+    "laborRevenue": "Arbeit",
+    "laborRevenueDesc": "Einnahmen aus Arbeitskosten",
+    "netProfit": "Gesamter Nettogewinn",
     "revenueDesc": "Gesamtbetrag aller abgeschlossenen Aufträge",
     "collectedDesc": "Erhaltene Zahlungen von Kunden",
     "outstandingDesc": "In Rechnung gestellt, aber noch nicht bezahlt",
     "servicesDesc": "Abgeschlossene Arbeitsaufträge gesamt",
     "partsCostDesc": "Einkaufskosten der verwendeten Teile",
-    "netProfitDesc": "Umsatz abzüglich Teilekosten",
+    "netProfitDesc": "Ersatzteil-Nettogewinn plus Arbeitsumsatz",
     "monthlyRevenue": "Monatlicher Umsatz",
     "revenueByType": "Umsatz nach Typ",
     "monthlyBreakdown": "Monatliche Aufschlüsselung",
@@ -44,6 +48,8 @@
       "revenue": "Umsatz",
       "collected": "Eingezogen",
       "partsCost": "Teilekosten",
+      "partsNetProfit": "Teile-Gewinn",
+      "laborRevenue": "Arbeit",
       "netProfit": "Nettogewinn",
       "count": "Anzahl"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Verwendete Teile",
     "partsRevenue": "Teileumsatz",
+    "partsCost": "Teilekosten",
+    "netProfit": "Nettogewinn",
     "topPartsByUsage": "Top-Teile nach Verwendung",
     "partsUsage": "Teileverwendung",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "Teilenr.",
       "usageCount": "Verwendungsanzahl",
       "totalQty": "Gesamtmenge",
-      "revenue": "Umsatz"
+      "revenue": "Umsatz",
+      "cost": "Kosten",
+      "netProfit": "Nettogewinn"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Fälligkeitsdatum",
     "daysPastDue": "Tage überfällig",
     "partsCost": "Teilekosten",
+    "partsNetProfit": "Teile-Nettogewinn",
+    "laborRevenue": "Arbeit",
     "netProfit": "Nettogewinn"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Teile-Nettogewinn",
+    "laborRevenue": "Arbeit",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Teilekosten",
+    "totalPartsNetProfit": "Nettogewinn",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 19 - 3
messages/en/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
-    "netProfit": "Net Profit",
+    "partsNetProfit": "Parts Net Profit",
+    "partsNetProfitDesc": "Parts revenue minus parts purchase cost",
+    "laborRevenue": "Labor",
+    "laborRevenueDesc": "Revenue from labor charges",
+    "netProfit": "Total Net Profit",
     "revenueDesc": "Total invoiced from all completed services",
     "collectedDesc": "Payments received from customers",
     "outstandingDesc": "Invoiced but not yet paid",
     "servicesDesc": "Total completed work orders",
     "partsCostDesc": "Purchase cost of parts used in services",
-    "netProfitDesc": "Revenue minus parts cost",
+    "netProfitDesc": "Parts net profit plus labor revenue",
     "monthlyRevenue": "Monthly Revenue",
     "revenueByType": "Revenue by Type",
     "monthlyBreakdown": "Monthly Breakdown",
@@ -44,6 +48,8 @@
       "revenue": "Revenue",
       "collected": "Collected",
       "partsCost": "Parts Cost",
+      "partsNetProfit": "Parts Profit",
+      "laborRevenue": "Labor",
       "netProfit": "Net Profit",
       "count": "Count"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Parts Used",
     "partsRevenue": "Parts Revenue",
+    "partsCost": "Parts Cost",
+    "netProfit": "Net Profit",
     "topPartsByUsage": "Top Parts by Usage",
     "partsUsage": "Parts Usage",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "Part #",
       "usageCount": "Usage Count",
       "totalQty": "Total Qty",
-      "revenue": "Revenue"
+      "revenue": "Revenue",
+      "cost": "Cost",
+      "netProfit": "Net Profit"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Due Date",
     "daysPastDue": "Days Past Due",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Parts Net Profit",
+    "laborRevenue": "Labor",
     "netProfit": "Net Profit"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Parts Net Profit",
+    "laborRevenue": "Labor",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Parts Cost",
+    "totalPartsNetProfit": "Net Profit",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 19 - 3
messages/es/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "Pendiente",
     "services": "Servicios",
     "partsCost": "Coste de piezas",
-    "netProfit": "Beneficio neto",
+    "partsNetProfit": "Beneficio Neto de Repuestos",
+    "partsNetProfitDesc": "Ingresos de repuestos menos costo de compra",
+    "laborRevenue": "Mano de Obra",
+    "laborRevenueDesc": "Ingresos por cargos de mano de obra",
+    "netProfit": "Beneficio Neto Total",
     "revenueDesc": "Total facturado de todos los servicios completados",
     "collectedDesc": "Pagos recibidos de clientes",
     "outstandingDesc": "Facturado pero aún no cobrado",
     "servicesDesc": "Total de órdenes de trabajo completadas",
     "partsCostDesc": "Coste de compra de piezas usadas en servicios",
-    "netProfitDesc": "Ingresos menos coste de piezas",
+    "netProfitDesc": "Beneficio neto de repuestos más ingresos de mano de obra",
     "monthlyRevenue": "Ingresos Mensuales",
     "revenueByType": "Ingresos por Tipo",
     "monthlyBreakdown": "Desglose Mensual",
@@ -44,6 +48,8 @@
       "revenue": "Ingresos",
       "collected": "Cobrado",
       "partsCost": "Coste de piezas",
+      "partsNetProfit": "Beneficio Repuestos",
+      "laborRevenue": "Mano de Obra",
       "netProfit": "Beneficio neto",
       "count": "Cantidad"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Piezas Utilizadas",
     "partsRevenue": "Ingresos por Piezas",
+    "partsCost": "Costo de Repuestos",
+    "netProfit": "Beneficio Neto",
     "topPartsByUsage": "Piezas Más Utilizadas",
     "partsUsage": "Uso de Piezas",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "N.º Pieza",
       "usageCount": "Veces Utilizada",
       "totalQty": "Cant. Total",
-      "revenue": "Ingresos"
+      "revenue": "Ingresos",
+      "cost": "Costo",
+      "netProfit": "Beneficio Neto"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Fecha de Vencimiento",
     "daysPastDue": "Días Vencidos",
     "partsCost": "Coste de piezas",
+    "partsNetProfit": "Beneficio Neto Repuestos",
+    "laborRevenue": "Mano de Obra",
     "netProfit": "Beneficio neto"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Beneficio Neto Repuestos",
+    "laborRevenue": "Mano de Obra",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Costo de Repuestos",
+    "totalPartsNetProfit": "Beneficio Neto",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 19 - 3
messages/fr/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "En attente",
     "services": "Services",
     "partsCost": "Coût des pièces",
-    "netProfit": "Bénéfice net",
+    "partsNetProfit": "Bénéfice Net Pièces",
+    "partsNetProfitDesc": "Revenus des pièces moins coût d'achat",
+    "laborRevenue": "Main-d'œuvre",
+    "laborRevenueDesc": "Revenus des frais de main-d'œuvre",
+    "netProfit": "Bénéfice Net Total",
     "revenueDesc": "Total facturé pour tous les services terminés",
     "collectedDesc": "Paiements reçus des clients",
     "outstandingDesc": "Facturé mais pas encore encaissé",
     "servicesDesc": "Total des ordres de travail terminés",
     "partsCostDesc": "Coût d'achat des pièces utilisées",
-    "netProfitDesc": "Chiffre d'affaires moins coût des pièces",
+    "netProfitDesc": "Bénéfice net pièces plus revenus main-d'œuvre",
     "monthlyRevenue": "Revenus Mensuels",
     "revenueByType": "Revenus par Type",
     "monthlyBreakdown": "Répartition Mensuelle",
@@ -44,6 +48,8 @@
       "revenue": "Revenus",
       "collected": "Encaissé",
       "partsCost": "Coût des pièces",
+      "partsNetProfit": "Bénéfice Pièces",
+      "laborRevenue": "Main-d'œuvre",
       "netProfit": "Bénéfice net",
       "count": "Nombre"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Pièces Utilisées",
     "partsRevenue": "Revenus Pièces",
+    "partsCost": "Coût des Pièces",
+    "netProfit": "Bénéfice Net",
     "topPartsByUsage": "Pièces les Plus Utilisées",
     "partsUsage": "Utilisation des Pièces",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "N° Pièce",
       "usageCount": "Nombre d'Utilisations",
       "totalQty": "Qté Totale",
-      "revenue": "Revenus"
+      "revenue": "Revenus",
+      "cost": "Coût",
+      "netProfit": "Bénéfice Net"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Date d'Échéance",
     "daysPastDue": "Jours de Retard",
     "partsCost": "Coût des pièces",
+    "partsNetProfit": "Bénéfice Net Pièces",
+    "laborRevenue": "Main-d'œuvre",
     "netProfit": "Bénéfice net"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Bénéfice Net Pièces",
+    "laborRevenue": "Main-d'œuvre",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Coût des Pièces",
+    "totalPartsNetProfit": "Bénéfice Net",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 19 - 3
messages/it/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "In sospeso",
     "services": "Servizi",
     "partsCost": "Costo ricambi",
-    "netProfit": "Utile netto",
+    "partsNetProfit": "Profitto Netto Ricambi",
+    "partsNetProfitDesc": "Ricavi ricambi meno costo di acquisto",
+    "laborRevenue": "Manodopera",
+    "laborRevenueDesc": "Ricavi da costi di manodopera",
+    "netProfit": "Profitto Netto Totale",
     "revenueDesc": "Totale fatturato da tutti i servizi completati",
     "collectedDesc": "Pagamenti ricevuti dai clienti",
     "outstandingDesc": "Fatturato ma non ancora incassato",
     "servicesDesc": "Totale ordini di lavoro completati",
     "partsCostDesc": "Costo di acquisto dei ricambi utilizzati",
-    "netProfitDesc": "Ricavi meno costo ricambi",
+    "netProfitDesc": "Profitto netto ricambi più ricavi manodopera",
     "monthlyRevenue": "Ricavi Mensili",
     "revenueByType": "Ricavi per Tipo",
     "monthlyBreakdown": "Riepilogo Mensile",
@@ -44,6 +48,8 @@
       "revenue": "Ricavi",
       "collected": "Incassato",
       "partsCost": "Costo ricambi",
+      "partsNetProfit": "Profitto Ricambi",
+      "laborRevenue": "Manodopera",
       "netProfit": "Utile netto",
       "count": "Conteggio"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Ricambi Utilizzati",
     "partsRevenue": "Ricavi Ricambi",
+    "partsCost": "Costo Ricambi",
+    "netProfit": "Profitto Netto",
     "topPartsByUsage": "Ricambi Più Utilizzati",
     "partsUsage": "Utilizzo Ricambi",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "N. Ricambio",
       "usageCount": "Utilizzi",
       "totalQty": "Qtà Totale",
-      "revenue": "Ricavi"
+      "revenue": "Ricavi",
+      "cost": "Costo",
+      "netProfit": "Profitto Netto"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Data di Scadenza",
     "daysPastDue": "Giorni di Ritardo",
     "partsCost": "Costo ricambi",
+    "partsNetProfit": "Profitto Netto Ricambi",
+    "laborRevenue": "Manodopera",
     "netProfit": "Utile netto"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Profitto Netto Ricambi",
+    "laborRevenue": "Manodopera",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Costo Ricambi",
+    "totalPartsNetProfit": "Profitto Netto",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 19 - 3
messages/nb/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "Utestående",
     "services": "Tjenester",
     "partsCost": "Delekostnad",
-    "netProfit": "Nettoresultat",
+    "partsNetProfit": "Netto fortjeneste deler",
+    "partsNetProfitDesc": "Deleomsetning minus innkjøpskostnad",
+    "laborRevenue": "Arbeid",
+    "laborRevenueDesc": "Inntekter fra arbeidskostnader",
+    "netProfit": "Total netto fortjeneste",
     "revenueDesc": "Totalt fakturert fra alle fullførte tjenester",
     "collectedDesc": "Betalinger mottatt fra kunder",
     "outstandingDesc": "Fakturert men ikke betalt ennå",
     "servicesDesc": "Totalt fullførte arbeidsordrer",
     "partsCostDesc": "Innkjøpskostnad for deler brukt i tjenester",
-    "netProfitDesc": "Inntekt minus delekostnad",
+    "netProfitDesc": "Netto fortjeneste deler pluss arbeidsinntekter",
     "monthlyRevenue": "Månedlige Inntekter",
     "revenueByType": "Inntekter etter Type",
     "monthlyBreakdown": "Månedlig Oversikt",
@@ -44,6 +48,8 @@
       "revenue": "Inntekter",
       "collected": "Innkrevd",
       "partsCost": "Delekostnad",
+      "partsNetProfit": "Fortjeneste deler",
+      "laborRevenue": "Arbeid",
       "netProfit": "Nettoresultat",
       "count": "Antall"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Deler Brukt",
     "partsRevenue": "Deleinntekter",
+    "partsCost": "Delekostnad",
+    "netProfit": "Netto fortjeneste",
     "topPartsByUsage": "Mest Brukte Deler",
     "partsUsage": "Deleforbruk",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "Delenr.",
       "usageCount": "Bruksantall",
       "totalQty": "Total Ant.",
-      "revenue": "Inntekter"
+      "revenue": "Inntekter",
+      "cost": "Kostnad",
+      "netProfit": "Netto fortjeneste"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Forfallsdato",
     "daysPastDue": "Dager Forfalt",
     "partsCost": "Delekostnad",
+    "partsNetProfit": "Netto fortjeneste deler",
+    "laborRevenue": "Arbeid",
     "netProfit": "Nettoresultat"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Netto fortjeneste deler",
+    "laborRevenue": "Arbeid",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Delekostnad",
+    "totalPartsNetProfit": "Netto fortjeneste",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 19 - 3
messages/nl/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "Openstaand",
     "services": "Diensten",
     "partsCost": "Onderdeelkosten",
-    "netProfit": "Nettowinst",
+    "partsNetProfit": "Nettowinst Onderdelen",
+    "partsNetProfitDesc": "Omzet onderdelen minus inkoopkosten",
+    "laborRevenue": "Arbeid",
+    "laborRevenueDesc": "Inkomsten uit arbeidskosten",
+    "netProfit": "Totale Nettowinst",
     "revenueDesc": "Totaal gefactureerd van alle voltooide diensten",
     "collectedDesc": "Ontvangen betalingen van klanten",
     "outstandingDesc": "Gefactureerd maar nog niet betaald",
     "servicesDesc": "Totaal voltooide werkorders",
     "partsCostDesc": "Inkoopkosten van gebruikte onderdelen",
-    "netProfitDesc": "Omzet minus onderdeelkosten",
+    "netProfitDesc": "Nettowinst onderdelen plus arbeidsinkomsten",
     "monthlyRevenue": "Maandelijkse Omzet",
     "revenueByType": "Omzet per Type",
     "monthlyBreakdown": "Maandelijks Overzicht",
@@ -44,6 +48,8 @@
       "revenue": "Omzet",
       "collected": "Geïnd",
       "partsCost": "Onderdeelkosten",
+      "partsNetProfit": "Winst Onderdelen",
+      "laborRevenue": "Arbeid",
       "netProfit": "Nettowinst",
       "count": "Aantal"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Gebruikte Onderdelen",
     "partsRevenue": "Onderdelen Omzet",
+    "partsCost": "Kosten Onderdelen",
+    "netProfit": "Nettowinst",
     "topPartsByUsage": "Meest Gebruikte Onderdelen",
     "partsUsage": "Onderdelengebruik",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "Onderdeelnr.",
       "usageCount": "Gebruiksaantal",
       "totalQty": "Totaal Aant.",
-      "revenue": "Omzet"
+      "revenue": "Omzet",
+      "cost": "Kosten",
+      "netProfit": "Nettowinst"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Vervaldatum",
     "daysPastDue": "Dagen Achterstallig",
     "partsCost": "Onderdeelkosten",
+    "partsNetProfit": "Nettowinst Onderdelen",
+    "laborRevenue": "Arbeid",
     "netProfit": "Nettowinst"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Nettowinst Onderdelen",
+    "laborRevenue": "Arbeid",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Kosten Onderdelen",
+    "totalPartsNetProfit": "Nettowinst",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 19 - 3
messages/pl/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "Zaległe",
     "services": "Usługi",
     "partsCost": "Koszt części",
-    "netProfit": "Zysk netto",
+    "partsNetProfit": "Zysk Netto z Części",
+    "partsNetProfitDesc": "Przychód z części minus koszt zakupu",
+    "laborRevenue": "Robocizna",
+    "laborRevenueDesc": "Przychód z opłat za robociznę",
+    "netProfit": "Całkowity Zysk Netto",
     "revenueDesc": "Łączna kwota faktur ze wszystkich usług",
     "collectedDesc": "Płatności otrzymane od klientów",
     "outstandingDesc": "Zafakturowane, ale jeszcze nieopłacone",
     "servicesDesc": "Łączna liczba zrealizowanych zleceń",
     "partsCostDesc": "Koszt zakupu części użytych w usługach",
-    "netProfitDesc": "Przychód minus koszt części",
+    "netProfitDesc": "Zysk netto z części plus przychód z robocizny",
     "monthlyRevenue": "Przychody Miesięczne",
     "revenueByType": "Przychody wg Typu",
     "monthlyBreakdown": "Zestawienie Miesięczne",
@@ -44,6 +48,8 @@
       "revenue": "Przychody",
       "collected": "Zebrano",
       "partsCost": "Koszt części",
+      "partsNetProfit": "Zysk z Części",
+      "laborRevenue": "Robocizna",
       "netProfit": "Zysk netto",
       "count": "Ilość"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Użyte Części",
     "partsRevenue": "Przychody z Części",
+    "partsCost": "Koszt Części",
+    "netProfit": "Zysk Netto",
     "topPartsByUsage": "Najczęściej Używane Części",
     "partsUsage": "Użycie Części",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "Nr Części",
       "usageCount": "Liczba Użyć",
       "totalQty": "Łączna Ilość",
-      "revenue": "Przychody"
+      "revenue": "Przychody",
+      "cost": "Koszt",
+      "netProfit": "Zysk Netto"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Termin Płatności",
     "daysPastDue": "Dni Przeterminowania",
     "partsCost": "Koszt części",
+    "partsNetProfit": "Zysk Netto z Części",
+    "laborRevenue": "Robocizna",
     "netProfit": "Zysk netto"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Zysk Netto z Części",
+    "laborRevenue": "Robocizna",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Koszt Części",
+    "totalPartsNetProfit": "Zysk Netto",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 19 - 3
messages/pt-BR/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "Pendente",
     "services": "Serviços",
     "partsCost": "Custo de peças",
-    "netProfit": "Lucro líquido",
+    "partsNetProfit": "Lucro Líquido de Peças",
+    "partsNetProfitDesc": "Receita de peças menos custo de compra",
+    "laborRevenue": "Mão de Obra",
+    "laborRevenueDesc": "Receita de cobranças de mão de obra",
+    "netProfit": "Lucro Líquido Total",
     "revenueDesc": "Total faturado de todos os serviços concluídos",
     "collectedDesc": "Pagamentos recebidos de clientes",
     "outstandingDesc": "Faturado mas ainda não pago",
     "servicesDesc": "Total de ordens de serviço concluídas",
     "partsCostDesc": "Custo de compra das peças usadas nos serviços",
-    "netProfitDesc": "Receita menos custo de peças",
+    "netProfitDesc": "Lucro líquido de peças mais receita de mão de obra",
     "monthlyRevenue": "Receita Mensal",
     "revenueByType": "Receita por Tipo",
     "monthlyBreakdown": "Detalhamento Mensal",
@@ -44,6 +48,8 @@
       "revenue": "Receita",
       "collected": "Recebido",
       "partsCost": "Custo de peças",
+      "partsNetProfit": "Lucro Peças",
+      "laborRevenue": "Mão de Obra",
       "netProfit": "Lucro líquido",
       "count": "Quantidade"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Peças Usadas",
     "partsRevenue": "Receita de Peças",
+    "partsCost": "Custo de Peças",
+    "netProfit": "Lucro Líquido",
     "topPartsByUsage": "Peças Mais Usadas",
     "partsUsage": "Uso de Peças",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "N.º Peça",
       "usageCount": "Vezes Usada",
       "totalQty": "Qtd. Total",
-      "revenue": "Receita"
+      "revenue": "Receita",
+      "cost": "Custo",
+      "netProfit": "Lucro Líquido"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Data de Vencimento",
     "daysPastDue": "Dias em Atraso",
     "partsCost": "Custo de peças",
+    "partsNetProfit": "Lucro Líquido Peças",
+    "laborRevenue": "Mão de Obra",
     "netProfit": "Lucro líquido"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Lucro Líquido Peças",
+    "laborRevenue": "Mão de Obra",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Custo de Peças",
+    "totalPartsNetProfit": "Lucro Líquido",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 19 - 3
messages/ru/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "Задолженность",
     "services": "Обслуживания",
     "partsCost": "Стоимость запчастей",
-    "netProfit": "Чистая прибыль",
+    "partsNetProfit": "Чистая прибыль по запчастям",
+    "partsNetProfitDesc": "Выручка от запчастей минус закупочная стоимость",
+    "laborRevenue": "Работа",
+    "laborRevenueDesc": "Доход от стоимости работ",
+    "netProfit": "Общая чистая прибыль",
     "revenueDesc": "Общая сумма счетов по всем выполненным услугам",
     "collectedDesc": "Полученные платежи от клиентов",
     "outstandingDesc": "Выставлено, но ещё не оплачено",
     "servicesDesc": "Всего выполненных заказ-нарядов",
     "partsCostDesc": "Закупочная стоимость запчастей, использованных в услугах",
-    "netProfitDesc": "Выручка минус стоимость запчастей",
+    "netProfitDesc": "Чистая прибыль по запчастям плюс доход от работ",
     "monthlyRevenue": "Ежемесячный доход",
     "revenueByType": "Доход по типам",
     "monthlyBreakdown": "Помесячная разбивка",
@@ -44,6 +48,8 @@
       "revenue": "Доход",
       "collected": "Собрано",
       "partsCost": "Стоимость запчастей",
+      "partsNetProfit": "Прибыль запчасти",
+      "laborRevenue": "Работа",
       "netProfit": "Чистая прибыль",
       "count": "Количество"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Использовано запчастей",
     "partsRevenue": "Доход от запчастей",
+    "partsCost": "Стоимость запчастей",
+    "netProfit": "Чистая прибыль",
     "topPartsByUsage": "Топ запчастей по использованию",
     "partsUsage": "Использование запчастей",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "Арт. №",
       "usageCount": "Раз использовано",
       "totalQty": "Общее кол-во",
-      "revenue": "Доход"
+      "revenue": "Доход",
+      "cost": "Стоимость",
+      "netProfit": "Чистая прибыль"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Срок оплаты",
     "daysPastDue": "Дней просрочки",
     "partsCost": "Стоимость запчастей",
+    "partsNetProfit": "Чистая прибыль запчасти",
+    "laborRevenue": "Работа",
     "netProfit": "Чистая прибыль"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Чистая прибыль запчасти",
+    "laborRevenue": "Работа",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Стоимость запчастей",
+    "totalPartsNetProfit": "Чистая прибыль",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 19 - 3
messages/tr/reports.json

@@ -29,13 +29,17 @@
     "outstanding": "Bekleyen",
     "services": "Hizmetler",
     "partsCost": "Parça maliyeti",
-    "netProfit": "Net kâr",
+    "partsNetProfit": "Parça Net Kârı",
+    "partsNetProfitDesc": "Parça geliri eksi satın alma maliyeti",
+    "laborRevenue": "İşçilik",
+    "laborRevenueDesc": "İşçilik ücretlerinden elde edilen gelir",
+    "netProfit": "Toplam Net Kâr",
     "revenueDesc": "Tamamlanan tüm servislerden toplam fatura",
     "collectedDesc": "Müşterilerden alınan ödemeler",
     "outstandingDesc": "Faturalandırılmış ancak henüz ödenmemiş",
     "servicesDesc": "Toplam tamamlanan iş emirleri",
     "partsCostDesc": "Servislerde kullanılan parçaların alış maliyeti",
-    "netProfitDesc": "Gelir eksi parça maliyeti",
+    "netProfitDesc": "Parça net kârı artı işçilik geliri",
     "monthlyRevenue": "Aylık Gelir",
     "revenueByType": "Türe Göre Gelir",
     "monthlyBreakdown": "Aylık Döküm",
@@ -44,6 +48,8 @@
       "revenue": "Gelir",
       "collected": "Tahsil Edilen",
       "partsCost": "Parça maliyeti",
+      "partsNetProfit": "Parça Kârı",
+      "laborRevenue": "İşçilik",
       "netProfit": "Net kâr",
       "count": "Adet"
     }
@@ -123,6 +129,8 @@
   "parts": {
     "partsUsed": "Kullanılan Parçalar",
     "partsRevenue": "Parça Geliri",
+    "partsCost": "Parça Maliyeti",
+    "netProfit": "Net Kâr",
     "topPartsByUsage": "Kullanıma Göre En Çok Parçalar",
     "partsUsage": "Parça Kullanımı",
     "tableHeaders": {
@@ -130,7 +138,9 @@
       "partNumber": "Parça No",
       "usageCount": "Kullanım Sayısı",
       "totalQty": "Toplam Adet",
-      "revenue": "Gelir"
+      "revenue": "Gelir",
+      "cost": "Maliyet",
+      "netProfit": "Net Kâr"
     }
   },
   "jobAnalytics": {
@@ -243,6 +253,8 @@
     "dueDate": "Vade Tarihi",
     "daysPastDue": "Gecikme Günü",
     "partsCost": "Parça maliyeti",
+    "partsNetProfit": "Parça Net Kârı",
+    "laborRevenue": "İşçilik",
     "netProfit": "Net kâr"
   },
   "pdf": {
@@ -273,6 +285,8 @@
     "outstanding": "Outstanding",
     "services": "Services",
     "partsCost": "Parts Cost",
+    "partsNetProfit": "Parça Net Kârı",
+    "laborRevenue": "İşçilik",
     "netProfit": "Net Profit",
     "month": "Month",
     "count": "Count",
@@ -312,6 +326,8 @@
     "usageCount": "Usage Count",
     "totalQty": "Total Qty",
     "totalPartsRevenue": "Parts Revenue",
+    "totalPartsCost": "Parça Maliyeti",
+    "totalPartsNetProfit": "Net Kâr",
     "totalPartsUsed": "Parts Used",
     "serviceType": "Service Type",
     "avgValue": "Avg Value",

+ 83 - 77
src/app/(authenticated)/reports/reports-client.tsx

@@ -306,7 +306,7 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
     if (activeTab === "financial") {
       switch (financialSubTab) {
         case "revenue":
-          if (revenueData) exportRevenueCsv(revenueData, currencyCode, [h("month"), h("revenue"), h("collected"), h("count"), h("partsCost"), h("netProfit")]);
+          if (revenueData) exportRevenueCsv(revenueData, currencyCode, [h("month"), h("revenue"), h("collected"), h("count"), h("partsCost"), h("partsNetProfit"), h("laborRevenue"), h("netProfit")]);
           break;
         case "past-due-invoices":
           if (pastDueData) exportPastDueInvoicesCsv(pastDueData, currencyCode, [h("customer"), h("company"), h("invoiceNumber"), h("totalAmount"), h("amountPaid"), h("amountDue"), h("dueDate"), h("daysPastDue")]);
@@ -330,7 +330,7 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
           if (technicianData) exportTechniciansCsv(technicianData, currencyCode, [h("technician"), h("jobs"), h("totalRevenue"), h("avgRevenue"), h("totalHours"), h("avgHours")]);
           break;
         case "parts":
-          if (partsData) exportPartsCsv(partsData, currencyCode, [h("partName"), h("partNumber"), h("usageCount"), h("totalQty"), h("totalRevenue")]);
+          if (partsData) exportPartsCsv(partsData, currencyCode, [h("partName"), h("partNumber"), h("usageCount"), h("totalQty"), h("totalRevenue"), h("partsCost"), h("netProfit")]);
           break;
         case "job-analytics":
           if (jobAnalyticsData) exportJobAnalyticsCsv(jobAnalyticsData, currencyCode, [h("serviceType"), h("count"), h("avgValue"), h("avgHours")]);
@@ -380,6 +380,8 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
         outstanding: t("pdf.outstanding"),
         services: t("pdf.services"),
         partsCost: t("pdf.partsCost"),
+        partsNetProfit: t("pdf.partsNetProfit"),
+        laborRevenue: t("pdf.laborRevenue"),
         netProfit: t("pdf.netProfit"),
         month: t("pdf.month"),
         count: t("pdf.count"),
@@ -419,6 +421,8 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
         usageCount: t("pdf.usageCount"),
         totalQty: t("pdf.totalQty"),
         totalPartsRevenue: t("pdf.totalPartsRevenue"),
+        totalPartsCost: t("pdf.totalPartsCost"),
+        totalPartsNetProfit: t("pdf.totalPartsNetProfit"),
         totalPartsUsed: t("pdf.totalPartsUsed"),
         serviceType: t("pdf.serviceType"),
         avgValue: t("pdf.avgValue"),
@@ -681,92 +685,56 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
           <TabsContent value="revenue">
             {!loading && revenueData && (
               <div className="space-y-4">
-                <div className="grid gap-3 grid-cols-2 lg:grid-cols-3">
+                {/* Top-line metrics */}
+                <div className="grid gap-2 grid-cols-3">
                   <Card className="border-0 shadow-sm">
-                    <CardContent className="flex items-center gap-3 p-4">
-                      <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-blue-500/10">
-                        <DollarSign className="h-4 w-4 text-blue-500" />
-                      </div>
-                      <div className="min-w-0">
-                        <p className="text-xs text-muted-foreground">{t("revenue.revenue")}</p>
-                        <p className="text-lg font-semibold truncate">
-                          {fmtCurrency(revenueData.summary.totalRevenue)}
-                        </p>
-                        <p className="text-[10px] leading-tight text-muted-foreground/70">{t("revenue.revenueDesc")}</p>
-                      </div>
+                    <CardContent className="px-3 py-1.5">
+                      <p className="text-[11px] text-muted-foreground">{t("revenue.revenue")}</p>
+                      <p className="text-base font-semibold truncate">{fmtCurrency(revenueData.summary.totalRevenue)}</p>
                     </CardContent>
                   </Card>
                   <Card className="border-0 shadow-sm">
-                    <CardContent className="flex items-center gap-3 p-4">
-                      <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-emerald-500/10">
-                        <TrendingUp className="h-4 w-4 text-emerald-500" />
-                      </div>
-                      <div className="min-w-0">
-                        <p className="text-xs text-muted-foreground">{t("revenue.collected")}</p>
-                        <p className="text-lg font-semibold truncate">
-                          {fmtCurrency(revenueData.summary.totalCollected)}
-                        </p>
-                        <p className="text-[10px] leading-tight text-muted-foreground/70">{t("revenue.collectedDesc")}</p>
-                      </div>
+                    <CardContent className="px-3 py-1.5">
+                      <p className="text-[11px] text-muted-foreground">{t("revenue.collected")}</p>
+                      <p className="text-base font-semibold truncate">{fmtCurrency(revenueData.summary.totalCollected)}</p>
                     </CardContent>
                   </Card>
                   <Card className="border-0 shadow-sm">
-                    <CardContent className="flex items-center gap-3 p-4">
-                      <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-amber-500/10">
-                        <AlertTriangle className="h-4 w-4 text-amber-500" />
-                      </div>
-                      <div className="min-w-0">
-                        <p className="text-xs text-muted-foreground">{t("revenue.outstanding")}</p>
-                        <p className="text-lg font-semibold truncate">
-                          {fmtCurrency(revenueData.summary.outstanding)}
-                        </p>
-                        <p className="text-[10px] leading-tight text-muted-foreground/70">{t("revenue.outstandingDesc")}</p>
-                      </div>
-                    </CardContent>
-                  </Card>
-                  <Card className="border-0 shadow-sm">
-                    <CardContent className="flex items-center gap-3 p-4">
-                      <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-violet-500/10">
-                        <BarChart3 className="h-4 w-4 text-violet-500" />
-                      </div>
-                      <div className="min-w-0">
-                        <p className="text-xs text-muted-foreground">{t("revenue.services")}</p>
-                        <p className="text-lg font-semibold">
-                          {revenueData.summary.totalCount}
-                        </p>
-                        <p className="text-[10px] leading-tight text-muted-foreground/70">{t("revenue.servicesDesc")}</p>
-                      </div>
+                    <CardContent className="px-3 py-1.5">
+                      <p className="text-[11px] text-muted-foreground">{t("revenue.outstanding")}</p>
+                      <p className="text-base font-semibold truncate">{fmtCurrency(revenueData.summary.outstanding)}</p>
                     </CardContent>
                   </Card>
-                  <Card className="border-0 shadow-sm">
-                    <CardContent className="flex items-center gap-3 p-4">
-                      <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-orange-500/10">
-                        <Package className="h-4 w-4 text-orange-500" />
-                      </div>
-                      <div className="min-w-0">
-                        <p className="text-xs text-muted-foreground">{t("revenue.partsCost")}</p>
-                        <p className="text-lg font-semibold truncate">
-                          {fmtCurrency(revenueData.summary.totalPartsCost)}
-                        </p>
-                        <p className="text-[10px] leading-tight text-muted-foreground/70">{t("revenue.partsCostDesc")}</p>
+                </div>
+
+                {/* Net Profit breakdown */}
+                <Card className="border-0 shadow-sm">
+                  <CardHeader className="pb-2">
+                    <CardTitle className="text-sm font-medium">{t("revenue.netProfit")}</CardTitle>
+                  </CardHeader>
+                  <CardContent>
+                    <div className="space-y-3">
+                      <div className="flex items-center justify-between">
+                        <div className="flex items-center gap-2">
+                          <div className="h-2 w-2 rounded-full bg-green-500" />
+                          <span className="text-sm text-muted-foreground">{t("revenue.partsNetProfit")}</span>
+                        </div>
+                        <span className="text-sm font-medium tabular-nums">{fmtCurrency(revenueData.summary.totalPartsNetProfit)}</span>
                       </div>
-                    </CardContent>
-                  </Card>
-                  <Card className="border-0 shadow-sm">
-                    <CardContent className="flex items-center gap-3 p-4">
-                      <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-green-500/10">
-                        <TrendingUp className="h-4 w-4 text-green-500" />
+                      <div className="flex items-center justify-between">
+                        <div className="flex items-center gap-2">
+                          <div className="h-2 w-2 rounded-full bg-blue-500" />
+                          <span className="text-sm text-muted-foreground">{t("revenue.laborRevenue")}</span>
+                        </div>
+                        <span className="text-sm font-medium tabular-nums">{fmtCurrency(revenueData.summary.totalLaborRevenue)}</span>
                       </div>
-                      <div className="min-w-0">
-                        <p className="text-xs text-muted-foreground">{t("revenue.netProfit")}</p>
-                        <p className="text-lg font-semibold truncate">
-                          {fmtCurrency(revenueData.summary.netProfit)}
-                        </p>
-                        <p className="text-[10px] leading-tight text-muted-foreground/70">{t("revenue.netProfitDesc")}</p>
+                      <div className="border-t pt-3 flex items-center justify-between">
+                        <span className="text-sm font-medium">{t("revenue.netProfit")}</span>
+                        <span className="text-base font-semibold tabular-nums">{fmtCurrency(revenueData.summary.netProfit)}</span>
                       </div>
-                    </CardContent>
-                  </Card>
-                </div>
+                    </div>
+                  </CardContent>
+                </Card>
 
                 <div className="grid gap-4 lg:grid-cols-5">
                   <Card className="border-0 shadow-sm lg:col-span-3">
@@ -804,6 +772,8 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
                             <TableHead className="text-right">{t("revenue.tableHeaders.revenue")}</TableHead>
                             <TableHead className="text-right">{t("revenue.tableHeaders.collected")}</TableHead>
                             <TableHead className="text-right">{t("revenue.tableHeaders.partsCost")}</TableHead>
+                            <TableHead className="text-right">{t("revenue.tableHeaders.partsNetProfit")}</TableHead>
+                            <TableHead className="text-right">{t("revenue.tableHeaders.laborRevenue")}</TableHead>
                             <TableHead className="text-right">{t("revenue.tableHeaders.netProfit")}</TableHead>
                             <TableHead className="text-right">{t("revenue.tableHeaders.count")}</TableHead>
                           </TableRow>
@@ -821,6 +791,12 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
                               <TableCell className="text-right text-sm">
                                 {fmtCurrency(row.partsCost)}
                               </TableCell>
+                              <TableCell className="text-right text-sm">
+                                {fmtCurrency(row.partsNetProfit)}
+                              </TableCell>
+                              <TableCell className="text-right text-sm">
+                                {fmtCurrency(row.laborRevenue)}
+                              </TableCell>
                               <TableCell className="text-right text-sm">
                                 {fmtCurrency(row.netProfit)}
                               </TableCell>
@@ -1450,7 +1426,7 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
       <TabsContent value="parts">
         {!loading && partsData && (
           <div className="space-y-4">
-            <div className="grid gap-3 grid-cols-2 max-w-lg">
+            <div className="grid gap-3 grid-cols-2 lg:grid-cols-4">
               <Card className="border-0 shadow-sm">
                 <CardContent className="flex items-center gap-3 p-4">
                   <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-blue-500/10">
@@ -1475,6 +1451,32 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
                   </div>
                 </CardContent>
               </Card>
+              <Card className="border-0 shadow-sm">
+                <CardContent className="flex items-center gap-3 p-4">
+                  <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-orange-500/10">
+                    <Package className="h-4 w-4 text-orange-500" />
+                  </div>
+                  <div className="min-w-0">
+                    <p className="text-xs text-muted-foreground">{t("parts.partsCost")}</p>
+                    <p className="text-lg font-semibold truncate">
+                      {fmtCurrency(partsData.totalPartsCost)}
+                    </p>
+                  </div>
+                </CardContent>
+              </Card>
+              <Card className="border-0 shadow-sm">
+                <CardContent className="flex items-center gap-3 p-4">
+                  <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-green-500/10">
+                    <TrendingUp className="h-4 w-4 text-green-500" />
+                  </div>
+                  <div className="min-w-0">
+                    <p className="text-xs text-muted-foreground">{t("parts.netProfit")}</p>
+                    <p className="text-lg font-semibold truncate">
+                      {fmtCurrency(partsData.totalPartsNetProfit)}
+                    </p>
+                  </div>
+                </CardContent>
+              </Card>
             </div>
             {partsData.parts.length > 0 && (
               <Card className="border-0 shadow-sm">
@@ -1500,6 +1502,8 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
                         <TableHead className="text-right">{t("parts.tableHeaders.usageCount")}</TableHead>
                         <TableHead className="text-right">{t("parts.tableHeaders.totalQty")}</TableHead>
                         <TableHead className="text-right">{t("parts.tableHeaders.revenue")}</TableHead>
+                        <TableHead className="text-right">{t("parts.tableHeaders.cost")}</TableHead>
+                        <TableHead className="text-right">{t("parts.tableHeaders.netProfit")}</TableHead>
                       </TableRow>
                     </TableHeader>
                     <TableBody>
@@ -1510,6 +1514,8 @@ export default function ReportsClient({ currencyCode, primaryColor }: ReportsCli
                           <TableCell className="text-right text-sm">{row.usageCount}</TableCell>
                           <TableCell className="text-right text-sm">{row.totalQuantity}</TableCell>
                           <TableCell className="text-right text-sm">{fmtCurrency(row.totalRevenue)}</TableCell>
+                          <TableCell className="text-right text-sm">{fmtCurrency(row.totalCost)}</TableCell>
+                          <TableCell className="text-right text-sm">{fmtCurrency(row.netProfit)}</TableCell>
                         </TableRow>
                       ))}
                     </TableBody>

+ 30 - 8
src/features/reports/Actions/reportActions.ts

@@ -26,17 +26,20 @@ export async function getRevenueReport(params: {
         type: true,
         manuallyPaid: true,
         payments: { select: { amount: true } },
-        partItems: { select: { unitCost: true, quantity: true } },
+        partItems: { select: { unitCost: true, quantity: true, total: true } },
+        laborItems: { select: { total: true } },
       },
       orderBy: [{ startDateTime: { sort: "asc", nulls: "last" } }, { serviceDate: "asc" }],
     });
 
     // Monthly breakdown
-    const monthly: Record<string, { revenue: number; collected: number; count: number; partsCost: number }> = {};
+    const monthly: Record<string, { revenue: number; collected: number; count: number; partsCost: number; partsRevenue: number; laborRevenue: number }> = {};
     let totalRevenue = 0;
     let totalCollected = 0;
     let totalCount = 0;
     let totalPartsCost = 0;
+    let totalPartsRevenue = 0;
+    let totalLaborRevenue = 0;
 
     // Type breakdown
     const byType: Record<string, { revenue: number; count: number }> = {};
@@ -45,19 +48,25 @@ 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 partsRevenue = r.partItems.reduce((s, p) => s + p.total, 0);
+      const laborRevenue = r.laborItems.reduce((s, l) => s + l.total, 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 };
+      if (!monthly[month]) monthly[month] = { revenue: 0, collected: 0, count: 0, partsCost: 0, partsRevenue: 0, laborRevenue: 0 };
       monthly[month].revenue += total;
       monthly[month].collected += paid;
       monthly[month].count += 1;
       monthly[month].partsCost += partsCost;
+      monthly[month].partsRevenue += partsRevenue;
+      monthly[month].laborRevenue += laborRevenue;
 
       totalRevenue += total;
       totalCollected += paid;
       totalCount += 1;
       totalPartsCost += partsCost;
+      totalPartsRevenue += partsRevenue;
+      totalLaborRevenue += laborRevenue;
 
       if (!byType[r.type]) byType[r.type] = { revenue: 0, count: 0 };
       byType[r.type].revenue += total;
@@ -68,7 +77,8 @@ export async function getRevenueReport(params: {
       monthly: Object.entries(monthly).map(([month, data]) => ({
         month,
         ...data,
-        netProfit: data.revenue - data.partsCost,
+        partsNetProfit: data.partsRevenue - data.partsCost,
+        netProfit: (data.partsRevenue - data.partsCost) + data.laborRevenue,
       })),
       byType: Object.entries(byType).map(([type, data]) => ({ type, ...data })),
       summary: {
@@ -77,7 +87,10 @@ export async function getRevenueReport(params: {
         outstanding: totalRevenue - totalCollected,
         totalCount,
         totalPartsCost,
-        netProfit: totalRevenue - totalPartsCost,
+        totalPartsRevenue,
+        totalPartsNetProfit: totalPartsRevenue - totalPartsCost,
+        totalLaborRevenue,
+        netProfit: (totalPartsRevenue - totalPartsCost) + totalLaborRevenue,
       },
     };
   }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.REPORTS }] });
@@ -247,17 +260,19 @@ export async function getPartsUsageReport(params: {
         partNumber: true,
         quantity: true,
         total: true,
+        unitCost: true,
       },
     });
 
-    const byPart: Record<string, { partNumber: string | null; usageCount: number; totalQuantity: number; totalRevenue: number }> = {};
+    const byPart: Record<string, { partNumber: string | null; usageCount: number; totalQuantity: number; totalRevenue: number; totalCost: number }> = {};
 
     for (const p of parts) {
       const key = p.name.toLowerCase();
-      if (!byPart[key]) byPart[key] = { partNumber: p.partNumber, usageCount: 0, totalQuantity: 0, totalRevenue: 0 };
+      if (!byPart[key]) byPart[key] = { partNumber: p.partNumber, usageCount: 0, totalQuantity: 0, totalRevenue: 0, totalCost: 0 };
       byPart[key].usageCount += 1;
       byPart[key].totalQuantity += p.quantity;
       byPart[key].totalRevenue += p.total;
+      byPart[key].totalCost += p.unitCost * p.quantity;
       if (p.partNumber && !byPart[key].partNumber) byPart[key].partNumber = p.partNumber;
     }
 
@@ -274,13 +289,20 @@ export async function getPartsUsageReport(params: {
         usageCount: data.usageCount,
         totalQuantity: data.totalQuantity,
         totalRevenue: data.totalRevenue,
+        totalCost: data.totalCost,
+        netProfit: data.totalRevenue - data.totalCost,
       }))
       .sort((a, b) => b.usageCount - a.usageCount)
       .slice(0, 20);
 
+    const totalPartsRevenue = parts.reduce((s, p) => s + p.total, 0);
+    const totalPartsCost = parts.reduce((s, p) => s + (p.unitCost * p.quantity), 0);
+
     return {
       parts: result,
-      totalPartsRevenue: parts.reduce((s, p) => s + p.total, 0),
+      totalPartsRevenue,
+      totalPartsCost,
+      totalPartsNetProfit: totalPartsRevenue - totalPartsCost,
       totalPartsUsed: parts.reduce((s, p) => s + p.quantity, 0),
     };
   }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.REPORTS }] });

+ 54 - 6
src/features/reports/Components/ReportPDF.tsx

@@ -222,6 +222,37 @@ function TableBlock({
   );
 }
 
+function ProfitBreakdown({
+  s,
+  title,
+  lines,
+  total,
+}: {
+  s: ReportStyles;
+  title: string;
+  lines: { label: string; value: string; color: string }[];
+  total: { label: string; value: string };
+}) {
+  return (
+    <View style={{ marginBottom: 14, backgroundColor: grayLight, borderRadius: 4, padding: 12 }}>
+      <Text style={{ fontSize: 9, fontFamily: "Roboto-Bold", marginBottom: 8 }}>{title}</Text>
+      {lines.map((line, i) => (
+        <View key={i} style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 5 }}>
+          <View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
+            <View style={{ width: 6, height: 6, borderRadius: 3, backgroundColor: line.color }} />
+            <Text style={{ fontSize: 8, color: gray }}>{line.label}</Text>
+          </View>
+          <Text style={{ fontSize: 8, fontFamily: "Roboto-Bold" }}>{line.value}</Text>
+        </View>
+      ))}
+      <View style={{ borderTopWidth: 0.5, borderTopColor: border, marginTop: 4, paddingTop: 6, flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
+        <Text style={{ fontSize: 9, fontFamily: "Roboto-Bold" }}>{total.label}</Text>
+        <Text style={{ fontSize: 11, fontFamily: "Roboto-Bold" }}>{total.value}</Text>
+      </View>
+    </View>
+  );
+}
+
 function PageFooter({ s }: { s: ReportStyles }) {
   return (
     <Text
@@ -272,12 +303,19 @@ export function ReportPDF({
               { label: l.revenue || "Revenue", value: fmt(revenueData.summary.totalRevenue) },
               { label: l.collected || "Collected", value: fmt(revenueData.summary.totalCollected) },
               { label: l.outstanding || "Outstanding", value: fmt(revenueData.summary.outstanding) },
-              { label: l.services || "Services", value: String(revenueData.summary.totalCount) },
-              { label: l.partsCost || "Parts Cost", value: fmt(revenueData.summary.totalPartsCost) },
-              { label: l.netProfit || "Net Profit", value: fmt(revenueData.summary.netProfit) },
             ]}
           />
 
+          <ProfitBreakdown
+            s={s}
+            title={l.netProfit || "Net Profit"}
+            lines={[
+              { label: l.partsNetProfit || "Parts Net Profit", value: fmt(revenueData.summary.totalPartsNetProfit), color: "#22c55e" },
+              { label: l.laborRevenue || "Labor", value: fmt(revenueData.summary.totalLaborRevenue), color: "#3b82f6" },
+            ]}
+            total={{ label: l.netProfit || "Net Profit", value: fmt(revenueData.summary.netProfit) }}
+          />
+
           <View style={s.section}>
             <Text style={s.sectionTitle}>{l.monthlyBreakdown || "Monthly Breakdown"}</Text>
             <TableBlock
@@ -287,15 +325,19 @@ export function ReportPDF({
                 l.revenue || "Revenue",
                 l.collected || "Collected",
                 l.partsCost || "Parts Cost",
+                l.partsNetProfit || "Parts Profit",
+                l.laborRevenue || "Labor",
                 l.netProfit || "Net Profit",
                 l.count || "Jobs",
               ]}
-              widths={[22, 18, 18, 16, 16, 10]}
+              widths={[16, 14, 14, 12, 12, 12, 12, 8]}
               rows={revenueData.monthly.map((m) => [
                 m.month,
                 fmt(m.revenue),
                 fmt(m.collected),
                 fmt(m.partsCost),
+                fmt(m.partsNetProfit),
+                fmt(m.laborRevenue),
                 fmt(m.netProfit),
                 String(m.count),
               ])}
@@ -576,8 +618,10 @@ export function ReportPDF({
           <SummaryCards
             s={s}
             items={[
-              { label: l.totalPartsRevenue || "Parts Revenue", value: fmt(partsData.totalPartsRevenue) },
               { label: l.totalPartsUsed || "Parts Used", value: String(partsData.totalPartsUsed) },
+              { label: l.totalPartsRevenue || "Parts Revenue", value: fmt(partsData.totalPartsRevenue) },
+              { label: l.totalPartsCost || "Parts Cost", value: fmt(partsData.totalPartsCost) },
+              { label: l.totalPartsNetProfit || "Net Profit", value: fmt(partsData.totalPartsNetProfit) },
             ]}
           />
 
@@ -590,14 +634,18 @@ export function ReportPDF({
                 l.usageCount || "Used",
                 l.totalQty || "Qty",
                 l.totalRevenue || "Revenue",
+                l.totalPartsCost || "Cost",
+                l.netProfit || "Profit",
               ]}
-              widths={[30, 20, 15, 15, 20]}
+              widths={[22, 16, 12, 12, 14, 12, 12]}
               rows={partsData.parts.map((p) => [
                 p.name,
                 p.partNumber || "-",
                 String(p.usageCount),
                 String(p.totalQuantity),
                 fmt(p.totalRevenue),
+                fmt(p.totalCost),
+                fmt(p.netProfit),
               ])}
             />
           </View>

+ 8 - 4
src/features/reports/Components/csv-export.ts

@@ -42,7 +42,7 @@ function downloadCsv(
 export function exportRevenueCsv(
   data: RevenueReport,
   currencyCode: string,
-  headers: [string, string, string, string, string, string] = ["Month", "Revenue", "Collected", "Count", "Parts Cost", "Net Profit"],
+  headers: [string, string, string, string, string, string, string, string] = ["Month", "Revenue", "Collected", "Count", "Parts Cost", "Parts Net Profit", "Labor", "Net Profit"],
 ) {
   const fmt = (n: number) => formatCurrency(n, currencyCode);
   const rows = data.monthly.map((m) => ({
@@ -51,9 +51,11 @@ export function exportRevenueCsv(
     collected: fmt(m.collected),
     count: m.count,
     partsCost: fmt(m.partsCost),
+    partsNetProfit: fmt(m.partsNetProfit),
+    laborRevenue: fmt(m.laborRevenue),
     netProfit: fmt(m.netProfit),
   }));
-  downloadCsv("revenue-report.csv", headers, rows, ["month", "revenue", "collected", "count", "partsCost", "netProfit"]);
+  downloadCsv("revenue-report.csv", headers, rows, ["month", "revenue", "collected", "count", "partsCost", "partsNetProfit", "laborRevenue", "netProfit"]);
 }
 
 export function exportServicesCsv(
@@ -118,7 +120,7 @@ export function exportTechniciansCsv(
 export function exportPartsCsv(
   data: PartsUsageReport,
   currencyCode: string,
-  headers: [string, string, string, string, string] = ["Part Name", "Part #", "Usage Count", "Total Qty", "Total Revenue"],
+  headers: [string, string, string, string, string, string, string] = ["Part Name", "Part #", "Usage Count", "Total Qty", "Revenue", "Cost", "Net Profit"],
 ) {
   const fmt = (n: number) => formatCurrency(n, currencyCode);
   const rows = data.parts.map((p) => ({
@@ -127,8 +129,10 @@ export function exportPartsCsv(
     usageCount: p.usageCount,
     totalQuantity: p.totalQuantity,
     totalRevenue: fmt(p.totalRevenue),
+    totalCost: fmt(p.totalCost),
+    netProfit: fmt(p.netProfit),
   }));
-  downloadCsv("parts-usage-report.csv", headers, rows, ["name", "partNumber", "usageCount", "totalQuantity", "totalRevenue"]);
+  downloadCsv("parts-usage-report.csv", headers, rows, ["name", "partNumber", "usageCount", "totalQuantity", "totalRevenue", "totalCost", "netProfit"]);
 }
 
 export function exportJobAnalyticsCsv(

+ 10 - 0
src/features/reports/Schema/reportTypes.ts

@@ -4,6 +4,9 @@ export interface MonthlyRevenue {
   collected: number;
   count: number;
   partsCost: number;
+  partsRevenue: number;
+  partsNetProfit: number;
+  laborRevenue: number;
   netProfit: number;
 }
 
@@ -19,6 +22,9 @@ export interface RevenueSummary {
   outstanding: number;
   totalCount: number;
   totalPartsCost: number;
+  totalPartsRevenue: number;
+  totalPartsNetProfit: number;
+  totalLaborRevenue: number;
   netProfit: number;
 }
 
@@ -99,11 +105,15 @@ export interface PartUsage {
   usageCount: number;
   totalQuantity: number;
   totalRevenue: number;
+  totalCost: number;
+  netProfit: number;
 }
 
 export interface PartsUsageReport {
   parts: PartUsage[];
   totalPartsRevenue: number;
+  totalPartsCost: number;
+  totalPartsNetProfit: number;
   totalPartsUsed: number;
 }
 

+ 19 - 11
src/lib/cron/report-schedules.ts

@@ -104,35 +104,40 @@ async function fetchRevenue(orgId: string, start: Date, end: Date) {
     select: {
       serviceDate: true, startDateTime: true, totalAmount: true, cost: true, type: true,
       manuallyPaid: true, payments: { select: { amount: true } },
-      partItems: { select: { unitCost: true, quantity: true } },
+      partItems: { select: { unitCost: true, quantity: true, total: true } },
+      laborItems: { select: { total: true } },
     },
     orderBy: [{ startDateTime: { sort: "asc", nulls: "last" } }, { serviceDate: "asc" }],
   });
 
-  const monthly: Record<string, { revenue: number; collected: number; count: number; partsCost: number }> = {};
+  const monthly: Record<string, { revenue: number; collected: number; count: number; partsCost: number; partsRevenue: number; laborRevenue: number }> = {};
   const byType: Record<string, { revenue: number; count: number }> = {};
-  let totalRevenue = 0, totalCollected = 0, totalCount = 0, totalPartsCost = 0;
+  let totalRevenue = 0, totalCollected = 0, totalCount = 0, totalPartsCost = 0, totalPartsRevenue = 0, totalLaborRevenue = 0;
 
   for (const r of records) {
     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 partsRevenue = r.partItems.reduce((s, p) => s + p.total, 0);
+    const laborRevenue = r.laborItems.reduce((s, l) => s + l.total, 0);
     const _d = r.startDateTime ?? r.serviceDate;
     const month = `${_d.getFullYear()}-${String(_d.getMonth() + 1).padStart(2, "0")}`;
 
-    if (!monthly[month]) monthly[month] = { revenue: 0, collected: 0, count: 0, partsCost: 0 };
+    if (!monthly[month]) monthly[month] = { revenue: 0, collected: 0, count: 0, partsCost: 0, partsRevenue: 0, laborRevenue: 0 };
     monthly[month].revenue += total; monthly[month].collected += paid;
     monthly[month].count += 1; monthly[month].partsCost += partsCost;
+    monthly[month].partsRevenue += partsRevenue; monthly[month].laborRevenue += laborRevenue;
     totalRevenue += total; totalCollected += paid; totalCount += 1; totalPartsCost += partsCost;
+    totalPartsRevenue += partsRevenue; totalLaborRevenue += laborRevenue;
 
     if (!byType[r.type]) byType[r.type] = { revenue: 0, count: 0 };
     byType[r.type].revenue += total; byType[r.type].count += 1;
   }
 
   return {
-    monthly: Object.entries(monthly).map(([month, d]) => ({ month, ...d, netProfit: d.revenue - d.partsCost })),
+    monthly: Object.entries(monthly).map(([month, d]) => ({ month, ...d, partsNetProfit: d.partsRevenue - d.partsCost, netProfit: (d.partsRevenue - d.partsCost) + d.laborRevenue })),
     byType: Object.entries(byType).map(([type, d]) => ({ type, ...d })),
-    summary: { totalRevenue, totalCollected, outstanding: totalRevenue - totalCollected, totalCount, totalPartsCost, netProfit: totalRevenue - totalPartsCost },
+    summary: { totalRevenue, totalCollected, outstanding: totalRevenue - totalCollected, totalCount, totalPartsCost, totalPartsRevenue, totalPartsNetProfit: totalPartsRevenue - totalPartsCost, totalLaborRevenue, netProfit: (totalPartsRevenue - totalPartsCost) + totalLaborRevenue },
   };
 }
 
@@ -192,17 +197,20 @@ async function fetchTechnicians(orgId: string, start: Date, end: Date) {
 async function fetchParts(orgId: string, start: Date, end: Date) {
   const parts = await db.servicePart.findMany({
     where: { serviceRecord: { vehicle: { organizationId: orgId }, startDateTime: { gte: start, lte: end } } },
-    select: { name: true, partNumber: true, quantity: true, total: true },
+    select: { name: true, partNumber: true, quantity: true, total: true, unitCost: true },
   });
-  const byPart: Record<string, { name: string; partNumber: string | null; usageCount: number; totalQuantity: number; totalRevenue: number }> = {};
+  const byPart: Record<string, { name: string; partNumber: string | null; usageCount: number; totalQuantity: number; totalRevenue: number; totalCost: number }> = {};
   for (const p of parts) {
     const key = p.name.toLowerCase();
-    if (!byPart[key]) byPart[key] = { name: p.name, partNumber: p.partNumber, usageCount: 0, totalQuantity: 0, totalRevenue: 0 };
+    if (!byPart[key]) byPart[key] = { name: p.name, partNumber: p.partNumber, usageCount: 0, totalQuantity: 0, totalRevenue: 0, totalCost: 0 };
     byPart[key].usageCount += 1; byPart[key].totalQuantity += p.quantity; byPart[key].totalRevenue += p.total;
+    byPart[key].totalCost += p.unitCost * p.quantity;
     if (p.partNumber && !byPart[key].partNumber) byPart[key].partNumber = p.partNumber;
   }
-  const result = Object.values(byPart).sort((a, b) => b.usageCount - a.usageCount).slice(0, 20);
-  return { parts: result, totalPartsRevenue: parts.reduce((s, p) => s + p.total, 0), totalPartsUsed: parts.reduce((s, p) => s + p.quantity, 0) };
+  const result = Object.values(byPart).map((d) => ({ ...d, netProfit: d.totalRevenue - d.totalCost })).sort((a, b) => b.usageCount - a.usageCount).slice(0, 20);
+  const totalPartsRevenue = parts.reduce((s, p) => s + p.total, 0);
+  const totalPartsCost = parts.reduce((s, p) => s + (p.unitCost * p.quantity), 0);
+  return { parts: result, totalPartsRevenue, totalPartsCost, totalPartsNetProfit: totalPartsRevenue - totalPartsCost, totalPartsUsed: parts.reduce((s, p) => s + p.quantity, 0) };
 }
 
 async function fetchJobAnalytics(orgId: string, start: Date, end: Date) {