Proformas => búsquedas rápidas
This commit is contained in:
parent
26e2ee59bd
commit
e8f40b1495
@ -0,0 +1,70 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
||||||
|
import { Op, Sequelize, type WhereOptions } from "sequelize";
|
||||||
|
|
||||||
|
import { buildProformaQuickSearchWhere } from "./proforma-quick-search.ts";
|
||||||
|
|
||||||
|
function buildWhereSql(quickSearch: string): string {
|
||||||
|
const database = new Sequelize("mysql://root:pass@localhost:3306/erp_test", {
|
||||||
|
logging: false,
|
||||||
|
});
|
||||||
|
const criteria = Criteria.fromPrimitives([], "proforma_date", "DESC", 25, 0, quickSearch);
|
||||||
|
const criteriaConverter = new CriteriaToSequelizeConverter();
|
||||||
|
const criteriaQuery = criteriaConverter.convert(criteria, {
|
||||||
|
searchableFields: ["proforma_reference", "reference", "description"],
|
||||||
|
fullTextTableAlias: "ProformaModel",
|
||||||
|
enableFullText: true,
|
||||||
|
database,
|
||||||
|
strictMode: true,
|
||||||
|
});
|
||||||
|
const where = {
|
||||||
|
[Op.and]: [
|
||||||
|
{ company_id: "company-1" },
|
||||||
|
buildProformaQuickSearchWhere({
|
||||||
|
quickSearch,
|
||||||
|
fullTextWhere: criteriaQuery.where as WhereOptions,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return database.getQueryInterface().queryGenerator.whereQuery(where);
|
||||||
|
}
|
||||||
|
|
||||||
|
test("genera fallback LIKE para referencia documental con guion", () => {
|
||||||
|
const sql = buildWhereSql("PF-0066");
|
||||||
|
|
||||||
|
assert.match(sql, /MATCH\(`ProformaModel`\.`proforma_reference`, `ProformaModel`\.`reference`, `ProformaModel`\.`description`\)/);
|
||||||
|
assert.match(sql, /\+PF\* \+0066\*/);
|
||||||
|
assert.match(sql, /`proforma_reference` LIKE '%PF-0066%'/);
|
||||||
|
assert.match(sql, /`proforma_number` LIKE '%PF-0066%'/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("incluye el TIN del current_customer en quick search", () => {
|
||||||
|
const sql = buildWhereSql("B12345678");
|
||||||
|
|
||||||
|
assert.match(sql, /`current_customer`\.`tin` LIKE '%B12345678%'/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("incluye nombre y trade name del current_customer en quick search", () => {
|
||||||
|
const sql = buildWhereSql("Acme");
|
||||||
|
|
||||||
|
assert.match(sql, /`current_customer`\.`name` LIKE '%Acme%'/);
|
||||||
|
assert.match(sql, /`current_customer`\.`trade_name` LIKE '%Acme%'/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("acepta operadores reservados de MariaDB sin generar SQL inválida", () => {
|
||||||
|
const sql = buildWhereSql('+"PF-0066"(test)');
|
||||||
|
|
||||||
|
assert.equal(typeof sql, "string");
|
||||||
|
assert.ok(sql.length > 0);
|
||||||
|
assert.match(sql, /\+PF\* \+0066\* \+test\*/);
|
||||||
|
assert.match(sql, /LIKE '%\+"PF-0066"\(test\)%'/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mantiene el aislamiento por empresa en la cláusula final", () => {
|
||||||
|
const sql = buildWhereSql("PF-0066");
|
||||||
|
|
||||||
|
assert.match(sql, /`company_id` = 'company-1'/);
|
||||||
|
});
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import type { ProformaModel } from "../../../../common";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Op,
|
||||||
|
Sequelize,
|
||||||
|
type InferAttributes,
|
||||||
|
type WhereOptions,
|
||||||
|
} from "sequelize";
|
||||||
|
|
||||||
|
type BuildProformaQuickSearchWhereParams = {
|
||||||
|
quickSearch?: string;
|
||||||
|
fullTextWhere?: WhereOptions<InferAttributes<ProformaModel>>;
|
||||||
|
customerAssociationAlias?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Añade condiciones explícitas para campos que no quedan bien cubiertos por FULLTEXT.
|
||||||
|
*
|
||||||
|
* Mantiene el MATCH ... AGAINST existente para las columnas indexadas, pero agrega
|
||||||
|
* búsquedas `LIKE` sobre la referencia visible y el recipient del listado.
|
||||||
|
*/
|
||||||
|
export function buildProformaQuickSearchWhere(
|
||||||
|
params: BuildProformaQuickSearchWhereParams
|
||||||
|
): WhereOptions<InferAttributes<ProformaModel>> | undefined {
|
||||||
|
const term = params.quickSearch?.trim();
|
||||||
|
|
||||||
|
if (!term) {
|
||||||
|
return params.fullTextWhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
const likePattern = `%${term}%`;
|
||||||
|
const customerAssociationAlias = params.customerAssociationAlias ?? "current_customer";
|
||||||
|
|
||||||
|
const clauses: WhereOptions<InferAttributes<ProformaModel>>[] = [
|
||||||
|
{ proforma_reference: { [Op.like]: likePattern } },
|
||||||
|
{ proforma_number: { [Op.like]: likePattern } },
|
||||||
|
{ reference: { [Op.like]: likePattern } },
|
||||||
|
{ description: { [Op.like]: likePattern } },
|
||||||
|
Sequelize.where(Sequelize.col(`${customerAssociationAlias}.name`), {
|
||||||
|
[Op.like]: likePattern,
|
||||||
|
}) as unknown as WhereOptions<InferAttributes<ProformaModel>>,
|
||||||
|
Sequelize.where(Sequelize.col(`${customerAssociationAlias}.trade_name`), {
|
||||||
|
[Op.like]: likePattern,
|
||||||
|
}) as unknown as WhereOptions<InferAttributes<ProformaModel>>,
|
||||||
|
Sequelize.where(Sequelize.col(`${customerAssociationAlias}.tin`), {
|
||||||
|
[Op.like]: likePattern,
|
||||||
|
}) as unknown as WhereOptions<InferAttributes<ProformaModel>>,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (params.fullTextWhere) {
|
||||||
|
clauses.unshift(params.fullTextWhere);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { [Op.or]: clauses };
|
||||||
|
}
|
||||||
@ -41,6 +41,7 @@ import {
|
|||||||
ProformaTaxModel,
|
ProformaTaxModel,
|
||||||
} from "../../../../common";
|
} from "../../../../common";
|
||||||
import type { SequelizeProformaV2DomainMapper, SequelizeProformaV2SummaryMapper } from "../mappers";
|
import type { SequelizeProformaV2DomainMapper, SequelizeProformaV2SummaryMapper } from "../mappers";
|
||||||
|
import { buildProformaQuickSearchWhere } from "./proforma-quick-search";
|
||||||
|
|
||||||
export class SequelizeProformaRepositoryV2
|
export class SequelizeProformaRepositoryV2
|
||||||
extends SequelizeRepository<Proforma>
|
extends SequelizeRepository<Proforma>
|
||||||
@ -406,6 +407,10 @@ export class SequelizeProformaRepositoryV2
|
|||||||
database: this.database,
|
database: this.database,
|
||||||
strictMode: true,
|
strictMode: true,
|
||||||
});
|
});
|
||||||
|
const quickSearchWhere = buildProformaQuickSearchWhere({
|
||||||
|
quickSearch: criteria.quickSearch,
|
||||||
|
fullTextWhere: criteriaQuery.where as WhereOptions<InferAttributes<ProformaModel>>,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restricciones propias del repositorio.
|
* Restricciones propias del repositorio.
|
||||||
@ -431,7 +436,7 @@ export class SequelizeProformaRepositoryV2
|
|||||||
baseWhere,
|
baseWhere,
|
||||||
scopeWhere,
|
scopeWhere,
|
||||||
...(options.where ? [options.where] : []),
|
...(options.where ? [options.where] : []),
|
||||||
...(criteriaQuery.where ? [criteriaQuery.where] : []),
|
...(quickSearchWhere ? [quickSearchWhere] : []),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -587,19 +592,31 @@ export class SequelizeProformaRepositoryV2
|
|||||||
database: this.database,
|
database: this.database,
|
||||||
strictMode: true,
|
strictMode: true,
|
||||||
});
|
});
|
||||||
|
const quickSearchWhere = buildProformaQuickSearchWhere({
|
||||||
|
quickSearch: criteria.quickSearch,
|
||||||
|
fullTextWhere: criteriaQuery.where as WhereOptions<InferAttributes<ProformaModel>>,
|
||||||
|
});
|
||||||
|
|
||||||
const where: WhereOptions<InferAttributes<ProformaModel>> = {
|
const where: WhereOptions<InferAttributes<ProformaModel>> = {
|
||||||
[Op.and]: [
|
[Op.and]: [
|
||||||
{ company_id: companyId.toString() },
|
{ company_id: companyId.toString() },
|
||||||
this.buildScopeWhere(scope),
|
this.buildScopeWhere(scope),
|
||||||
...(options.where ? [options.where] : []),
|
...(options.where ? [options.where] : []),
|
||||||
...(criteriaQuery.where ? [criteriaQuery.where] : []),
|
...(quickSearchWhere ? [quickSearchWhere] : []),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const rows = (await ProformaModel.findAll({
|
const rows = (await ProformaModel.findAll({
|
||||||
attributes: ["status", [fn("COUNT", col("ProformaModel.id")), "count"]],
|
attributes: ["status", [fn("COUNT", col("ProformaModel.id")), "count"]],
|
||||||
group: ["status"],
|
group: ["status"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: this.database.models.CustomerModel,
|
||||||
|
as: "current_customer",
|
||||||
|
required: false,
|
||||||
|
attributes: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
paranoid: scope !== "deleted",
|
paranoid: scope !== "deleted",
|
||||||
raw: true,
|
raw: true,
|
||||||
transaction,
|
transaction,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user