import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } from "@erp/core/api"; import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server"; import { UniqueID } from "@repo/rdx-ddd"; import { Collection, Result } from "@repo/rdx-utils"; import { Transaction } from "sequelize"; import { CustomerInvoice, ICustomerInvoiceRepository } from "../../domain"; import { CustomerInvoiceListDTO, ICustomerInvoiceDomainMapper, ICustomerInvoiceListMapper, } from "../mappers"; import { CustomerInvoiceItemTaxModel } from "./models/customer-invoice-item-tax.model"; import { CustomerInvoiceItemModel } from "./models/customer-invoice-item.model"; import { CustomerInvoiceTaxModel } from "./models/customer-invoice-tax.model"; import { CustomerInvoiceModel } from "./models/customer-invoice.model"; export class CustomerInvoiceRepository extends SequelizeRepository implements ICustomerInvoiceRepository { // Listado por tenant con criteria saneada /* async searchInCompany(criteria: any, companyId: string): Promise<{ rows: InvoiceListRow[]; total: number; limit: number; offset: number; }> { const { where, order, limit, offset, attributes } = sanitizeListCriteria(criteria); // WHERE con scope de company const scopedWhere = { ...where, company_id: companyId }; const options: FindAndCountOptions = { where: scopedWhere, order, limit, offset, attributes, raw: true, // devolvemos objetos planos -> más rápido nest: false, distinct: true // por si en el futuro añadimos includes no duplicar count }; const { rows, count } = await CustomerInvoiceModel.findAndCountAll(options); return { rows: rows as unknown as InvoiceListRow[], total: typeof count === "number" ? count : (count as any[]).length, limit, offset, }; } */ /** * * Persiste una nueva factura o actualiza una existente. * * @param invoice - El agregado a guardar. * @param transaction - Transacción activa para la operación. * @returns Result */ async save( invoice: CustomerInvoice, transaction: Transaction ): Promise> { try { const mapper: ICustomerInvoiceDomainMapper = this._registry.getDomainMapper({ resource: "customer-invoice", }); const mapperData = mapper.mapToPersistence(invoice); if (mapperData.isFailure) { return Result.fail(mapperData.error); } const { data } = mapperData; const [instance] = await CustomerInvoiceModel.upsert(data, { transaction, returning: true }); const savedInvoice = mapper.mapToDomain(instance); return savedInvoice; } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); } } /** * Comprueba si existe una factura con un `id` dentro de una `company`. * * @param companyId - Identificador UUID de la empresa a la que pertenece la factura. * @param id - Identificador UUID de la factura. * @param transaction - Transacción activa para la operación. * @returns Result */ async existsByIdInCompany( companyId: UniqueID, id: UniqueID, transaction?: Transaction ): Promise> { try { const count = await CustomerInvoiceModel.count({ where: { id: id.toString(), company_id: companyId.toString() }, transaction, }); return Result.ok(Boolean(count > 0)); } catch (error: unknown) { return Result.fail(translateSequelizeError(error)); } } /** * * Busca una factura por su identificador único. * * @param companyId - Identificador UUID de la empresa a la que pertenece la factura. * @param id - UUID de la factura. * @param transaction - Transacción activa para la operación. * @returns Result */ async getByIdInCompany( companyId: UniqueID, id: UniqueID, transaction: Transaction ): Promise> { try { const mapper: ICustomerInvoiceDomainMapper = this._registry.getDomainMapper("FULL"); const { CustomerModel } = this._database.models; const row = await CustomerInvoiceModel.findOne({ where: { id: id.toString(), company_id: companyId.toString() }, include: [ { model: CustomerModel, as: "current_customer", required: false, // false => LEFT JOIN }, { model: CustomerInvoiceItemModel, as: "items", required: false, include: [ { model: CustomerInvoiceItemTaxModel, as: "taxes", required: false, }, ], }, { model: CustomerInvoiceTaxModel, as: "taxes", required: false, }, ], transaction, }); if (!row) { return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString())); } const customer = mapper.mapToDomain(row); return customer; } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); } } /** * * Consulta facturas usando un objeto Criteria (filtros, orden, paginación). * * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. * @param criteria - Criterios de búsqueda. * @param transaction - Transacción activa para la operación. * @returns Result * * @see Criteria */ public async findByCriteriaInCompany( companyId: UniqueID, criteria: Criteria, transaction: Transaction ): Promise, Error>> { try { const mapper: ICustomerInvoiceListMapper = this._registry.getQueryMapper("LIST"); const { CustomerModel } = this._database.models; const converter = new CriteriaToSequelizeConverter(); const query = converter.convert(criteria); query.where = { ...query.where, company_id: companyId.toString(), }; query.include = [ { model: CustomerModel, as: "current_customer", required: false, // false => LEFT JOIN }, { model: CustomerInvoiceTaxModel, as: "taxes", required: false, }, ]; const { rows, count } = await CustomerInvoiceModel.findAndCountAll({ ...query, transaction, }); return mapper.mapToDTOCollection(rows, count); } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); } } /** * * Elimina o marca como eliminada una factura. * * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. * @param id - UUID de la factura a eliminar. * @param transaction - Transacción activa para la operación. * @returns Result */ async deleteByIdInCompany( companyId: UniqueID, id: UniqueID, transaction: any ): Promise> { try { const deleted = await CustomerInvoiceModel.destroy({ where: { id: id.toString(), company_id: companyId.toString() }, transaction, }); return Result.ok(); } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); } } }