@floristcloud/api-lib 1.2.113 → 1.2.115
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/build/commands/cash-flow-item/create-cash-flow-item.command.js +1 -0
- package/build/commands/cash-flow-item/update-cash-flow-item.command.js +1 -0
- package/build/commands/report/get-cash-flow.query.js +14 -0
- package/build/constant/contact-phone.constant.js +11 -1
- package/build/constant/validation-i18n-key.js +2 -0
- package/build/constant/validation-i18n-message.js +2 -0
- package/build/schemas/cash-flow-item/cash-flow-item.schema.js +3 -0
- package/build/schemas/settings/settings.schema.js +34 -3
- package/commands/cash-flow-item/create-cash-flow-item.command.ts +1 -0
- package/commands/cash-flow-item/update-cash-flow-item.command.ts +1 -0
- package/commands/report/get-cash-flow.query.ts +14 -0
- package/constant/contact-phone.constant.ts +13 -0
- package/constant/validation-i18n-key.ts +2 -0
- package/constant/validation-i18n-message.ts +2 -0
- package/package.json +1 -1
- package/schemas/cash-flow-item/cash-flow-item.schema.ts +3 -0
- package/schemas/settings/settings.schema.ts +41 -6
|
@@ -6,6 +6,7 @@ const schemas_1 = require("../../schemas");
|
|
|
6
6
|
const CreateCashFlowItemRequestSchema = zod_1.z.object({
|
|
7
7
|
name: zod_1.z.string().trim().min(1).max(255),
|
|
8
8
|
sortOrder: zod_1.z.coerce.number().int().optional(),
|
|
9
|
+
excludeFromReports: zod_1.z.boolean().optional(),
|
|
9
10
|
});
|
|
10
11
|
const CreateCashFlowItemResponseSchema = zod_1.z.object({
|
|
11
12
|
message: zod_1.z.string().optional(),
|
|
@@ -8,6 +8,7 @@ const UpdateCashFlowItemRequestSchema = zod_1.z.object({
|
|
|
8
8
|
name: zod_1.z.string().trim().min(1).max(255).optional(),
|
|
9
9
|
status: zod_1.z.nativeEnum(cash_flow_item_status_enum_1.CashFlowItemStatusEnum).optional(),
|
|
10
10
|
sortOrder: zod_1.z.coerce.number().int().optional(),
|
|
11
|
+
excludeFromReports: zod_1.z.boolean().optional(),
|
|
11
12
|
});
|
|
12
13
|
const UpdateCashFlowItemResponseSchema = zod_1.z.object({
|
|
13
14
|
message: zod_1.z.string().optional(),
|
|
@@ -32,7 +32,14 @@ const CashFlowItemRowSchema = zod_1.z.object({
|
|
|
32
32
|
cashFlowItemName: zod_1.z.string().nullable(),
|
|
33
33
|
income: money_schema_1.MoneySchema,
|
|
34
34
|
expense: money_schema_1.MoneySchema,
|
|
35
|
+
// Часть `expense`, которая является расходом бизнеса: без выплат клиентам и возвратов
|
|
36
|
+
// предоплат — они двигают баланс клиента, а не прибыль. Для большинства статей совпадает
|
|
37
|
+
// с `expense`. Флаг статьи здесь НЕ применён: строка со своей суммой видна всегда, а
|
|
38
|
+
// складывать её или нет — решает `excludeFromReports`.
|
|
39
|
+
operatingExpense: money_schema_1.MoneySchema,
|
|
35
40
|
transactionCount: zod_1.z.number(),
|
|
41
|
+
// Копия флага статьи. У строки "Без статьи" всегда false — такие платежи учитываются.
|
|
42
|
+
excludeFromReports: zod_1.z.boolean(),
|
|
36
43
|
});
|
|
37
44
|
const CashFlowMonthRowSchema = CashFlowItemRowSchema.extend({
|
|
38
45
|
month: zod_1.z.string(),
|
|
@@ -48,6 +55,13 @@ const GetCashFlowResponseSchema = zod_1.z.object({
|
|
|
48
55
|
// Сальдо (`income - expense`) — знаковая величина, минус здесь легален,
|
|
49
56
|
// поэтому ограничения на знак нет и быть не должно.
|
|
50
57
|
total: money_schema_1.MoneySchema,
|
|
58
|
+
// Расход, который вычитается из прибыли на странице отчётов: сумма `operatingExpense`
|
|
59
|
+
// по строкам с `excludeFromReports = false`, включая строку "Без статьи".
|
|
60
|
+
expenseForReports: money_schema_1.MoneySchema,
|
|
61
|
+
// Часть `expenseForReports` без проставленной статьи — величина "из них", а не слагаемое.
|
|
62
|
+
expenseWithoutItem: money_schema_1.MoneySchema,
|
|
63
|
+
// Сколько денег отсекли флагом статьи. Для сноски "мимо отчёта прошло X".
|
|
64
|
+
excludedExpense: money_schema_1.MoneySchema,
|
|
51
65
|
}),
|
|
52
66
|
}),
|
|
53
67
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CONTACT_PHONE_LABEL_MAX_LENGTH = exports.CONTACT_PHONE_MAX_LENGTH = exports.CONTACT_PHONE_LIST_MAX_LENGTH = void 0;
|
|
3
|
+
exports.MESSENGERS_REQUIRING_URL = exports.CONTACT_MESSENGER_URL_MAX_LENGTH = exports.CONTACT_PHONE_LABEL_MAX_LENGTH = exports.CONTACT_PHONE_MAX_LENGTH = exports.CONTACT_PHONE_LIST_MAX_LENGTH = void 0;
|
|
4
|
+
const contact_messenger_enum_1 = require("../enum/contact-messenger.enum");
|
|
4
5
|
// Ограничение на длину списка контактов вебшопа: блок контактов рисуется в шапке и в мобильном
|
|
5
6
|
// меню, больше десятка номеров туда не помещается физически.
|
|
6
7
|
exports.CONTACT_PHONE_LIST_MAX_LENGTH = 10;
|
|
@@ -9,3 +10,12 @@ exports.CONTACT_PHONE_LIST_MAX_LENGTH = 10;
|
|
|
9
10
|
// укладывается с запасом, подпись отдела («отдел продаж», «склад») — в 64.
|
|
10
11
|
exports.CONTACT_PHONE_MAX_LENGTH = 32;
|
|
11
12
|
exports.CONTACT_PHONE_LABEL_MAX_LENGTH = 64;
|
|
13
|
+
// Ссылка на мессенджер. Домены не фиксируем — max.ru уже отдаёт три разных формата (`/u/<хеш>`,
|
|
14
|
+
// `/@bot`, `/join/<код>`), и привязка к хосту означала бы деплой на каждое изменение у мессенджера.
|
|
15
|
+
exports.CONTACT_MESSENGER_URL_MAX_LENGTH = 255;
|
|
16
|
+
// Мессенджеры, до которых нельзя дойти по номеру телефона: у них ссылка обязательна.
|
|
17
|
+
// WhatsApp единственный выводится из номера — `https://wa.me/<цифры>`.
|
|
18
|
+
// Telegram по номеру открывается только схемой `tg://` в установленном приложении, в вебе не
|
|
19
|
+
// работает, поэтому нужен юзернейм. У MAX маршрута по номеру нет вовсе — только профиль по хешу
|
|
20
|
+
// из QR-кода, `@бот` или инвайт в канал.
|
|
21
|
+
exports.MESSENGERS_REQUIRING_URL = [contact_messenger_enum_1.ContactMessengerEnum.TELEGRAM, contact_messenger_enum_1.ContactMessengerEnum.MAX];
|
|
@@ -36,4 +36,6 @@ exports.ValidationI18nKey = {
|
|
|
36
36
|
standingOrderCategoryRequired: 'validation.standingOrderCategoryRequired',
|
|
37
37
|
standingOrderNameRequired: 'validation.standingOrderNameRequired',
|
|
38
38
|
standingOrderQuantityRequired: 'validation.standingOrderQuantityRequired',
|
|
39
|
+
contactMessengerUrlRequired: 'validation.contactMessengerUrlRequired',
|
|
40
|
+
contactMessengerDuplicate: 'validation.contactMessengerDuplicate',
|
|
39
41
|
};
|
|
@@ -39,6 +39,8 @@ exports.VALIDATION_I18N_MESSAGE = {
|
|
|
39
39
|
[validation_i18n_key_1.ValidationI18nKey.standingOrderCategoryRequired]: 'Category is required',
|
|
40
40
|
[validation_i18n_key_1.ValidationI18nKey.standingOrderNameRequired]: 'Name is required',
|
|
41
41
|
[validation_i18n_key_1.ValidationI18nKey.standingOrderQuantityRequired]: 'Quantity is required',
|
|
42
|
+
[validation_i18n_key_1.ValidationI18nKey.contactMessengerUrlRequired]: 'This messenger cannot be reached by phone number, provide a link',
|
|
43
|
+
[validation_i18n_key_1.ValidationI18nKey.contactMessengerDuplicate]: 'The same messenger is listed twice for this contact',
|
|
42
44
|
};
|
|
43
45
|
/**
|
|
44
46
|
* A validation `message` is a stable i18n token when it matches this pattern (e.g. `validation.dateRange`).
|
|
@@ -9,6 +9,9 @@ exports.CashFlowItemSchema = zod_1.z.object({
|
|
|
9
9
|
name: zod_1.z.string(),
|
|
10
10
|
status: zod_1.z.nativeEnum(cash_flow_item_status_enum_1.CashFlowItemStatusEnum),
|
|
11
11
|
sortOrder: zod_1.z.number(),
|
|
12
|
+
// Статья не участвует в расчёте прибыли: перевод между кассами, оплата поставщику (уже сидит
|
|
13
|
+
// в себестоимости), выплата учредителю. На отчёт ДДС не влияет — там остаются все обороты.
|
|
14
|
+
excludeFromReports: zod_1.z.boolean(),
|
|
12
15
|
createdAt: zod_1.z.date(),
|
|
13
16
|
updatedAt: zod_1.z.date(),
|
|
14
17
|
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UpdateSettingsRequestSchema = exports.CreateSettingsSchema = exports.SettingsForAdminSchema = exports.SettingsSchema = exports.DeliveryPricingTierSchema = exports.DisableOrdersMessageSchema = exports.ContactPhoneListSchema = exports.ContactPhoneSchema = exports.ImportantMessageSchema = exports.SettingsWorkingHoursSchema = void 0;
|
|
3
|
+
exports.UpdateSettingsRequestSchema = exports.CreateSettingsSchema = exports.SettingsForAdminSchema = exports.SettingsSchema = exports.DeliveryPricingTierSchema = exports.DisableOrdersMessageSchema = exports.ContactPhoneListSchema = exports.ContactPhoneSchema = exports.ContactMessengerLinkSchema = exports.ImportantMessageSchema = exports.SettingsWorkingHoursSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const enum_1 = require("../../enum");
|
|
6
6
|
const contact_phone_constant_1 = require("../../constant/contact-phone.constant");
|
|
7
7
|
const settings_constant_1 = require("../../constant/settings.constant");
|
|
8
|
+
const validation_i18n_key_1 = require("../../constant/validation-i18n-key");
|
|
8
9
|
const webshop_main_display_mode_enum_1 = require("../../enum/webshop-main-display-mode.enum");
|
|
9
10
|
const menu_items_url_enum_1 = require("../../enum/menu-items-url.enum");
|
|
10
11
|
const non_negative_money_schema_1 = require("../non-negative-money.schema");
|
|
@@ -23,12 +24,39 @@ exports.ImportantMessageSchema = zod_1.z.object({
|
|
|
23
24
|
description: zod_1.z.string(),
|
|
24
25
|
isActive: zod_1.z.boolean(),
|
|
25
26
|
});
|
|
27
|
+
// Мессенджер контакта. `url === null` означает «собрать ссылку из номера» — так умеет только
|
|
28
|
+
// WhatsApp, поэтому для остальных ссылка обязательна (см. MESSENGERS_REQUIRING_URL и проверку ниже).
|
|
29
|
+
exports.ContactMessengerLinkSchema = zod_1.z.object({
|
|
30
|
+
type: zod_1.z.nativeEnum(enum_1.ContactMessengerEnum),
|
|
31
|
+
url: zod_1.z.string().url().max(contact_phone_constant_1.CONTACT_MESSENGER_URL_MAX_LENGTH).nullable(),
|
|
32
|
+
});
|
|
26
33
|
// Один контакт из блока контактов вебшопа: номер, подпись отдела («отдел продаж», «склад») и
|
|
27
34
|
// мессенджеры, доступные по этому же номеру. Порядок вывода задаётся порядком элементов в массиве.
|
|
28
|
-
exports.ContactPhoneSchema = zod_1.z
|
|
35
|
+
exports.ContactPhoneSchema = zod_1.z
|
|
36
|
+
.object({
|
|
29
37
|
phone: zod_1.z.string().min(1).max(contact_phone_constant_1.CONTACT_PHONE_MAX_LENGTH),
|
|
30
38
|
label: zod_1.z.string().max(contact_phone_constant_1.CONTACT_PHONE_LABEL_MAX_LENGTH).nullable(),
|
|
31
|
-
messengers: zod_1.z.array(
|
|
39
|
+
messengers: zod_1.z.array(exports.ContactMessengerLinkSchema),
|
|
40
|
+
})
|
|
41
|
+
.superRefine(({ messengers }, ctx) => {
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
messengers.forEach((messenger, index) => {
|
|
44
|
+
if (!messenger.url && contact_phone_constant_1.MESSENGERS_REQUIRING_URL.includes(messenger.type)) {
|
|
45
|
+
ctx.addIssue({
|
|
46
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
47
|
+
path: ['messengers', index, 'url'],
|
|
48
|
+
message: validation_i18n_key_1.ValidationI18nKey.contactMessengerUrlRequired,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (seen.has(messenger.type)) {
|
|
52
|
+
ctx.addIssue({
|
|
53
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
54
|
+
path: ['messengers', index, 'type'],
|
|
55
|
+
message: validation_i18n_key_1.ValidationI18nKey.contactMessengerDuplicate,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
seen.add(messenger.type);
|
|
59
|
+
});
|
|
32
60
|
});
|
|
33
61
|
exports.ContactPhoneListSchema = zod_1.z.array(exports.ContactPhoneSchema).max(contact_phone_constant_1.CONTACT_PHONE_LIST_MAX_LENGTH);
|
|
34
62
|
// Один потолок на все три схемы настроек: колонка `disable_orders_message` — TEXT, длину держит
|
|
@@ -85,6 +113,7 @@ exports.SettingsSchema = zod_1.z.object({
|
|
|
85
113
|
phoneSales: zod_1.z.string().nullable(),
|
|
86
114
|
phoneWhatsappSales: zod_1.z.string().nullable(),
|
|
87
115
|
telegramChannel: zod_1.z.string().nullable(),
|
|
116
|
+
maxChannel: zod_1.z.string().nullable(),
|
|
88
117
|
contactPhoneList: exports.ContactPhoneListSchema.nullable(),
|
|
89
118
|
menuItemsSort: zod_1.z.array(zod_1.z.nativeEnum(menu_items_url_enum_1.MenuItemsUrlEnum)).nullable(),
|
|
90
119
|
phoneCountryCode: zod_1.z.array(zod_1.z.string()),
|
|
@@ -173,6 +202,7 @@ exports.CreateSettingsSchema = zod_1.z.object({
|
|
|
173
202
|
phoneSales: zod_1.z.string().nullable().optional(),
|
|
174
203
|
phoneWhatsappSales: zod_1.z.string().nullable().optional(),
|
|
175
204
|
telegramChannel: zod_1.z.string().nullable().optional(),
|
|
205
|
+
maxChannel: zod_1.z.string().nullable().optional(),
|
|
176
206
|
contactPhoneList: exports.ContactPhoneListSchema.nullable().optional(),
|
|
177
207
|
menuItemsSort: zod_1.z.array(zod_1.z.nativeEnum(menu_items_url_enum_1.MenuItemsUrlEnum)).nullable().optional(),
|
|
178
208
|
phoneCountryCode: zod_1.z.array(zod_1.z.string()).optional(),
|
|
@@ -236,6 +266,7 @@ exports.UpdateSettingsRequestSchema = zod_1.z.object({
|
|
|
236
266
|
phoneSales: zod_1.z.string().nullable().optional(),
|
|
237
267
|
phoneWhatsappSales: zod_1.z.string().nullable().optional(),
|
|
238
268
|
telegramChannel: zod_1.z.string().nullable().optional(),
|
|
269
|
+
maxChannel: zod_1.z.string().nullable().optional(),
|
|
239
270
|
contactPhoneList: exports.ContactPhoneListSchema.nullable().optional(),
|
|
240
271
|
menuItemsSort: zod_1.z.array(zod_1.z.nativeEnum(menu_items_url_enum_1.MenuItemsUrlEnum)).nullable().optional(),
|
|
241
272
|
phoneCountryCode: zod_1.z.array(zod_1.z.string()).optional(),
|
|
@@ -4,6 +4,7 @@ import { CashFlowItemSchema } from '../../schemas';
|
|
|
4
4
|
const CreateCashFlowItemRequestSchema = z.object({
|
|
5
5
|
name: z.string().trim().min(1).max(255),
|
|
6
6
|
sortOrder: z.coerce.number().int().optional(),
|
|
7
|
+
excludeFromReports: z.boolean().optional(),
|
|
7
8
|
});
|
|
8
9
|
|
|
9
10
|
const CreateCashFlowItemResponseSchema = z.object({
|
|
@@ -6,6 +6,7 @@ const UpdateCashFlowItemRequestSchema = z.object({
|
|
|
6
6
|
name: z.string().trim().min(1).max(255).optional(),
|
|
7
7
|
status: z.nativeEnum(CashFlowItemStatusEnum).optional(),
|
|
8
8
|
sortOrder: z.coerce.number().int().optional(),
|
|
9
|
+
excludeFromReports: z.boolean().optional(),
|
|
9
10
|
});
|
|
10
11
|
|
|
11
12
|
const UpdateCashFlowItemResponseSchema = z.object({
|
|
@@ -31,7 +31,14 @@ const CashFlowItemRowSchema = z.object({
|
|
|
31
31
|
cashFlowItemName: z.string().nullable(),
|
|
32
32
|
income: MoneySchema,
|
|
33
33
|
expense: MoneySchema,
|
|
34
|
+
// Часть `expense`, которая является расходом бизнеса: без выплат клиентам и возвратов
|
|
35
|
+
// предоплат — они двигают баланс клиента, а не прибыль. Для большинства статей совпадает
|
|
36
|
+
// с `expense`. Флаг статьи здесь НЕ применён: строка со своей суммой видна всегда, а
|
|
37
|
+
// складывать её или нет — решает `excludeFromReports`.
|
|
38
|
+
operatingExpense: MoneySchema,
|
|
34
39
|
transactionCount: z.number(),
|
|
40
|
+
// Копия флага статьи. У строки "Без статьи" всегда false — такие платежи учитываются.
|
|
41
|
+
excludeFromReports: z.boolean(),
|
|
35
42
|
});
|
|
36
43
|
|
|
37
44
|
const CashFlowMonthRowSchema = CashFlowItemRowSchema.extend({
|
|
@@ -49,6 +56,13 @@ const GetCashFlowResponseSchema = z.object({
|
|
|
49
56
|
// Сальдо (`income - expense`) — знаковая величина, минус здесь легален,
|
|
50
57
|
// поэтому ограничения на знак нет и быть не должно.
|
|
51
58
|
total: MoneySchema,
|
|
59
|
+
// Расход, который вычитается из прибыли на странице отчётов: сумма `operatingExpense`
|
|
60
|
+
// по строкам с `excludeFromReports = false`, включая строку "Без статьи".
|
|
61
|
+
expenseForReports: MoneySchema,
|
|
62
|
+
// Часть `expenseForReports` без проставленной статьи — величина "из них", а не слагаемое.
|
|
63
|
+
expenseWithoutItem: MoneySchema,
|
|
64
|
+
// Сколько денег отсекли флагом статьи. Для сноски "мимо отчёта прошло X".
|
|
65
|
+
excludedExpense: MoneySchema,
|
|
52
66
|
}),
|
|
53
67
|
}),
|
|
54
68
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ContactMessengerEnum } from '../enum/contact-messenger.enum';
|
|
2
|
+
|
|
1
3
|
// Ограничение на длину списка контактов вебшопа: блок контактов рисуется в шапке и в мобильном
|
|
2
4
|
// меню, больше десятка номеров туда не помещается физически.
|
|
3
5
|
export const CONTACT_PHONE_LIST_MAX_LENGTH = 10;
|
|
@@ -7,3 +9,14 @@ export const CONTACT_PHONE_LIST_MAX_LENGTH = 10;
|
|
|
7
9
|
// укладывается с запасом, подпись отдела («отдел продаж», «склад») — в 64.
|
|
8
10
|
export const CONTACT_PHONE_MAX_LENGTH = 32;
|
|
9
11
|
export const CONTACT_PHONE_LABEL_MAX_LENGTH = 64;
|
|
12
|
+
|
|
13
|
+
// Ссылка на мессенджер. Домены не фиксируем — max.ru уже отдаёт три разных формата (`/u/<хеш>`,
|
|
14
|
+
// `/@bot`, `/join/<код>`), и привязка к хосту означала бы деплой на каждое изменение у мессенджера.
|
|
15
|
+
export const CONTACT_MESSENGER_URL_MAX_LENGTH = 255;
|
|
16
|
+
|
|
17
|
+
// Мессенджеры, до которых нельзя дойти по номеру телефона: у них ссылка обязательна.
|
|
18
|
+
// WhatsApp единственный выводится из номера — `https://wa.me/<цифры>`.
|
|
19
|
+
// Telegram по номеру открывается только схемой `tg://` в установленном приложении, в вебе не
|
|
20
|
+
// работает, поэтому нужен юзернейм. У MAX маршрута по номеру нет вовсе — только профиль по хешу
|
|
21
|
+
// из QR-кода, `@бот` или инвайт в канал.
|
|
22
|
+
export const MESSENGERS_REQUIRING_URL: ContactMessengerEnum[] = [ContactMessengerEnum.TELEGRAM, ContactMessengerEnum.MAX];
|
|
@@ -33,6 +33,8 @@ export const ValidationI18nKey = {
|
|
|
33
33
|
standingOrderCategoryRequired: 'validation.standingOrderCategoryRequired',
|
|
34
34
|
standingOrderNameRequired: 'validation.standingOrderNameRequired',
|
|
35
35
|
standingOrderQuantityRequired: 'validation.standingOrderQuantityRequired',
|
|
36
|
+
contactMessengerUrlRequired: 'validation.contactMessengerUrlRequired',
|
|
37
|
+
contactMessengerDuplicate: 'validation.contactMessengerDuplicate',
|
|
36
38
|
} as const;
|
|
37
39
|
|
|
38
40
|
export type ValidationI18nKeyType = (typeof ValidationI18nKey)[keyof typeof ValidationI18nKey];
|
|
@@ -37,6 +37,8 @@ export const VALIDATION_I18N_MESSAGE: Record<ValidationI18nKeyType, string> = {
|
|
|
37
37
|
[ValidationI18nKey.standingOrderCategoryRequired]: 'Category is required',
|
|
38
38
|
[ValidationI18nKey.standingOrderNameRequired]: 'Name is required',
|
|
39
39
|
[ValidationI18nKey.standingOrderQuantityRequired]: 'Quantity is required',
|
|
40
|
+
[ValidationI18nKey.contactMessengerUrlRequired]: 'This messenger cannot be reached by phone number, provide a link',
|
|
41
|
+
[ValidationI18nKey.contactMessengerDuplicate]: 'The same messenger is listed twice for this contact',
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
/**
|
package/package.json
CHANGED
|
@@ -7,6 +7,9 @@ export const CashFlowItemSchema = z.object({
|
|
|
7
7
|
name: z.string(),
|
|
8
8
|
status: z.nativeEnum(CashFlowItemStatusEnum),
|
|
9
9
|
sortOrder: z.number(),
|
|
10
|
+
// Статья не участвует в расчёте прибыли: перевод между кассами, оплата поставщику (уже сидит
|
|
11
|
+
// в себестоимости), выплата учредителю. На отчёт ДДС не влияет — там остаются все обороты.
|
|
12
|
+
excludeFromReports: z.boolean(),
|
|
10
13
|
createdAt: z.date(),
|
|
11
14
|
updatedAt: z.date(),
|
|
12
15
|
});
|
|
@@ -8,14 +8,16 @@ import {
|
|
|
8
8
|
WebshopModeEnum,
|
|
9
9
|
} from '../../enum';
|
|
10
10
|
import {
|
|
11
|
+
CONTACT_MESSENGER_URL_MAX_LENGTH,
|
|
11
12
|
CONTACT_PHONE_LABEL_MAX_LENGTH,
|
|
12
13
|
CONTACT_PHONE_LIST_MAX_LENGTH,
|
|
13
14
|
CONTACT_PHONE_MAX_LENGTH,
|
|
15
|
+
MESSENGERS_REQUIRING_URL,
|
|
14
16
|
} from '../../constant/contact-phone.constant';
|
|
15
17
|
import { DISABLE_ORDERS_MESSAGE_MAX_LENGTH } from '../../constant/settings.constant';
|
|
18
|
+
import { ValidationI18nKey } from '../../constant/validation-i18n-key';
|
|
16
19
|
import { WebshopMainDisplayModeEnum } from '../../enum/webshop-main-display-mode.enum';
|
|
17
20
|
import { MenuItemsUrlEnum } from '../../enum/menu-items-url.enum';
|
|
18
|
-
import { MoneySchema } from '../money.schema';
|
|
19
21
|
import { NonNegativeMoneySchema } from '../non-negative-money.schema';
|
|
20
22
|
|
|
21
23
|
export const SettingsWorkingHoursSchema = z.object({
|
|
@@ -35,13 +37,43 @@ export const ImportantMessageSchema = z.object({
|
|
|
35
37
|
isActive: z.boolean(),
|
|
36
38
|
});
|
|
37
39
|
|
|
40
|
+
// Мессенджер контакта. `url === null` означает «собрать ссылку из номера» — так умеет только
|
|
41
|
+
// WhatsApp, поэтому для остальных ссылка обязательна (см. MESSENGERS_REQUIRING_URL и проверку ниже).
|
|
42
|
+
export const ContactMessengerLinkSchema = z.object({
|
|
43
|
+
type: z.nativeEnum(ContactMessengerEnum),
|
|
44
|
+
url: z.string().url().max(CONTACT_MESSENGER_URL_MAX_LENGTH).nullable(),
|
|
45
|
+
});
|
|
46
|
+
|
|
38
47
|
// Один контакт из блока контактов вебшопа: номер, подпись отдела («отдел продаж», «склад») и
|
|
39
48
|
// мессенджеры, доступные по этому же номеру. Порядок вывода задаётся порядком элементов в массиве.
|
|
40
|
-
export const ContactPhoneSchema = z
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
export const ContactPhoneSchema = z
|
|
50
|
+
.object({
|
|
51
|
+
phone: z.string().min(1).max(CONTACT_PHONE_MAX_LENGTH),
|
|
52
|
+
label: z.string().max(CONTACT_PHONE_LABEL_MAX_LENGTH).nullable(),
|
|
53
|
+
messengers: z.array(ContactMessengerLinkSchema),
|
|
54
|
+
})
|
|
55
|
+
.superRefine(({ messengers }, ctx) => {
|
|
56
|
+
const seen = new Set<ContactMessengerEnum>();
|
|
57
|
+
|
|
58
|
+
messengers.forEach((messenger, index) => {
|
|
59
|
+
if (!messenger.url && MESSENGERS_REQUIRING_URL.includes(messenger.type)) {
|
|
60
|
+
ctx.addIssue({
|
|
61
|
+
code: z.ZodIssueCode.custom,
|
|
62
|
+
path: ['messengers', index, 'url'],
|
|
63
|
+
message: ValidationI18nKey.contactMessengerUrlRequired,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (seen.has(messenger.type)) {
|
|
68
|
+
ctx.addIssue({
|
|
69
|
+
code: z.ZodIssueCode.custom,
|
|
70
|
+
path: ['messengers', index, 'type'],
|
|
71
|
+
message: ValidationI18nKey.contactMessengerDuplicate,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
seen.add(messenger.type);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
45
77
|
|
|
46
78
|
export const ContactPhoneListSchema = z.array(ContactPhoneSchema).max(CONTACT_PHONE_LIST_MAX_LENGTH);
|
|
47
79
|
|
|
@@ -101,6 +133,7 @@ export const SettingsSchema = z.object({
|
|
|
101
133
|
phoneSales: z.string().nullable(),
|
|
102
134
|
phoneWhatsappSales: z.string().nullable(),
|
|
103
135
|
telegramChannel: z.string().nullable(),
|
|
136
|
+
maxChannel: z.string().nullable(),
|
|
104
137
|
contactPhoneList: ContactPhoneListSchema.nullable(),
|
|
105
138
|
menuItemsSort: z.array(z.nativeEnum(MenuItemsUrlEnum)).nullable(),
|
|
106
139
|
phoneCountryCode: z.array(z.string()),
|
|
@@ -193,6 +226,7 @@ export const CreateSettingsSchema = z.object({
|
|
|
193
226
|
phoneSales: z.string().nullable().optional(),
|
|
194
227
|
phoneWhatsappSales: z.string().nullable().optional(),
|
|
195
228
|
telegramChannel: z.string().nullable().optional(),
|
|
229
|
+
maxChannel: z.string().nullable().optional(),
|
|
196
230
|
contactPhoneList: ContactPhoneListSchema.nullable().optional(),
|
|
197
231
|
menuItemsSort: z.array(z.nativeEnum(MenuItemsUrlEnum)).nullable().optional(),
|
|
198
232
|
phoneCountryCode: z.array(z.string()).optional(),
|
|
@@ -259,6 +293,7 @@ export const UpdateSettingsRequestSchema = z.object({
|
|
|
259
293
|
phoneSales: z.string().nullable().optional(),
|
|
260
294
|
phoneWhatsappSales: z.string().nullable().optional(),
|
|
261
295
|
telegramChannel: z.string().nullable().optional(),
|
|
296
|
+
maxChannel: z.string().nullable().optional(),
|
|
262
297
|
contactPhoneList: ContactPhoneListSchema.nullable().optional(),
|
|
263
298
|
menuItemsSort: z.array(z.nativeEnum(MenuItemsUrlEnum)).nullable().optional(),
|
|
264
299
|
phoneCountryCode: z.array(z.string()).optional(),
|