This commit is contained in:
David Arranz 2024-05-09 12:16:31 +02:00
parent fc18ec9fac
commit e4d403472d
11 changed files with 178 additions and 27 deletions

View File

@ -43,7 +43,7 @@ export class ListArticlesUseCase
}
async execute(
params: Partial<IListArticlesParams>
params: Partial<IListArticlesParams>,
): Promise<ListArticlesResult> {
const { queryCriteria } = params;
@ -58,12 +58,11 @@ export class ListArticlesUseCase
let products: ICollection<Article> = 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,
),
);
}
}

View File

@ -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<IArticleProps> implements IArticle {
public static create(
props: IArticleProps,
id?: UniqueID
id?: UniqueID,
): Result<Article, IDomainError> {
//const isNew = !!id === false;
@ -54,14 +55,14 @@ export class Article extends AggregateRoot<IArticleProps> 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;
}

View File

@ -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<number> {
protected static validate(
value: NullOr<number | string>,
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<NullOr<number>>(rules, value);
}
public static create(
value: NullOr<number | string>,
options: IArticleIdentifierOptions = {},
) {
const _options = {
label: "ArticleIdentifier",
...options,
};
const validationResult = ArticleIdentifier.validate(value, _options);
if (validationResult.isFailure) {
return Result.fail(validationResult.error);
}
let _value: NullOr<number> = null;
if (typeof validationResult.object === "string") {
_value = parseInt(validationResult.object, 10);
} else {
_value = validationResult.object;
}
return Result.ok<ArticleIdentifier>(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);
}
}

View File

@ -1 +1,2 @@
export * from "./Article";
export * from "./ArticleIdentifier";

View File

@ -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 }),
),
};

View File

@ -0,0 +1 @@
export * from "./article.mapper";

View File

@ -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;

View File

@ -0,0 +1 @@
export * from "./article.model";

View File

@ -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<IQueryCriteriaServiceProps>
params: Partial<IQueryCriteriaServiceProps>,
): 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;
}

View File

@ -44,7 +44,7 @@ export class SequelizeQueryBuilder implements ISequelizeQueryBuilder {
private applyQuickSearch(
model: ModelDefined<any, any>,
quickSearchCriteria: QuickSearchCriteria
quickSearchCriteria: QuickSearchCriteria,
): any {
let _model = model;
if (!quickSearchCriteria.isEmpty()) {

View File

@ -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 {