Uecko_ERP/packages/rdx-criteria/src/criteria-to-sequelize-converter.ts
2025-09-30 12:59:32 +02:00

152 lines
4.8 KiB
TypeScript

import { FindOptions, Op, OrderItem, Sequelize, WhereOptions } from "sequelize";
import { Criteria } from "./critera";
import { type ConvertParams, type CriteriaMappings, ICriteriaToOrmConverter } from "./types";
import { appendOrder, prependOrder } from "./utils";
export class CriteriaToSequelizeConverter implements ICriteriaToOrmConverter {
//convert(fieldsToSelect: string[], criteria: Criteria, mappings: Mappings = {}): FindOptions {
convert(criteria: Criteria, params: ConvertParams = {}): FindOptions {
const options: FindOptions = {};
const { mappings = {} } = params;
// Selección de campos
/*if (fieldsToSelect.length > 0) {
options.attributes = fieldsToSelect;
}*/
this.applyFilters(options, criteria, mappings);
this.applyQuickSearch(options, criteria, params);
this.applyOrder(options, criteria, mappings);
this.applyPagination(options, criteria);
return options;
}
public applyFilters(options: FindOptions, criteria: Criteria, mappings: CriteriaMappings) {
const filterConditions: WhereOptions = {};
if (criteria.hasFilters()) {
criteria.filters.value.forEach((filter) => {
const field = mappings[filter.field.value] || filter.field.value;
const operator = this.mapOperator(filter.operator.value);
const value = filter.value.value;
if (!filterConditions[field]) {
filterConditions[field] = {};
}
filterConditions[field][operator] = this.transformValue(operator, value);
});
if (options.where) {
options.where = { [Op.and]: [options.where, { [Op.or]: filterConditions }] };
} else {
options.where = { [Op.or]: filterConditions };
}
}
}
public applyQuickSearch(options: FindOptions, criteria: Criteria, params: ConvertParams): void {
const {
mappings = {},
searchableFields = [],
database,
} = params as ConvertParams & {
database: Sequelize;
};
const term = typeof criteria.quickSearch === "string" ? criteria.quickSearch.trim() : "";
// Si no hay término de búsqueda o no hay campos configurados, no hacemos nada
if (term === "" || searchableFields.length === 0) {
return;
}
// Construimos query de boolean mode con prefijo
const booleanTerm = term
.split(/\s+/)
.map((w) => `+${w}*`)
.join(" ");
// Campos reales (con mappings aplicados)
const mappedFields = searchableFields.map((field) => mappings[field] || field);
const matchExpr = `MATCH(${mappedFields.join(", ")}) AGAINST (${database.escape(
booleanTerm
)} IN BOOLEAN MODE)`;
const matchLiteral = Sequelize.literal(matchExpr);
// Añadimos score a los attributes (sin machacar si ya existen)
if (!options.attributes) {
options.attributes = { include: [] };
}
if (Array.isArray(options.attributes)) {
options.attributes.push([matchLiteral, "score"]);
} else {
options.attributes.include = options.attributes.include || [];
options.attributes.include.push([matchLiteral, "score"]);
}
// WHERE score > 0
const scoreCondition = Sequelize.where(matchLiteral, { [Op.gt]: 0 });
if (options.where) {
options.where = { [Op.and]: [options.where, { [Op.or]: scoreCondition }] };
} else {
options.where = { [Op.and]: scoreCondition };
}
// Ordenar por relevancia (score)
prependOrder(options, [Sequelize.literal("score"), "DESC"]);
}
public applyOrder(options: FindOptions, criteria: Criteria, mappings: CriteriaMappings): void {
if (criteria.hasOrder()) {
const field = mappings[criteria.order.orderBy.value] || criteria.order.orderBy.value;
const direction = criteria.order.orderType.value.toUpperCase();
appendOrder(options, [[field, direction]] as OrderItem[]);
}
}
public applyPagination(options: FindOptions, criteria: Criteria): void {
if (criteria.pageSize !== null) {
options.limit = criteria.pageSize;
}
if (criteria.pageSize !== null && criteria.pageNumber !== null) {
options.offset = criteria.pageSize * criteria.pageNumber;
}
}
private mapOperator(operator: string): symbol {
switch (operator) {
case "CONTAINS":
return Op.like;
case "NOT_CONTAINS":
return Op.notLike;
case "NOT_EQUALS":
return Op.ne;
case "GREATER_THAN":
return Op.gt;
case "GREATER_THAN_OR_EQUAL":
return Op.gte;
case "LOWER_THAN":
return Op.lt;
case "LOWER_THAN_OR_EQUAL":
return Op.lte;
case "EQUALS":
return Op.eq;
default:
return Op.eq;
}
}
private transformValue(operator: symbol, value: any): any {
if (operator === Op.like || operator === Op.notLike) {
return `%${value}%`;
}
return value;
}
}