File size: 2,938 Bytes
122c1d2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf4edb5
122c1d2
 
 
bf4edb5
122c1d2
 
bf4edb5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122c1d2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
const money = new Intl.NumberFormat('en-GB', {
  style: 'currency',
  currency: 'EUR',
  maximumFractionDigits: 0,
});

const decimal = new Intl.NumberFormat('en-GB', { maximumFractionDigits: 1 });

export const formatMoney = (value: number): string => money.format(value);
export const formatDecimal = (value: number): string => decimal.format(value);

/** A shipment carries its own currency, so the symbol is not assumed to be €. */
export function formatAmount(value: number, currency: string): string {
  try {
    return new Intl.NumberFormat('en-GB', {
      style: 'currency',
      currency,
      maximumFractionDigits: 2,
    }).format(value);
  } catch {
    return `${formatNumber(value)} ${currency}`;
  }
}

export function formatNumber(value: number, maximumFractionDigits = 2): string {
  return new Intl.NumberFormat('en-GB', { maximumFractionDigits }).format(value);
}

/** The backend already scores out of 100 — `recompute.py` multiplies by 100.0. */
export const formatScore = (score: number): string => `${formatNumber(score, 1)}%`;

/** `2026-09-22` as `22.09.2026`; anything unparseable is returned untouched. */
export function formatDate(value: string | null | undefined): string {
  if (!value) return '—';
  const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(value);
  return match ? `${match[3]}.${match[2]}.${match[1]}` : value;
}

/** `22.09.2026` (or with `/` or `-`) as `2026-09-22`; null unless it is a real day. */
export function parseDate(text: string): string | null {
  const match = /^(\d{1,2})[./-](\d{1,2})[./-](\d{4})$/.exec(text.trim());
  if (!match) return null;
  const [day, month, year] = [Number(match[1]), Number(match[2]), Number(match[3])];
  const date = new Date(year, month - 1, day);
  if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
    return null;
  }
  return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
}

/** The local calendar day — `toISOString` would give the UTC one, which in
 *  Europe is still yesterday for the first hour or two after midnight. */
export function today(): string {
  const now = new Date();
  return [
    now.getFullYear(),
    String(now.getMonth() + 1).padStart(2, '0'),
    String(now.getDate()).padStart(2, '0'),
  ].join('-');
}

/** The same rule as `normalize_header` in `importer/utils.py`, so a column
 *  header matches here exactly when it matches on the server. */
export const normalizeHeader = (value: string): string =>
  value.trim().toLowerCase().replace(/[^a-z0-9]/g, '');

/** `6109100010` as `6109 10 00 10`, the way a declarant reads it. */
export function formatCode(code: string | null | undefined): string {
  if (!code) return '';
  const digits = code.replace(/\D/g, '');
  if (digits.length < 6) return code;
  return [digits.slice(0, 4), digits.slice(4, 6), digits.slice(6, 8), digits.slice(8, 10)]
    .filter(Boolean)
    .join(' ');
}