@swan-io/shared-business 13.4.9 → 13.4.11

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swan-io/shared-business",
3
- "version": "13.4.9",
3
+ "version": "13.4.11",
4
4
  "engines": {
5
5
  "node": ">22.12.0"
6
6
  },
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "license": "MIT",
27
27
  "peerDependencies": {
28
- "@swan-io/lake": "13.4.9"
28
+ "@swan-io/lake": "13.4.11"
29
29
  },
30
30
  "dependencies": {
31
31
  "@formatjs/intl": "^3.1.6",
@@ -50,6 +50,6 @@
50
50
  "@types/react-dom": "^19.1.7",
51
51
  "@types/react-native": "^0.72.8",
52
52
  "type-fest": "^4.41.0",
53
- "@swan-io/lake": "13.4.9"
53
+ "@swan-io/lake": "13.4.11"
54
54
  }
55
55
  }
@@ -5,7 +5,6 @@ import { colors, spacings } from "@swan-io/lake/src/constants/design";
5
5
  import { isNotNullish } from "@swan-io/lake/src/utils/nullish";
6
6
  import { StyleSheet, Text } from "react-native";
7
7
  import { match } from "ts-pattern";
8
- import { getCountryName } from "../constants/countries";
9
8
  import { t } from "../utils/i18n";
10
9
  import { getTextStyle } from "../utils/style";
