110 lines
2.8 KiB
TypeScript
110 lines
2.8 KiB
TypeScript
import { z } from "zod";
|
|
import { validationMessage } from "@/shared/i18n";
|
|
|
|
const ISO_MONTH_LENGTH = 7;
|
|
const MAX_CARD_NAME_LENGTH = 100;
|
|
|
|
function bankToday() {
|
|
return new Intl.DateTimeFormat("en-CA", {
|
|
timeZone: "Europe/Moscow",
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
}).format(new Date());
|
|
}
|
|
function bankDateInputValue(value: string) {
|
|
return value.replace(/^(\d{2})\.(\d{2})\.(\d{4})$/, "$3-$2-$1");
|
|
}
|
|
function bankMonth(value: string | null) {
|
|
return value && /^\d{4}-(0[1-9]|1[0-2])$/.test(value)
|
|
? value
|
|
: bankToday().slice(0, ISO_MONTH_LENGTH);
|
|
}
|
|
function bankMoneyValue(value: string) {
|
|
return value
|
|
.trim()
|
|
.replace(",", ".")
|
|
.replace(/^(-?)0+(?=\d)/, "$1");
|
|
}
|
|
function moneyCents(value: string): bigint | null {
|
|
const normalized = value.trim().replace(",", ".");
|
|
if (!/^-?\d+(\.\d{1,2})?$/.test(normalized)) return null;
|
|
const [whole, fraction = ""] = normalized.replace("-", "").split(".");
|
|
return (
|
|
BigInt(`${whole}${fraction.padEnd(2, "0")}`) *
|
|
(normalized.startsWith("-") ? -1n : 1n)
|
|
);
|
|
}
|
|
const MIN_BALANCE_KOPECKS = -9_223_372_036_854_775_808n;
|
|
const MAX_BALANCE_KOPECKS = 9_223_372_036_854_775_807n;
|
|
|
|
const moneySchema = z.string().refine(
|
|
(value) => {
|
|
const cents = moneyCents(value);
|
|
return (
|
|
cents !== null &&
|
|
cents >= MIN_BALANCE_KOPECKS &&
|
|
cents <= MAX_BALANCE_KOPECKS
|
|
);
|
|
},
|
|
{ error: validationMessage("bank.validation.money") },
|
|
);
|
|
const positiveMoneySchema = moneySchema.refine(
|
|
(value) => {
|
|
const cents = moneyCents(value);
|
|
return cents === null || cents > 0n;
|
|
},
|
|
{ error: validationMessage("bank.validation.positive") },
|
|
);
|
|
const bankDateSchema = z
|
|
.string()
|
|
.refine(
|
|
(value) =>
|
|
/^\d{4}-\d{2}-\d{2}$/.test(value) &&
|
|
value >= "0001-01-01" &&
|
|
!Number.isNaN(Date.parse(`${value}T00:00:00Z`)) &&
|
|
new Date(`${value}T00:00:00Z`).toISOString().slice(0, 10) === value &&
|
|
value <= bankToday(),
|
|
{ error: validationMessage("bank.validation.date") },
|
|
);
|
|
const bankCardSchema = z.object({
|
|
name: z
|
|
.string()
|
|
.trim()
|
|
.min(1, validationMessage("bank.validation.name")(undefined))
|
|
.max(
|
|
MAX_CARD_NAME_LENGTH,
|
|
validationMessage("bank.validation.name")(undefined),
|
|
),
|
|
bankName: z
|
|
.string()
|
|
.trim()
|
|
.max(
|
|
MAX_CARD_NAME_LENGTH,
|
|
validationMessage("bank.validation.bankName")(undefined),
|
|
),
|
|
lastFour: z.string().refine((value) => !value || /^\d{4}$/.test(value), {
|
|
error: validationMessage("bank.validation.lastFour"),
|
|
}),
|
|
paymentSystem: z
|
|
.enum(["", "mir", "visa", "mastercard"], {
|
|
error: validationMessage("bank.validation.paymentSystem"),
|
|
})
|
|
.optional(),
|
|
openingBalance: moneySchema,
|
|
openingDate: bankDateSchema,
|
|
});
|
|
type BankCardValues = z.infer<typeof bankCardSchema>;
|
|
|
|
export {
|
|
type BankCardValues,
|
|
bankCardSchema,
|
|
bankDateInputValue,
|
|
bankDateSchema,
|
|
bankMoneyValue,
|
|
bankMonth,
|
|
bankToday,
|
|
moneyCents,
|
|
positiveMoneySchema,
|
|
};
|