From e4d403472d5ef12d4bfb4e39494ace7110380d91 Mon Sep 17 00:00:00 2001 From: David Arranz Date: Thu, 9 May 2024 12:16:31 +0200 Subject: [PATCH] . --- .../application/ListArticlesUseCase.ts | 11 +-- .../catalog/domain/entities/Article.ts | 11 ++- .../domain/entities/ArticleIdentifier.ts | 98 +++++++++++++++++++ .../contexts/catalog/domain/entities/index.ts | 1 + .../infrastructure/mappers/article.mapper.ts | 14 ++- .../catalog/infrastructure/mappers/index.ts | 1 + .../infrastructure/sequelize/article.model.ts | 49 +++++++++- .../catalog/infrastructure/sequelize/index.ts | 1 + .../services/QueryCriteriaService.ts | 13 +-- .../queryBuilder/SequelizeQueryBuilder.ts | 2 +- .../QueryCriteria/Pagination/OffsetPaging.ts | 4 +- 11 files changed, 178 insertions(+), 27 deletions(-) create mode 100644 server/src/contexts/catalog/domain/entities/ArticleIdentifier.ts create mode 100644 server/src/contexts/catalog/infrastructure/sequelize/index.ts diff --git a/server/src/contexts/catalog/application/ListArticlesUseCase.ts b/server/src/contexts/catalog/application/ListArticlesUseCase.ts index 4bb36c2..1b3b8ba 100644 --- a/server/src/contexts/catalog/application/ListArticlesUseCase.ts +++ b/server/src/contexts/catalog/application/ListArticlesUseCase.ts @@ -43,7 +43,7 @@ export class ListArticlesUseCase } async execute( - params: Partial + params: Partial, ): Promise { const { queryCriteria } = params; @@ -58,12 +58,11 @@ export class ListArticlesUseCase let products: ICollection
= new Collection(); try { - transaction.complete(async (t) => { + await transaction.complete(async (t) => { products = await productRepoBuilder({ transaction: t }).findAll( - queryCriteria + queryCriteria, ); }); - return Result.ok(products); } catch (error: unknown) { const _error = error as IInfrastructureError; @@ -71,8 +70,8 @@ export class ListArticlesUseCase handleUseCaseError( UseCaseError.REPOSITORY_ERROR, "Error al listar el catálogo", - _error - ) + _error, + ), ); } } diff --git a/server/src/contexts/catalog/domain/entities/Article.ts b/server/src/contexts/catalog/domain/entities/Article.ts index 5e2e142..ce89ca2 100644 --- a/server/src/contexts/catalog/domain/entities/Article.ts +++ b/server/src/contexts/catalog/domain/entities/Article.ts @@ -8,10 +8,11 @@ import { UniqueID, UnitPrice, } from "@shared/contexts"; +import { ArticleIdentifier } from "./ArticleIdentifier"; export interface IArticleProps { catalog_name: Slug; - id_article: Description; + id_article: ArticleIdentifier; reference: Slug; family: Description; subfamily: Description; @@ -23,7 +24,7 @@ export interface IArticleProps { export interface IArticle { id: UniqueID; catalog_name: Slug; - id_article: Description; + id_article: ArticleIdentifier; reference: Slug; family: Description; subfamily: Description; @@ -35,7 +36,7 @@ export interface IArticle { export class Article extends AggregateRoot implements IArticle { public static create( props: IArticleProps, - id?: UniqueID + id?: UniqueID, ): Result { //const isNew = !!id === false; @@ -54,14 +55,14 @@ export class Article extends AggregateRoot implements IArticle { } get id(): UniqueID { - return this.id; + return this._id; } get catalog_name(): Slug { return this.props.catalog_name; } - get id_article(): Description { + get id_article(): ArticleIdentifier { return this.props.id_article; } diff --git a/server/src/contexts/catalog/domain/entities/ArticleIdentifier.ts b/server/src/contexts/catalog/domain/entities/ArticleIdentifier.ts new file mode 100644 index 0000000..bb65dbe --- /dev/null +++ b/server/src/contexts/catalog/domain/entities/ArticleIdentifier.ts @@ -0,0 +1,98 @@ +import { + INullableValueObjectOptions, + NullableValueObject, + Result, + RuleValidator, +} from "@shared/contexts"; +import { NullOr } from "@shared/utilities"; +import Joi from "joi"; + +export interface IArticleIdentifierOptions + extends INullableValueObjectOptions {} + +export class ArticleIdentifier extends NullableValueObject { + protected static validate( + value: NullOr, + options: IArticleIdentifierOptions = {}, + ) { + const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(null); + + const ruleNumber = RuleValidator.RULE_IS_TYPE_NUMBER.label( + options.label ? options.label : "ArticleIdentifier", + ); + + const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex( + /^[-]?\d+$/, + ).label(options.label ? options.label : "ArticleIdentifier"); + + const rules = Joi.alternatives(ruleNull, ruleNumber, ruleString); + + return RuleValidator.validate>(rules, value); + } + + public static create( + value: NullOr, + options: IArticleIdentifierOptions = {}, + ) { + const _options = { + label: "ArticleIdentifier", + ...options, + }; + + const validationResult = ArticleIdentifier.validate(value, _options); + + if (validationResult.isFailure) { + return Result.fail(validationResult.error); + } + + let _value: NullOr = null; + + if (typeof validationResult.object === "string") { + _value = parseInt(validationResult.object, 10); + } else { + _value = validationResult.object; + } + + return Result.ok(new ArticleIdentifier(_value)); + } + + public toNumber(): number { + return this.isNull() ? 0 : Number(this.value); + } + + public toString(): string { + return this.isNull() ? "" : String(this.value); + } + + public toPrimitive(): number { + return this.toNumber(); + } + + public increment(amount: number = 1) { + const validationResult = ArticleIdentifier.validate(amount); + + if (validationResult.isFailure) { + return Result.fail(validationResult.error); + } + + if (this.value === null) { + return ArticleIdentifier.create(amount); + } + + return ArticleIdentifier.create(this.value + amount); + } + + public decrement(amount: number = 1) { + const validationResult = ArticleIdentifier.validate(amount); + + if (validationResult.isFailure) { + return Result.fail(validationResult.error); + } + + if (this.value === null) { + return ArticleIdentifier.create(amount); + } + + return ArticleIdentifier.create(this.value - amount); + } +} diff --git a/server/src/contexts/catalog/domain/entities/index.ts b/server/src/contexts/catalog/domain/entities/index.ts index a31e575..5ff5aef 100644 --- a/server/src/contexts/catalog/domain/entities/index.ts +++ b/server/src/contexts/catalog/domain/entities/index.ts @@ -1 +1,2 @@ export * from "./Article"; +export * from "./ArticleIdentifier"; diff --git a/server/src/contexts/catalog/infrastructure/mappers/article.mapper.ts b/server/src/contexts/catalog/infrastructure/mappers/article.mapper.ts index 1e63f33..bb6a99e 100644 --- a/server/src/contexts/catalog/infrastructure/mappers/article.mapper.ts +++ b/server/src/contexts/catalog/infrastructure/mappers/article.mapper.ts @@ -10,7 +10,11 @@ import { UnitPrice, } from "@shared/contexts"; import { ICatalogContext } from ".."; -import { Article, IArticleProps } from "../../domain/entities"; +import { + Article, + ArticleIdentifier, + IArticleProps, +} from "../../domain/entities"; import { Article_Model, TCreationArticle_Attributes, @@ -34,14 +38,18 @@ class ArticleMapper protected toDomainMappingImpl(source: Article_Model, params: any): Article { const props: IArticleProps = { catalog_name: this.mapsValue(source, "catalog_name", Slug.create), - id_article: this.mapsValue(source, "id_article", Description.create), + id_article: this.mapsValue( + source, + "id_article", + ArticleIdentifier.create, + ), reference: this.mapsValue(source, "reference", Slug.create), family: this.mapsValue(source, "family", Description.create), subfamily: this.mapsValue(source, "subfamily", Description.create), description: this.mapsValue(source, "description", Description.create), points: this.mapsValue(source, "points", Quantity.create), retail_price: this.mapsValue(source, "retail_price", (value: any) => - UnitPrice.create({ amount: value, precision: 4 }) + UnitPrice.create({ amount: value, precision: 4 }), ), }; diff --git a/server/src/contexts/catalog/infrastructure/mappers/index.ts b/server/src/contexts/catalog/infrastructure/mappers/index.ts index e69de29..4fd38de 100644 --- a/server/src/contexts/catalog/infrastructure/mappers/index.ts +++ b/server/src/contexts/catalog/infrastructure/mappers/index.ts @@ -0,0 +1 @@ +export * from "./article.mapper"; diff --git a/server/src/contexts/catalog/infrastructure/sequelize/article.model.ts b/server/src/contexts/catalog/infrastructure/sequelize/article.model.ts index 4f27833..5ba8196 100644 --- a/server/src/contexts/catalog/infrastructure/sequelize/article.model.ts +++ b/server/src/contexts/catalog/infrastructure/sequelize/article.model.ts @@ -4,6 +4,7 @@ import { InferAttributes, InferCreationAttributes, Model, + Op, Sequelize, } from "sequelize"; @@ -41,13 +42,22 @@ export default (sequelize: Sequelize) => { primaryKey: true, }, - catalog_name: DataTypes.STRING(), - id_article: DataTypes.STRING(), + catalog_name: { + type: DataTypes.STRING(), + allowNull: false, + }, + id_article: { + type: DataTypes.BIGINT().UNSIGNED, + allowNull: false, + }, reference: DataTypes.STRING(), family: DataTypes.STRING(), subfamily: DataTypes.STRING(), description: DataTypes.STRING(), - points: DataTypes.SMALLINT().UNSIGNED, + points: { + type: DataTypes.SMALLINT().UNSIGNED, + defaultValue: 0, + }, retail_price: DataTypes.BIGINT(), }, { @@ -69,7 +79,38 @@ export default (sequelize: Sequelize) => { { name: "family_subfamily_idx", fields: ["family", "subfamily"] }, { name: "updated_at_idx", fields: ["updated_at"] }, ], - } + + scopes: { + quickSearch(value) { + return { + where: { + [Op.or]: [ + { + reference: { + [Op.like]: `%${value}%`, + }, + }, + { + family: { + [Op.like]: `%${value}%`, + }, + }, + { + subfamily: { + [Op.like]: `%${value}%`, + }, + }, + { + description: { + [Op.like]: `%${value}%`, + }, + }, + ], + }, + }; + }, + }, + }, ); return Article_Model; diff --git a/server/src/contexts/catalog/infrastructure/sequelize/index.ts b/server/src/contexts/catalog/infrastructure/sequelize/index.ts new file mode 100644 index 0000000..bf079f1 --- /dev/null +++ b/server/src/contexts/catalog/infrastructure/sequelize/index.ts @@ -0,0 +1 @@ +export * from "./article.model"; diff --git a/server/src/contexts/common/application/services/QueryCriteriaService.ts b/server/src/contexts/common/application/services/QueryCriteriaService.ts index 0e2f10b..7b8080d 100644 --- a/server/src/contexts/common/application/services/QueryCriteriaService.ts +++ b/server/src/contexts/common/application/services/QueryCriteriaService.ts @@ -15,18 +15,18 @@ export interface IQueryCriteriaServiceProps { sort_by: string; fields: string; filters: string; - quick_search: string; + q: string; } export class QueryCriteriaService extends ApplicationService { public static parse( - params: Partial + params: Partial, ): IQueryCriteria { const { page = undefined, limit = undefined, sort_by = undefined, - quick_search = undefined, + q = undefined, // eslint-disable-next-line @typescript-eslint/no-unused-vars fields = null, // fields / select @@ -43,7 +43,7 @@ export class QueryCriteriaService extends ApplicationService { const _order: OrderCriteria = QueryCriteriaService.parseOrder(sort_by); const _quickSearch: QuickSearchCriteria = - QueryCriteriaService.parseQuickSearch(quick_search); + QueryCriteriaService.parseQuickSearch(q); return QueryCriteria.create({ pagination: _pagination, @@ -59,9 +59,10 @@ export class QueryCriteriaService extends ApplicationService { } const paginationOrError = OffsetPaging.create({ - offset: String(page), - limit: String(limit), + offset: page, + limit, }); + if (paginationOrError.isFailure) { throw paginationOrError.error; } diff --git a/server/src/contexts/common/infrastructure/sequelize/queryBuilder/SequelizeQueryBuilder.ts b/server/src/contexts/common/infrastructure/sequelize/queryBuilder/SequelizeQueryBuilder.ts index 073d683..b405e98 100644 --- a/server/src/contexts/common/infrastructure/sequelize/queryBuilder/SequelizeQueryBuilder.ts +++ b/server/src/contexts/common/infrastructure/sequelize/queryBuilder/SequelizeQueryBuilder.ts @@ -44,7 +44,7 @@ export class SequelizeQueryBuilder implements ISequelizeQueryBuilder { private applyQuickSearch( model: ModelDefined, - quickSearchCriteria: QuickSearchCriteria + quickSearchCriteria: QuickSearchCriteria, ): any { let _model = model; if (!quickSearchCriteria.isEmpty()) { diff --git a/shared/lib/contexts/common/domain/entities/QueryCriteria/Pagination/OffsetPaging.ts b/shared/lib/contexts/common/domain/entities/QueryCriteria/Pagination/OffsetPaging.ts index 57e31e9..8389df7 100644 --- a/shared/lib/contexts/common/domain/entities/QueryCriteria/Pagination/OffsetPaging.ts +++ b/shared/lib/contexts/common/domain/entities/QueryCriteria/Pagination/OffsetPaging.ts @@ -4,8 +4,8 @@ import { Result } from "../../Result"; import { ValueObject } from "../../ValueObject"; export interface IOffsetPagingProps { - offset: number | string; - limit: number | string; + offset: number | string | undefined; + limit: number | string | undefined; } export interface IOffsetPaging {