@floristcloud/api-lib 1.2.132 → 1.2.134
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/consignment/analyze-consignment-product-enrichment.query.js +68 -0
- package/build/commands/consignment/execute-consignment-product-enrichment.command.js +40 -0
- package/build/commands/consignment/index.js +2 -0
- package/build/commands/pre-order-collection-item/get-pre-order-collection-item-extended-with-clients-pre-order-product-list.query.js +17 -2
- package/build/constant/consignment-product-enrichment.constant.js +12 -0
- package/build/constant/index.js +1 -0
- package/build/enum/index.js +2 -0
- package/build/enum/product-enrichment-field-state.enum.js +17 -0
- package/build/enum/product-enrichment-field.enum.js +14 -0
- package/build/helpers/pagination.helper.js +10 -1
- package/build/schemas/pre-order-collection-item/pre-order-collection-item.schema.js +13 -2
- package/commands/consignment/analyze-consignment-product-enrichment.query.ts +78 -0
- package/commands/consignment/execute-consignment-product-enrichment.command.ts +47 -0
- package/commands/consignment/index.ts +2 -0
- package/commands/pre-order-collection-item/get-pre-order-collection-item-extended-with-clients-pre-order-product-list.query.ts +24 -9
- package/constant/consignment-product-enrichment.constant.ts +9 -0
- package/constant/index.ts +1 -0
- package/enum/index.ts +2 -0
- package/enum/product-enrichment-field-state.enum.ts +13 -0
- package/enum/product-enrichment-field.enum.ts +14 -0
- package/helpers/pagination.helper.ts +10 -0
- package/package.json +1 -1
- package/schemas/pre-order-collection-item/pre-order-collection-item.schema.ts +14 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AnalyzeConsignmentProductEnrichmentContractQuery = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const query_boolean_helper_1 = require("../../helpers/query-boolean.helper");
|
|
6
|
+
const consignment_product_enrichment_constant_1 = require("../../constant/consignment-product-enrichment.constant");
|
|
7
|
+
const product_enrichment_field_enum_1 = require("../../enum/product-enrichment-field.enum");
|
|
8
|
+
const product_enrichment_field_state_enum_1 = require("../../enum/product-enrichment-field-state.enum");
|
|
9
|
+
const AnalyzeConsignmentProductEnrichmentRequestSchema = zod_1.z.object({
|
|
10
|
+
page: zod_1.z.coerce.number().int().min(1).default(1),
|
|
11
|
+
count: zod_1.z.coerce
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.min(1)
|
|
15
|
+
.max(consignment_product_enrichment_constant_1.CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS.MAX_PAGE_SIZE)
|
|
16
|
+
.default(consignment_product_enrichment_constant_1.CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS.DEFAULT_PAGE_SIZE),
|
|
17
|
+
// Показывать только товары, у которых есть хотя бы одно поле к заполнению или конфликт.
|
|
18
|
+
// По умолчанию false: пользователь видит всю поставку и понимает, почему строк меньше, чем товаров.
|
|
19
|
+
// Не `z.coerce.boolean()`: в query приходит текст, а `Boolean('false') === true` — флаг нельзя
|
|
20
|
+
// было бы выключить явным значением.
|
|
21
|
+
isOnlyChanged: query_boolean_helper_1.queryBooleanSchema,
|
|
22
|
+
});
|
|
23
|
+
const ProductEnrichmentFieldSchema = zod_1.z.object({
|
|
24
|
+
current: zod_1.z.string().nullable(),
|
|
25
|
+
proposed: zod_1.z.string().nullable(),
|
|
26
|
+
state: zod_1.z.nativeEnum(product_enrichment_field_state_enum_1.ProductEnrichmentFieldStateEnum),
|
|
27
|
+
});
|
|
28
|
+
const ProductEnrichmentItemSchema = zod_1.z.object({
|
|
29
|
+
productUUID: zod_1.z.string(),
|
|
30
|
+
productName: zod_1.z.string(),
|
|
31
|
+
article: zod_1.z.string().nullable(),
|
|
32
|
+
size: zod_1.z.string().nullable(),
|
|
33
|
+
categoryName: zod_1.z.string().nullable(),
|
|
34
|
+
imageUUID: zod_1.z.string(),
|
|
35
|
+
imageName: zod_1.z.string(),
|
|
36
|
+
imageUrl: zod_1.z.string(),
|
|
37
|
+
fields: zod_1.z.record(zod_1.z.nativeEnum(product_enrichment_field_enum_1.ProductEnrichmentFieldEnum), ProductEnrichmentFieldSchema),
|
|
38
|
+
});
|
|
39
|
+
const ProductEnrichmentFieldStatsSchema = zod_1.z.object({
|
|
40
|
+
fillable: zod_1.z.number(),
|
|
41
|
+
conflicts: zod_1.z.number(),
|
|
42
|
+
});
|
|
43
|
+
const ProductEnrichmentSummarySchema = zod_1.z.object({
|
|
44
|
+
// Всего товаров в поставке.
|
|
45
|
+
totalProducts: zod_1.z.number(),
|
|
46
|
+
// Из них с привязанной карточкой — только они попадают в таблицу.
|
|
47
|
+
linkedProducts: zod_1.z.number(),
|
|
48
|
+
// Товаров, у которых есть хотя бы одно поле к заполнению.
|
|
49
|
+
fillableProducts: zod_1.z.number(),
|
|
50
|
+
// Товаров, у которых есть хотя бы один конфликт.
|
|
51
|
+
conflictProducts: zod_1.z.number(),
|
|
52
|
+
byField: zod_1.z.record(zod_1.z.nativeEnum(product_enrichment_field_enum_1.ProductEnrichmentFieldEnum), ProductEnrichmentFieldStatsSchema),
|
|
53
|
+
});
|
|
54
|
+
const AnalyzeConsignmentProductEnrichmentResponseSchema = zod_1.z.object({
|
|
55
|
+
data: zod_1.z.object({
|
|
56
|
+
items: zod_1.z.array(ProductEnrichmentItemSchema),
|
|
57
|
+
total: zod_1.z.number(),
|
|
58
|
+
page: zod_1.z.number(),
|
|
59
|
+
count: zod_1.z.number(),
|
|
60
|
+
// Считается по всей поставке, а не по текущей странице: иначе счётчики прыгали бы при листании.
|
|
61
|
+
summary: ProductEnrichmentSummarySchema,
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
var AnalyzeConsignmentProductEnrichmentContractQuery;
|
|
65
|
+
(function (AnalyzeConsignmentProductEnrichmentContractQuery) {
|
|
66
|
+
AnalyzeConsignmentProductEnrichmentContractQuery.RequestSchema = AnalyzeConsignmentProductEnrichmentRequestSchema;
|
|
67
|
+
AnalyzeConsignmentProductEnrichmentContractQuery.ResponseSchema = AnalyzeConsignmentProductEnrichmentResponseSchema;
|
|
68
|
+
})(AnalyzeConsignmentProductEnrichmentContractQuery || (exports.AnalyzeConsignmentProductEnrichmentContractQuery = AnalyzeConsignmentProductEnrichmentContractQuery = {}));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExecuteConsignmentProductEnrichmentContractCommand = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const error_message_schema_1 = require("../../schemas/error-message.schema");
|
|
6
|
+
const consignment_product_enrichment_constant_1 = require("../../constant/consignment-product-enrichment.constant");
|
|
7
|
+
const product_enrichment_field_enum_1 = require("../../enum/product-enrichment-field.enum");
|
|
8
|
+
const ExecuteConsignmentProductEnrichmentItemSchema = zod_1.z.object({
|
|
9
|
+
productUUID: zod_1.z.string().uuid(),
|
|
10
|
+
// Какие поля переносим из карточки в этот товар. Пустой список — товар пропускается.
|
|
11
|
+
fields: zod_1.z.array(zod_1.z.nativeEnum(product_enrichment_field_enum_1.ProductEnrichmentFieldEnum)).min(1),
|
|
12
|
+
});
|
|
13
|
+
const ExecuteConsignmentProductEnrichmentRequestSchema = zod_1.z.object({
|
|
14
|
+
// Фронт шлёт пачками по EXECUTE_BATCH_SIZE и сам собирает итог: одна поставка может
|
|
15
|
+
// содержать до 1200 товаров, и одним запросом это отправлять не нужно.
|
|
16
|
+
// Повторы `productUUID` допустимы и схлопываются на сервере в объединение полей: товар
|
|
17
|
+
// обновляется и логируется один раз, сколько бы записей о нём ни пришло.
|
|
18
|
+
items: zod_1.z.array(ExecuteConsignmentProductEnrichmentItemSchema).min(1).max(consignment_product_enrichment_constant_1.CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS.EXECUTE_BATCH_SIZE),
|
|
19
|
+
});
|
|
20
|
+
const ExecuteConsignmentProductEnrichmentResponseSchema = zod_1.z.object({
|
|
21
|
+
data: zod_1.z.object({
|
|
22
|
+
success: zod_1.z.boolean(),
|
|
23
|
+
// Товаров, у которых изменилось хотя бы одно поле.
|
|
24
|
+
successCount: zod_1.z.number(),
|
|
25
|
+
// Товаров, где менять было нечего: значение уже совпадало или карточка опустела с момента разбора.
|
|
26
|
+
skippedCount: zod_1.z.number(),
|
|
27
|
+
// Товаров, которые кто-то изменил между разбором и применением. Отдельно от skipped, потому
|
|
28
|
+
// что это единственный исход, требующий действия: пользователю нужно перечитать разбор.
|
|
29
|
+
conflictedCount: zod_1.z.number(),
|
|
30
|
+
failedCount: zod_1.z.number(),
|
|
31
|
+
errors: zod_1.z.array(error_message_schema_1.ErrorMessageSchema.extend({
|
|
32
|
+
uuid: zod_1.z.string(),
|
|
33
|
+
})),
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
var ExecuteConsignmentProductEnrichmentContractCommand;
|
|
37
|
+
(function (ExecuteConsignmentProductEnrichmentContractCommand) {
|
|
38
|
+
ExecuteConsignmentProductEnrichmentContractCommand.RequestSchema = ExecuteConsignmentProductEnrichmentRequestSchema;
|
|
39
|
+
ExecuteConsignmentProductEnrichmentContractCommand.ResponseSchema = ExecuteConsignmentProductEnrichmentResponseSchema;
|
|
40
|
+
})(ExecuteConsignmentProductEnrichmentContractCommand || (exports.ExecuteConsignmentProductEnrichmentContractCommand = ExecuteConsignmentProductEnrichmentContractCommand = {}));
|
|
@@ -29,3 +29,5 @@ __exportStar(require("./get-access-clients-consignment.query"), exports);
|
|
|
29
29
|
__exportStar(require("./get-consignment-filters-values.query"), exports);
|
|
30
30
|
__exportStar(require("./execute-consignment-product-update.command"), exports);
|
|
31
31
|
__exportStar(require("./determine-consignment-product-images.command"), exports);
|
|
32
|
+
__exportStar(require("./analyze-consignment-product-enrichment.query"), exports);
|
|
33
|
+
__exportStar(require("./execute-consignment-product-enrichment.command"), exports);
|
|
@@ -4,16 +4,31 @@ exports.GetPreOrderCollectionItemExtendedWithClientsPreOrderProductListContractQ
|
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const schemas_1 = require("../../schemas");
|
|
6
6
|
const enum_1 = require("../../enum");
|
|
7
|
-
const
|
|
7
|
+
const pagination_helper_1 = require("../../helpers/pagination.helper");
|
|
8
|
+
const GetPreOrderCollectionItemExtendedWithClientsPreOrderProductListRequestSchema = zod_1.z
|
|
9
|
+
.object({
|
|
10
|
+
// Пагинация опциональна: без page/count отдаётся весь отфильтрованный список (поведение до FC-475).
|
|
11
|
+
// Неполная пара отклоняется — иначе `?count=20` молча вернул бы всю коллекцию с HTTP 200.
|
|
12
|
+
page: zod_1.z.coerce.number().int().positive().optional().describe('1-based page number, requires "count".'),
|
|
13
|
+
count: zod_1.z.coerce.number().int().positive().optional().describe('Positions per page. Without it the whole list is returned.'),
|
|
8
14
|
preOrderCollectionUUID: zod_1.z.string().optional(),
|
|
15
|
+
// Точечный режим: одна позиция коллекции — «кто её предзаказал».
|
|
16
|
+
preOrderCollectionItemUUID: zod_1.z.string().optional(),
|
|
17
|
+
// Поиск по позиции (название / категория / артикул).
|
|
9
18
|
searchQuery: zod_1.z.string().optional(),
|
|
19
|
+
// Поиск по клиенту (имя / код) внутри вложенного списка предзаказов.
|
|
20
|
+
clientSearchQuery: zod_1.z.string().optional(),
|
|
10
21
|
statuses: zod_1.z.nativeEnum(enum_1.PreOrderStatusEnum).array().optional(),
|
|
11
22
|
preOrderCreatedAtFrom: zod_1.z.coerce.date().optional(),
|
|
12
23
|
preOrderCreatedAtTo: zod_1.z.coerce.date().optional(),
|
|
24
|
+
})
|
|
25
|
+
.refine(pagination_helper_1.isPaginationPairComplete, {
|
|
26
|
+
message: 'validation.pageAndCountGoTogether',
|
|
27
|
+
path: ['count'],
|
|
13
28
|
});
|
|
14
29
|
const GetPreOrderCollectionItemExtendedWithClientsPreOrderProductListResponseSchema = zod_1.z.object({
|
|
15
30
|
message: zod_1.z.string().optional(),
|
|
16
|
-
data:
|
|
31
|
+
data: schemas_1.PreOrderCollectionItemExtendedWithClientsPreOrderProductListSchema,
|
|
17
32
|
});
|
|
18
33
|
var GetPreOrderCollectionItemExtendedWithClientsPreOrderProductListContractQuery;
|
|
19
34
|
(function (GetPreOrderCollectionItemExtendedWithClientsPreOrderProductListContractQuery) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS = void 0;
|
|
4
|
+
// Лимиты операции «получить характеристики из номенклатуры».
|
|
5
|
+
// Анализ читает всю поставку целиком (потолок задан лимитами импорта: MAX_ROWS_WITHOUT_IMAGES = 1200),
|
|
6
|
+
// но наружу отдаёт страницами, а применение принимает пачками — чтобы ни ответ, ни запрос не росли
|
|
7
|
+
// вместе с размером поставки.
|
|
8
|
+
exports.CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS = {
|
|
9
|
+
DEFAULT_PAGE_SIZE: 50,
|
|
10
|
+
MAX_PAGE_SIZE: 200,
|
|
11
|
+
EXECUTE_BATCH_SIZE: 100,
|
|
12
|
+
};
|
package/build/constant/index.js
CHANGED
|
@@ -25,3 +25,4 @@ __exportStar(require("./oksm"), exports);
|
|
|
25
25
|
__exportStar(require("./okei"), exports);
|
|
26
26
|
__exportStar(require("./contact-phone.constant"), exports);
|
|
27
27
|
__exportStar(require("./settings.constant"), exports);
|
|
28
|
+
__exportStar(require("./consignment-product-enrichment.constant"), exports);
|
package/build/enum/index.js
CHANGED
|
@@ -122,3 +122,5 @@ __exportStar(require("./transfer-document-state.enum"), exports);
|
|
|
122
122
|
__exportStar(require("./transfer-document-kind.enum"), exports);
|
|
123
123
|
__exportStar(require("./transfer-document-item-kind.enum"), exports);
|
|
124
124
|
__exportStar(require("./write-off-report-date-basis.enum"), exports);
|
|
125
|
+
__exportStar(require("./product-enrichment-field.enum"), exports);
|
|
126
|
+
__exportStar(require("./product-enrichment-field-state.enum"), exports);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProductEnrichmentFieldStateEnum = void 0;
|
|
4
|
+
// Состояние одного поля товара относительно привязанной карточки номенклатуры.
|
|
5
|
+
// Определяет и подсказку в интерфейсе («собственные данные» / «из номенклатуры»), и то,
|
|
6
|
+
// отмечен ли чекбокс по умолчанию: предзаполняется только FILLABLE.
|
|
7
|
+
var ProductEnrichmentFieldStateEnum;
|
|
8
|
+
(function (ProductEnrichmentFieldStateEnum) {
|
|
9
|
+
// У товара пусто, в карточке значение есть — это и есть «дополнить».
|
|
10
|
+
ProductEnrichmentFieldStateEnum["FILLABLE"] = "FILLABLE";
|
|
11
|
+
// Оба заполнены и различаются. Показываем, но по умолчанию не отмечаем.
|
|
12
|
+
ProductEnrichmentFieldStateEnum["CONFLICT"] = "CONFLICT";
|
|
13
|
+
// Значения совпадают — брать нечего.
|
|
14
|
+
ProductEnrichmentFieldStateEnum["SAME"] = "SAME";
|
|
15
|
+
// В карточке пусто — источника нет.
|
|
16
|
+
ProductEnrichmentFieldStateEnum["NO_SOURCE"] = "NO_SOURCE";
|
|
17
|
+
})(ProductEnrichmentFieldStateEnum || (exports.ProductEnrichmentFieldStateEnum = ProductEnrichmentFieldStateEnum = {}));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PRODUCT_ENRICHMENT_FIELD_LIST = exports.ProductEnrichmentFieldEnum = void 0;
|
|
4
|
+
// Поля товара, которые можно дополнить из привязанной карточки номенклатуры (ProductImage).
|
|
5
|
+
// Значения совпадают с именами полей Product и ProductImage — это ключи, по которым фронт
|
|
6
|
+
// собирает решения, поэтому переименование значения ломает контракт.
|
|
7
|
+
var ProductEnrichmentFieldEnum;
|
|
8
|
+
(function (ProductEnrichmentFieldEnum) {
|
|
9
|
+
ProductEnrichmentFieldEnum["COLOR"] = "color";
|
|
10
|
+
ProductEnrichmentFieldEnum["COUNTRY"] = "country";
|
|
11
|
+
ProductEnrichmentFieldEnum["GROWER"] = "grower";
|
|
12
|
+
})(ProductEnrichmentFieldEnum || (exports.ProductEnrichmentFieldEnum = ProductEnrichmentFieldEnum = {}));
|
|
13
|
+
const PRODUCT_ENRICHMENT_FIELD_KEYS = Object.keys(ProductEnrichmentFieldEnum);
|
|
14
|
+
exports.PRODUCT_ENRICHMENT_FIELD_LIST = PRODUCT_ENRICHMENT_FIELD_KEYS.map(key => ProductEnrichmentFieldEnum[key]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isPageAccompaniedByCount = void 0;
|
|
3
|
+
exports.isPaginationPairComplete = exports.isPageAccompaniedByCount = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* `page` имеет смысл только вместе с `count`.
|
|
6
6
|
*
|
|
@@ -10,3 +10,12 @@ exports.isPageAccompaniedByCount = void 0;
|
|
|
10
10
|
*/
|
|
11
11
|
const isPageAccompaniedByCount = (value) => value.page === undefined || value.count !== undefined;
|
|
12
12
|
exports.isPageAccompaniedByCount = isPageAccompaniedByCount;
|
|
13
|
+
/**
|
|
14
|
+
* Строгий вариант: валидны только «оба параметра» или «ни одного».
|
|
15
|
+
*
|
|
16
|
+
* Нужен там, где `count` в одиночку — правдоподобная форма запроса («дай 20 строк»), а не описка:
|
|
17
|
+
* такой запрос молча вернул бы весь список с HTTP 200, и клиент не отличил бы это от «данных
|
|
18
|
+
* ровно столько». Отчёты обходятся односторонней проверкой выше — там пара всегда приходит из UI.
|
|
19
|
+
*/
|
|
20
|
+
const isPaginationPairComplete = (value) => (value.page === undefined) === (value.count === undefined);
|
|
21
|
+
exports.isPaginationPairComplete = isPaginationPairComplete;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PreOrderCollectionItemGroupedListSchema = exports.PreOrderCollectionItemGroupedSchema = exports.PreOrderCollectionItemExtendedWithPreOrderCollectionListSchema = exports.PreOrderCollectionItemExtendedWithPreOrderCollectionSchema = exports.PreOrderCollectionItemExtendedListSchema = exports.PreOrderCollectionItemExtendedWithClientsPreOrderProductSchema = exports.PreOrderCollectionItemExtendedSchema = exports.MatchingProductSchema = exports.UpdatePreOrderCollectionItemSchema = exports.CreatePreOrderCollectionItemSchema = exports.PreOrderCollectionItemSchema = void 0;
|
|
3
|
+
exports.PreOrderCollectionItemGroupedListSchema = exports.PreOrderCollectionItemGroupedSchema = exports.PreOrderCollectionItemExtendedWithPreOrderCollectionListSchema = exports.PreOrderCollectionItemExtendedWithPreOrderCollectionSchema = exports.PreOrderCollectionItemExtendedListSchema = exports.PreOrderCollectionItemExtendedWithClientsPreOrderProductListSchema = exports.PreOrderCollectionItemExtendedWithClientsPreOrderProductSchema = exports.PreOrderCollectionItemClientPreOrderProductSchema = exports.PreOrderCollectionItemExtendedSchema = exports.MatchingProductSchema = exports.UpdatePreOrderCollectionItemSchema = exports.CreatePreOrderCollectionItemSchema = exports.PreOrderCollectionItemSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const type_schema_1 = require("../type/type.schema");
|
|
6
6
|
const category_schema_1 = require("../category/category.schema");
|
|
7
7
|
const product_image_schema_1 = require("../product-image/product-image.schema");
|
|
8
8
|
const client_1 = require("../client");
|
|
9
9
|
const pre_order_product_schema_1 = require("../pre-order-product/pre-order-product.schema");
|
|
10
|
+
const pre_order_schema_1 = require("../pre-order/pre-order.schema");
|
|
10
11
|
const enum_1 = require("../../enum");
|
|
11
12
|
const pre_order_collection_item_multiplicity_option_schema_1 = require("./pre-order-collection-item-multiplicity-option.schema");
|
|
12
13
|
const money_schema_1 = require("../money.schema");
|
|
@@ -112,8 +113,18 @@ exports.PreOrderCollectionItemExtendedSchema = exports.PreOrderCollectionItemSch
|
|
|
112
113
|
multiplicityOptions: zod_1.z.array(pre_order_collection_item_multiplicity_option_schema_1.PreOrderCollectionItemMultiplicityOptionSchema).optional(),
|
|
113
114
|
preOrderedQuantity: zod_1.z.number().optional(),
|
|
114
115
|
});
|
|
116
|
+
// Метаданные предзаказа для колонок «№ / дата / статус» в списке «кто предзаказал позицию».
|
|
117
|
+
exports.PreOrderCollectionItemClientPreOrderProductSchema = zod_1.z.object({
|
|
118
|
+
client: client_1.ClientSchema,
|
|
119
|
+
preOrderProduct: pre_order_product_schema_1.PreOrderProductSchema,
|
|
120
|
+
preOrder: pre_order_schema_1.PreOrderSchema.pick({ uuid: true, preOrderNum: true, status: true, createdAt: true }),
|
|
121
|
+
});
|
|
115
122
|
exports.PreOrderCollectionItemExtendedWithClientsPreOrderProductSchema = exports.PreOrderCollectionItemExtendedSchema.extend({
|
|
116
|
-
clientsWithPreOrderProduct: zod_1.z.array(
|
|
123
|
+
clientsWithPreOrderProduct: zod_1.z.array(exports.PreOrderCollectionItemClientPreOrderProductSchema),
|
|
124
|
+
});
|
|
125
|
+
exports.PreOrderCollectionItemExtendedWithClientsPreOrderProductListSchema = zod_1.z.object({
|
|
126
|
+
list: zod_1.z.array(exports.PreOrderCollectionItemExtendedWithClientsPreOrderProductSchema),
|
|
127
|
+
total: zod_1.z.number(),
|
|
117
128
|
});
|
|
118
129
|
exports.PreOrderCollectionItemExtendedListSchema = zod_1.z.object({
|
|
119
130
|
list: zod_1.z.array(exports.PreOrderCollectionItemExtendedSchema),
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { queryBooleanSchema } from '../../helpers/query-boolean.helper';
|
|
3
|
+
import { CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS } from '../../constant/consignment-product-enrichment.constant';
|
|
4
|
+
import { ProductEnrichmentFieldEnum } from '../../enum/product-enrichment-field.enum';
|
|
5
|
+
import { ProductEnrichmentFieldStateEnum } from '../../enum/product-enrichment-field-state.enum';
|
|
6
|
+
|
|
7
|
+
const AnalyzeConsignmentProductEnrichmentRequestSchema = z.object({
|
|
8
|
+
page: z.coerce.number().int().min(1).default(1),
|
|
9
|
+
count: z.coerce
|
|
10
|
+
.number()
|
|
11
|
+
.int()
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS.MAX_PAGE_SIZE)
|
|
14
|
+
.default(CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS.DEFAULT_PAGE_SIZE),
|
|
15
|
+
// Показывать только товары, у которых есть хотя бы одно поле к заполнению или конфликт.
|
|
16
|
+
// По умолчанию false: пользователь видит всю поставку и понимает, почему строк меньше, чем товаров.
|
|
17
|
+
// Не `z.coerce.boolean()`: в query приходит текст, а `Boolean('false') === true` — флаг нельзя
|
|
18
|
+
// было бы выключить явным значением.
|
|
19
|
+
isOnlyChanged: queryBooleanSchema,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const ProductEnrichmentFieldSchema = z.object({
|
|
23
|
+
current: z.string().nullable(),
|
|
24
|
+
proposed: z.string().nullable(),
|
|
25
|
+
state: z.nativeEnum(ProductEnrichmentFieldStateEnum),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const ProductEnrichmentItemSchema = z.object({
|
|
29
|
+
productUUID: z.string(),
|
|
30
|
+
productName: z.string(),
|
|
31
|
+
article: z.string().nullable(),
|
|
32
|
+
size: z.string().nullable(),
|
|
33
|
+
categoryName: z.string().nullable(),
|
|
34
|
+
imageUUID: z.string(),
|
|
35
|
+
imageName: z.string(),
|
|
36
|
+
imageUrl: z.string(),
|
|
37
|
+
fields: z.record(z.nativeEnum(ProductEnrichmentFieldEnum), ProductEnrichmentFieldSchema),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const ProductEnrichmentFieldStatsSchema = z.object({
|
|
41
|
+
fillable: z.number(),
|
|
42
|
+
conflicts: z.number(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const ProductEnrichmentSummarySchema = z.object({
|
|
46
|
+
// Всего товаров в поставке.
|
|
47
|
+
totalProducts: z.number(),
|
|
48
|
+
// Из них с привязанной карточкой — только они попадают в таблицу.
|
|
49
|
+
linkedProducts: z.number(),
|
|
50
|
+
// Товаров, у которых есть хотя бы одно поле к заполнению.
|
|
51
|
+
fillableProducts: z.number(),
|
|
52
|
+
// Товаров, у которых есть хотя бы один конфликт.
|
|
53
|
+
conflictProducts: z.number(),
|
|
54
|
+
byField: z.record(z.nativeEnum(ProductEnrichmentFieldEnum), ProductEnrichmentFieldStatsSchema),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const AnalyzeConsignmentProductEnrichmentResponseSchema = z.object({
|
|
58
|
+
data: z.object({
|
|
59
|
+
items: z.array(ProductEnrichmentItemSchema),
|
|
60
|
+
total: z.number(),
|
|
61
|
+
page: z.number(),
|
|
62
|
+
count: z.number(),
|
|
63
|
+
// Считается по всей поставке, а не по текущей странице: иначе счётчики прыгали бы при листании.
|
|
64
|
+
summary: ProductEnrichmentSummarySchema,
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export namespace AnalyzeConsignmentProductEnrichmentContractQuery {
|
|
69
|
+
export const RequestSchema = AnalyzeConsignmentProductEnrichmentRequestSchema;
|
|
70
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
71
|
+
|
|
72
|
+
export const ResponseSchema = AnalyzeConsignmentProductEnrichmentResponseSchema;
|
|
73
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
74
|
+
|
|
75
|
+
export type Item = z.infer<typeof ProductEnrichmentItemSchema>;
|
|
76
|
+
export type FieldDiff = z.infer<typeof ProductEnrichmentFieldSchema>;
|
|
77
|
+
export type Summary = z.infer<typeof ProductEnrichmentSummarySchema>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ErrorMessageSchema } from '../../schemas/error-message.schema';
|
|
3
|
+
import { CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS } from '../../constant/consignment-product-enrichment.constant';
|
|
4
|
+
import { ProductEnrichmentFieldEnum } from '../../enum/product-enrichment-field.enum';
|
|
5
|
+
|
|
6
|
+
const ExecuteConsignmentProductEnrichmentItemSchema = z.object({
|
|
7
|
+
productUUID: z.string().uuid(),
|
|
8
|
+
// Какие поля переносим из карточки в этот товар. Пустой список — товар пропускается.
|
|
9
|
+
fields: z.array(z.nativeEnum(ProductEnrichmentFieldEnum)).min(1),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const ExecuteConsignmentProductEnrichmentRequestSchema = z.object({
|
|
13
|
+
// Фронт шлёт пачками по EXECUTE_BATCH_SIZE и сам собирает итог: одна поставка может
|
|
14
|
+
// содержать до 1200 товаров, и одним запросом это отправлять не нужно.
|
|
15
|
+
// Повторы `productUUID` допустимы и схлопываются на сервере в объединение полей: товар
|
|
16
|
+
// обновляется и логируется один раз, сколько бы записей о нём ни пришло.
|
|
17
|
+
items: z.array(ExecuteConsignmentProductEnrichmentItemSchema).min(1).max(CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS.EXECUTE_BATCH_SIZE),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const ExecuteConsignmentProductEnrichmentResponseSchema = z.object({
|
|
21
|
+
data: z.object({
|
|
22
|
+
success: z.boolean(),
|
|
23
|
+
// Товаров, у которых изменилось хотя бы одно поле.
|
|
24
|
+
successCount: z.number(),
|
|
25
|
+
// Товаров, где менять было нечего: значение уже совпадало или карточка опустела с момента разбора.
|
|
26
|
+
skippedCount: z.number(),
|
|
27
|
+
// Товаров, которые кто-то изменил между разбором и применением. Отдельно от skipped, потому
|
|
28
|
+
// что это единственный исход, требующий действия: пользователю нужно перечитать разбор.
|
|
29
|
+
conflictedCount: z.number(),
|
|
30
|
+
failedCount: z.number(),
|
|
31
|
+
errors: z.array(
|
|
32
|
+
ErrorMessageSchema.extend({
|
|
33
|
+
uuid: z.string(),
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export namespace ExecuteConsignmentProductEnrichmentContractCommand {
|
|
40
|
+
export const RequestSchema = ExecuteConsignmentProductEnrichmentRequestSchema;
|
|
41
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
42
|
+
|
|
43
|
+
export const ResponseSchema = ExecuteConsignmentProductEnrichmentResponseSchema;
|
|
44
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
45
|
+
|
|
46
|
+
export type Item = z.infer<typeof ExecuteConsignmentProductEnrichmentItemSchema>;
|
|
47
|
+
}
|
|
@@ -13,3 +13,5 @@ export * from './get-access-clients-consignment.query';
|
|
|
13
13
|
export * from './get-consignment-filters-values.query';
|
|
14
14
|
export * from './execute-consignment-product-update.command';
|
|
15
15
|
export * from './determine-consignment-product-images.command';
|
|
16
|
+
export * from './analyze-consignment-product-enrichment.query';
|
|
17
|
+
export * from './execute-consignment-product-enrichment.command';
|
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { PreOrderCollectionItemExtendedWithClientsPreOrderProductListSchema } from '../../schemas';
|
|
3
3
|
import { PreOrderStatusEnum } from '../../enum';
|
|
4
|
+
import { isPaginationPairComplete } from '../../helpers/pagination.helper';
|
|
4
5
|
|
|
5
|
-
const GetPreOrderCollectionItemExtendedWithClientsPreOrderProductListRequestSchema = z
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const GetPreOrderCollectionItemExtendedWithClientsPreOrderProductListRequestSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
// Пагинация опциональна: без page/count отдаётся весь отфильтрованный список (поведение до FC-475).
|
|
9
|
+
// Неполная пара отклоняется — иначе `?count=20` молча вернул бы всю коллекцию с HTTP 200.
|
|
10
|
+
page: z.coerce.number().int().positive().optional().describe('1-based page number, requires "count".'),
|
|
11
|
+
count: z.coerce.number().int().positive().optional().describe('Positions per page. Without it the whole list is returned.'),
|
|
12
|
+
preOrderCollectionUUID: z.string().optional(),
|
|
13
|
+
// Точечный режим: одна позиция коллекции — «кто её предзаказал».
|
|
14
|
+
preOrderCollectionItemUUID: z.string().optional(),
|
|
15
|
+
// Поиск по позиции (название / категория / артикул).
|
|
16
|
+
searchQuery: z.string().optional(),
|
|
17
|
+
// Поиск по клиенту (имя / код) внутри вложенного списка предзаказов.
|
|
18
|
+
clientSearchQuery: z.string().optional(),
|
|
19
|
+
statuses: z.nativeEnum(PreOrderStatusEnum).array().optional(),
|
|
20
|
+
preOrderCreatedAtFrom: z.coerce.date().optional(),
|
|
21
|
+
preOrderCreatedAtTo: z.coerce.date().optional(),
|
|
22
|
+
})
|
|
23
|
+
.refine(isPaginationPairComplete, {
|
|
24
|
+
message: 'validation.pageAndCountGoTogether',
|
|
25
|
+
path: ['count'],
|
|
26
|
+
});
|
|
12
27
|
|
|
13
28
|
const GetPreOrderCollectionItemExtendedWithClientsPreOrderProductListResponseSchema = z.object({
|
|
14
29
|
message: z.string().optional(),
|
|
15
|
-
data:
|
|
30
|
+
data: PreOrderCollectionItemExtendedWithClientsPreOrderProductListSchema,
|
|
16
31
|
});
|
|
17
32
|
|
|
18
33
|
export namespace GetPreOrderCollectionItemExtendedWithClientsPreOrderProductListContractQuery {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Лимиты операции «получить характеристики из номенклатуры».
|
|
2
|
+
// Анализ читает всю поставку целиком (потолок задан лимитами импорта: MAX_ROWS_WITHOUT_IMAGES = 1200),
|
|
3
|
+
// но наружу отдаёт страницами, а применение принимает пачками — чтобы ни ответ, ни запрос не росли
|
|
4
|
+
// вместе с размером поставки.
|
|
5
|
+
export const CONSIGNMENT_PRODUCT_ENRICHMENT_LIMITS = {
|
|
6
|
+
DEFAULT_PAGE_SIZE: 50,
|
|
7
|
+
MAX_PAGE_SIZE: 200,
|
|
8
|
+
EXECUTE_BATCH_SIZE: 100,
|
|
9
|
+
} as const;
|
package/constant/index.ts
CHANGED
package/enum/index.ts
CHANGED
|
@@ -106,3 +106,5 @@ export * from './transfer-document-state.enum';
|
|
|
106
106
|
export * from './transfer-document-kind.enum';
|
|
107
107
|
export * from './transfer-document-item-kind.enum';
|
|
108
108
|
export * from './write-off-report-date-basis.enum';
|
|
109
|
+
export * from './product-enrichment-field.enum';
|
|
110
|
+
export * from './product-enrichment-field-state.enum';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Состояние одного поля товара относительно привязанной карточки номенклатуры.
|
|
2
|
+
// Определяет и подсказку в интерфейсе («собственные данные» / «из номенклатуры»), и то,
|
|
3
|
+
// отмечен ли чекбокс по умолчанию: предзаполняется только FILLABLE.
|
|
4
|
+
export enum ProductEnrichmentFieldStateEnum {
|
|
5
|
+
// У товара пусто, в карточке значение есть — это и есть «дополнить».
|
|
6
|
+
FILLABLE = 'FILLABLE',
|
|
7
|
+
// Оба заполнены и различаются. Показываем, но по умолчанию не отмечаем.
|
|
8
|
+
CONFLICT = 'CONFLICT',
|
|
9
|
+
// Значения совпадают — брать нечего.
|
|
10
|
+
SAME = 'SAME',
|
|
11
|
+
// В карточке пусто — источника нет.
|
|
12
|
+
NO_SOURCE = 'NO_SOURCE',
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Поля товара, которые можно дополнить из привязанной карточки номенклатуры (ProductImage).
|
|
2
|
+
// Значения совпадают с именами полей Product и ProductImage — это ключи, по которым фронт
|
|
3
|
+
// собирает решения, поэтому переименование значения ломает контракт.
|
|
4
|
+
export enum ProductEnrichmentFieldEnum {
|
|
5
|
+
COLOR = 'color',
|
|
6
|
+
COUNTRY = 'country',
|
|
7
|
+
GROWER = 'grower',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const PRODUCT_ENRICHMENT_FIELD_KEYS = Object.keys(ProductEnrichmentFieldEnum) as Array<keyof typeof ProductEnrichmentFieldEnum>;
|
|
11
|
+
|
|
12
|
+
export const PRODUCT_ENRICHMENT_FIELD_LIST: readonly ProductEnrichmentFieldEnum[] = PRODUCT_ENRICHMENT_FIELD_KEYS.map(
|
|
13
|
+
key => ProductEnrichmentFieldEnum[key],
|
|
14
|
+
);
|
|
@@ -7,3 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export const isPageAccompaniedByCount = (value: { page?: number; count?: number }): boolean =>
|
|
9
9
|
value.page === undefined || value.count !== undefined;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Строгий вариант: валидны только «оба параметра» или «ни одного».
|
|
13
|
+
*
|
|
14
|
+
* Нужен там, где `count` в одиночку — правдоподобная форма запроса («дай 20 строк»), а не описка:
|
|
15
|
+
* такой запрос молча вернул бы весь список с HTTP 200, и клиент не отличил бы это от «данных
|
|
16
|
+
* ровно столько». Отчёты обходятся односторонней проверкой выше — там пара всегда приходит из UI.
|
|
17
|
+
*/
|
|
18
|
+
export const isPaginationPairComplete = (value: { page?: number; count?: number }): boolean =>
|
|
19
|
+
(value.page === undefined) === (value.count === undefined);
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import { CategorySchema } from '../category/category.schema';
|
|
|
4
4
|
import { ProductImageSchema } from '../product-image/product-image.schema';
|
|
5
5
|
import { ClientSchema } from '../client';
|
|
6
6
|
import { PreOrderProductSchema } from '../pre-order-product/pre-order-product.schema';
|
|
7
|
+
import { PreOrderSchema } from '../pre-order/pre-order.schema';
|
|
7
8
|
import { SalesUnitEnum } from '../../enum';
|
|
8
9
|
import { PreOrderCollectionItemMultiplicityOptionSchema } from './pre-order-collection-item-multiplicity-option.schema';
|
|
9
10
|
import { MoneySchema } from '../money.schema';
|
|
@@ -115,8 +116,20 @@ export const PreOrderCollectionItemExtendedSchema = PreOrderCollectionItemSchema
|
|
|
115
116
|
preOrderedQuantity: z.number().optional(),
|
|
116
117
|
});
|
|
117
118
|
|
|
119
|
+
// Метаданные предзаказа для колонок «№ / дата / статус» в списке «кто предзаказал позицию».
|
|
120
|
+
export const PreOrderCollectionItemClientPreOrderProductSchema = z.object({
|
|
121
|
+
client: ClientSchema,
|
|
122
|
+
preOrderProduct: PreOrderProductSchema,
|
|
123
|
+
preOrder: PreOrderSchema.pick({ uuid: true, preOrderNum: true, status: true, createdAt: true }),
|
|
124
|
+
});
|
|
125
|
+
|
|
118
126
|
export const PreOrderCollectionItemExtendedWithClientsPreOrderProductSchema = PreOrderCollectionItemExtendedSchema.extend({
|
|
119
|
-
clientsWithPreOrderProduct: z.array(
|
|
127
|
+
clientsWithPreOrderProduct: z.array(PreOrderCollectionItemClientPreOrderProductSchema),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export const PreOrderCollectionItemExtendedWithClientsPreOrderProductListSchema = z.object({
|
|
131
|
+
list: z.array(PreOrderCollectionItemExtendedWithClientsPreOrderProductSchema),
|
|
132
|
+
total: z.number(),
|
|
120
133
|
});
|
|
121
134
|
|
|
122
135
|
export const PreOrderCollectionItemExtendedListSchema = z.object({
|