@flowselections/klanten-installatie-techniek-planner 1.0.21 → 1.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist-lib/components/KlantenDetailPage.d.ts.map +1 -1
  2. package/dist-lib/components/KlantenDetailPage.js +4 -1
  3. package/dist-lib/components/KlantenListPage.d.ts.map +1 -1
  4. package/dist-lib/components/KlantenListPage.js +3 -1
  5. package/dist-lib/components/klanten/KlantDialog.d.ts.map +1 -1
  6. package/dist-lib/components/klanten/KlantDialog.js +6 -1
  7. package/dist-lib/components/klanten/KlantNotitiesCard.d.ts +2 -1
  8. package/dist-lib/components/klanten/KlantNotitiesCard.d.ts.map +1 -1
  9. package/dist-lib/components/klanten/KlantNotitiesCard.js +12 -3
  10. package/dist-lib/components/klanten/KlantRisicoBanner.d.ts +18 -0
  11. package/dist-lib/components/klanten/KlantRisicoBanner.d.ts.map +1 -0
  12. package/dist-lib/components/klanten/KlantRisicoBanner.js +43 -0
  13. package/dist-lib/components/klanten/KlantRisicoStatusCard.d.ts +7 -0
  14. package/dist-lib/components/klanten/KlantRisicoStatusCard.d.ts.map +1 -0
  15. package/dist-lib/components/klanten/KlantRisicoStatusCard.js +65 -0
  16. package/dist-lib/components/klanten/KlantStatsCard.d.ts.map +1 -1
  17. package/dist-lib/components/klanten/KlantStatsCard.js +14 -4
  18. package/dist-lib/components/klanten/KlantenTrendCard.d.ts +2 -0
  19. package/dist-lib/components/klanten/KlantenTrendCard.d.ts.map +1 -0
  20. package/dist-lib/components/klanten/KlantenTrendCard.js +52 -0
  21. package/dist-lib/index.d.ts +1 -0
  22. package/dist-lib/index.d.ts.map +1 -1
  23. package/dist-lib/index.js +2 -1
  24. package/dist-lib/integrations/supabase/client.js +1 -1
  25. package/dist-lib/integrations/supabase/client.server.js +1 -1
  26. package/dist-lib/integrations/supabase/types.d.ts +9209 -261
  27. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/types.js +1 -0
  29. package/dist-lib/lib/itkClient.d.ts +3 -1
  30. package/dist-lib/lib/itkClient.d.ts.map +1 -1
  31. package/dist-lib/lib/klantRules.d.ts +44 -0
  32. package/dist-lib/lib/klantRules.d.ts.map +1 -1
  33. package/dist-lib/lib/klantRules.js +46 -1
  34. package/dist-lib/lib/klantRules.test.js +59 -0
  35. package/dist-lib/lib/klantStatsClient.d.ts +25 -0
  36. package/dist-lib/lib/klantStatsClient.d.ts.map +1 -1
  37. package/dist-lib/lib/klantStatsClient.js +37 -1
  38. package/dist-lib/lib/statusConfig.d.ts +4 -0
  39. package/dist-lib/lib/statusConfig.d.ts.map +1 -1
  40. package/dist-lib/lib/statusConfig.js +7 -0
  41. package/package.json +39 -10
@@ -40,6 +40,7 @@ export const Constants = {
40
40
  "afleverbrief",
41
41
  "veilingbrief",
42
42
  "traysticker",
43
+ "eigen_sticker",
43
44
  ],
44
45
  doc_orientation: ["portrait", "landscape"],
45
46
  exact_env: ["staging", "live"],
@@ -13,10 +13,12 @@ export interface Customer {
13
13
  general_email: string | null;
14
14
  general_phone: string | null;
15
15
  legacy_customer_id: string | null;
16
+ risk_status?: "none" | "risk" | "blocked";
17
+ risk_reason?: string | null;
16
18
  created_at: string;
17
19
  updated_at: string;
18
20
  }
