@floristcloud/api-lib 1.2.133 → 1.2.135
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/attribute-normalization/analyze-attribute-normalization.query.js +19 -0
- package/build/commands/attribute-normalization/execute-attribute-normalization.command.js +28 -0
- package/build/commands/attribute-normalization/get-grower-suggestions.query.js +26 -0
- package/build/commands/attribute-normalization/index.js +9 -0
- package/build/commands/index.js +1 -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/commands/product-image/analyze-product-image-merge.query.js +22 -0
- package/build/commands/product-image/execute-product-image-merge.command.js +25 -0
- package/build/commands/product-image/index.js +5 -0
- package/build/constant/attribute-normalization.constant.js +12 -0
- package/build/constant/error.js +50 -0
- package/build/constant/index.js +2 -0
- package/build/constant/product-image-merge.constant.js +11 -0
- package/build/enum/action-log-type.enum.js +2 -0
- package/build/enum/index.js +5 -0
- package/build/enum/normalization-decision-action.enum.js +13 -0
- package/build/enum/normalization-target.enum.js +12 -0
- package/build/enum/normalization-value-state.enum.js +13 -0
- package/build/enum/normalized-attribute.enum.js +10 -0
- package/build/enum/product-image-duplicate-mode.enum.js +11 -0
- package/build/helpers/pagination.helper.js +10 -1
- package/build/schemas/action-log/action-log-payload.schema.js +4 -0
- package/build/schemas/action-log/merge-product-images-action-log.schema.js +11 -0
- package/build/schemas/action-log/normalize-attributes-action-log.schema.js +21 -0
- package/build/schemas/attribute-normalization/attribute-normalization-scope.schema.js +32 -0
- package/build/schemas/attribute-normalization/normalization-decision.schema.js +20 -0
- package/build/schemas/attribute-normalization/normalization-row-count.schema.js +8 -0
- package/build/schemas/attribute-normalization/normalization-summary.schema.js +30 -0
- package/build/schemas/attribute-normalization/normalization-value.schema.js +19 -0
- package/build/schemas/attribute-normalization/product-image-duplicate-forecast.schema.js +15 -0
- package/build/schemas/index.js +8 -0
- package/build/schemas/pre-order-collection-item/pre-order-collection-item.schema.js +13 -2
- package/build/schemas/product-image/product-image-merge-candidate.schema.js +22 -0
- package/build/schemas/product-image/product-image-merge-group.schema.js +11 -0
- package/commands/attribute-normalization/analyze-attribute-normalization.query.ts +24 -0
- package/commands/attribute-normalization/execute-attribute-normalization.command.ts +32 -0
- package/commands/attribute-normalization/get-grower-suggestions.query.ts +28 -0
- package/commands/attribute-normalization/index.ts +3 -0
- package/commands/index.ts +1 -0
- package/commands/pre-order-collection-item/get-pre-order-collection-item-extended-with-clients-pre-order-product-list.query.ts +24 -9
- package/commands/product-image/analyze-product-image-merge.query.ts +28 -0
- package/commands/product-image/execute-product-image-merge.command.ts +27 -0
- package/commands/product-image/index.ts +2 -0
- package/constant/attribute-normalization.constant.ts +9 -0
- package/constant/error.ts +50 -0
- package/constant/index.ts +2 -0
- package/constant/product-image-merge.constant.ts +8 -0
- package/enum/action-log-type.enum.ts +2 -0
- package/enum/index.ts +5 -0
- package/enum/normalization-decision-action.enum.ts +9 -0
- package/enum/normalization-target.enum.ts +8 -0
- package/enum/normalization-value-state.enum.ts +9 -0
- package/enum/normalized-attribute.enum.ts +6 -0
- package/enum/product-image-duplicate-mode.enum.ts +7 -0
- package/helpers/pagination.helper.ts +10 -0
- package/package.json +1 -1
- package/schemas/action-log/action-log-payload.schema.ts +4 -0
- package/schemas/action-log/merge-product-images-action-log.schema.ts +9 -0
- package/schemas/action-log/normalize-attributes-action-log.schema.ts +21 -0
- package/schemas/attribute-normalization/attribute-normalization-scope.schema.ts +31 -0
- package/schemas/attribute-normalization/normalization-decision.schema.ts +18 -0
- package/schemas/attribute-normalization/normalization-row-count.schema.ts +6 -0
- package/schemas/attribute-normalization/normalization-summary.schema.ts +28 -0
- package/schemas/attribute-normalization/normalization-value.schema.ts +17 -0
- package/schemas/attribute-normalization/product-image-duplicate-forecast.schema.ts +13 -0
- package/schemas/index.ts +8 -0
- package/schemas/pre-order-collection-item/pre-order-collection-item.schema.ts +14 -1
- package/schemas/product-image/product-image-merge-candidate.schema.ts +20 -0
- package/schemas/product-image/product-image-merge-group.schema.ts +9 -0
|
@@ -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,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProductImageMergeCandidateSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
// Карточка — участник группы дублей, с числом ссылок на неё.
|
|
6
|
+
exports.ProductImageMergeCandidateSchema = zod_1.z.object({
|
|
7
|
+
uuid: zod_1.z.string(),
|
|
8
|
+
name: zod_1.z.string(),
|
|
9
|
+
// Ключ файла в хранилище: без него нечем показать превью фото группы, а основную
|
|
10
|
+
// карточку админ выбирает в том числе по картинке.
|
|
11
|
+
productImageUrl: zod_1.z.string(),
|
|
12
|
+
categoryUUID: zod_1.z.string().nullable(),
|
|
13
|
+
grower: zod_1.z.string().nullable(),
|
|
14
|
+
country: zod_1.z.string().nullable(),
|
|
15
|
+
color: zod_1.z.string().nullable(),
|
|
16
|
+
productCount: zod_1.z.number(),
|
|
17
|
+
preOrderCollectionItemCount: zod_1.z.number(),
|
|
18
|
+
aliasCount: zod_1.z.number(),
|
|
19
|
+
// Карточка, которую операция предлагает оставить: больше всего ссылок, при равенстве —
|
|
20
|
+
// меньший uuid, чтобы повторный анализ давал тот же ответ.
|
|
21
|
+
isPrimary: zod_1.z.boolean(),
|
|
22
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProductImageMergeGroupSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const product_image_merge_candidate_schema_1 = require("./product-image-merge-candidate.schema");
|
|
6
|
+
// Группа карточек, неразличимых по идентичности.
|
|
7
|
+
exports.ProductImageMergeGroupSchema = zod_1.z.object({
|
|
8
|
+
// Ключ группы — нормализованная идентичность, по которой карточки признаны одинаковыми.
|
|
9
|
+
key: zod_1.z.string(),
|
|
10
|
+
candidates: zod_1.z.array(product_image_merge_candidate_schema_1.ProductImageMergeCandidateSchema),
|
|
11
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AttributeNormalizationScopeSchema } from '../../schemas/attribute-normalization/attribute-normalization-scope.schema';
|
|
3
|
+
import { NormalizationValueSchema } from '../../schemas/attribute-normalization/normalization-value.schema';
|
|
4
|
+
import { NormalizationSummarySchema } from '../../schemas/attribute-normalization/normalization-summary.schema';
|
|
5
|
+
|
|
6
|
+
const AnalyzeAttributeNormalizationRequestSchema = AttributeNormalizationScopeSchema;
|
|
7
|
+
|
|
8
|
+
const AnalyzeAttributeNormalizationResponseSchema = z.object({
|
|
9
|
+
data: z.object({
|
|
10
|
+
values: z.array(NormalizationValueSchema),
|
|
11
|
+
summary: NormalizationSummarySchema,
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export namespace AnalyzeAttributeNormalizationContractQuery {
|
|
16
|
+
export const RequestSchema = AnalyzeAttributeNormalizationRequestSchema;
|
|
17
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
18
|
+
|
|
19
|
+
export const ResponseSchema = AnalyzeAttributeNormalizationResponseSchema;
|
|
20
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
21
|
+
|
|
22
|
+
export type Value = z.infer<typeof NormalizationValueSchema>;
|
|
23
|
+
export type Summary = z.infer<typeof NormalizationSummarySchema>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AttributeNormalizationScopeSchema } from '../../schemas/attribute-normalization/attribute-normalization-scope.schema';
|
|
3
|
+
import { NormalizationDecisionSchema } from '../../schemas/attribute-normalization/normalization-decision.schema';
|
|
4
|
+
import { NormalizationRowCountSchema } from '../../schemas/attribute-normalization/normalization-row-count.schema';
|
|
5
|
+
|
|
6
|
+
const ExecuteAttributeNormalizationRequestSchema = AttributeNormalizationScopeSchema.extend({
|
|
7
|
+
// Верхней границы нет намеренно: анализ отдаёт все написания компании, и применение обязано
|
|
8
|
+
// принять их целиком — иначе прогон пришлось бы дробить, а разбиение на части снова открывает
|
|
9
|
+
// цепочки A→B и B→C, которые валидатор ловит только внутри одного запроса.
|
|
10
|
+
// Объём ограничен размером тела запроса, а стоимость — чанкованием при записи.
|
|
11
|
+
decisions: z.array(NormalizationDecisionSchema).min(1),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const ExecuteAttributeNormalizationResponseSchema = z.object({
|
|
15
|
+
data: z.object({
|
|
16
|
+
// Фактически изменённых строк по каждой таблице — считается по результату UPDATE,
|
|
17
|
+
// а не по предсказанию анализа: охват мог измениться.
|
|
18
|
+
updatedRows: NormalizationRowCountSchema,
|
|
19
|
+
appliedDecisions: z.number(),
|
|
20
|
+
actionLogUUID: z.string().nullable(),
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export namespace ExecuteAttributeNormalizationContractCommand {
|
|
25
|
+
export const RequestSchema = ExecuteAttributeNormalizationRequestSchema;
|
|
26
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
27
|
+
|
|
28
|
+
export const ResponseSchema = ExecuteAttributeNormalizationResponseSchema;
|
|
29
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
30
|
+
|
|
31
|
+
export type Decision = z.infer<typeof NormalizationDecisionSchema>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ATTRIBUTE_NORMALIZATION_LIMITS } from '../../constant/attribute-normalization.constant';
|
|
3
|
+
|
|
4
|
+
// Автокомплит производителя на фронте: union уникальных написаний по товарам, карточкам
|
|
5
|
+
// и позициям коллекций. Справочника Grower нет и мы его не заводим.
|
|
6
|
+
const GetGrowerSuggestionsRequestSchema = z.object({
|
|
7
|
+
search: z.string().trim().min(1).max(ATTRIBUTE_NORMALIZATION_LIMITS.MAX_VALUE_LENGTH).optional(),
|
|
8
|
+
count: z.coerce
|
|
9
|
+
.number()
|
|
10
|
+
.int()
|
|
11
|
+
.min(1)
|
|
12
|
+
.max(ATTRIBUTE_NORMALIZATION_LIMITS.MAX_GROWER_SUGGESTIONS)
|
|
13
|
+
.default(ATTRIBUTE_NORMALIZATION_LIMITS.MAX_GROWER_SUGGESTIONS),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const GetGrowerSuggestionsResponseSchema = z.object({
|
|
17
|
+
data: z.object({
|
|
18
|
+
growers: z.array(z.string()),
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export namespace GetGrowerSuggestionsContractQuery {
|
|
23
|
+
export const RequestSchema = GetGrowerSuggestionsRequestSchema;
|
|
24
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
25
|
+
|
|
26
|
+
export const ResponseSchema = GetGrowerSuggestionsResponseSchema;
|
|
27
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { AnalyzeAttributeNormalizationContractQuery } from './analyze-attribute-normalization.query';
|
|
2
|
+
export { ExecuteAttributeNormalizationContractCommand } from './execute-attribute-normalization.command';
|
|
3
|
+
export { GetGrowerSuggestionsContractQuery } from './get-grower-suggestions.query';
|
package/commands/index.ts
CHANGED
|
@@ -61,3 +61,4 @@ export { CreateWsTicketContractCommand } from './session/create-ws-ticket.comman
|
|
|
61
61
|
export { ValidateWsTicketS2SContractQuery } from './session/validate-ws-ticket-s2s.query';
|
|
62
62
|
export * from './consignment-transfer';
|
|
63
63
|
export * from './transfer-document';
|
|
64
|
+
export * from './attribute-normalization';
|
|
@@ -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,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ProductImageDuplicateModeEnum } from '../../enum/product-image-duplicate-mode.enum';
|
|
3
|
+
import { ProductImageMergeGroupSchema } from '../../schemas/product-image/product-image-merge-group.schema';
|
|
4
|
+
import { ProductImageMergeCandidateSchema } from '../../schemas/product-image/product-image-merge-candidate.schema';
|
|
5
|
+
|
|
6
|
+
const AnalyzeProductImageMergeRequestSchema = z.object({
|
|
7
|
+
mode: z.nativeEnum(ProductImageDuplicateModeEnum).default(ProductImageDuplicateModeEnum.CURRENT),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const AnalyzeProductImageMergeResponseSchema = z.object({
|
|
11
|
+
data: z.object({
|
|
12
|
+
groups: z.array(ProductImageMergeGroupSchema),
|
|
13
|
+
// Живых карточек у компании всего — чтобы фронт показал масштаб.
|
|
14
|
+
totalImages: z.number(),
|
|
15
|
+
duplicateImages: z.number(),
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export namespace AnalyzeProductImageMergeContractQuery {
|
|
20
|
+
export const RequestSchema = AnalyzeProductImageMergeRequestSchema;
|
|
21
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
22
|
+
|
|
23
|
+
export const ResponseSchema = AnalyzeProductImageMergeResponseSchema;
|
|
24
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
25
|
+
|
|
26
|
+
export type Group = z.infer<typeof ProductImageMergeGroupSchema>;
|
|
27
|
+
export type Candidate = z.infer<typeof ProductImageMergeCandidateSchema>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { PRODUCT_IMAGE_MERGE_LIMITS } from '../../constant/product-image-merge.constant';
|
|
3
|
+
|
|
4
|
+
const ExecuteProductImageMergeRequestSchema = z.object({
|
|
5
|
+
primaryUUID: z.string().uuid(),
|
|
6
|
+
// Верхняя граница на группу: склейка идёт одной транзакцией, и неограниченный список
|
|
7
|
+
// упёрся бы в её таймаут. Реальные группы дублей — единицы карточек.
|
|
8
|
+
mergedUUIDList: z.array(z.string().uuid()).min(1).max(PRODUCT_IMAGE_MERGE_LIMITS.MAX_MERGED_PER_REQUEST),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const ExecuteProductImageMergeResponseSchema = z.object({
|
|
12
|
+
data: z.object({
|
|
13
|
+
relinkedProducts: z.number(),
|
|
14
|
+
relinkedPreOrderCollectionItems: z.number(),
|
|
15
|
+
movedAliases: z.number(),
|
|
16
|
+
droppedAliases: z.number(),
|
|
17
|
+
mergedImages: z.number(),
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export namespace ExecuteProductImageMergeContractCommand {
|
|
22
|
+
export const RequestSchema = ExecuteProductImageMergeRequestSchema;
|
|
23
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
24
|
+
|
|
25
|
+
export const ResponseSchema = ExecuteProductImageMergeResponseSchema;
|
|
26
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
27
|
+
}
|
|
@@ -14,3 +14,5 @@ export * from './alias/update-product-image-alias.command';
|
|
|
14
14
|
export * from './alias/delete-product-image-alias.command';
|
|
15
15
|
export * from './alias/bulk-create-product-image-alias.command';
|
|
16
16
|
export * from './alias/bulk-create-product-image-alias-from-pre-order-collection-item.command';
|
|
17
|
+
export { AnalyzeProductImageMergeContractQuery } from './analyze-product-image-merge.query';
|
|
18
|
+
export { ExecuteProductImageMergeContractCommand } from './execute-product-image-merge.command';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const ATTRIBUTE_NORMALIZATION_LIMITS = {
|
|
2
|
+
// Размер чанка для UPDATE ... WHERE поле IN (...).
|
|
3
|
+
UPDATE_CHUNK_SIZE: 200,
|
|
4
|
+
// Сколько уникальных написаний производителя отдавать в автокомплит.
|
|
5
|
+
MAX_GROWER_SUGGESTIONS: 500,
|
|
6
|
+
// Колонки атрибутов — text без ограничения; без границы одно решение раскатало бы
|
|
7
|
+
// многомегабайтную строку по всей истории компании и осело бы в журнале действий.
|
|
8
|
+
MAX_VALUE_LENGTH: 255,
|
|
9
|
+
} as const;
|
package/constant/error.ts
CHANGED
|
@@ -1579,4 +1579,54 @@ export const ERRORS = {
|
|
|
1579
1579
|
message: 'Transfer document number start cannot be decreased',
|
|
1580
1580
|
httpCode: 400,
|
|
1581
1581
|
},
|
|
1582
|
+
ATTRIBUTE_NORMALIZATION_ANALYZE_FAILED: {
|
|
1583
|
+
code: 'ANORM001',
|
|
1584
|
+
message: 'Error when analyzing attribute normalization',
|
|
1585
|
+
httpCode: 500,
|
|
1586
|
+
},
|
|
1587
|
+
ATTRIBUTE_NORMALIZATION_EXECUTE_FAILED: {
|
|
1588
|
+
code: 'ANORM002',
|
|
1589
|
+
message: 'Error when applying attribute normalization',
|
|
1590
|
+
httpCode: 500,
|
|
1591
|
+
},
|
|
1592
|
+
ATTRIBUTE_NORMALIZATION_EMPTY_SCOPE: {
|
|
1593
|
+
code: 'ANORM003',
|
|
1594
|
+
message: 'None of the selected targets stores the selected attributes',
|
|
1595
|
+
httpCode: 400,
|
|
1596
|
+
},
|
|
1597
|
+
PRODUCT_IMAGE_MERGE_TOO_MANY_IMAGES: {
|
|
1598
|
+
code: 'PI015',
|
|
1599
|
+
message: 'Too many product images to analyze duplicates in one pass',
|
|
1600
|
+
httpCode: 400,
|
|
1601
|
+
},
|
|
1602
|
+
ATTRIBUTE_NORMALIZATION_INVALID_DECISIONS: {
|
|
1603
|
+
code: 'ANORM006',
|
|
1604
|
+
message: 'Normalization decisions are inconsistent',
|
|
1605
|
+
httpCode: 400,
|
|
1606
|
+
},
|
|
1607
|
+
CATALOG_BULK_REWRITE_LOCK_UNAVAILABLE: {
|
|
1608
|
+
code: 'ANORM007',
|
|
1609
|
+
message: 'Cannot guarantee exclusive execution right now, try again later',
|
|
1610
|
+
httpCode: 503,
|
|
1611
|
+
},
|
|
1612
|
+
ATTRIBUTE_NORMALIZATION_ALREADY_RUNNING: {
|
|
1613
|
+
code: 'ANORM005',
|
|
1614
|
+
message: 'Another attribute normalization run is already in progress for this company',
|
|
1615
|
+
httpCode: 409,
|
|
1616
|
+
},
|
|
1617
|
+
PRODUCT_IMAGE_MERGE_NOT_DUPLICATES: {
|
|
1618
|
+
code: 'PI017',
|
|
1619
|
+
message: 'Selected product images are not duplicates of the primary one',
|
|
1620
|
+
httpCode: 409,
|
|
1621
|
+
},
|
|
1622
|
+
PRODUCT_IMAGE_MERGE_ALREADY_RUNNING: {
|
|
1623
|
+
code: 'PI016',
|
|
1624
|
+
message: 'Another product image merge is already in progress for this company',
|
|
1625
|
+
httpCode: 409,
|
|
1626
|
+
},
|
|
1627
|
+
GROWER_SUGGESTIONS_FETCH_FAILED: {
|
|
1628
|
+
code: 'ANORM004',
|
|
1629
|
+
message: 'Error when retrieving grower suggestions',
|
|
1630
|
+
httpCode: 500,
|
|
1631
|
+
},
|
|
1582
1632
|
} as const;
|
package/constant/index.ts
CHANGED
|
@@ -10,3 +10,5 @@ export * from './okei';
|
|
|
10
10
|
export * from './contact-phone.constant';
|
|
11
11
|
export * from './settings.constant';
|
|
12
12
|
export * from './consignment-product-enrichment.constant';
|
|
13
|
+
export * from './attribute-normalization.constant';
|
|
14
|
+
export * from './product-image-merge.constant';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const PRODUCT_IMAGE_MERGE_LIMITS = {
|
|
2
|
+
// Поиск дублей группирует карточки в приложении, а не в SQL: канон цвета живёт в словаре
|
|
3
|
+
// на стороне кода. Значит все карточки компании оказываются в памяти, и объём надо ограничить
|
|
4
|
+
// явно — иначе на очень крупной компании анализ молча съест память процесса.
|
|
5
|
+
MAX_ANALYZED_IMAGES: 100_000,
|
|
6
|
+
// Сколько карточек можно поглотить за один запрос: всё идёт одной транзакцией.
|
|
7
|
+
MAX_MERGED_PER_REQUEST: 200,
|
|
8
|
+
} as const;
|
|
@@ -19,4 +19,6 @@ export enum ActionLogTypeEnum {
|
|
|
19
19
|
DELETE_CONSIGNMENT_ITEMS = 'DELETE_CONSIGNMENT_ITEMS',
|
|
20
20
|
GROUP_UPDATE_CONSIGNMENT_ITEMS = 'GROUP_UPDATE_CONSIGNMENT_ITEMS',
|
|
21
21
|
TRANSFER_CONSIGNMENT_ITEMS = 'TRANSFER_CONSIGNMENT_ITEMS',
|
|
22
|
+
NORMALIZE_ATTRIBUTES = 'NORMALIZE_ATTRIBUTES',
|
|
23
|
+
MERGE_PRODUCT_IMAGES = 'MERGE_PRODUCT_IMAGES',
|
|
22
24
|
}
|
package/enum/index.ts
CHANGED
|
@@ -108,3 +108,8 @@ export * from './transfer-document-item-kind.enum';
|
|
|
108
108
|
export * from './write-off-report-date-basis.enum';
|
|
109
109
|
export * from './product-enrichment-field.enum';
|
|
110
110
|
export * from './product-enrichment-field-state.enum';
|
|
111
|
+
export * from './normalized-attribute.enum';
|
|
112
|
+
export * from './normalization-target.enum';
|
|
113
|
+
export * from './normalization-value-state.enum';
|
|
114
|
+
export * from './normalization-decision-action.enum';
|
|
115
|
+
export * from './product-image-duplicate-mode.enum';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Решение человека по одному написанию. Правила в БД не сохраняются — решение живёт один прогон.
|
|
2
|
+
export enum NormalizationDecisionActionEnum {
|
|
3
|
+
// Переписать на указанное значение.
|
|
4
|
+
MAP = 'MAP',
|
|
5
|
+
// Очистить поле (значение — мусор).
|
|
6
|
+
CLEAR = 'CLEAR',
|
|
7
|
+
// Оставить как есть.
|
|
8
|
+
KEEP = 'KEEP',
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Таблицы, попадающие в охват нормализации. PRODUCT_CONFIGURATION сужается только компанией:
|
|
2
|
+
// правила кратности нормализуются целиком, иначе они разъедутся с товарами.
|
|
3
|
+
export enum NormalizationTargetEnum {
|
|
4
|
+
PRODUCT = 'PRODUCT',
|
|
5
|
+
PRODUCT_IMAGE = 'PRODUCT_IMAGE',
|
|
6
|
+
PRE_ORDER_COLLECTION_ITEM = 'PRE_ORDER_COLLECTION_ITEM',
|
|
7
|
+
PRODUCT_CONFIGURATION = 'PRODUCT_CONFIGURATION',
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Что анализ знает про конкретное написание атрибута.
|
|
2
|
+
export enum NormalizationValueStateEnum {
|
|
3
|
+
// Словарь (или кластеризация для производителя) предложил другое написание.
|
|
4
|
+
SUGGESTED = 'SUGGESTED',
|
|
5
|
+
// Значение уже каноничное, менять нечего.
|
|
6
|
+
CANONICAL = 'CANONICAL',
|
|
7
|
+
// Словарь значение не узнал — решение принимает человек в рамках прогона.
|
|
8
|
+
UNRECOGNIZED = 'UNRECOGNIZED',
|
|
9
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// В каком виде искать дубли карточек.
|
|
2
|
+
export enum ProductImageDuplicateModeEnum {
|
|
3
|
+
// По значениям как они лежат сейчас.
|
|
4
|
+
CURRENT = 'CURRENT',
|
|
5
|
+
// По значениям, приведённым к канону — превью того, что слипнется после нормализации.
|
|
6
|
+
AFTER_NORMALIZATION = 'AFTER_NORMALIZATION',
|
|
7
|
+
}
|
|
@@ -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
|
@@ -17,6 +17,8 @@ import { ConsignmentItemsCreateActionLogSchema } from './consignment-items-creat
|
|
|
17
17
|
import { ConsignmentItemsDeleteActionLogSchema } from './consignment-items-delete-action-log.schema';
|
|
18
18
|
import { GroupUpdateConsignmentItemsActionLogSchema } from './group-update-consignment-items-action-log.schema';
|
|
19
19
|
import { TransferConsignmentItemsActionLogSchema } from './transfer-consignment-items-action-log.schema';
|
|
20
|
+
import { NormalizeAttributesActionLogSchema } from './normalize-attributes-action-log.schema';
|
|
21
|
+
import { MergeProductImagesActionLogSchema } from './merge-product-images-action-log.schema';
|
|
20
22
|
|
|
21
23
|
export const ActionLogPayloadSchema = z.union([
|
|
22
24
|
OrderDeleteActionLogPayloadSchema,
|
|
@@ -37,5 +39,7 @@ export const ActionLogPayloadSchema = z.union([
|
|
|
37
39
|
ConsignmentItemsDeleteActionLogSchema,
|
|
38
40
|
GroupUpdateConsignmentItemsActionLogSchema,
|
|
39
41
|
TransferConsignmentItemsActionLogSchema,
|
|
42
|
+
NormalizeAttributesActionLogSchema,
|
|
43
|
+
MergeProductImagesActionLogSchema,
|
|
40
44
|
z.null(),
|
|
41
45
|
]);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const MergeProductImagesActionLogSchema = z.object({
|
|
4
|
+
primaryImageUUID: z.string(),
|
|
5
|
+
mergedImageUUIDList: z.array(z.string()),
|
|
6
|
+
relinkedProducts: z.number(),
|
|
7
|
+
relinkedPreOrderCollectionItems: z.number(),
|
|
8
|
+
movedAliases: z.number(),
|
|
9
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { NormalizedAttributeEnum } from '../../enum/normalized-attribute.enum';
|
|
3
|
+
import { NormalizationTargetEnum } from '../../enum/normalization-target.enum';
|
|
4
|
+
import { NormalizationDecisionActionEnum } from '../../enum/normalization-decision-action.enum';
|
|
5
|
+
|
|
6
|
+
// Одна агрегатная запись на прогон: построчный лог тут не нужен, операция работает
|
|
7
|
+
// на уникальных значениях, а не на строках.
|
|
8
|
+
export const NormalizeAttributesActionLogSchema = z.object({
|
|
9
|
+
attributes: z.array(z.nativeEnum(NormalizedAttributeEnum)),
|
|
10
|
+
targets: z.array(z.nativeEnum(NormalizationTargetEnum)),
|
|
11
|
+
decisions: z.array(
|
|
12
|
+
z.object({
|
|
13
|
+
attribute: z.nativeEnum(NormalizedAttributeEnum),
|
|
14
|
+
raw: z.string(),
|
|
15
|
+
action: z.nativeEnum(NormalizationDecisionActionEnum),
|
|
16
|
+
value: z.string().nullable(),
|
|
17
|
+
}),
|
|
18
|
+
),
|
|
19
|
+
updatedRows: z.partialRecord(z.nativeEnum(NormalizationTargetEnum), z.number()),
|
|
20
|
+
totalUpdatedRows: z.number(),
|
|
21
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { NormalizedAttributeEnum } from '../../enum/normalized-attribute.enum';
|
|
3
|
+
import { NormalizationTargetEnum } from '../../enum/normalization-target.enum';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Охват прогона. Общий для анализа и применения: execute перепроверяет его заново, потому что
|
|
7
|
+
* между анализом и применением в таблицы могли приехать новые строки.
|
|
8
|
+
*
|
|
9
|
+
* Ограничения охвата, которые нельзя выразить схемой и которые проверяет сервис:
|
|
10
|
+
* - consignmentUUIDList сужает только PRODUCT — у карточек и позиций коллекций нет поставки;
|
|
11
|
+
* - preOrderCollectionUUIDList сужает только PRE_ORDER_COLLECTION_ITEM;
|
|
12
|
+
* - PRODUCT_CONFIGURATION не сужается ничем, кроме компании: правила нормализуются целиком,
|
|
13
|
+
* иначе часть из них разъедется с товарами.
|
|
14
|
+
*/
|
|
15
|
+
// Дедупликация в схеме, а не в сервисе: списки читают и analyze, и execute, и запись в журнал —
|
|
16
|
+
// повтор «PRODUCT» тысячу раз означал бы тысячу полных проходов по таблице товаров.
|
|
17
|
+
const dedupe = <T>(list: T[]): T[] => Array.from(new Set(list));
|
|
18
|
+
|
|
19
|
+
export const AttributeNormalizationScopeSchema = z.object({
|
|
20
|
+
attributes: z.array(z.nativeEnum(NormalizedAttributeEnum)).min(1).max(Object.keys(NormalizedAttributeEnum).length).transform(dedupe),
|
|
21
|
+
targets: z.array(z.nativeEnum(NormalizationTargetEnum)).min(1).max(Object.keys(NormalizationTargetEnum).length).transform(dedupe),
|
|
22
|
+
// Без верхней границы: списки уходят в запрос одним параметром-массивом, длина упирается
|
|
23
|
+
// только в размер тела запроса.
|
|
24
|
+
consignmentUUIDList: z.array(z.string().uuid()).optional(),
|
|
25
|
+
preOrderCollectionUUIDList: z.array(z.string().uuid()).optional(),
|
|
26
|
+
createdFrom: z.coerce.date().optional(),
|
|
27
|
+
createdTo: z.coerce.date().optional(),
|
|
28
|
+
// У поставки нет признака закрытости — закрытость живёт на товаре (Product.isOpen).
|
|
29
|
+
// По умолчанию закрытые товары не трогаем: это распроданная история.
|
|
30
|
+
isIncludingClosedProducts: z.boolean().default(false),
|
|
31
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { NormalizedAttributeEnum } from '../../enum/normalized-attribute.enum';
|
|
3
|
+
import { NormalizationDecisionActionEnum } from '../../enum/normalization-decision-action.enum';
|
|
4
|
+
import { ATTRIBUTE_NORMALIZATION_LIMITS } from '../../constant/attribute-normalization.constant';
|
|
5
|
+
|
|
6
|
+
// Решение человека по одному написанию. Правила в БД не сохраняются — решение живёт один прогон.
|
|
7
|
+
export const NormalizationDecisionSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
attribute: z.nativeEnum(NormalizedAttributeEnum),
|
|
10
|
+
raw: z.string().min(1).max(ATTRIBUTE_NORMALIZATION_LIMITS.MAX_VALUE_LENGTH),
|
|
11
|
+
action: z.nativeEnum(NormalizationDecisionActionEnum),
|
|
12
|
+
// Целевое написание — обязательно и только для MAP.
|
|
13
|
+
value: z.string().min(1).max(ATTRIBUTE_NORMALIZATION_LIMITS.MAX_VALUE_LENGTH).optional(),
|
|
14
|
+
})
|
|
15
|
+
.refine(decision => (decision.action === NormalizationDecisionActionEnum.MAP) === (decision.value !== undefined), {
|
|
16
|
+
message: 'value is required for MAP and forbidden otherwise',
|
|
17
|
+
path: ['value'],
|
|
18
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { NormalizationTargetEnum } from '../../enum/normalization-target.enum';
|
|
3
|
+
|
|
4
|
+
// Сколько строк приходится на каждую таблицу охвата. Ключи неполные: значение живёт
|
|
5
|
+
// не во всех таблицах сразу, а таблица может не хранить нужный атрибут.
|
|
6
|
+
export const NormalizationRowCountSchema = z.partialRecord(z.nativeEnum(NormalizationTargetEnum), z.number());
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { NormalizedAttributeEnum } from '../../enum/normalized-attribute.enum';
|
|
3
|
+
import { NormalizationTargetEnum } from '../../enum/normalization-target.enum';
|
|
4
|
+
import { NormalizationRowCountSchema } from './normalization-row-count.schema';
|
|
5
|
+
import { ProductImageDuplicateForecastSchema } from './product-image-duplicate-forecast.schema';
|
|
6
|
+
|
|
7
|
+
// Итоги анализа: всё, что фронт показывает перед запуском необратимой операции.
|
|
8
|
+
export const NormalizationSummarySchema = z.object({
|
|
9
|
+
// Уникальных написаний по каждому атрибуту.
|
|
10
|
+
uniqueValues: z.partialRecord(z.nativeEnum(NormalizedAttributeEnum), z.number()),
|
|
11
|
+
// Строк к изменению по каждой таблице, если принять все предложения.
|
|
12
|
+
affectedRows: NormalizationRowCountSchema,
|
|
13
|
+
// Строк, которые прогон пропустит из-за того, что позиция закрыта. Нужно фронту, чтобы
|
|
14
|
+
// предложить включить их флагом: по умолчанию закрытая история не трогается.
|
|
15
|
+
// null означает «пропускать нечего»: либо флаг уже включён, либо ни одна таблица охвата
|
|
16
|
+
// не различает открытые и закрытые позиции.
|
|
17
|
+
skippedClosedRows: NormalizationRowCountSchema.nullable(),
|
|
18
|
+
// Строк заказов, отображающих затрагиваемые товары. Нужно фронту для предупреждения:
|
|
19
|
+
// операция необратима, а в заказах видно старое написание.
|
|
20
|
+
affectedOrderProductRows: z.number(),
|
|
21
|
+
// Таблицы, которых не касается сужение по выбранным поставкам и коллекциям: у карточки
|
|
22
|
+
// номенклатуры нет поставки, у товара нет коллекции. Для них охват остаётся широким —
|
|
23
|
+
// фронт обязан предупредить, иначе админ будет думать, что тронет одну поставку,
|
|
24
|
+
// а прогон перепишет всю компанию.
|
|
25
|
+
targetsIgnoringUuidScope: z.array(z.nativeEnum(NormalizationTargetEnum)),
|
|
26
|
+
// null означает, что карточки в охват не входят и прогон их не трогает.
|
|
27
|
+
productImageDuplicatesAfterRun: ProductImageDuplicateForecastSchema.nullable(),
|
|
28
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { NormalizedAttributeEnum } from '../../enum/normalized-attribute.enum';
|
|
3
|
+
import { NormalizationValueStateEnum } from '../../enum/normalization-value-state.enum';
|
|
4
|
+
import { NormalizationRowCountSchema } from './normalization-row-count.schema';
|
|
5
|
+
|
|
6
|
+
// Одно написание атрибута в истории компании и то, что операция про него знает.
|
|
7
|
+
export const NormalizationValueSchema = z.object({
|
|
8
|
+
attribute: z.nativeEnum(NormalizedAttributeEnum),
|
|
9
|
+
// Написание как оно лежит в базе — оно же ключ решения в execute.
|
|
10
|
+
raw: z.string(),
|
|
11
|
+
// Что предлагает словарь (для производителя — кластеризация). null, если предложить нечего.
|
|
12
|
+
suggested: z.string().nullable(),
|
|
13
|
+
state: z.nativeEnum(NormalizationValueStateEnum),
|
|
14
|
+
rowCount: NormalizationRowCountSchema,
|
|
15
|
+
// Другие написания того же кластера — только для производителя, у цвета и страны словарь.
|
|
16
|
+
clusterRaw: z.array(z.string()),
|
|
17
|
+
});
|