@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 +3 -3
- package/src/components/AccountStatement.js +18 -19
- package/src/components/AccountStatementFooter.js +1 -0
- package/src/components/AccountStatementHeader.js +9 -10
- package/src/locales/de.json +3 -0
- package/src/locales/en.json +3 -0
- package/src/locales/es.json +3 -0
- package/src/locales/fi.json +3 -0
- package/src/locales/fr.json +3 -0
- package/src/locales/it.json +3 -0
- package/src/locales/nl.json +3 -0
- package/src/locales/pt.json +3 -0
- package/src/utils/__tests__/validation.test.js +67 -1
- package/src/utils/__tests__/vat.test.d.ts +1 -0
- package/src/utils/__tests__/vat.test.js +201 -0
- package/src/utils/validation.d.ts +5 -0
- package/src/utils/validation.js +41 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swan-io/shared-business",
|
|
3
|
-
"version": "13.4.
|
|
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.
|
|
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.
|
|
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",
|
|
16
|
+
...getTextStyle("sans", 12),
|
|
18
17
|
color: colors.swan[500],
|
|
19
|
-
fontWeight: "
|
|
18
|
+
fontWeight: "500",
|
|
20
19
|
textTransform: "uppercase",
|
|
21
20
|
paddingBottom: spacings[4],
|
|
22
21
|
marginTop: "30%",
|
|
23
22
|
},
|
|
24
23
|
sectionTitle: {
|
|
25
|
-
...getTextStyle("sans",
|
|
24
|
+
...getTextStyle("sans", 12),
|
|
26
25
|
color: colors.swan[500],
|
|
27
|
-
fontWeight: "
|
|
26
|
+
fontWeight: "500",
|
|
28
27
|
textTransform: "uppercase",
|
|
29
28
|
paddingBottom: spacings[4],
|
|
30
29
|
},
|
|
31
30
|
dateTitle: {
|
|
32
|
-
...getTextStyle("sans",
|
|
31
|
+
...getTextStyle("sans", 10),
|
|
33
32
|
color: colors.swan[500],
|
|
34
33
|
paddingBottom: spacings[4],
|
|
35
34
|
},
|
|
36
35
|
totalAmount: {
|
|
37
|
-
...getTextStyle("sans",
|
|
38
|
-
fontWeight: "
|
|
36
|
+
...getTextStyle("sans", 16),
|
|
37
|
+
fontWeight: "500",
|
|
39
38
|
textAlign: "right",
|
|
40
39
|
},
|
|
41
40
|
pageTitle: {
|
|
42
|
-
...getTextStyle("sans",
|
|
43
|
-
fontWeight: "
|
|
41
|
+
...getTextStyle("sans", 16),
|
|
42
|
+
fontWeight: "500",
|
|
44
43
|
color: "#26232F",
|
|
45
44
|
},
|
|
46
45
|
titleColumn: {
|
|
47
|
-
...getTextStyle("sans",
|
|
48
|
-
fontWeight: "
|
|
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",
|
|
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",
|
|
60
|
+
...getTextStyle("sans", 10),
|
|
62
61
|
},
|
|
63
62
|
pageSubTitle: {
|
|
64
|
-
...getTextStyle("sans",
|
|
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: "
|
|
70
|
+
fontWeight: "500",
|
|
72
71
|
width: "15%",
|
|
73
72
|
},
|
|
74
73
|
closingBalanceRow: {
|
|
75
|
-
...getTextStyle("sans",
|
|
74
|
+
...getTextStyle("sans", 10),
|
|
76
75
|
backgroundColor: colors.gray[50],
|
|
77
76
|
width: "50%",
|
|
78
77
|
},
|
|
79
78
|
closingBalanceRowText: {
|
|
80
|
-
paddingLeft: "
|
|
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 }),
|
|
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 }))
|
|
@@ -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 =
|
|
7
|
-
const LOGO_MAX_WIDTH =
|
|
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:
|
|
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
|
|
25
|
+
padding: "7px 10px",
|
|
26
26
|
marginLeft: "8px",
|
|
27
27
|
},
|
|
28
28
|
partnershipText: {
|
|
29
|
-
fontSize: "
|
|
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: "
|
|
47
|
+
fontSize: "13px",
|
|
49
48
|
color: "#26232F",
|
|
50
|
-
fontWeight: "
|
|
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" }), "
|
|
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
|
};
|
package/src/locales/de.json
CHANGED
|
@@ -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}",
|
package/src/locales/en.json
CHANGED
|
@@ -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}",
|
package/src/locales/es.json
CHANGED
|
@@ -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}",
|
package/src/locales/fi.json
CHANGED
|
@@ -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}",
|
package/src/locales/fr.json
CHANGED
|
@@ -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}",
|
package/src/locales/it.json
CHANGED
|
@@ -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}",
|
package/src/locales/nl.json
CHANGED
|
@@ -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}",
|
package/src/locales/pt.json
CHANGED
|
@@ -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";
|
package/src/utils/validation.js
CHANGED
|
@@ -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;
|