19
- export type CustomerNoteType = "general" | "call" | "complaint" | "visit" | "other";
21
+ export type CustomerNoteType = "general" | "call" | "complaint" | "visit" | "other" | "risk_status";
20
22
  export interface CustomerNote {
21
23
  id: string;
22
24
  customer_id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"itkClient.d.ts","sourceRoot":"","sources":["../../src/lib/itkClient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"itkClient.d.ts","sourceRoot":"","sources":["../../src/lib/itkClient.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,9 @@
1
+ import { type CustomerRiskStatus } from "./statusConfig";
2
+ export declare function normalizeRiskStatus(v: unknown): CustomerRiskStatus;
3
+ /** Alleen geblokkeerde klanten mogen geen nieuwe werkbon krijgen. */
4
+ export declare function canCreateWerkbon(status: unknown): boolean;
5
+ /** Risicoklant/Geblokkeerd vereisen een interne reden. */
6
+ export declare function riskReasonMissing(status: unknown, reason: unknown): boolean;
1
7
  export type Rang = 1 | 2 | 3;
2
8
  export interface TopKlant {
3
9
  customer_id: string;
@@ -24,4 +30,42 @@ export interface KlantBasis {
24
30
  /** Geeft de labels van ontbrekende verplichte velden terug (leeg = ok). */
25
31
  export declare function missingRequiredFields(k: KlantBasis): string[];
26
32
  export declare function chunk<T>(list: T[], size: number): T[][];
33
+ export interface WinstInput {
34
+ omzetCents: number;
35
+ materiaalCents: number | null;
36
+ /** Werk- + reisuren × uurloon van de monteur die ze schreef. */
37
+ arbeidCents: number | null;
38
+ }
39
+ export interface WinstResult {
40
+ materiaalCents: number;
41
+ arbeidCents: number;
42
+ kostenCents: number;
43
+ winstCents: number;
44
+ /** null als er geen omzet is. */
45
+ marge: number | null;
46
+ }
47
+ /**
48
+ * Arbeidskosten per urenregel: (minuten / 60) × uurloon (€) van die monteur.
49
+ * Werk en reis tegen hetzelfde uurloon; regels zonder uurloon tellen niet mee.
50
+ * (Zelfde regel als in de databasefunctie itk_customer_stats.)
51
+ */
52
+ export declare function arbeidKostenCents(regels: {
53
+ minuten: number;
54
+ uurloonEuro: number | null;
55
+ }[]): number;
56
+ /** Winst = betaalde omzet − (materialen + arbeidskosten). null zonder brondata. */
57
+ export declare function berekenWinst(i: WinstInput): WinstResult | null;
58
+ /** Te laat: betaald na vervaldatum, of nog open en vervaldatum voorbij. */
59
+ export declare function isTeLaat(inv: {
60
+ due_date?: string | null;
61
+ paid_at?: string | null;
62
+ }, today: string): boolean;
63
+ /** Trekt een maand-tot-nu-waarde door naar de hele maand. */
64
+ export declare function projectMonth(waarde: number, dagVanMaand: number, dagenInMaand: number): number;
65
+ export interface Trend {
66
+ verschilCents: number;
67
+ /** null als vorig jaar 0 was (geen vergelijking mogelijk). */
68
+ procent: number | null;
69
+ }
70
+ export declare function trend(nu: number, vorigJaar: number): Trend;
27
71
  //# sourceMappingURL=klantRules.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"klantRules.d.ts","sourceRoot":"","sources":["../../src/lib/klantRules.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAE7B,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,EAAE,CAMxE;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,QAAQ,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,GAAG,MAAM,CAK9F;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAWD,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,EAAE,CAE7D;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,EAAE,EAAE,CAIvD"}
1
+ {"version":3,"file":"klantRules.d.ts","sourceRoot":"","sources":["../../src/lib/klantRules.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,15 @@
1
- import { isPaidInvoiceStatus } from "./statusConfig";
1
+ import { CUSTOMER_RISK_STATUSES, isPaidInvoiceStatus } from "./statusConfig";
2
+ export function normalizeRiskStatus(v) {
3
+ return CUSTOMER_RISK_STATUSES.includes(String(v)) ? v : "none";
4
+ }
5
+ /** Alleen geblokkeerde klanten mogen geen nieuwe werkbon krijgen. */
6
+ export function canCreateWerkbon(status) {
7
+ return normalizeRiskStatus(status) !== "blocked";
8
+ }
9
+ /** Risicoklant/Geblokkeerd vereisen een interne reden. */
10
+ export function riskReasonMissing(status, reason) {
11
+ return normalizeRiskStatus(status) !== "none" && !String(reason ?? "").trim();
12
+ }
2
13
  /** Top 3 op omzet: alleen omzet > 0, hoogste eerst, maximaal 3. */
3
14
  export function rankTop3(rows) {
4
15
  return (Array.isArray(rows) ? rows : [])
@@ -29,3 +40,37 @@ export function chunk(list, size) {
29
40
  out.push(list.slice(i, i + size));
30
41
  return out;
31
42
  }
43
+ /**
44
+ * Arbeidskosten per urenregel: (minuten / 60) × uurloon (€) van die monteur.
45
+ * Werk en reis tegen hetzelfde uurloon; regels zonder uurloon tellen niet mee.
46
+ * (Zelfde regel als in de databasefunctie itk_customer_stats.)
47
+ */
48
+ export function arbeidKostenCents(regels) {
49
+ return Math.round((Array.isArray(regels) ? regels : []).reduce((s, r) => (r?.uurloonEuro != null && r.uurloonEuro > 0 ? s + ((Number(r.minuten) || 0) / 60) * r.uurloonEuro * 100 : s), 0));
50
+ }
51
+ /** Winst = betaalde omzet − (materialen + arbeidskosten). null zonder brondata. */
52
+ export function berekenWinst(i) {
53
+ if (i.materiaalCents == null || i.arbeidCents == null)
54
+ return null;
55
+ const materiaalCents = Math.round(i.materiaalCents);
56
+ const arbeidCents = Math.round(i.arbeidCents);
57
+ const kostenCents = materiaalCents + arbeidCents;
58
+ const winstCents = Math.round(i.omzetCents) - kostenCents;
59
+ return { materiaalCents, arbeidCents, kostenCents, winstCents, marge: i.omzetCents > 0 ? winstCents / i.omzetCents : null };
60
+ }
61
+ /** Te laat: betaald na vervaldatum, of nog open en vervaldatum voorbij. */
62
+ export function isTeLaat(inv, today) {
63
+ if (!inv?.due_date)
64
+ return false;
65
+ const due = inv.due_date.slice(0, 10);
66
+ return inv.paid_at ? inv.paid_at.slice(0, 10) > due : today > due;
67
+ }
68
+ /** Trekt een maand-tot-nu-waarde door naar de hele maand. */
69
+ export function projectMonth(waarde, dagVanMaand, dagenInMaand) {
70
+ if (!(dagVanMaand > 0) || !(dagenInMaand > 0))
71
+ return 0;
72
+ return Math.round((waarde / dagVanMaand) * dagenInMaand);
73
+ }
74
+ export function trend(nu, vorigJaar) {
75
+ return { verschilCents: nu - vorigJaar, procent: vorigJaar !== 0 ? (nu - vorigJaar) / Math.abs(vorigJaar) : null };
76
+ }
@@ -48,3 +48,62 @@ describe("klantnummer", () => {
48
48
  it("chunk splitst in delen", () => {
49
49
  expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
50
50
  });
51
+ import { canCreateWerkbon, riskReasonMissing } from "./klantRules";
52
+ describe("klantstatus", () => {
53
+ it("geblokkeerde klant krijgt geen nieuwe werkbon, risicoklant wel", () => {
54
+ expect(canCreateWerkbon("blocked")).toBe(false);
55
+ expect(canCreateWerkbon("risk")).toBe(true);
56
+ expect(canCreateWerkbon("none")).toBe(true);
57
+ });
58
+ it("reden verplicht bij risicoklant en geblokkeerd", () => {
59
+ expect(riskReasonMissing("risk", " ")).toBe(true);
60
+ expect(riskReasonMissing("blocked", "")).toBe(true);
61
+ expect(riskReasonMissing("blocked", "Niet betaald")).toBe(false);
62
+ expect(riskReasonMissing("none", "")).toBe(false);
63
+ });
64
+ });
65
+ import { arbeidKostenCents, berekenWinst, isTeLaat } from "./klantRules";
66
+ describe("winstgevendheid", () => {
67
+ it("omzet min materialen en arbeidskosten", () => {
68
+ const r = berekenWinst({ omzetCents: 100000, materiaalCents: 20000, arbeidCents: 9000 });
69
+ expect(r?.kostenCents).toBe(29000);
70
+ expect(r?.winstCents).toBe(71000);
71
+ expect(r?.marge).toBeCloseTo(0.71);
72
+ });
73
+ it("per monteur eigen uurloon, werk en reis zelfde tarief", () => {
74
+ // monteur A € 35: 2 uur werk + 30 min reis; monteur B € 40: 1 uur werk
75
+ expect(arbeidKostenCents([
76
+ { minuten: 120, uurloonEuro: 35 },
77
+ { minuten: 30, uurloonEuro: 35 },
78
+ { minuten: 60, uurloonEuro: 40 },
79
+ ])).toBe(7000 + 1750 + 4000);
80
+ });
81
+ it("uren van monteur zonder uurloon tellen niet mee", () => {
82
+ expect(arbeidKostenCents([{ minuten: 60, uurloonEuro: 35 }, { minuten: 60, uurloonEuro: null }])).toBe(3500);
83
+ });
84
+ it("geen uitkomst zonder arbeidsdata", () => {
85
+ expect(berekenWinst({ omzetCents: 1, materiaalCents: 0, arbeidCents: null })).toBeNull();
86
+ });
87
+ });
88
+ describe("te laat betaald", () => {
89
+ it("betaald na vervaldatum telt als te laat", () => {
90
+ expect(isTeLaat({ due_date: "2026-10-01", paid_at: "2026-10-02T09:00:00Z" }, "2026-10-29")).toBe(true);
91
+ expect(isTeLaat({ due_date: "2026-10-01", paid_at: "2026-10-01T09:00:00Z" }, "2026-10-29")).toBe(false);
92
+ });
93
+ it("open en vervallen telt als te laat", () => {
94
+ expect(isTeLaat({ due_date: "2026-10-01", paid_at: null }, "2026-10-29")).toBe(true);
95
+ expect(isTeLaat({ due_date: "2026-11-01", paid_at: null }, "2026-10-29")).toBe(false);
96
+ });
97
+ });
98
+ import { projectMonth, trend } from "./klantRules";
99
+ describe("maandtrend", () => {
100
+ it("doortrekken naar hele maand", () => {
101
+ expect(projectMonth(600000, 10, 31)).toBe(1860000);
102
+ });
103
+ it("stijging in procent en euro t.o.v. vorig jaar", () => {
104
+ expect(trend(1840000, 1520000)).toEqual({ verschilCents: 320000, procent: (1840000 - 1520000) / 1520000 });
105
+ });
106
+ it("geen percentage als vorig jaar 0 is", () => {
107
+ expect(trend(5000, 0).procent).toBeNull();
108
+ });
109
+ });
@@ -5,6 +5,16 @@ export interface KlantStats {
5
5
  open: number;
6
6
  bezig: number;
7
7
  afgerond: number;
8
+ /** null = bron niet beschikbaar in deze omgeving. */
9
+ teLaat: number | null;
10
+ teLaatOpen: number | null;
11
+ gemBetaaltermijnDagen: number | null;
12
+ materiaalKostenCents: number | null;
13
+ werkMinuten: number | null;
14
+ reisMinuten: number | null;
15
+ /** Werk+reis × uurloon per monteur; null = urenregistratie niet beschikbaar. */
16
+ arbeidKostenCents: number | null;
17
+ monteursZonderLoon: string[];
8
18
  }
9
19
  export declare const emptyStats: () => KlantStats;
10
20
  /**
@@ -16,4 +26,19 @@ export declare function fetchKlantStats(customerIds: string[]): Promise<Record<s
16
26
  /** Top-klanten op betaalde omzet over álle klanten. */
17
27
  export declare function fetchTopKlanten(limit?: number): Promise<TopKlant[]>;
18
28
  export declare const formatEuro: (cents: number) => string;
29
+ export interface MonthPeriod {
30
+ van: string;
31
+ tot: string;
32
+ omzetCents: number;
33
+ materiaalKostenCents: number | null;
34
+ werkMinuten: number | null;
35
+ reisMinuten: number | null;
36
+ arbeidKostenCents: number | null;
37
+ monteursZonderLoon: string[];
38
+ }
39
+ /** Deze maand tot vandaag en dezelfde volledige maand een jaar eerder (alle klanten). */
40
+ export declare function fetchMonthTrend(today: string): Promise<{
41
+ nu: MonthPeriod;
42
+ vorigJaar: MonthPeriod;
43
+ }>;
19
44
  //# sourceMappingURL=klantStatsClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"klantStatsClient.d.ts","sourceRoot":"","sources":["../../src/lib/klantStatsClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAeD,eAAO,MAAM,UAAU,QAAO,UAA6E,CAAC;AAE5G;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAmBhG;AAED,uDAAuD;AACvD,wBAAsB,eAAe,CAAC,KAAK,SAAI,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAQpE;AAED,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,WACoD,CAAC"}
1
+ {"version":3,"file":"klantStatsClient.d.ts","sourceRoot":"","sources":["../../src/lib/klantStatsClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gFAAgF;IAChF,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAyBD,eAAO,MAAM,UAAU,QAAO,UAI5B,CAAC;AAEH;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CA2BhG;AAED,uDAAuD;AACvD,wBAAsB,eAAe,CAAC,KAAK,SAAI,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAQpE;AAED,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,WACoD,CAAC;AAE7F,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,yFAAyF;AACzF,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,WAAW,CAAA;CAAE,CAAC,CAmBzG"}
@@ -1,7 +1,12 @@
1
1
  import { supabase } from "@flowselections/core";
2
2
  import { chunk } from "./klantRules";
3
+ const num = (v) => (v == null || v === "" || !Number.isFinite(Number(v)) ? null : Number(v));
3
4
  const rpc = supabase;
4
- export const emptyStats = () => ({ omzetCents: 0, klussen: 0, open: 0, bezig: 0, afgerond: 0 });
5
+ export const emptyStats = () => ({
6
+ omzetCents: 0, klussen: 0, open: 0, bezig: 0, afgerond: 0,
7
+ teLaat: null, teLaatOpen: null, gemBetaaltermijnDagen: null, materiaalKostenCents: null, werkMinuten: null, reisMinuten: null,
8
+ arbeidKostenCents: null, monteursZonderLoon: [],
9
+ });
5
10
  /**
6
11
  * Omzet (alleen betaalde facturen) en klussen per klant, berekend in de
7
12
  * database. Gooit een fout als het ophalen mislukt, zodat de UI geen
@@ -25,6 +30,14 @@ export async function fetchKlantStats(customerIds) {
25
30
  open: Number(r.open) || 0,
26
31
  bezig: Number(r.bezig) || 0,
27
32
  afgerond: Number(r.afgerond) || 0,
33
+ teLaat: num(r.te_laat),
34
+ teLaatOpen: num(r.te_laat_open),
35
+ gemBetaaltermijnDagen: num(r.gem_betaaltermijn_dagen),
36
+ materiaalKostenCents: num(r.materiaal_kosten_cents),
37
+ werkMinuten: num(r.werk_minuten),
38
+ reisMinuten: num(r.reis_minuten),
39
+ arbeidKostenCents: num(r.arbeid_kosten_cents),
40
+ monteursZonderLoon: Array.isArray(r.monteurs_zonder_loon) ? r.monteurs_zonder_loon : [],
28
41
  };
29
42
  }
30
43
  }
@@ -42,3 +55,26 @@ export async function fetchTopKlanten(limit = 3) {
42
55
  }));
43
56
  }
44
57
  export const formatEuro = (cents) => new Intl.NumberFormat("nl-NL", { style: "currency", currency: "EUR" }).format(cents / 100);
58
+ /** Deze maand tot vandaag en dezelfde volledige maand een jaar eerder (alle klanten). */
59
+ export async function fetchMonthTrend(today) {
60
+ const { data, error } = await rpc.rpc("itk_month_trend", { _today: today });
61
+ if (error)
62
+ throw new Error(error.message);
63
+ const rows = Array.isArray(data) ? data : [];
64
+ const map = (k) => {
65
+ const r = rows.find((x) => x?.periode === k);
66
+ if (!r)
67
+ throw new Error("Geen maanddata");
68
+ return {
69
+ van: String(r.van),
70
+ tot: String(r.tot),
71
+ omzetCents: num(r.omzet_cents) ?? 0,
72
+ materiaalKostenCents: num(r.materiaal_kosten_cents),
73
+ werkMinuten: num(r.werk_minuten),
74
+ reisMinuten: num(r.reis_minuten),
75
+ arbeidKostenCents: num(r.arbeid_kosten_cents),
76
+ monteursZonderLoon: Array.isArray(r.monteurs_zonder_loon) ? r.monteurs_zonder_loon : [],
77
+ };
78
+ };
79
+ return { nu: map("nu"), vorigJaar: map("vorig_jaar") };
80
+ }
@@ -1,6 +1,10 @@
1
1
  export declare const PAID_INVOICE_STATUSES: readonly ["betaald", "paid"];
2
2
  export declare const WERKBON_OPEN_STATUSES: readonly ["unassigned", "planned_hidden", "planned_visible"];
3
3
  export declare const WERKBON_DONE_STATUSES: readonly ["completed", "invoicing"];
4
+ /** Klantstatus (itk_customers.risk_status). Moet gelijk blijven aan de database-check. */
5
+ export declare const CUSTOMER_RISK_STATUSES: readonly ["none", "risk", "blocked"];
6
+ export type CustomerRiskStatus = (typeof CUSTOMER_RISK_STATUSES)[number];
7
+ export declare const CUSTOMER_RISK_LABELS: Record<CustomerRiskStatus, string>;
4
8
  /** Klantnummerformaat (toegekend door database-trigger). */
5
9
  export declare const CUSTOMER_NUMBER_PATTERN: RegExp;
6
10
  export declare const isPaidInvoiceStatus: (status: unknown) => boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"statusConfig.d.ts","sourceRoot":"","sources":["../../src/lib/statusConfig.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,qBAAqB,8BAA+B,CAAC;AAClE,eAAO,MAAM,qBAAqB,8DAA+D,CAAC;AAClG,eAAO,MAAM,qBAAqB,qCAAsC,CAAC;AAEzE,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,QAAoB,CAAC;AAEzD,eAAO,MAAM,mBAAmB,GAAI,QAAQ,OAAO,YACwC,CAAC"}
1
+ {"version":3,"file":"statusConfig.d.ts","sourceRoot":"","sources":["../../src/lib/statusConfig.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,qBAAqB,8BAA+B,CAAC;AAClE,eAAO,MAAM,qBAAqB,8DAA+D,CAAC;AAClG,eAAO,MAAM,qBAAqB,qCAAsC,CAAC;AAEzE,0FAA0F;AAC1F,eAAO,MAAM,sBAAsB,sCAAuC,CAAC;AAC3E,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AACzE,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAInE,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,QAAoB,CAAC;AAEzD,eAAO,MAAM,mBAAmB,GAAI,QAAQ,OAAO,YACwC,CAAC"}
@@ -3,6 +3,13 @@
3
3
  export const PAID_INVOICE_STATUSES = ["betaald", "paid"];
4
4
  export const WERKBON_OPEN_STATUSES = ["unassigned", "planned_hidden", "planned_visible"];
5
5
  export const WERKBON_DONE_STATUSES = ["completed", "invoicing"];
6
+ /** Klantstatus (itk_customers.risk_status). Moet gelijk blijven aan de database-check. */
7
+ export const CUSTOMER_RISK_STATUSES = ["none", "risk", "blocked"];
8
+ export const CUSTOMER_RISK_LABELS = {
9
+ none: "Normaal",
10
+ risk: "Risicoklant",
11
+ blocked: "Geblokkeerd",
12
+ };
6
13
  /** Klantnummerformaat (toegekend door database-trigger). */
7
14
  export const CUSTOMER_NUMBER_PATTERN = /^KL\.1\.\d{5,}$/;
8
15
  export const isPaidInvoiceStatus = (status) => PAID_INVOICE_STATUSES.includes(String(status ?? "").toLowerCase());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/klanten-installatie-techniek-planner",
3
- "version": "1.0.21",
3
+ "version": "1.0.29",
4
4
  "flowselections": {
5
5
  "moduleId": "installatietechniek-klanten-planner",
6
6
  "pages": [
@@ -30,24 +30,33 @@
30
30
  "itk_dynamic_fields",
31
31
  "itk_customer_dynamic_values",
32
32
  "itk_customer_notes",
33
- "planning_items"
33
+ "planning_items",
34
+ "itk_cost_settings"
34
35
  ],
35
36
  "leestTabellen": [
36
37
  "werkbonnen",
38
+ "monteur_contracten",
37
39
  "invoices",
38
40
  "planning_item_assistants",
39
- "profiles"
41
+ "profiles",
42
+ "werkbon_materials",
43
+ "werkbon_package_lines",
44
+ "werkbon_package_items",
45
+ "werkbon_time_logs"
40
46
  ],
41
47
  "schrijftTabellen": [
42
48
  "itk_customers",
43
49
  "itk_contact_persons",
44
50
  "itk_work_locations",
45
51
  "itk_customer_dynamic_values",
46
- "itk_customer_notes"
52
+ "itk_customer_notes",
53
+ "itk_cost_settings"
47
54
  ],
48
55
  "biedtFuncties": [
49
56
  "itk_customer_stats",
50
- "itk_top_customers_by_revenue"
57
+ "itk_top_customers_by_revenue",
58
+ "itk_set_customer_risk",
59
+ "itk_month_trend"
51
60
  ],
52
61
  "gebruiktFuncties": [],
53
62
  "eigenaarschap": {
@@ -57,7 +66,8 @@
57
66
  "itk_customer_notes": "Deze module is eigenaar en beheert structuur en migraties (besluit beheerder). Andere modules lezen/schrijven er zelfstandig direct in en noteren dat bij hun gelezen/geschreven tabellen; er is geen doorgeeffunctie.",
58
67
  "itk_dynamic_fields": "Deze module is eigenaar en beheert structuur en migraties (besluit beheerder). Andere modules lezen/schrijven er zelfstandig direct in en noteren dat bij hun gelezen/geschreven tabellen; er is geen doorgeeffunctie.",
59
68
  "itk_work_locations": "Deze module is eigenaar en beheert structuur en migraties (besluit beheerder). Andere modules lezen/schrijven er zelfstandig direct in en noteren dat bij hun gelezen/geschreven tabellen; er is geen doorgeeffunctie.",
60
- "planning_items": "Deze module is eigenaar van planning_items (besluit beheerder). Andere modules beschrijven deze tabel niet meer als eigen bezit. Elke module schrijft zelf direct met een eigen insert of update; er is geen doorgeeffunctie."
69
+ "planning_items": "Deze module is eigenaar van planning_items (besluit beheerder). Andere modules beschrijven deze tabel niet meer als eigen bezit. Elke module schrijft zelf direct met een eigen insert of update; er is geen doorgeeffunctie.",
70
+ "invoices.paid_at": "Deze module voegt kolom paid_at toe aan invoices (eigendom Facturen-module), met trigger itk_invoice_set_paid_at die de datum zet bij overgang naar status betaald/paid. De Facturen-module hoeft niets te wijzigen."
61
71
  },
62
72
  "schrijfAfspraak": {
63
73
  "itk_customers": {
@@ -70,12 +80,21 @@
70
80
  "billing_postcode",
71
81
  "general_email",
72
82
  "general_phone",
73
- "legacy_customer_id"
83
+ "legacy_customer_id",
84
+ "risk_status",
85
+ "risk_reason",
86
+ "risk_set_at",
87
+ "risk_set_by"
74
88
  ],
75
89
  "statuswaarden": {
76
90
  "segment": [
77
91
  "Business",
78
92
  "Private"
93
+ ],
94
+ "risk_status": [
95
+ "none",
96
+ "risk",
97
+ "blocked"
79
98
  ]
80
99
  },
81
100
  "beheer": "customer_number wordt automatisch toegekend via database-trigger (formaat KL.1.#####) en wordt door geen enkele module geschreven."
@@ -111,14 +130,24 @@
111
130
  "general_phone",
112
131
  "legacy_customer_id",
113
132
  "created_at",
114
- "updated_at"
133
+ "updated_at",
134
+ "risk_status",
135
+ "risk_reason",
136
+ "risk_set_at",
137
+ "risk_set_by"
115
138
  ],
116
139
  "statuswaarden": {
117
140
  "segment": [
118
141
  "Business",
119
142
  "Private"
143
+ ],
144
+ "risk_status": [
145
+ "none",
146
+ "risk",
147
+ "blocked"
120
148
  ]
121
- }
149
+ },
150
+ "klantstatus": "risk = Risicoklant (toon waarschuwing met risk_reason bij aanvraag en werkbon), blocked = Geblokkeerd (database-trigger weigert INSERT op werkbonnen met deze customer_id). Status wijzigen via itk_set_customer_risk zodat er een notitie (note_type risk_status) bij komt."
122
151
  },
123
152
  "planning_items": {
124
153
  "afspraak": "Lezers lezen planning_items rechtstreeks met een eigen query. De eigenaar wijzigt onderstaande kolommen en statuswaarden niet zonder aankondiging.",
@@ -195,7 +224,7 @@
195
224
  "devDependencies": {
196
225
  "@cloudflare/vite-plugin": "^1.25.5",
197
226
  "@eslint/js": "^9.32.0",
198
- "@flowselections/core": "1.0.29",
227
+ "@flowselections/core": "1.0.30",
199
228
  "@lovable.dev/vite-tanstack-config": "2.26.5",
200
229
  "@rsbuild/core": "^1.0.0",
201
230
  "@tailwindcss/vite": "^4.2.1",