11
10
  const styles = StyleSheet.create({
@@ -14,43 +13,43 @@ const styles = StyleSheet.create({
14
13
  padding: 10,
15
14
  },
16
15
  firstSectionTitle: {
17
- ...getTextStyle("sans", 14),
16
+ ...getTextStyle("sans", 12),
18
17
  color: colors.swan[500],
19
- fontWeight: "600",
18
+ fontWeight: "500",
20
19
  textTransform: "uppercase",
21
20
  paddingBottom: spacings[4],
22
21
  marginTop: "30%",
23
22
  },
24
23
  sectionTitle: {
25
- ...getTextStyle("sans", 14),
24
+ ...getTextStyle("sans", 12),
26
25
  color: colors.swan[500],
27
- fontWeight: "600",
26
+ fontWeight: "500",
28
27
  textTransform: "uppercase",
29
28
  paddingBottom: spacings[4],
30
29
  },
31
30
  dateTitle: {
32
- ...getTextStyle("sans", 14),
31
+ ...getTextStyle("sans", 10),
33
32
  color: colors.swan[500],
34
33
  paddingBottom: spacings[4],
35
34
  },
36
35
  totalAmount: {
37
- ...getTextStyle("sans", 18),
38
- fontWeight: "600",
36
+ ...getTextStyle("sans", 16),
37
+ fontWeight: "500",
39
38
  textAlign: "right",
40
39
  },
41
40
  pageTitle: {
42
- ...getTextStyle("sans", 18),
43
- fontWeight: "600",
41
+ ...getTextStyle("sans", 16),
42
+ fontWeight: "500",
44
43
  color: "#26232F",
45
44
  },
46
45
  titleColumn: {
47
- ...getTextStyle("sans", 12),
48
- fontWeight: "600",
46
+ ...getTextStyle("sans", 10),
47
+ fontWeight: "500",
49
48
  paddingVertical: spacings[4],
50
49
  minWidth: spacings[96],
51
50
  },
52
51
  openingBalanceText: {
53
- ...getTextStyle("sans", 13),
52
+ ...getTextStyle("sans", 10),
54
53
  textAlign: "right",
55
54
  },
56
55
  textColumn: {
@@ -58,26 +57,26 @@ const styles = StyleSheet.create({
58
57
  ...getTextStyle("sans", 10),
59
58
  },
60
59
  text: {
61
- ...getTextStyle("sans", 12),
60
+ ...getTextStyle("sans", 10),
62
61
  },
63
62
  pageSubTitle: {
64
- ...getTextStyle("sans", 12),
63
+ ...getTextStyle("sans", 10),
65
64
  color: "#454348",
66
65
  paddingBottom: spacings[12],
67
66
  },
68
67
  row: {
69
68
  textAlign: "right",
70
69
  paddingVertical: spacings[4],
71
- fontWeight: "600",
70
+ fontWeight: "500",
72
71
  width: "15%",
73
72
  },
74
73
  closingBalanceRow: {
75
- ...getTextStyle("sans", 12),
74
+ ...getTextStyle("sans", 10),
76
75
  backgroundColor: colors.gray[50],
77
76
  width: "50%",
78
77
  },
79
78
  closingBalanceRowText: {
80
- paddingLeft: "10%"
79
+ paddingLeft: "20%"
81
80
  },
82
81
  });
83
82
  const Title = ({ text, align = "left", style, }) => (_jsx(Text, { style: [
@@ -99,7 +98,7 @@ const translateTransaction = (transaction) => {
99
98
  export const AccountStatementV1 = ({ style, accountHolderName, accountHolderAddress, accountHolderType, iban, bic, openingDate, closingDate, openingBalance, transactions, feesDebit, feesCredit, totalsCredit, totalsDebit, closingBalance, }) => {
100
99
  return (_jsx(Box, { style: style, children: _jsx(Box, { style: styles.container, direction: "column", justifyContent: "spaceBetween", children: _jsxs(Box, { children: [_jsx(Box, { direction: "row", justifyContent: "spaceBetween", children: _jsx(Text, { style: styles.pageTitle, children: t("accountStatement.titleDocument") }) }), _jsxs(Box, { direction: "row", justifyContent: "spaceBetween", children: [_jsxs(Box, { direction: "column", children: [_jsx(Text, { style: styles.pageSubTitle, children: accountHolderType === "Company"
101
100
  ? t("accountStatement.titleDocument.companyDescription")
102
- : t("accountStatement.titleDocument.individualDescription") }), _jsx(Text, { style: styles.sectionTitle, children: accountHolderName.toUpperCase() }), _jsx(Text, { style: styles.text, children: accountHolderAddress.street }), _jsx(Text, { style: styles.text, children: accountHolderAddress.city }), isNotNullish(accountHolderAddress.country) && (_jsx(Text, { style: styles.text, children: getCountryName(accountHolderAddress.country) }))] }), _jsxs(Box, { direction: "column", alignItems: "end", children: [_jsx(Text, { style: styles.firstSectionTitle, children: t("accountStatement.contactSupport") }), _jsx(Text, { style: styles.text, children: "support.swan.io" })] })] }), _jsx(Space, { height: 24 }), _jsx(Text, { style: styles.sectionTitle, children: t("accountStatement.iban") }), _jsx(Text, { style: styles.text, children: iban }), _jsx(Space, { height: 12 }), _jsx(Text, { style: styles.sectionTitle, children: t("accountStatement.bic") }), _jsx(Text, { style: styles.text, children: bic }), _jsx(Space, { height: 48 }), _jsxs(Box, { direction: "row", justifyContent: "spaceBetween", children: [_jsx(Box, { direction: "column", children: _jsx(Text, { style: styles.dateTitle, children: t("accountStatement.date", { openingDate, closingDate }) }) }), _jsxs(Box, { direction: "column", children: [_jsx(Text, { style: styles.openingBalanceText, children: t("accountStatement.openingBalance") }), _jsx(Text, { style: styles.totalAmount, children: openingBalance.value })] })] }), _jsx(Space, { height: 24 }), _jsxs(_Fragment, { children: [_jsxs(Box, { direction: "row", style: { backgroundColor: colors.gray[50] }, children: [_jsx(Text, { style: [styles.titleColumn, { width: "15%" }], children: t("accountStatement.column.date") }), _jsx(Text, { style: [styles.titleColumn, { width: "22%" }], children: t("accountStatement.column.type") }), _jsx(Text, { style: [styles.titleColumn, { width: "33%" }], children: t("accountStatement.column.description") }), _jsx(Text, { style: [styles.titleColumn, { width: "15%", textAlign: "right" }], children: t("accountStatement.column.credit") }), _jsx(Text, { style: [styles.titleColumn, { width: "15%", textAlign: "right" }], children: t("accountStatement.column.debit") })] }), _jsx(Box, { direction: "column", children: transactions.map(transaction => (_jsxs(Box, { direction: "row", children: [_jsx(Text, { style: [styles.textColumn, { width: "15%" }], children: transaction.date }), _jsx(Text, { style: [styles.textColumn, { width: "22%" }], children: translateTransaction(transaction.type) }), _jsx(Text, { style: [styles.textColumn, { width: "33%" }], children: transaction.label }), _jsx(Text, { style: [styles.textColumn, { width: "15%", textAlign: "right" }], children: transaction.credit ? transaction.credit.value : "" }), _jsx(Text, { style: [styles.textColumn, { width: "15%", textAlign: "right" }], children: transaction.debit ? transaction.debit.value : "" })] }, transaction.id))) })] }), _jsx(Space, { height: 20 }), _jsxs(Box, { direction: "column", children: [_jsxs(Box, { direction: "row", justifyContent: "end", children: [_jsx(Text, { style: styles.row, children: t("accountStatement.column.fees") }), _jsx(Text, { style: styles.row, children: feesCredit.value }), _jsx(Text, { style: styles.row, children: feesDebit.value })] }), _jsxs(Box, { direction: "row", justifyContent: "end", children: [_jsx(Text, { style: styles.row, children: t("accountStatement.column.total") }), _jsx(Text, { style: styles.row, children: totalsCredit.value }), _jsx(Text, { style: styles.row, children: totalsDebit.value })] })] }), _jsx(Box, { direction: "row", justifyContent: "end", children: _jsxs(Box, { direction: "row", alignItems: "center", style: styles.closingBalanceRow, justifyContent: "spaceBetween", children: [_jsx(Title, { style: styles.closingBalanceRowText, text: t("accountStatement.closingBalance") }), _jsx(Title, { text: closingBalance.value, style: styles.totalAmount, align: "right" })] }) })] }) }) }));
101
+ : t("accountStatement.titleDocument.individualDescription") }), _jsx(Text, { style: styles.sectionTitle, children: accountHolderName.toUpperCase() }), _jsx(Text, { style: styles.text, children: accountHolderAddress.street }), _jsxs(Text, { style: styles.text, children: [accountHolderAddress.city, ", ", isNotNullish(accountHolderAddress.country) && (accountHolderAddress.country)] })] }), _jsxs(Box, { direction: "column", alignItems: "end", children: [_jsx(Text, { style: styles.firstSectionTitle, children: t("accountStatement.contactSupport") }), _jsx(Text, { style: styles.text, children: "support.swan.io" })] })] }), _jsx(Space, { height: 24 }), isNotNullish(iban) && (_jsxs(_Fragment, { children: [_jsx(Text, { style: styles.sectionTitle, children: t("accountStatement.iban") }), _jsx(Text, { style: styles.text, children: iban })] })), _jsx(Space, { height: 12 }), isNotNullish(bic) && (_jsxs(_Fragment, { children: [_jsx(Text, { style: styles.sectionTitle, children: t("accountStatement.bic") }), _jsx(Text, { style: styles.text, children: bic })] })), _jsx(Space, { height: 48 }), _jsxs(Box, { direction: "row", justifyContent: "spaceBetween", children: [_jsx(Box, { direction: "column", children: _jsx(Text, { style: styles.dateTitle, children: t("accountStatement.date", { openingDate, closingDate }) }) }), _jsxs(Box, { direction: "column", children: [_jsx(Text, { style: styles.openingBalanceText, children: t("accountStatement.openingBalance") }), _jsx(Text, { style: styles.totalAmount, children: openingBalance.value })] })] }), _jsx(Space, { height: 24 }), _jsxs(_Fragment, { children: [_jsxs(Box, { direction: "row", style: { backgroundColor: colors.gray[50] }, children: [_jsx(Text, { style: [styles.titleColumn, { width: "15%" }], children: t("accountStatement.column.date") }), _jsx(Text, { style: [styles.titleColumn, { width: "22%" }], children: t("accountStatement.column.type") }), _jsx(Text, { style: [styles.titleColumn, { width: "33%" }], children: t("accountStatement.column.description") }), _jsx(Text, { style: [styles.titleColumn, { width: "15%", textAlign: "right" }], children: t("accountStatement.column.credit") }), _jsx(Text, { style: [styles.titleColumn, { width: "15%", textAlign: "right" }], children: t("accountStatement.column.debit") })] }), _jsx(Box, { direction: "column", children: transactions.map(transaction => (_jsxs(Box, { direction: "row", children: [_jsx(Text, { style: [styles.textColumn, { width: "15%" }], children: transaction.date }), _jsx(Text, { style: [styles.textColumn, { width: "22%" }], children: translateTransaction(transaction.type) }), _jsx(Text, { style: [styles.textColumn, { width: "33%" }], children: transaction.label }), _jsx(Text, { style: [styles.textColumn, { width: "15%", textAlign: "right" }], children: transaction.credit ? transaction.credit.value : "" }), _jsx(Text, { style: [styles.textColumn, { width: "15%", textAlign: "right" }], children: transaction.debit ? transaction.debit.value : "" })] }, transaction.id))) })] }), _jsx(Space, { height: 20 }), _jsxs(Box, { direction: "column", children: [_jsxs(Box, { direction: "row", justifyContent: "end", children: [_jsx(Text, { style: styles.row, children: t("accountStatement.column.fees") }), _jsx(Text, { style: styles.row, children: feesCredit.value }), _jsx(Text, { style: styles.row, children: feesDebit.value })] }), _jsxs(Box, { direction: "row", justifyContent: "end", children: [_jsx(Text, { style: styles.row, children: t("accountStatement.column.total") }), _jsx(Text, { style: styles.row, children: totalsCredit.value }), _jsx(Text, { style: styles.row, children: totalsDebit.value })] })] }), _jsx(Box, { direction: "row", justifyContent: "end", children: _jsxs(Box, { direction: "row", alignItems: "center", style: styles.closingBalanceRow, justifyContent: "spaceBetween", children: [_jsx(Title, { style: styles.closingBalanceRowText, text: t("accountStatement.closingBalance") }), _jsx(Title, { text: closingBalance.value, style: styles.totalAmount, align: "right" })] }) })] }) }) }));
103
102
  };
