@swan-io/shared-business 13.4.10 → 13.4.12
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 +12 -13
- package/src/components/AccountStatementFooter.js +1 -1
- package/src/components/AccountStatementHeader.js +9 -8
- 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 +4 -1
- 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.12",
|
|
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.12"
|
|
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.12"
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -2,10 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { Box } from "@swan-io/lake/src/components/Box";
|
|
3
3
|
import { Space } from "@swan-io/lake/src/components/Space";
|
|
4
4
|
import { colors, spacings } from "@swan-io/lake/src/constants/design";
|
|
5
|
-
import { isNotNullish } from "@swan-io/lake/src/utils/nullish";
|
|
5
|
+
import { isNotEmpty, 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({
|
|
@@ -16,7 +15,7 @@ const styles = StyleSheet.create({
|
|
|
16
15
|
firstSectionTitle: {
|
|
17
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%",
|
|
@@ -24,38 +23,38 @@ const styles = StyleSheet.create({
|
|
|
24
23
|
sectionTitle: {
|
|
25
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
36
|
...getTextStyle("sans", 16),
|
|
38
|
-
fontWeight: "
|
|
37
|
+
fontWeight: "500",
|
|
39
38
|
textAlign: "right",
|
|
40
39
|
},
|
|
41
40
|
pageTitle: {
|
|
42
41
|
...getTextStyle("sans", 16),
|
|
43
|
-
fontWeight: "
|
|
42
|
+
fontWeight: "500",
|
|
44
43
|
color: "#26232F",
|
|
45
44
|
},
|
|
46
45
|
titleColumn: {
|
|
47
46
|
...getTextStyle("sans", 10),
|
|
48
|
-
fontWeight: "
|
|
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: {
|
|
57
56
|
paddingVertical: spacings[4],
|
|
58
|
-
...getTextStyle("sans",
|
|
57
|
+
...getTextStyle("sans", 10),
|
|
59
58
|
},
|
|
60
59
|
text: {
|
|
61
60
|
...getTextStyle("sans", 10),
|
|
@@ -68,7 +67,7 @@ const styles = StyleSheet.create({
|
|
|
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: {
|
|
@@ -77,7 +76,7 @@ const styles = StyleSheet.create({
|
|
|
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 }), isNotEmpty(iban) && (_jsxs(_Fragment, { children: [_jsx(Text, { style: styles.sectionTitle, children: t("accountStatement.iban") }), _jsx(Text, { style: styles.text, children: iban })] })), _jsx(Space, { height: 12 }), isNotEmpty(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,16 +22,17 @@ 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: "
|
|
29
|
+
fontSize: "13px",
|
|
30
30
|
lineHeight: "24px"
|
|
31
31
|
},
|
|
32
32
|
logo: {
|
|
33
33
|
display: "flex",
|
|
34
34
|
alignItems: "center",
|
|
35
|
+
marginLeft: 10,
|
|
35
36
|
},
|
|
36
37
|
defaultLogo: {
|
|
37
38
|
height: LOGO_MAX_HEIGHT,
|
|
@@ -44,9 +45,9 @@ const styles = {
|
|
|
44
45
|
top: 0.5,
|
|
45
46
|
},
|
|
46
47
|
pageNumber: {
|
|
47
|
-
fontSize: "
|
|
48
|
+
fontSize: "14px",
|
|
48
49
|
color: "#26232F",
|
|
49
|
-
fontWeight: "
|
|
50
|
+
fontWeight: "500",
|
|
50
51
|
textTransform: "uppercase",
|
|
51
52
|
},
|
|
52
53
|
};
|
|
@@ -58,5 +59,5 @@ const logoStyle = {
|
|
|
58
59
|
};
|
|
59
60
|
export const AccountStatementHeader = ({ partnerLogoUrl, language, }) => {
|
|
60
61
|
const t = useTranslation(language);
|
|
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" }), "/",
|
|
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" })] })] }));
|
|
62
63
|
};
|
package/src/locales/de.json
CHANGED
|
@@ -102,6 +102,9 @@
|
|
|
102
102
|
"datePicker.year": "Jahr",
|
|
103
103
|
"error.generic": "Ein Fehler ist aufgetreten",
|
|
104
104
|
"error.iban.invalid": "Diese IBAN sieht nicht korrekt aus. Bitte versuchen Sie es erneut.",
|
|
105
|
+
"error.invalidEmail": "Ungültige E-Mail-Adresse",
|
|
106
|
+
"error.invalidName": "Ungültiger Name",
|
|
107
|
+
"error.invalidVatNumber": "Ungültige USt.-ID",
|
|
105
108
|
"error.network.500": "Interner Serverfehler",
|
|
106
109
|
"error.network.503": "Dienst nicht verfügbar",
|
|
107
110
|
"error.requiredField": "Das ist ein Pflichtfeld",
|
package/src/locales/en.json
CHANGED
|
@@ -102,6 +102,9 @@
|
|
|
102
102
|
"datePicker.year": "Year",
|
|
103
103
|
"error.generic": "An error occurred",
|
|
104
104
|
"error.iban.invalid": "This IBAN doesn't look right. Trying entering it again.",
|
|
105
|
+
"error.invalidEmail": "Invalid email",
|
|
106
|
+
"error.invalidName": "Invalid name",
|
|
107
|
+
"error.invalidVatNumber": "Invalid VAT number",
|
|
105
108
|
"error.network.500": "Internal server error",
|
|
106
109
|
"error.network.503": "Service unavailable",
|
|
107
110
|
"error.requiredField": "This field is required",
|
package/src/locales/es.json
CHANGED
|
@@ -102,6 +102,9 @@
|
|
|
102
102
|
"datePicker.year": "Año",
|
|
103
103
|
"error.generic": "Ha ocurrido un error",
|
|
104
104
|
"error.iban.invalid": "El IBAN no es correcto. Vuelve a intentarlo.",
|
|
105
|
+
"error.invalidEmail": "Este correo electrónico no es válido",
|
|
106
|
+
"error.invalidName": "Invalid name",
|
|
107
|
+
"error.invalidVatNumber": "Este número de IVA no es válido",
|
|
105
108
|
"error.network.500": "Error de servidor interno",
|
|
106
109
|
"error.network.503": "Servicio no disponible",
|
|
107
110
|
"error.requiredField": "Este campo es obligatorio",
|
package/src/locales/fi.json
CHANGED
|
@@ -102,6 +102,9 @@
|
|
|
102
102
|
"datePicker.year": "Vuosi",
|
|
103
103
|
"error.generic": "Virhe tapahtui",
|
|
104
104
|
"error.iban.invalid": "IBAN-tilinumero ei näytä olevan oikein. Yritä syöttää se uudelleen.",
|
|
105
|
+
"error.invalidEmail": "Virheellinen sähköpostiosoite ",
|
|
106
|
+
"error.invalidName": "Nimi on väärässä muodossa",
|
|
107
|
+
"error.invalidVatNumber": "Virheellinen ALV-numero",
|
|
105
108
|
"error.network.500": "Sisäinen palvelinvirhe",
|
|
106
109
|
"error.network.503": "Palvelua ei saatavilla",
|
|
107
110
|
"error.requiredField": "Vaadittu kenttä",
|
package/src/locales/fr.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"accountStatement.column.credit": "Crédit (€)",
|
|
7
7
|
"accountStatement.column.date": "Date",
|
|
8
8
|
"accountStatement.column.debit": "Débit (€)",
|
|
9
|
-
"accountStatement.column.description": "
|
|
9
|
+
"accountStatement.column.description": "Description",
|
|
10
10
|
"accountStatement.column.fees": "Frais",
|
|
11
11
|
"accountStatement.column.total": "Total",
|
|
12
12
|
"accountStatement.column.totals": "Totaux",
|
|
@@ -102,6 +102,9 @@
|
|
|
102
102
|
"datePicker.year": "Année",
|
|
103
103
|
"error.generic": "Une erreur est survenue",
|
|
104
104
|
"error.iban.invalid": "Cet IBAN semble incorrect. Veuillez le ressaisir.",
|
|
105
|
+
"error.invalidEmail": "E-mail non valide",
|
|
106
|
+
"error.invalidName": "Nom non valide",
|
|
107
|
+
"error.invalidVatNumber": "Numéro de TVA non valide",
|
|
105
108
|
"error.network.500": "Erreur de serveur interne",
|
|
106
109
|
"error.network.503": "Service indisponible",
|
|
107
110
|
"error.requiredField": "Ce champ est requis",
|
package/src/locales/it.json
CHANGED
|
@@ -102,6 +102,9 @@
|
|
|
102
102
|
"datePicker.year": "Anno",
|
|
103
103
|
"error.generic": "Si è verificato un errore",
|
|
104
104
|
"error.iban.invalid": "L'IBAN non è corretto. Provi a inserirlo di nuovo.",
|
|
105
|
+
"error.invalidEmail": "L'indirizzo e-mail non è valido",
|
|
106
|
+
"error.invalidName": "Nome non valido",
|
|
107
|
+
"error.invalidVatNumber": "Numero di partita IVA non valido",
|
|
105
108
|
"error.network.500": "Errore interno del server",
|
|
106
109
|
"error.network.503": "Servizio non disponibile",
|
|
107
110
|
"error.requiredField": "Questo campo è obbligatorio",
|
package/src/locales/nl.json
CHANGED
|
@@ -102,6 +102,9 @@
|
|
|
102
102
|
"datePicker.year": "Jaar",
|
|
103
103
|
"error.generic": "Er is een fout opgetreden",
|
|
104
104
|
"error.iban.invalid": "Dit IBAN nummer lijkt niet juist. Probeer het nog eens.",
|
|
105
|
+
"error.invalidEmail": "Dit e-mailadres is ongeldig",
|
|
106
|
+
"error.invalidName": "Ongeldige naam",
|
|
107
|
+
"error.invalidVatNumber": "Dit BTW-nummer is ongeldig",
|
|
105
108
|
"error.network.500": "Interne Server Fout",
|
|
106
109
|
"error.network.503": "Dienst niet beschikbaar",
|
|
107
110
|
"error.requiredField": "Dit veld is verplicht",
|
package/src/locales/pt.json
CHANGED
|
@@ -102,6 +102,9 @@
|
|
|
102
102
|
"datePicker.year": "Ano",
|
|
103
103
|
"error.generic": "Ocorreu um erro",
|
|
104
104
|
"error.iban.invalid": "Este IBAN não parece correto. Tente inserir novamente.",
|
|
105
|
+
"error.invalidEmail": "Este e-mail é inválido",
|
|
106
|
+
"error.invalidName": "Nome inválido",
|
|
107
|
+
"error.invalidVatNumber": "Número de IVA inválido",
|
|
105
108
|
"error.network.500": "Erro interno do servidor",
|
|
106
109
|
"error.network.503": "Serviço indisponível",
|
|
107
110
|
"error.requiredField": "Este campo é obrigatório",
|
|
@@ -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;
|