@floristcloud/api-lib 1.2.112 → 1.2.114

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.
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
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");
5
+ // Ограничение на длину списка контактов вебшопа: блок контактов рисуется в шапке и в мобильном
6
+ // меню, больше десятка номеров туда не помещается физически.
7
+ exports.CONTACT_PHONE_LIST_MAX_LENGTH = 10;
8
+ // Потолки для полей одного контакта. Список лежит в JSON-колонке, где длину не держит тип колонки,
9
+ // поэтому единственная граница — схема. Номер с кодом страны и разделителями в 32 символа
10
+ // укладывается с запасом, подпись отдела («отдел продаж», «склад») — в 64.
11
+ exports.CONTACT_PHONE_MAX_LENGTH = 32;
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];
@@ -81,6 +81,9 @@ exports.ERRORS = {
81
81
  SETTINGS_NOT_UPDATED: { code: 'S003', message: 'Failed to update settings', httpCode: 500 },
82
82
  SETTINGS_NOT_FOUND: { code: 'S005', message: 'Settings not found', httpCode: 404 },
83
83
  SETTINGS_FETCH_FAILED: { code: 'S006', message: 'Error when retrieving a Settings', httpCode: 500 },
84
+ // 409, а не 403: фильтр исключений маскирует текст любого 403 общим «Access denied», а здесь
85
+ // клиенту важно получить причину — заказы отключены временно, доступ ни при чём.
86
+ ORDERS_DISABLED_BY_SETTINGS: { code: 'S007', message: 'Ordering is temporarily disabled by the company', httpCode: 409 },
84
87
  FORBIDDEN_API: { code: 'A011', message: 'Access denied', httpCode: 403 },
85
88
  RESOURCE_ACCESS_DENIED: { code: 'A022', message: 'Resource does not belong to your company', httpCode: 400 },
86
89
  RESOURCE_NOT_FOUND: { code: 'A023', message: 'Resource not found', httpCode: 404 },
@@ -21,3 +21,5 @@ __exportStar(require("./import.constant"), exports);
21
21
  __exportStar(require("./validation-i18n-key"), exports);
22
22
  __exportStar(require("./validation-i18n-message"), exports);
23
23
  __exportStar(require("./report.constant"), exports);
24
+ __exportStar(require("./contact-phone.constant"), exports);
25
+ __exportStar(require("./settings.constant"), exports);
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DISABLE_ORDERS_MESSAGE_MAX_LENGTH = void 0;
4
+ // Текст режима «запрет заказов» («Возобновим работу в 16.00 после инвентаризации»). Витрина рисует
5
+ // его баннером вместо каталога, поэтому это короткое объявление, а не страница: 300 символов.
6
+ exports.DISABLE_ORDERS_MESSAGE_MAX_LENGTH = 300;
@@ -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`).
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContactMessengerEnum = void 0;
4
+ var ContactMessengerEnum;
5
+ (function (ContactMessengerEnum) {
6
+ ContactMessengerEnum["WHATSAPP"] = "whatsapp";
7
+ ContactMessengerEnum["TELEGRAM"] = "telegram";
8
+ ContactMessengerEnum["MAX"] = "max";
9
+ })(ContactMessengerEnum || (exports.ContactMessengerEnum = ContactMessengerEnum = {}));
@@ -97,6 +97,7 @@ __exportStar(require("./order-webshop-available-status-list"), exports);
97
97
  __exportStar(require("./transaction-type-groups"), exports);
98
98
  __exportStar(require("./chat-has-manager-mode.enum"), exports);
99
99
  __exportStar(require("./chat-no-manager-mode.enum"), exports);
100
+ __exportStar(require("./contact-messenger.enum"), exports);
100
101
  __exportStar(require("./match-confidence.enum"), exports);
101
102
  __exportStar(require("./session-platform.enum"), exports);
102
103
  __exportStar(require("./device-platform.enum"), exports);
@@ -1,8 +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.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
+ const contact_phone_constant_1 = require("../../constant/contact-phone.constant");
7
+ const settings_constant_1 = require("../../constant/settings.constant");
8
+ const validation_i18n_key_1 = require("../../constant/validation-i18n-key");
6
9
  const webshop_main_display_mode_enum_1 = require("../../enum/webshop-main-display-mode.enum");
7
10
  const menu_items_url_enum_1 = require("../../enum/menu-items-url.enum");
8
11
  const non_negative_money_schema_1 = require("../non-negative-money.schema");
@@ -21,6 +24,44 @@ exports.ImportantMessageSchema = zod_1.z.object({
21
24
  description: zod_1.z.string(),
22
25
  isActive: zod_1.z.boolean(),
23
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
+ });
33
+ // Один контакт из блока контактов вебшопа: номер, подпись отдела («отдел продаж», «склад») и
34
+ // мессенджеры, доступные по этому же номеру. Порядок вывода задаётся порядком элементов в массиве.
35
+ exports.ContactPhoneSchema = zod_1.z
36
+ .object({
37
+ phone: zod_1.z.string().min(1).max(contact_phone_constant_1.CONTACT_PHONE_MAX_LENGTH),
38
+ label: zod_1.z.string().max(contact_phone_constant_1.CONTACT_PHONE_LABEL_MAX_LENGTH).nullable(),
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
+ });
60
+ });
61
+ exports.ContactPhoneListSchema = zod_1.z.array(exports.ContactPhoneSchema).max(contact_phone_constant_1.CONTACT_PHONE_LIST_MAX_LENGTH);
62
+ // Один потолок на все три схемы настроек: колонка `disable_orders_message` — TEXT, длину держит
63
+ // только валидация.
64
+ exports.DisableOrdersMessageSchema = zod_1.z.string().max(settings_constant_1.DISABLE_ORDERS_MESSAGE_MAX_LENGTH);
24
65
  // Обе величины — деньги: `minSubtotal` сравнивается с суммой заказа, `cost` попадает в
25
66
  // `order.delivery_cost`. Живут они внутри JSON-колонки, куда ALTER TABLE не достаёт, поэтому
26
67
  // точность здесь держит не тип колонки, а формат значения — та же десятичная строка, что и на проводе.
@@ -72,6 +113,8 @@ exports.SettingsSchema = zod_1.z.object({
72
113
  phoneSales: zod_1.z.string().nullable(),
73
114
  phoneWhatsappSales: zod_1.z.string().nullable(),
74
115
  telegramChannel: zod_1.z.string().nullable(),
116
+ maxChannel: zod_1.z.string().nullable(),
117
+ contactPhoneList: exports.ContactPhoneListSchema.nullable(),
75
118
  menuItemsSort: zod_1.z.array(zod_1.z.nativeEnum(menu_items_url_enum_1.MenuItemsUrlEnum)).nullable(),
76
119
  phoneCountryCode: zod_1.z.array(zod_1.z.string()),
77
120
  aboutCompany: zod_1.z.string().nullable(),
@@ -82,6 +125,7 @@ exports.SettingsSchema = zod_1.z.object({
82
125
  deliveryPolicy: zod_1.z.string().nullable(),
83
126
  writeOffPolicy: zod_1.z.string().nullable(),
84
127
  disableOrders: zod_1.z.boolean(),
128
+ disableOrdersMessage: exports.DisableOrdersMessageSchema.nullable(),
85
129
  bankHookEnabled: zod_1.z.coerce.boolean().optional(),
86
130
  loginImageList: zod_1.z.array(zod_1.z.string()),
87
131
  loginImageKeyList: zod_1.z.array(zod_1.z.string()),
@@ -158,6 +202,8 @@ exports.CreateSettingsSchema = zod_1.z.object({
158
202
  phoneSales: zod_1.z.string().nullable().optional(),
159
203
  phoneWhatsappSales: zod_1.z.string().nullable().optional(),
160
204
  telegramChannel: zod_1.z.string().nullable().optional(),
205
+ maxChannel: zod_1.z.string().nullable().optional(),
206
+ contactPhoneList: exports.ContactPhoneListSchema.nullable().optional(),
161
207
  menuItemsSort: zod_1.z.array(zod_1.z.nativeEnum(menu_items_url_enum_1.MenuItemsUrlEnum)).nullable().optional(),
162
208
  phoneCountryCode: zod_1.z.array(zod_1.z.string()).optional(),
163
209
  aboutCompany: zod_1.z.string().nullable().optional(),
@@ -168,6 +214,7 @@ exports.CreateSettingsSchema = zod_1.z.object({
168
214
  deliveryPolicy: zod_1.z.string().nullable().optional(),
169
215
  writeOffPolicy: zod_1.z.string().nullable().optional(),
170
216
  disableOrders: zod_1.z.boolean().optional(),
217
+ disableOrdersMessage: exports.DisableOrdersMessageSchema.nullable().optional(),
171
218
  isMessengerEnabled: zod_1.z.coerce.boolean().optional(),
172
219
  isWriteOffAcceptanceEnabled: zod_1.z.coerce.boolean().optional(),
173
220
  isInvoiceDownloadEnabled: zod_1.z.coerce.boolean().optional(),
@@ -219,6 +266,8 @@ exports.UpdateSettingsRequestSchema = zod_1.z.object({
219
266
  phoneSales: zod_1.z.string().nullable().optional(),
220
267
  phoneWhatsappSales: zod_1.z.string().nullable().optional(),
221
268
  telegramChannel: zod_1.z.string().nullable().optional(),
269
+ maxChannel: zod_1.z.string().nullable().optional(),
270
+ contactPhoneList: exports.ContactPhoneListSchema.nullable().optional(),
222
271
  menuItemsSort: zod_1.z.array(zod_1.z.nativeEnum(menu_items_url_enum_1.MenuItemsUrlEnum)).nullable().optional(),
223
272
  phoneCountryCode: zod_1.z.array(zod_1.z.string()).optional(),
224
273
  aboutCompany: zod_1.z.string().nullable().optional(),
@@ -229,6 +278,7 @@ exports.UpdateSettingsRequestSchema = zod_1.z.object({
229
278
  deliveryPolicy: zod_1.z.string().nullable().optional(),
230
279
  writeOffPolicy: zod_1.z.string().nullable().optional(),
231
280
  disableOrders: zod_1.z.boolean().optional(),
281
+ disableOrdersMessage: exports.DisableOrdersMessageSchema.nullable().optional(),
232
282
  isMessengerEnabled: zod_1.z.coerce.boolean().optional(),
233
283
  isWriteOffAcceptanceEnabled: zod_1.z.coerce.boolean().optional(),
234
284
  isInvoiceDownloadEnabled: zod_1.z.coerce.boolean().optional(),
@@ -0,0 +1,22 @@
1
+ import { ContactMessengerEnum } from '../enum/contact-messenger.enum';
2
+
3
+ // Ограничение на длину списка контактов вебшопа: блок контактов рисуется в шапке и в мобильном
4
+ // меню, больше десятка номеров туда не помещается физически.
5
+ export const CONTACT_PHONE_LIST_MAX_LENGTH = 10;
6
+
7
+ // Потолки для полей одного контакта. Список лежит в JSON-колонке, где длину не держит тип колонки,
8
+ // поэтому единственная граница — схема. Номер с кодом страны и разделителями в 32 символа
9
+ // укладывается с запасом, подпись отдела («отдел продаж», «склад») — в 64.
10
+ export const CONTACT_PHONE_MAX_LENGTH = 32;
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];
package/constant/error.ts CHANGED
@@ -81,6 +81,9 @@ export const ERRORS = {
81
81
  SETTINGS_NOT_UPDATED: { code: 'S003', message: 'Failed to update settings', httpCode: 500 },
82
82
  SETTINGS_NOT_FOUND: { code: 'S005', message: 'Settings not found', httpCode: 404 },
83
83
  SETTINGS_FETCH_FAILED: { code: 'S006', message: 'Error when retrieving a Settings', httpCode: 500 },
84
+ // 409, а не 403: фильтр исключений маскирует текст любого 403 общим «Access denied», а здесь
85
+ // клиенту важно получить причину — заказы отключены временно, доступ ни при чём.
86
+ ORDERS_DISABLED_BY_SETTINGS: { code: 'S007', message: 'Ordering is temporarily disabled by the company', httpCode: 409 },
84
87
 
85
88
  FORBIDDEN_API: { code: 'A011', message: 'Access denied', httpCode: 403 },
86
89
  RESOURCE_ACCESS_DENIED: { code: 'A022', message: 'Resource does not belong to your company', httpCode: 400 },
package/constant/index.ts CHANGED
@@ -5,3 +5,5 @@ export * from './import.constant';
5
5
  export * from './validation-i18n-key';
6
6
  export * from './validation-i18n-message';
7
7
  export * from './report.constant';
8
+ export * from './contact-phone.constant';
9
+ export * from './settings.constant';
@@ -0,0 +1,3 @@
1
+ // Текст режима «запрет заказов» («Возобновим работу в 16.00 после инвентаризации»). Витрина рисует
2
+ // его баннером вместо каталога, поэтому это короткое объявление, а не страница: 300 символов.
3
+ export const DISABLE_ORDERS_MESSAGE_MAX_LENGTH = 300;
@@ -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
  /**
@@ -0,0 +1,5 @@
1
+ export enum ContactMessengerEnum {
2
+ WHATSAPP = 'whatsapp',
3
+ TELEGRAM = 'telegram',
4
+ MAX = 'max',
5
+ }
package/enum/index.ts CHANGED
@@ -81,6 +81,7 @@ export * from './order-webshop-available-status-list';
81
81
  export * from './transaction-type-groups';
82
82
  export * from './chat-has-manager-mode.enum';
83
83
  export * from './chat-no-manager-mode.enum';
84
+ export * from './contact-messenger.enum';
84
85
  export * from './match-confidence.enum';
85
86
  export * from './session-platform.enum';
86
87
  export * from './device-platform.enum';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floristcloud/api-lib",
3
- "version": "1.2.112",
3
+ "version": "1.2.114",
4
4
  "description": "",
5
5
  "main": "./build/index.js",
6
6
  "scripts": {
@@ -1,8 +1,23 @@
1
1
  import { z } from 'zod';
2
- import { ChatHasManagerModeEnum, ChatNoManagerModeEnum, DiscountPriceRoundingEnum, LanguageEnum, WebshopModeEnum } from '../../enum';
2
+ import {
3
+ ChatHasManagerModeEnum,
4
+ ChatNoManagerModeEnum,
5
+ ContactMessengerEnum,
6
+ DiscountPriceRoundingEnum,
7
+ LanguageEnum,
8
+ WebshopModeEnum,
9
+ } from '../../enum';
10
+ import {
11
+ CONTACT_MESSENGER_URL_MAX_LENGTH,
12
+ CONTACT_PHONE_LABEL_MAX_LENGTH,
13
+ CONTACT_PHONE_LIST_MAX_LENGTH,
14
+ CONTACT_PHONE_MAX_LENGTH,
15
+ MESSENGERS_REQUIRING_URL,
16
+ } from '../../constant/contact-phone.constant';
17
+ import { DISABLE_ORDERS_MESSAGE_MAX_LENGTH } from '../../constant/settings.constant';
18
+ import { ValidationI18nKey } from '../../constant/validation-i18n-key';
3
19
  import { WebshopMainDisplayModeEnum } from '../../enum/webshop-main-display-mode.enum';
4
20
  import { MenuItemsUrlEnum } from '../../enum/menu-items-url.enum';
5
- import { MoneySchema } from '../money.schema';
6
21
  import { NonNegativeMoneySchema } from '../non-negative-money.schema';
7
22
 
8
23
  export const SettingsWorkingHoursSchema = z.object({
@@ -22,6 +37,50 @@ export const ImportantMessageSchema = z.object({
22
37
  isActive: z.boolean(),
23
38
  });
24
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
+
47
+ // Один контакт из блока контактов вебшопа: номер, подпись отдела («отдел продаж», «склад») и
48
+ // мессенджеры, доступные по этому же номеру. Порядок вывода задаётся порядком элементов в массиве.
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
+ });
77
+
78
+ export const ContactPhoneListSchema = z.array(ContactPhoneSchema).max(CONTACT_PHONE_LIST_MAX_LENGTH);
79
+
80
+ // Один потолок на все три схемы настроек: колонка `disable_orders_message` — TEXT, длину держит
81
+ // только валидация.
82
+ export const DisableOrdersMessageSchema = z.string().max(DISABLE_ORDERS_MESSAGE_MAX_LENGTH);
83
+
25
84
  // Обе величины — деньги: `minSubtotal` сравнивается с суммой заказа, `cost` попадает в
26
85
  // `order.delivery_cost`. Живут они внутри JSON-колонки, куда ALTER TABLE не достаёт, поэтому
27
86
  // точность здесь держит не тип колонки, а формат значения — та же десятичная строка, что и на проводе.
@@ -74,6 +133,8 @@ export const SettingsSchema = z.object({
74
133
  phoneSales: z.string().nullable(),
75
134
  phoneWhatsappSales: z.string().nullable(),
76
135
  telegramChannel: z.string().nullable(),
136
+ maxChannel: z.string().nullable(),
137
+ contactPhoneList: ContactPhoneListSchema.nullable(),
77
138
  menuItemsSort: z.array(z.nativeEnum(MenuItemsUrlEnum)).nullable(),
78
139
  phoneCountryCode: z.array(z.string()),
79
140
  aboutCompany: z.string().nullable(),
@@ -84,6 +145,7 @@ export const SettingsSchema = z.object({
84
145
  deliveryPolicy: z.string().nullable(),
85
146
  writeOffPolicy: z.string().nullable(),
86
147
  disableOrders: z.boolean(),
148
+ disableOrdersMessage: DisableOrdersMessageSchema.nullable(),
87
149
  bankHookEnabled: z.coerce.boolean().optional(),
88
150
  loginImageList: z.array(z.string()),
89
151
  loginImageKeyList: z.array(z.string()),
@@ -164,6 +226,8 @@ export const CreateSettingsSchema = z.object({
164
226
  phoneSales: z.string().nullable().optional(),
165
227
  phoneWhatsappSales: z.string().nullable().optional(),
166
228
  telegramChannel: z.string().nullable().optional(),
229
+ maxChannel: z.string().nullable().optional(),
230
+ contactPhoneList: ContactPhoneListSchema.nullable().optional(),
167
231
  menuItemsSort: z.array(z.nativeEnum(MenuItemsUrlEnum)).nullable().optional(),
168
232
  phoneCountryCode: z.array(z.string()).optional(),
169
233
  aboutCompany: z.string().nullable().optional(),
@@ -174,6 +238,7 @@ export const CreateSettingsSchema = z.object({
174
238
  deliveryPolicy: z.string().nullable().optional(),
175
239
  writeOffPolicy: z.string().nullable().optional(),
176
240
  disableOrders: z.boolean().optional(),
241
+ disableOrdersMessage: DisableOrdersMessageSchema.nullable().optional(),
177
242
  isMessengerEnabled: z.coerce.boolean().optional(),
178
243
  isWriteOffAcceptanceEnabled: z.coerce.boolean().optional(),
179
244
  isInvoiceDownloadEnabled: z.coerce.boolean().optional(),
@@ -228,6 +293,8 @@ export const UpdateSettingsRequestSchema = z.object({
228
293
  phoneSales: z.string().nullable().optional(),
229
294
  phoneWhatsappSales: z.string().nullable().optional(),
230
295
  telegramChannel: z.string().nullable().optional(),
296
+ maxChannel: z.string().nullable().optional(),
297
+ contactPhoneList: ContactPhoneListSchema.nullable().optional(),
231
298
  menuItemsSort: z.array(z.nativeEnum(MenuItemsUrlEnum)).nullable().optional(),
232
299
  phoneCountryCode: z.array(z.string()).optional(),
233
300
  aboutCompany: z.string().nullable().optional(),
@@ -238,6 +305,7 @@ export const UpdateSettingsRequestSchema = z.object({
238
305
  deliveryPolicy: z.string().nullable().optional(),
239
306
  writeOffPolicy: z.string().nullable().optional(),
240
307
  disableOrders: z.boolean().optional(),
308
+ disableOrdersMessage: DisableOrdersMessageSchema.nullable().optional(),
241
309
  isMessengerEnabled: z.coerce.boolean().optional(),
242
310
  isWriteOffAcceptanceEnabled: z.coerce.boolean().optional(),
243
311
  isInvoiceDownloadEnabled: z.coerce.boolean().optional(),