104
103
  export const AccountStatement = (props) => match(props)
105
104
  .with({ version: "v1" }, props => _jsx(AccountStatementV1, { ...props }))
@@ -11,6 +11,7 @@ const styles = {
11
11
  color: "#26232F",
12
12
  lineHeight: "12.5px",
13
13
  fontWeight: "300",
14
+ marginLeft: "17px",
14
15
  },
15
16
  wrapper: {
16
17
  display: "flex",
@@ -3,13 +3,13 @@ import { SwanLogo } from "@swan-io/lake/src/components/SwanLogo";
3
3
  import { colors } from "@swan-io/lake/src/constants/design";
4
4
  import { isNotNullishOrEmpty } from "@swan-io/lake/src/utils/nullish";
5
5
  import { useTranslation } from "../utils/i18n";
6
- const LOGO_MAX_HEIGHT = 24;
7
- const LOGO_MAX_WIDTH = 150;
6
+ const LOGO_MAX_HEIGHT = 45;
7
+ const LOGO_MAX_WIDTH = 275;
8
8
  // Used to generate inline styles without css variables for PDF generation
9
9
  const styles = {
10
10
  container: {
11
11
  width: 793,
12
- padding: "10px",
12
+ padding: 10,
13
13
  fontFamily: "Inter, -apple-system, system-ui, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif",
14
14
  boxSizing: "border-box",
15
15
  color: "#737276",
@@ -22,13 +22,12 @@ const styles = {
22
22
  },
23
23
  partnership: {
24
24
  borderLeft: "1px solid #e8e7e8",
25
- padding: "7px 8px",
25
+ padding: "7px 10px",
26
26
  marginLeft: "8px",
27
27
  },
28
28
  partnershipText: {
29
- fontSize: "12px",
30
- lineHeight: "24px",
31
- marginBottom: "4px",
29
+ fontSize: "13px",
30
+ lineHeight: "24px"
32
31
  },
33
32
  logo: {
34
33
  display: "flex",
@@ -45,9 +44,9 @@ const styles = {
45
44
  top: 0.5,
46
45
  },
47
46
  pageNumber: {
48
- fontSize: "18px",
47
+ fontSize: "13px",
49
48
  color: "#26232F",
50
- fontWeight: "600",
49
+ fontWeight: "500",
51
50
  textTransform: "uppercase",
52
51
  },
53
52
  };
@@ -59,5 +58,5 @@ const logoStyle = {
59
58
  };
60
59
  export const AccountStatementHeader = ({ partnerLogoUrl, language, }) => {
61
60
  const t = useTranslation(language);
62
- return (_jsxs("div", { style: styles.container, children: [_jsxs("div", { style: styles.logo, children: [isNotNullishOrEmpty(partnerLogoUrl) ? (_jsx("img", { src: partnerLogoUrl, style: logoStyle })) : (_jsx(SwanLogo, { style: styles.defaultLogo })), _jsxs("div", { style: styles.partnership, children: [_jsx("div", { style: styles.partnershipText, children: t("accountStatement.partnership") }), _jsx(SwanLogo, { color: colors.gray[900], style: styles.swanLogo })] })] }), _jsxs("div", { style: styles.pageNumber, children: [t("accountStatement.page"), " ", _jsx("span", { className: "pageNumber" }), " /", " ", _jsx("span", { className: "totalPages" })] })] }));
61
+ return (_jsxs("div", { style: styles.container, children: [_jsxs("div", { style: styles.logo, children: [isNotNullishOrEmpty(partnerLogoUrl) ? (_jsx("img", { src: partnerLogoUrl, style: logoStyle })) : (_jsx(SwanLogo, { style: styles.defaultLogo })), _jsxs("div", { style: styles.partnership, children: [_jsx("div", { style: styles.partnershipText, children: t("accountStatement.partnership") }), _jsx(SwanLogo, { color: colors.gray[900], style: styles.swanLogo })] })] }), _jsxs("div", { style: styles.pageNumber, children: [t("accountStatement.page"), " ", _jsx("span", { className: "pageNumber" }), "/", _jsx("span", { className: "totalPages" })] })] }));
63
62
  };
@@ -105,6 +105,9 @@
105
105
  "error.network.500": "Interner Serverfehler",
106
106
  "error.network.503": "Dienst nicht verfügbar",
107
107
  "error.requiredField": "Das ist ein Pflichtfeld",
108
+ "error.invalidVatNumber": "Ungültige USt.-ID",
109
+ "error.invalidEmail": "Ungültige E-Mail-Adresse",
110
+ "error.invalidName": "Ungültiger Name",
108
111
  "fileInput.browse": "Durchsuchen",
109
112
  "fileInput.clickToModify": "Zum Ändern hier klicken",
110
113
  "fileInput.dropFile": "Legen Sie Ihre Datei ab, oder {browse}",
@@ -105,6 +105,9 @@
105
105
  "error.network.500": "Internal server error",
106
106
  "error.network.503": "Service unavailable",
107
107
  "error.requiredField": "This field is required",
108
+ "error.invalidVatNumber": "Invalid VAT number",
109
+ "error.invalidEmail": "Invalid email",
110
+ "error.invalidName": "Invalid name",
108
111
  "fileInput.browse": "browse",
109
112
  "fileInput.clickToModify": "Click to modify",
110
113
  "fileInput.dropFile": "Drop your file, or {browse}",
@@ -105,6 +105,9 @@
105
105
  "error.network.500": "Error de servidor interno",
106
106
  "error.network.503": "Servicio no disponible",
107
107
  "error.requiredField": "Este campo es obligatorio",
108
+ "error.invalidVatNumber": "Este número de IVA no es válido",
109
+ "error.invalidEmail": "Este correo electrónico no es válido",
110
+ "error.invalidName": "Invalid name",
108
111
  "fileInput.browse": "usa el explorador",
109
112
  "fileInput.clickToModify": "Haz clic para modificar",
110
113
  "fileInput.dropFile": "Suelta el archivo o {browse}",
@@ -105,6 +105,9 @@
105
105
  "error.network.500": "Sisäinen palvelinvirhe",
106
106
  "error.network.503": "Palvelua ei saatavilla",
107
107
  "error.requiredField": "Vaadittu kenttä",
108
+ "error.invalidVatNumber": "Virheellinen ALV-numero",
109
+ "error.invalidEmail": "Virheellinen sähköpostiosoite ",
110
+ "error.invalidName": "Nimi on väärässä muodossa",
108
111
  "fileInput.browse": "selaa",
109
112
  "fileInput.clickToModify": "Napsauta muokkkaaksesi",
110
113
  "fileInput.dropFile": "Pudota tiedostosi tähän tai {browse}",
@@ -105,6 +105,9 @@
105
105
  "error.network.500": "Erreur de serveur interne",
106
106
  "error.network.503": "Service indisponible",
107
107
  "error.requiredField": "Ce champ est requis",
108
+ "error.invalidVatNumber": "Numéro de TVA non valide",
109
+ "error.invalidEmail": "E-mail non valide",
110
+ "error.invalidName": "Nom non valide",
108
111
  "fileInput.browse": "parcourir",
109
112
  "fileInput.clickToModify": "Cliquez pour modifier",
110
113
  "fileInput.dropFile": "Déposez votre fichier, ou {browse}",
@@ -105,6 +105,9 @@
105
105
  "error.network.500": "Errore interno del server",
106
106
  "error.network.503": "Servizio non disponibile",
107
107
  "error.requiredField": "Questo campo è obbligatorio",
108
+ "error.invalidVatNumber": "Numero di partita IVA non valido",
109
+ "error.invalidEmail": "L'indirizzo e-mail non è valido",
110
+ "error.invalidName": "Nome non valido",
108
111
  "fileInput.browse": "sfoglia",
109
112
  "fileInput.clickToModify": "Clicchi per modificare",
110
113
  "fileInput.dropFile": "Carica i file, o {browse}",
@@ -105,6 +105,9 @@
105
105
  "error.network.500": "Interne Server Fout",
106
106
  "error.network.503": "Dienst niet beschikbaar",
107
107
  "error.requiredField": "Dit veld is verplicht",
108
+ "error.invalidVatNumber": "Dit BTW-nummer is ongeldig",
109
+ "error.invalidEmail": "Dit e-mailadres is ongeldig",
110
+ "error.invalidName": "Ongeldige naam",
108
111
  "fileInput.browse": "browsen",
109
112
  "fileInput.clickToModify": "Klik om te bewerken",
110
113
  "fileInput.dropFile": "Sleep uw bestand hiernaartoe, of {browse}",
@@ -105,6 +105,9 @@
105
105
  "error.network.500": "Erro interno do servidor",
106
106
  "error.network.503": "Serviço indisponível",
107
107
  "error.requiredField": "Este campo é obrigatório",
108
+ "error.invalidVatNumber": "Número de IVA inválido",
109
+ "error.invalidEmail": "Este e-mail é inválido",
110
+ "error.invalidName": "Nome inválido",
108
111
  "fileInput.browse": "navegar",
109
112
  "fileInput.clickToModify": "Clique para modificar",
110
113
  "fileInput.dropFile": "Arraste o seu ficheiro, ou {browse}",
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "vitest";
2
- import { getCompanyTaxNumberHelp, getCompanyTaxNumberPlaceholder, getIndividualTaxNumberHelp, getIndividualTaxNumberPlaceholder, getTaxNumberTooltip, isValidEmail, sanitizeDecimal, validateBooleanRequired, validateCompanyTaxNumber, validateIndividualTaxNumber, validateNullableRequired, validateRequired, } from "../validation";
2
+ import { getCompanyTaxNumberHelp, getCompanyTaxNumberPlaceholder, getIndividualTaxNumberHelp, getIndividualTaxNumberPlaceholder, getTaxNumberTooltip, isValidEmail, sanitizeDecimal, validateArrayRequired, validateBooleanRequired, validateBooleanTypeRequired, validateCompanyTaxNumber, validateDate, validateIndividualTaxNumber, validateName, validateNullableRequired, validateRequired, } from "../validation";
3
3
  describe("isValidEmail", () => {
4
4
  test("validates correct email addresses", () => {
5
5
  expect(isValidEmail("test@example.com")).toBe(true);
@@ -42,6 +42,56 @@ describe("validateBooleanRequired", () => {
42
42
  expect(validateBooleanRequired(true)).toBeUndefined();
43
43
  });
44
44
  });
45
+ describe("validateBooleanTypeRequired", () => {
46
+ test("returns error for undefined", () => {
47
+ expect(validateBooleanTypeRequired(undefined)).toBeDefined();
48
+ });
49
+ test("returns undefined for true or false", () => {
50
+ expect(validateBooleanTypeRequired(true)).toBeUndefined();
51
+ expect(validateBooleanTypeRequired(false)).toBeUndefined();
52
+ });
53
+ });
54
+ describe("validateArrayRequired", () => {
55
+ test("returns error for undefined", () => {
56
+ expect(validateArrayRequired(undefined)).toBeDefined();
57
+ });
58
+ test("returns error for empty arrays", () => {
59
+ expect(validateArrayRequired([])).toBeDefined();
60
+ });
61
+ test("returns undefined for arrays with at least one element", () => {
62
+ expect(validateArrayRequired(["value"])).toBeUndefined();
63
+ expect(validateArrayRequired(["value1", "value2"])).toBeUndefined();
64
+ });
65
+ });
66
+ describe("validateName", () => {
67
+ test("returns error for empty or falsy values", () => {
68
+ expect(validateName("")).toBeDefined();
69
+ });
70
+ test("returns error for names longer than 100 characters", () => {
71
+ const longName = "A".repeat(101);
72
+ expect(validateName(longName)).toBeDefined();
73
+ });
74
+ test("accepts names with exactly 100 characters", () => {
75
+ const name100 = "A".repeat(100);
76
+ expect(validateName(name100)).toBeUndefined();
77
+ });
78
+ test("accepts valid names with basic ASCII letters", () => {
79
+ expect(validateName("José")).toBeUndefined();
80
+ expect(validateName("Mary Jane")).toBeUndefined();
81
+ expect(validateName("O'Brien")).toBeUndefined();
82
+ expect(validateName("Jean-Pierre")).toBeUndefined();
83
+ expect(validateName("Smith, Jr.")).toBeUndefined();
84
+ });
85
+ test("rejects names with invalid characters", () => {
86
+ expect(validateName("John123")).toBeDefined(); // contains numbers
87
+ expect(validateName("John@Doe")).toBeDefined(); // contains @
88
+ expect(validateName("John#Doe")).toBeDefined(); // contains #
89
+ expect(validateName("John$Doe")).toBeDefined(); // contains $
90
+ expect(validateName("John_Doe")).toBeDefined(); // contains underscore
91
+ expect(validateName("John!Doe")).toBeDefined(); // contains !
92
+ expect(validateName("John?Doe")).toBeDefined(); // contains ?
93
+ });
94
+ });
45
95
  describe("validateIndividualTaxNumber", () => {
46
96
  describe("BEL (Belgium)", () => {
47
97
  const validator = validateIndividualTaxNumber("BEL");
@@ -289,6 +339,22 @@ describe("Tax number helper functions", () => {
289
339
  });
290
340
  });
291
341
  });
342
+ describe("validateDate", () => {
343
+ test("returns error for undefined or null", () => {
344
+ expect(validateDate(undefined)).toBeDefined();
345
+ });
346
+ test("returns undefined for valid ExtractedDate", () => {
347
+ expect(validateDate({ day: "5", month: "03", year: "2024" })).toBeUndefined();
348
+ });
349
+ test("handles padded and unpadded values", () => {
350
+ expect(validateDate({ day: "05", month: " 3", year: "2024" })).toBeUndefined();
351
+ });
352
+ test("returns error for invalid ExtractedDate", () => {
353
+ expect(validateDate({ day: "32", month: "01", year: "2024" })).toBeDefined(); // Invalid day
354
+ expect(validateDate({ day: "15", month: "13", year: "2024" })).toBeDefined(); // Invalid month
355
+ expect(validateDate({ day: "31", month: "04", year: "2024" })).toBeDefined(); // April has 30 days
356
+ });
357
+ });
292
358
  describe("sanitizeDecimal", () => {
293
359
  test("removes spaces from decimal input", () => {
294
360
  expect(sanitizeDecimal("1 000")).toBe("1000");
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,201 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { isValidVatNumber } from "../validation";
3
+ describe("isValidVatNumber", () => {
4
+ describe("AT (Austria)", () => {
5
+ test("accepts valid VAT numbers", () => {
6
+ expect(isValidVatNumber("U12345678")).toBe(true);
7
+ expect(isValidVatNumber("ATU12345678")).toBe(true);
8
+ });
9
+ test("rejects invalid VAT numbers", () => {
10
+ expect(isValidVatNumber("AT12345678")).toBe(false); // U missing
11
+ });
12
+ });
13
+ describe("BE (Belgium)", () => {
14
+ test("accepts valid VAT numbers", () => {
15
+ expect(isValidVatNumber("0123456789")).toBe(true);
16
+ expect(isValidVatNumber("1123456789")).toBe(true);
17
+ expect(isValidVatNumber("BE0123456789")).toBe(true);
18
+ });
19
+ test("rejects invalid VAT numbers", () => {
20
+ expect(isValidVatNumber("BE2123456789")).toBe(false); // not starting with 1
21
+ });
22
+ });
23
+ describe("BG (Bulgaria)", () => {
24
+ test("accepts valid VAT numbers", () => {
25
+ expect(isValidVatNumber("123456789")).toBe(true);
26
+ expect(isValidVatNumber("1234567890")).toBe(true);
27
+ expect(isValidVatNumber("BG123456789")).toBe(true);
28
+ });
29
+ });
30
+ describe("CY (Cyprus)", () => {
31
+ test("accepts valid VAT numbers", () => {
32
+ expect(isValidVatNumber("12345678L")).toBe(true);
33
+ expect(isValidVatNumber("CY12345678L")).toBe(true);
34
+ });
35
+ test("rejects invalid VAT numbers", () => {
36
+ expect(isValidVatNumber("CY12345678")).toBe(false); // not finishing with L
37
+ });
38
+ });
39
+ describe("CZ (Czech Republic)", () => {
40
+ test("accepts valid VAT numbers", () => {
41
+ expect(isValidVatNumber("12345678")).toBe(true);
42
+ expect(isValidVatNumber("1234567890")).toBe(true);
43
+ expect(isValidVatNumber("CZ12345678")).toBe(true);
44
+ });
45
+ });
46
+ describe("DE (Germany)", () => {
47
+ test("accepts valid VAT numbers", () => {
48
+ expect(isValidVatNumber("123456789")).toBe(true);
49
+ expect(isValidVatNumber("DE123456789")).toBe(true);
50
+ });
51
+ });
52
+ describe("DK (Denmark)", () => {
53
+ test("accepts valid VAT numbers", () => {
54
+ expect(isValidVatNumber("12345678")).toBe(true);
55
+ expect(isValidVatNumber("DK12345678")).toBe(true);
56
+ });
57
+ });
58
+ describe("EE (Estonia)", () => {
59
+ test("accepts valid VAT numbers", () => {
60
+ expect(isValidVatNumber("123456789")).toBe(true);
61
+ expect(isValidVatNumber("EE123456789")).toBe(true);
62
+ });
63
+ });
64
+ describe("EL/GR (Greece)", () => {
65
+ test("accepts valid VAT numbers", () => {
66
+ expect(isValidVatNumber("123456789")).toBe(true);
67
+ expect(isValidVatNumber("EL123456789")).toBe(true);
68
+ expect(isValidVatNumber("GR123456789")).toBe(true);
69
+ });
70
+ });
71
+ describe("ES (Spain)", () => {
72
+ test("accepts valid VAT numbers", () => {
73
+ expect(isValidVatNumber("A1234567B")).toBe(true);
74
+ expect(isValidVatNumber("12345678A")).toBe(true);
75
+ expect(isValidVatNumber("ES12345678A")).toBe(true);
76
+ });
77
+ });
78
+ describe("FI (Finland)", () => {
79
+ test("accepts valid VAT numbers", () => {
80
+ expect(isValidVatNumber("12345678")).toBe(true);
81
+ expect(isValidVatNumber("FI12345678")).toBe(true);
82
+ });
83
+ });
84
+ describe("FR (France)", () => {
85
+ test("accepts valid VAT numbers", () => {
86
+ expect(isValidVatNumber("AB123456789")).toBe(true);
87
+ expect(isValidVatNumber("12345678901")).toBe(true);
88
+ expect(isValidVatNumber("FRAB123456789")).toBe(true);
89
+ });
90
+ });
91
+ describe("GB (United Kingdom)", () => {
92
+ test("accepts valid VAT numbers", () => {
93
+ expect(isValidVatNumber("123456789")).toBe(true);
94
+ expect(isValidVatNumber("123456789123")).toBe(true);
95
+ expect(isValidVatNumber("AB123")).toBe(true);
96
+ expect(isValidVatNumber("GB123456789")).toBe(true);
97
+ });
98
+ test("rejects invalid VAT numbers", () => {
99
+ expect(isValidVatNumber("GBB123")).toBe(false); // Must be 2 letter after GB
100
+ });
101
+ });
102
+ describe("HU (Hungary)", () => {
103
+ test("accepts valid VAT numbers", () => {
104
+ expect(isValidVatNumber("12345678")).toBe(true);
105
+ expect(isValidVatNumber("HU12345678")).toBe(true);
106
+ });
107
+ });
108
+ describe("IE (Ireland)", () => {
109
+ test("accepts valid VAT numbers", () => {
110
+ expect(isValidVatNumber("1S23456L")).toBe(true);
111
+ expect(isValidVatNumber("IE1S23456L")).toBe(true);
112
+ });
113
+ test("rejects invalid VAT numbers", () => {
114
+ expect(isValidVatNumber("IE1S23456")).toBe(false); // Must have L at the end
115
+ expect(isValidVatNumber("IE123456L")).toBe(false); // Must have S
116
+ });
117
+ });
118
+ describe("IT (Italy)", () => {
119
+ test("accepts valid VAT numbers", () => {
120
+ expect(isValidVatNumber("12345678901")).toBe(true);
121
+ expect(isValidVatNumber("IT12345678901")).toBe(true);
122
+ });
123
+ });
124
+ describe("LT (Lithuania)", () => {
125
+ test("accepts valid VAT numbers", () => {
126
+ expect(isValidVatNumber("123456789")).toBe(true);
127
+ expect(isValidVatNumber("123456789012")).toBe(true);
128
+ expect(isValidVatNumber("LT123456789")).toBe(true);
129
+ });
130
+ });
131
+ describe("LU (Luxembourg)", () => {
132
+ test("accepts valid VAT numbers", () => {
133
+ expect(isValidVatNumber("12345678")).toBe(true);
134
+ expect(isValidVatNumber("LU12345678")).toBe(true);
135
+ });
136
+ });
137
+ describe("LV (Latvia)", () => {
138
+ test("accepts valid VAT numbers", () => {
139
+ expect(isValidVatNumber("12345678901")).toBe(true);
140
+ expect(isValidVatNumber("LV12345678901")).toBe(true);
141
+ });
142
+ });
143
+ describe("MT (Malta)", () => {
144
+ test("accepts valid VAT numbers", () => {
145
+ expect(isValidVatNumber("12345678")).toBe(true);
146
+ expect(isValidVatNumber("MT12345678")).toBe(true);
147
+ });
148
+ });
149
+ describe("NL (Netherlands)", () => {
150
+ test("accepts valid VAT numbers", () => {
151
+ expect(isValidVatNumber("123456789B12")).toBe(true);
152
+ expect(isValidVatNumber("NL123456789B12")).toBe(true);
153
+ });
154
+ test("rejects invalid VAT numbers", () => {
155
+ expect(isValidVatNumber("NL12345678912")).toBe(false); // Must have B
156
+ });
157
+ });
158
+ describe("PL (Poland)", () => {
159
+ test("accepts valid VAT numbers", () => {
160
+ expect(isValidVatNumber("1234567890")).toBe(true);
161
+ expect(isValidVatNumber("PL1234567890")).toBe(true);
162
+ });
163
+ });
164
+ describe("PT (Portugal)", () => {
165
+ test("accepts valid VAT numbers", () => {
166
+ expect(isValidVatNumber("123456789")).toBe(true);
167
+ expect(isValidVatNumber("PT123456789")).toBe(true);
168
+ });
169
+ });
170
+ describe("RO (Romania)", () => {
171
+ test("accepts valid VAT numbers", () => {
172
+ expect(isValidVatNumber("12")).toBe(true);
173
+ expect(isValidVatNumber("1234567890")).toBe(true);
174
+ expect(isValidVatNumber("RO1234567890")).toBe(true);
175
+ });
176
+ });
177
+ describe("SE (Sweden)", () => {
178
+ test("accepts valid VAT numbers", () => {
179
+ expect(isValidVatNumber("123456789012")).toBe(true);
180
+ expect(isValidVatNumber("SE123456789012")).toBe(true);
181
+ });
182
+ });
183
+ describe("SI (Slovenia)", () => {
184
+ test("accepts valid VAT numbers", () => {
185
+ expect(isValidVatNumber("12345678")).toBe(true);
186
+ expect(isValidVatNumber("SI12345678")).toBe(true);
187
+ });
188
+ });
189
+ describe("SK (Slovakia)", () => {
190
+ test("accepts valid VAT numbers", () => {
191
+ expect(isValidVatNumber("1234567890")).toBe(true);
192
+ expect(isValidVatNumber("SK1234567890")).toBe(true);
193
+ });
194
+ });
195
+ describe("Unsuported", () => {
196
+ test("refuse unvalid VAT numbers", () => {
197
+ expect(isValidVatNumber("123450972374924")).toBe(false); // too long
198
+ expect(isValidVatNumber("RU1234567890")).toBe(false); // country not supported
199
+ });
200
+ });
201
+ });
@@ -7,6 +7,11 @@ export declare const sanitizeDecimal: (value: string) => string;
7
7
  export declare const validateNullableRequired: Validator<string | undefined>;
8
8
  export declare const validateRequired: Validator<string>;
9
9
  export declare const validateBooleanRequired: Validator<boolean | undefined>;
10
+ export declare const validateBooleanTypeRequired: Validator<boolean | undefined>;
11
+ export declare const validateArrayRequired: Validator<string[] | undefined>;
12
+ export declare const validateVatNumber: Validator<string>;
13
+ export declare const validateEmail: Validator<string>;
14
+ export declare const validateName: Validator<string>;
10
15
  export declare const validateIndividualTaxNumber: (country: IndividualCountryCCA3) => Validator<string | undefined>;
11
16
  export declare const validateCompanyTaxNumber: (country: CompanyCountryCCA3) => Validator<string | undefined>;
12
17
  export { printFormat as printIbanFormat } from "iban";
@@ -122,11 +122,52 @@ export const validateRequired = value => {
122
122
  return t("error.requiredField");
123
123
  }
124
124
  };
125
+ // False or undefined will return an error message
125
126
  export const validateBooleanRequired = value => {
126
127
  if (value == null || !value) {
127
128
  return t("error.requiredField");
128
129
  }
129
130
  };
131
+ export const validateBooleanTypeRequired = value => {
132
+ if (typeof value !== "boolean") {
133
+ return t("error.requiredField");
134
+ }
135
+ };
136
+ export const validateArrayRequired = value => {
137
+ if (value == null || value.length < 1) {
138
+ return t("error.requiredField");
139
+ }
140
+ };
141
+ export const validateVatNumber = value => {
142
+ const cleaned = value.replace(/[^A-Z0-9]/gi, "");
143
+ if (cleaned.length === 0) {
144
+ return;
145
+ }
146
+ if (!isValidVatNumber(cleaned)) {
147
+ return t("error.invalidVatNumber");
148
+ }
149
+ };
150
+ export const validateEmail = value => {
151
+ if (!isValidEmail(value)) {
152
+ return t("error.invalidEmail");
153
+ }
154
+ };
155
+ // This regex was copied from the backend to ensure that the validation is the same
156
+ // Matches all unicode letters, spaces, dashes, apostrophes, commas, and single quotes
157
+ const VALID_NAME_RE = /^(?:[A-Za-zÀ-ÖÙ-öù-ƿDŽ-ʯʹ-ʽΈ-ΊΎ-ΡΣ-ҁҊ-Ֆա-ևႠ-Ⴥა-ჺᄀ-፜፩-ᎏᵫ-ᶚḀ-῾ⴀ-ⴥ⺀-⿕ぁ-ゖゝ-ㇿ㋿-鿯鿿-ꒌꙀ-ꙮꚀ-ꚙꜦ-ꞇꞍ-ꞿꥠ-ꥼA-Za-z.]| |'|-|Ά|Ό|,)*$/;
158
+ export const validateName = value => {
159
+ if (!value) {
160
+ return t("error.requiredField");
161
+ }
162
+ // Rule copied from the backend
163
+ if (value.length > 100) {
164
+ return t("error.invalidName");
165
+ }
166
+ const isValid = VALID_NAME_RE.test(value);
167
+ if (!isValid) {
168
+ return t("error.invalidName");
169
+ }
170
+ };
130
171
  export const validateIndividualTaxNumber = (country) => value => {
131
172
  if (value == null || !value) {
132
173
  return;