From 67c76c3185976ae172948443543e3a3e17ae49aa Mon Sep 17 00:00:00 2001 From: david Date: Tue, 9 Sep 2025 17:48:12 +0200 Subject: [PATCH] Facturas de cliente --- .vscode/settings.json | 3 + .../sequelize/sequelize-mapper.ts | 19 +-- .../sequelize-transaction-manager.ts | 8 + .../api/domain/aggregates/customer-invoice.ts | 36 ++--- .../value-objects/customer-invoice-status.ts | 33 ++--- .../src/api/infrastructure/dependencies.ts | 2 +- .../mappers/customer-invoice-item.mapper.ts | 9 +- .../mappers/customer-invoice.mapper.ts | 137 ++++++++---------- .../customer-invoiceParticipant.mapper.ts.bak | 119 --------------- ...er-invoiceParticipantAddress.mapper.ts.bak | 87 ----------- .../mappers/invoice-recipient.mapper.ts | 100 +++++++++++++ .../mappers/item-taxes.mapper.ts | 32 ++++ .../infrastructure/mappers/taxes.mapper.ts | 17 +++ .../sequelize/contact.mo.del.ts.bak | 2 +- .../sequelize/contactAddress.mo.del.ts.bak | 2 +- ....ts => customer-invoice-item-tax.model.ts} | 23 +-- .../sequelize/customer-invoice-item.model.ts | 34 ++++- ...model.ts => customer-invoice-tax.model.ts} | 30 ++-- .../sequelize/customer-invoice.model.ts | 84 +++++++---- .../sequelize/customer-invoice.repository.ts | 29 +++- .../customer-invoiceParticipant.mo.del.ts.bak | 4 +- ...er-invoiceParticipantAddress.mo.del.ts.bak | 4 +- .../src/api/infrastructure/sequelize/index.ts | 8 +- modules/customers/src/api/index.ts | 2 + 24 files changed, 414 insertions(+), 410 deletions(-) delete mode 100644 modules/customer-invoices/src/api/infrastructure/mappers/customer-invoiceParticipant.mapper.ts.bak delete mode 100644 modules/customer-invoices/src/api/infrastructure/mappers/customer-invoiceParticipantAddress.mapper.ts.bak create mode 100644 modules/customer-invoices/src/api/infrastructure/mappers/invoice-recipient.mapper.ts create mode 100644 modules/customer-invoices/src/api/infrastructure/mappers/item-taxes.mapper.ts create mode 100644 modules/customer-invoices/src/api/infrastructure/mappers/taxes.mapper.ts rename modules/customer-invoices/src/api/infrastructure/sequelize/{customer-invoice-item-taxes.model.ts => customer-invoice-item-tax.model.ts} (78%) rename modules/customer-invoices/src/api/infrastructure/sequelize/{customer-invoice-taxes.model.ts => customer-invoice-tax.model.ts} (76%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 78bcc5e5..675787fa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -60,5 +60,8 @@ // other vscode settings "[handlebars]": { "editor.defaultFormatter": "vscode.html-language-features" + }, + "[sql]": { + "editor.defaultFormatter": "cweijan.vscode-mysql-client2" } // <- your root font size here } diff --git a/modules/core/src/api/infrastructure/sequelize/sequelize-mapper.ts b/modules/core/src/api/infrastructure/sequelize/sequelize-mapper.ts index 9e809785..87e107f3 100644 --- a/modules/core/src/api/infrastructure/sequelize/sequelize-mapper.ts +++ b/modules/core/src/api/infrastructure/sequelize/sequelize-mapper.ts @@ -1,10 +1,9 @@ -import { DomainEntity } from "@repo/rdx-ddd"; import { Collection, Result } from "@repo/rdx-utils"; import { Model } from "sequelize"; export type MapperParamsType = Record; -interface IDomainMapper> { +interface IDomainMapper { mapToDomain(source: TModel, params?: MapperParamsType): Result; mapArrayToDomain(source: TModel[], params?: MapperParamsType): Result, Error>; mapArrayAndCountToDomain( @@ -14,7 +13,7 @@ interface IDomainMapper> ): Result, Error>; } -interface IPersistenceMapper> { +interface IPersistenceMapper { mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes; mapCollectionToPersistence( source: Collection, @@ -22,18 +21,12 @@ interface IPersistenceMapper ): TModelAttributes[]; } -export interface ISequelizeMapper< - TModel extends Model, - TModelAttributes, - TEntity extends DomainEntity, -> extends IDomainMapper, +export interface ISequelizeMapper + extends IDomainMapper, IPersistenceMapper {} -export abstract class SequelizeMapper< - TModel extends Model, - TModelAttributes, - TEntity extends DomainEntity, -> implements ISequelizeMapper +export abstract class SequelizeMapper + implements ISequelizeMapper { public abstract mapToDomain(source: TModel, params?: MapperParamsType): Result; diff --git a/modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts b/modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts index 49135665..868b58b0 100644 --- a/modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts +++ b/modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts @@ -1,5 +1,6 @@ import { Sequelize, Transaction } from "sequelize"; import { TransactionManager } from "../database"; +import { InfrastructureUnavailableError } from "../errors"; export class SequelizeTransactionManager extends TransactionManager { protected _database: Sequelize | null = null; @@ -26,4 +27,11 @@ export class SequelizeTransactionManager extends TransactionManager { super(); this._database = database; } + + get database(): Sequelize { + if (!this._database) { + throw new InfrastructureUnavailableError("Database not available"); + } + return this._database; + } } diff --git a/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts b/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts index 43192456..dc0e075c 100644 --- a/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts +++ b/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts @@ -20,6 +20,7 @@ import { export interface CustomerInvoiceProps { companyId: UniqueID; + isProforma: boolean; invoiceNumber: CustomerInvoiceNumber; status: CustomerInvoiceStatus; series: Maybe; @@ -28,7 +29,7 @@ export interface CustomerInvoiceProps { operationDate: Maybe; customerId: UniqueID; - + recipient: Maybe; notes: Maybe; @@ -56,7 +57,6 @@ export interface CustomerInvoiceProps { //totalAmount: MoneyValue; - items: CustomerInvoiceItems; } @@ -79,17 +79,17 @@ export class CustomerInvoice extends AggregateRoot { const customerInvoice = new CustomerInvoice(props, id); // Reglas de negocio / validaciones - - if (!customerInvoice.isDraft() && !customerInvoice.hasRecipient()) { - return Result.fail( - new DomainValidationError( - "MISSING_CUSTOMER_DATA", - "customerData", - "Customer data must be provided for non-draft invoices" - ) - ); - } + if (!customerInvoice.isProforma && !customerInvoice.hasRecipient) { + return Result.fail( + new DomainValidationError( + "MISSING_CUSTOMER_DATA", + "recipient", + "Customer data must be provided for non-proforma invoices" + ) + ); + } + // 馃敼 Disparar evento de dominio "CustomerInvoiceAuthenticatedEvent" //const { customerInvoice } = props; //user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString())); @@ -109,6 +109,10 @@ export class CustomerInvoice extends AggregateRoot { return this.props.customerId; } + public get isProforma(): boolean { + return this.props.isProforma; + } + public get status(): CustomerInvoiceStatus { return this.props.status; } @@ -174,12 +178,8 @@ export class CustomerInvoice extends AggregateRoot { return this._items; } - public isDraft() { - return this.status.isDraft(); - } - - public hasRecipient() { - return this.recipient.isSome() + get hasRecipient() { + return this.recipient.isSome(); } /*get senderId(): UniqueID { diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts index c49eac32..10df8eed 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts @@ -7,23 +7,22 @@ interface ICustomerInvoiceStatusProps { } export enum INVOICE_STATUS { - DRAFT = "draft", - EMITTED = "emitted", - SENT = "sent", - RECEIVED = "received", - REJECTED = "rejected", + DRAFT = "draft", // <- Proforma + SENT = "sent", // <- Proforma + APPROVED = "approved", // <- Proforma + REJECTED = "rejected", // <- Proforma + EMITTED = "emitted", // <- Factura y enviada a Veri*Factu } export class CustomerInvoiceStatus extends ValueObject { - private static readonly ALLOWED_STATUSES = ["draft", "emitted", "sent", "received", "rejected"]; + private static readonly ALLOWED_STATUSES = ["draft", "sent", "approved", "rejected", "emitted"]; private static readonly FIELD = "invoiceStatus"; private static readonly ERROR_CODE = "INVALID_INVOICE_STATUS"; private static readonly TRANSITIONS: Record = { - draft: [INVOICE_STATUS.EMITTED], - emitted: [INVOICE_STATUS.SENT, INVOICE_STATUS.REJECTED, INVOICE_STATUS.DRAFT], - sent: [INVOICE_STATUS.RECEIVED, INVOICE_STATUS.REJECTED], - received: [], - rejected: [], + draft: [INVOICE_STATUS.SENT], + sent: [INVOICE_STATUS.APPROVED, INVOICE_STATUS.REJECTED], + approved: [INVOICE_STATUS.EMITTED], + rejected: [INVOICE_STATUS.DRAFT], }; static create(value: string): Result { @@ -44,9 +43,9 @@ export class CustomerInvoiceStatus extends ValueObject Name.create(value)), - "customer_name", - errors - ); - - const customerTin = extractOrPushError( - maybeFromNullableVO(source.customer_tin, (value) => TINNumber.create(value)), - "customer_tin", - errors - ); - - const customerStreet = extractOrPushError( - maybeFromNullableVO(source.customer_street, (value) => Street.create(value)), - "customer_street", - errors - ); - - const customerStreet2 = extractOrPushError( - maybeFromNullableVO(source.customer_street2, (value) => Street.create(value)), - "customer_street2", - errors - ); - - const customerCity = extractOrPushError( - maybeFromNullableVO(source.customer_city, (value) => City.create(value)), - "customer_city", - errors - ); - - const customerProvince = extractOrPushError( - maybeFromNullableVO(source.customer_province, (value) => Province.create(value)), - "customer_province", - errors - ); - - const customerPostalCode = extractOrPushError( - maybeFromNullableVO(source.customer_postal_code, (value) => PostalCode.create(value)), - "customer_postal_code", - errors - ); - - const customerCountry = extractOrPushError( - maybeFromNullableVO(source.customer_country, (value) => Country.create(value)), - "customer_country", - errors - ); + // Recipient (customer data) (snapshot) + const recipient = this._recipientMapper.mapToDomain(source, { + errors, + ...params, + }); // Mapear los items de la factura - const items = this._itemsMapper.mapArrayToDomain(source.items, { - sourceParent: source, + const itemsOrResult = this._itemsMapper.mapArrayToDomain(source.items, { + parent: source, errors, ...params, }); // Mapear los impuestos + const taxesOrResult = this._taxesMapper.mapArrayToDomain(source.taxes, { + parent: source, + errors, + ...params, + }); if (errors.length > 0) { return Result.fail( @@ -196,6 +160,8 @@ export class CustomerInvoiceMapper const invoiceProps: CustomerInvoiceProps = { companyId: companyId!, + + isProforma: isProforma, status: status!, series: series!, invoiceNumber: invoiceNumber!, @@ -203,6 +169,7 @@ export class CustomerInvoiceMapper operationDate: operationDate!, customerId: customerId!, + recipient: recipient, notes: notes!, @@ -211,9 +178,12 @@ export class CustomerInvoiceMapper discountPercentage: discountPercentage!, + taxes: taxesOrResult, + items: CustomerInvoiceItems.create({ languageCode: languageCode!, currencyCode: currencyCode!, + items: itemsOrResult.isSuccess ? itemsOrResult.data.getAll() : [], }), }; @@ -229,27 +199,39 @@ export class CustomerInvoiceMapper ): CustomerInvoiceCreationAttributes { const items = this._itemsMapper.mapCollectionToPersistence(source.items, params); - const customer = source.recipient.match((recipient) => ({ - customer_id: recipient.id.toPrimitive(), - customer_tin: recipient.tin.toPrimitive(), - customer_name: recipient.name.toPrimitive(), - customer_street: toNullable(recipient.address.street, (street) => street.toPrimitive()), - customer_street2: toNullable(recipient.address.street2, (street2) => street2.toPrimitive()), - customer_city: toNullable(recipient.address.city, (city) => city.toPrimitive()), - customer_province: toNullable(recipient.address.province, (province) => province.toPrimitive()), - customer_postal_code: toNullable(recipient.address.postalCode, (postalCode) => postalCode.toPrimitive()), - customer_country: toNullable(recipient.address.country, (country) => country.toPrimitive()), - }) as any, () => ({ - customer_id: source.customerId.toPrimitive(), - customer_tin: null, - customer_name: null, - customer_street: null, - customer_street2: null, - customer_city: null, - customer_province: null, - customer_postal_code: null, - customer_country: null, - })) as any + const customer = source.recipient.match( + (recipient) => + ({ + customer_id: recipient.id.toPrimitive(), + customer_tin: recipient.tin.toPrimitive(), + customer_name: recipient.name.toPrimitive(), + customer_street: toNullable(recipient.address.street, (street) => street.toPrimitive()), + customer_street2: toNullable(recipient.address.street2, (street2) => + street2.toPrimitive() + ), + customer_city: toNullable(recipient.address.city, (city) => city.toPrimitive()), + customer_province: toNullable(recipient.address.province, (province) => + province.toPrimitive() + ), + customer_postal_code: toNullable(recipient.address.postalCode, (postalCode) => + postalCode.toPrimitive() + ), + customer_country: toNullable(recipient.address.country, (country) => + country.toPrimitive() + ), + }) as any, + () => ({ + customer_id: source.customerId.toPrimitive(), + customer_tin: null, + customer_name: null, + customer_street: null, + customer_street2: null, + customer_city: null, + customer_province: null, + customer_postal_code: null, + customer_country: null, + }) + ) as any; return { id: source.id.toPrimitive(), @@ -289,7 +271,6 @@ export class CustomerInvoiceMapper items, ...customer, - }; } } diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoiceParticipant.mapper.ts.bak b/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoiceParticipant.mapper.ts.bak deleted file mode 100644 index 42757756..00000000 --- a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoiceParticipant.mapper.ts.bak +++ /dev/null @@ -1,119 +0,0 @@ -import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure"; -import { Name, TINNumber, UniqueID } from "@shared/contexts"; -import { - ICustomerInvoiceCustomerProps, - CustomerInvoice, - CustomerInvoiceCustomer, - CustomerInvoiceParticipantBillingAddress, - CustomerInvoiceParticipantShippingAddress, -} from "../../domain"; -import { IInvoicingContext } from "../InvoicingContext"; -import { CustomerInvoiceParticipant_Model, TCreationCustomerInvoiceParticipant_Model } from "../sequelize"; -import { - ICustomerInvoiceParticipantAddressMapper, - createCustomerInvoiceParticipantAddressMapper, -} from "./customer-invoiceParticipantAddress.mapper"; - -export interface ICustomerInvoiceParticipantMapper - extends ISequelizeMapper< - CustomerInvoiceParticipant_Model, - TCreationCustomerInvoiceParticipant_Model, - CustomerInvoiceCustomer - > {} - -export const createCustomerInvoiceParticipantMapper = ( - context: IInvoicingContext -): ICustomerInvoiceParticipantMapper => - new CustomerInvoiceParticipantMapper({ - context, - addressMapper: createCustomerInvoiceParticipantAddressMapper(context), - }); - -class CustomerInvoiceParticipantMapper - extends SequelizeMapper< - CustomerInvoiceParticipant_Model, - TCreationCustomerInvoiceParticipant_Model, - CustomerInvoiceCustomer - > - implements ICustomerInvoiceParticipantMapper -{ - public constructor(props: { - addressMapper: ICustomerInvoiceParticipantAddressMapper; - context: IInvoicingContext; - }) { - super(props); - } - - protected toDomainMappingImpl(source: CustomerInvoiceParticipant_Model, params: any) { - /*if (!source.billingAddress) { - this.handleRequiredFieldError( - "billingAddress", - new Error("Missing participant's billing address"), - ); - } - - if (!source.shippingAddress) { - this.handleRequiredFieldError( - "shippingAddress", - new Error("Missing participant's shipping address"), - ); - } -*/ - const billingAddress = source.billingAddress - ? ((this.props.addressMapper as ICustomerInvoiceParticipantAddressMapper).mapToDomain( - source.billingAddress, - params - ) as CustomerInvoiceParticipantBillingAddress) - : undefined; - - const shippingAddress = source.shippingAddress - ? ((this.props.addressMapper as ICustomerInvoiceParticipantAddressMapper).mapToDomain( - source.shippingAddress, - params - ) as CustomerInvoiceParticipantShippingAddress) - : undefined; - - const props: ICustomerInvoiceCustomerProps = { - tin: this.mapsValue(source, "tin", TINNumber.create), - firstName: this.mapsValue(source, "first_name", Name.create), - lastName: this.mapsValue(source, "last_name", Name.create), - companyName: this.mapsValue(source, "company_name", Name.create), - billingAddress, - shippingAddress, - }; - - const id = this.mapsValue(source, "participant_id", UniqueID.create); - const participantOrError = CustomerInvoiceCustomer.create(props, id); - - if (participantOrError.isFailure) { - throw participantOrError.error; - } - - return participantOrError.object; - } - - protected toPersistenceMappingImpl( - source: CustomerInvoiceCustomer, - params: { sourceParent: CustomerInvoice } - ): TCreationCustomerInvoiceParticipant_Model { - const { sourceParent } = params; - - return { - customerInvoice_id: sourceParent.id.toPrimitive(), - - participant_id: source.id.toPrimitive(), - tin: source.tin.toPrimitive(), - first_name: source.firstName.toPrimitive(), - last_name: source.lastName.toPrimitive(), - company_name: source.companyName.toPrimitive(), - - billingAddress: ( - this.props.addressMapper as ICustomerInvoiceParticipantAddressMapper - ).mapToPersistence(source.billingAddress!, { sourceParent: source }), - - shippingAddress: ( - this.props.addressMapper as ICustomerInvoiceParticipantAddressMapper - ).mapToPersistence(source.shippingAddress!, { sourceParent: source }), - }; - } -} diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoiceParticipantAddress.mapper.ts.bak b/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoiceParticipantAddress.mapper.ts.bak deleted file mode 100644 index 519ea617..00000000 --- a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoiceParticipantAddress.mapper.ts.bak +++ /dev/null @@ -1,87 +0,0 @@ -import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure"; -import { - City, - Country, - Email, - Note, - Phone, - PostalCode, - Province, - Street, - UniqueID, -} from "@shared/contexts"; -import { - ICustomerInvoiceParticipantAddressProps, - CustomerInvoiceCustomer, - CustomerInvoiceParticipantAddress, -} from "../../domain"; -import { IInvoicingContext } from "../InvoicingContext"; -import { - CustomerInvoiceParticipantAddress_Model, - TCreationCustomerInvoiceParticipantAddress_Model, -} from "../sequelize"; - -export interface ICustomerInvoiceParticipantAddressMapper - extends ISequelizeMapper< - CustomerInvoiceParticipantAddress_Model, - TCreationCustomerInvoiceParticipantAddress_Model, - CustomerInvoiceParticipantAddress - > {} - -export const createCustomerInvoiceParticipantAddressMapper = ( - context: IInvoicingContext -): ICustomerInvoiceParticipantAddressMapper => new CustomerInvoiceParticipantAddressMapper({ context }); - -class CustomerInvoiceParticipantAddressMapper - extends SequelizeMapper< - CustomerInvoiceParticipantAddress_Model, - TCreationCustomerInvoiceParticipantAddress_Model, - CustomerInvoiceParticipantAddress - > - implements ICustomerInvoiceParticipantAddressMapper -{ - protected toDomainMappingImpl(source: CustomerInvoiceParticipantAddress_Model, params: any) { - const id = this.mapsValue(source, "address_id", UniqueID.create); - - const props: ICustomerInvoiceParticipantAddressProps = { - type: source.type, - street: this.mapsValue(source, "street", Street.create), - city: this.mapsValue(source, "city", City.create), - province: this.mapsValue(source, "province", Province.create), - postalCode: this.mapsValue(source, "postal_code", PostalCode.create), - country: this.mapsValue(source, "country", Country.create), - email: this.mapsValue(source, "email", Email.create), - phone: this.mapsValue(source, "phone", Phone.create), - notes: this.mapsValue(source, "notes", Note.create), - }; - - const addressOrError = CustomerInvoiceParticipantAddress.create(props, id); - - if (addressOrError.isFailure) { - throw addressOrError.error; - } - - return addressOrError.object; - } - - protected toPersistenceMappingImpl( - source: CustomerInvoiceParticipantAddress, - params: { sourceParent: CustomerInvoiceCustomer } - ) { - const { sourceParent } = params; - - return { - address_id: source.id.toPrimitive(), - participant_id: sourceParent.id.toPrimitive(), - type: String(source.type), - title: source.title, - street: source.street.toPrimitive(), - city: source.city.toPrimitive(), - postal_code: source.postalCode.toPrimitive(), - province: source.province.toPrimitive(), - country: source.country.toPrimitive(), - email: source.email.toPrimitive(), - phone: source.phone.toPrimitive(), - }; - } -} diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/invoice-recipient.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/invoice-recipient.mapper.ts new file mode 100644 index 00000000..5d3f1127 --- /dev/null +++ b/modules/customer-invoices/src/api/infrastructure/mappers/invoice-recipient.mapper.ts @@ -0,0 +1,100 @@ +import { + City, + Country, + Name, + PostalCode, + Province, + Street, + TINNumber, + maybeFromNullableVO, +} from "@repo/rdx-ddd"; + +import { MapperParamsType, ValidationErrorDetail, extractOrPushError } from "@erp/core/api"; +import { Maybe, isNullishOrEmpty } from "@repo/rdx-utils"; +import { InferCreationAttributes } from "sequelize"; +import { CustomerInvoice, InvoiceRecipient } from "../../domain"; +import { CustomerInvoiceModel } from "../sequelize"; + +export class InvoiceRecipientMapper { + public mapToDomain(source: CustomerInvoiceModel, params?: MapperParamsType) { + const { errors } = params as { + errors: ValidationErrorDetail[]; + }; + + // Customer (snapshot) + + const customerName = extractOrPushError( + Name.create(source.customer_name), + "customer_name", + errors + ); + + const customerTin = extractOrPushError( + TINNumber.create(source.customer_tin), + "customer_tin", + errors + ); + + const customerStreet = extractOrPushError( + maybeFromNullableVO(source.customer_street, (value) => Street.create(value)), + "customer_street", + errors + ); + + const customerStreet2 = extractOrPushError( + maybeFromNullableVO(source.customer_street2, (value) => Street.create(value)), + "customer_street2", + errors + ); + + const customerCity = extractOrPushError( + maybeFromNullableVO(source.customer_city, (value) => City.create(value)), + "customer_city", + errors + ); + + const customerProvince = extractOrPushError( + maybeFromNullableVO(source.customer_province, (value) => Province.create(value)), + "customer_province", + errors + ); + + const customerPostalCode = extractOrPushError( + maybeFromNullableVO(source.customer_postal_code, (value) => PostalCode.create(value)), + "customer_postal_code", + errors + ); + + const customerCountry = extractOrPushError( + maybeFromNullableVO(source.customer_country, (value) => Country.create(value)), + "customer_country", + errors + ); + + const recipientOrError = InvoiceRecipient.create({ + name: customerName!, + tin: customerTin!, + street: customerStreet!, + street2: customerStreet2!, + city: customerCity!, + postalCode: customerPostalCode!, + province: customerProvince!, + country: customerCountry!, + }); + + return isNullishOrEmpty(recipientOrError) + ? Maybe.none() + : Maybe.some(recipientOrError.data); + } + + public mapToPersistence( + source: InvoiceRecipient, + params?: MapperParamsType + ): Partial> { + 1; + const { index, sourceParent } = params as { + index: number; + sourceParent: CustomerInvoice; + }; + } +} diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/item-taxes.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/item-taxes.mapper.ts new file mode 100644 index 00000000..320bf542 --- /dev/null +++ b/modules/customer-invoices/src/api/infrastructure/mappers/item-taxes.mapper.ts @@ -0,0 +1,32 @@ +import { MapperParamsType, Taxes } from "@erp/core/api"; +import { InferCreationAttributes } from "sequelize"; +import { CustomerInvoiceItemModel, CustomerInvoiceItemTaxModel } from "../sequelize"; + +export class ItemTaxesMapper { + public mapArrayToDomain(item: CustomerInvoiceItemModel, params?: MapperParamsType) { + const taxes = Taxes.create({ items: [] }); + + item.taxes.split(",").every((tax_code, taxIndex) => { + const taxResult = Tax.createFromCode(tax_code, this.taxCatalog); + if (taxResult.isSuccess) { + taxes.add(taxResult.data); + } else { + this.errors.push({ + path: `items[${itemIndex}].taxes[${taxIndex}]`, + message: taxResult.error.message, + }); + } + }); + return taxes; + } + + public mapToPersistence( + source: Taxes, + params?: MapperParamsType + ): InferCreationAttributes { + /*const { index, sourceParent } = params as { + index: number; + sourceParent: CustomerInvoice; + };*/ + } +} diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/taxes.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/taxes.mapper.ts new file mode 100644 index 00000000..9e2dbd68 --- /dev/null +++ b/modules/customer-invoices/src/api/infrastructure/mappers/taxes.mapper.ts @@ -0,0 +1,17 @@ +import { MapperParamsType, Taxes } from "@erp/core/api"; +import { InferCreationAttributes } from "sequelize"; +import { CustomerInvoiceItemTaxModel, CustomerInvoiceTaxModel } from "../sequelize"; + +export class TaxesMapper { + public mapArrayToDomain(taxes: CustomerInvoiceTaxModel[], params?: MapperParamsType) {} + + public mapToPersistence( + source: Taxes, + params?: MapperParamsType + ): InferCreationAttributes { + /*const { index, sourceParent } = params as { + index: number; + sourceParent: CustomerInvoice; + };*/ + } +} diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/contact.mo.del.ts.bak b/modules/customer-invoices/src/api/infrastructure/sequelize/contact.mo.del.ts.bak index d5b11b17..7c062411 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/contact.mo.del.ts.bak +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/contact.mo.del.ts.bak @@ -57,7 +57,7 @@ export default (sequelize: Sequelize) => { Contact_Model.init( { id: { - type: new DataTypes.UUID(), + type: DataTypes.UUID, primaryKey: true, }, tin: { diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/contactAddress.mo.del.ts.bak b/modules/customer-invoices/src/api/infrastructure/sequelize/contactAddress.mo.del.ts.bak index 7260bbcc..67e85d9e 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/contactAddress.mo.del.ts.bak +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/contactAddress.mo.del.ts.bak @@ -54,7 +54,7 @@ export default (sequelize: Sequelize) => { type: DataTypes.UUID, primaryKey: true, }, - customer_id: new DataTypes.UUID(), + customer_id: DataTypes.UUID, type: DataTypes.STRING(), street: DataTypes.STRING(), postal_code: DataTypes.STRING(), diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-taxes.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-tax.model.ts similarity index 78% rename from modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-taxes.model.ts rename to modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-tax.model.ts index 8e7431b6..0c1efa11 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-taxes.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-tax.model.ts @@ -8,14 +8,14 @@ import { } from "sequelize"; import { CustomerInvoiceItem } from "../../domain"; -export type CustomerInvoiceItemTaxesCreationAttributes = InferCreationAttributes< - CustomerInvoiceItemTaxesModel, - {} +export type CustomerInvoiceItemTaxCreationAttributes = InferCreationAttributes< + CustomerInvoiceItemTaxModel, + { omit: "item" } >; -export class CustomerInvoiceItemTaxesModel extends Model< - InferAttributes, - InferCreationAttributes +export class CustomerInvoiceItemTaxModel extends Model< + InferAttributes, + InferCreationAttributes > { declare tax_id: string; declare item_id: string; @@ -36,11 +36,12 @@ export class CustomerInvoiceItemTaxesModel extends Model< static associate(database: Sequelize) { const { CustomerInvoiceItemModel } = database.models; - CustomerInvoiceItemTaxesModel.belongsTo(CustomerInvoiceItemModel, { + CustomerInvoiceItemTaxModel.belongsTo(CustomerInvoiceItemModel, { as: "item", targetKey: "item_id", foreignKey: "item_id", onDelete: "CASCADE", + onUpdate: "CASCADE", }); } @@ -48,10 +49,10 @@ export class CustomerInvoiceItemTaxesModel extends Model< } export default (database: Sequelize) => { - CustomerInvoiceItemTaxesModel.init( + CustomerInvoiceItemTaxModel.init( { tax_id: { - type: new DataTypes.UUID(), + type: DataTypes.UUID, primaryKey: true, }, @@ -89,7 +90,7 @@ export default (database: Sequelize) => { }, { sequelize: database, - tableName: "customer_invoices_item_taxes", + tableName: "customer_invoice_item_taxes", underscored: true, @@ -103,5 +104,5 @@ export default (database: Sequelize) => { } ); - return CustomerInvoiceItemTaxesModel; + return CustomerInvoiceItemTaxModel; }; diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts index f981bc88..ce08af9d 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts @@ -6,16 +6,22 @@ import { NonAttribute, Sequelize, } from "sequelize"; +import { + CustomerInvoiceItemTaxCreationAttributes, + CustomerInvoiceItemTaxModel, +} from "./customer-invoice-item-tax.model"; import { CustomerInvoiceModel } from "./customer-invoice.model"; export type CustomerInvoiceItemCreationAttributes = InferCreationAttributes< CustomerInvoiceItemModel, - { omit: "invoice" } ->; + { omit: "invoice" | "taxes" } +> & { + taxes?: CustomerInvoiceItemTaxCreationAttributes[]; +}; export class CustomerInvoiceItemModel extends Model< InferAttributes, - InferCreationAttributes + InferCreationAttributes > { declare item_id: string; declare invoice_id: string; @@ -55,15 +61,27 @@ export class CustomerInvoiceItemModel extends Model< declare total_amount_scale: number; declare invoice: NonAttribute; + declare taxes: NonAttribute; static associate(database: Sequelize) { - const { CustomerInvoiceModel, CustomerInvoiceItemModel } = database.models; + const { CustomerInvoiceModel, CustomerInvoiceItemModel, CustomerInvoiceItemTaxModel } = + database.models; CustomerInvoiceItemModel.belongsTo(CustomerInvoiceModel, { - as: "customerInvoice", + as: "invoice", targetKey: "id", foreignKey: "invoice_id", onDelete: "CASCADE", + onUpdate: "CASCADE", + }); + + CustomerInvoiceItemModel.hasMany(CustomerInvoiceItemTaxModel, { + as: "taxes", + foreignKey: "item_id", + sourceKey: "item_id", + constraints: true, + onDelete: "CASCADE", + onUpdate: "CASCADE", }); } } @@ -72,12 +90,12 @@ export default (database: Sequelize) => { CustomerInvoiceItemModel.init( { item_id: { - type: new DataTypes.UUID(), + type: DataTypes.UUID, primaryKey: true, }, invoice_id: { - type: new DataTypes.UUID(), + type: DataTypes.UUID, allowNull: false, }, @@ -195,7 +213,7 @@ export default (database: Sequelize) => { underscored: true, - indexes: [{ name: "invoice_idx", fields: ["invoice_id"], unique: false }], + indexes: [], whereMergeStrategy: "and", // <- c贸mo tratar el merge de un scope diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-taxes.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-tax.model.ts similarity index 76% rename from modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-taxes.model.ts rename to modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-tax.model.ts index 5b09062e..a8846fe3 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-taxes.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-tax.model.ts @@ -8,14 +8,14 @@ import { } from "sequelize"; import { CustomerInvoice } from "../../domain"; -export type CustomerInvoiceTaxesCreationAttributes = InferCreationAttributes< - CustomerInvoiceTaxesModel, - {} +export type CustomerInvoiceTaxCreationAttributes = InferCreationAttributes< + CustomerInvoiceTaxModel, + { omit: "invoice" } >; -export class CustomerInvoiceTaxesModel extends Model< - InferAttributes, - InferCreationAttributes +export class CustomerInvoiceTaxModel extends Model< + InferAttributes, + InferCreationAttributes > { declare tax_id: string; declare invoice_id: string; @@ -36,7 +36,7 @@ export class CustomerInvoiceTaxesModel extends Model< static associate(database: Sequelize) { const { CustomerInvoiceModel } = database.models; - CustomerInvoiceTaxesModel.belongsTo(CustomerInvoiceModel, { + CustomerInvoiceTaxModel.belongsTo(CustomerInvoiceModel, { as: "invoice", targetKey: "id", foreignKey: "invoice_id", @@ -48,10 +48,10 @@ export class CustomerInvoiceTaxesModel extends Model< } export default (database: Sequelize) => { - CustomerInvoiceTaxesModel.init( + CustomerInvoiceTaxModel.init( { tax_id: { - type: new DataTypes.UUID(), + type: DataTypes.UUID, primaryKey: true, }, @@ -89,11 +89,17 @@ export default (database: Sequelize) => { }, { sequelize: database, - tableName: "customer_invoices_taxes", + tableName: "customer_invoice_taxes", underscored: true, - indexes: [], + indexes: [ + { + name: "invoice_id_idx", + fields: ["invoice_id"], + unique: false, + }, + ], whereMergeStrategy: "and", // <- c贸mo tratar el merge de un scope @@ -103,5 +109,5 @@ export default (database: Sequelize) => { } ); - return CustomerInvoiceTaxesModel; + return CustomerInvoiceTaxModel; }; diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.model.ts index dba2cd72..f44b252f 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.model.ts @@ -11,20 +11,28 @@ import { CustomerInvoiceItemModel, } from "./customer-invoice-item.model"; +import { CustomerModel } from "@erp/customers/api"; +import { + CustomerInvoiceTaxCreationAttributes, + CustomerInvoiceTaxModel, +} from "./customer-invoice-tax.model"; + export type CustomerInvoiceCreationAttributes = InferCreationAttributes< CustomerInvoiceModel, - { omit: "items" } + { omit: "items" | "taxes" | "currentCustomer" } > & { items?: CustomerInvoiceItemCreationAttributes[]; + taxes?: CustomerInvoiceTaxCreationAttributes[]; }; export class CustomerInvoiceModel extends Model< InferAttributes, - InferCreationAttributes + InferCreationAttributes > { declare id: string; declare company_id: string; + declare is_proforma: boolean; declare status: string; declare series: string; declare invoice_number: string; @@ -51,9 +59,9 @@ export class CustomerInvoiceModel extends Model< declare taxable_amount_value: number; declare taxable_amount_scale: number; - // Total tax amount / taxes total - declare tax_amount_value: number; - declare tax_amount_scale: number; + // Total taxes amount / taxes total + declare taxes_amount_value: number; + declare taxes_amount_scale: number; // Total declare total_amount_value: number; @@ -72,38 +80,50 @@ export class CustomerInvoiceModel extends Model< // Relaciones declare items: NonAttribute; - //declare customer: NonAttribute; + declare taxes: NonAttribute; + declare currentCustomer: NonAttribute; static associate(database: Sequelize) { - const { CustomerInvoiceModel, CustomerInvoiceItemModel } = database.models; + const { + CustomerInvoiceModel, + CustomerInvoiceItemModel, + CustomerModel, + CustomerInvoiceTaxModel, + } = database.models; + + CustomerInvoiceModel.belongsTo(CustomerModel, { + as: "currentCustomer", + foreignKey: "customer_id", + constraints: false, + }); CustomerInvoiceModel.hasMany(CustomerInvoiceItemModel, { as: "items", foreignKey: "invoice_id", sourceKey: "id", - onDelete: "CASCADE", constraints: true, + onDelete: "CASCADE", + onUpdate: "CASCADE", + }); + + CustomerInvoiceModel.hasMany(CustomerInvoiceTaxModel, { + as: "taxes", + foreignKey: "invoice_id", + sourceKey: "id", + constraints: true, + onDelete: "CASCADE", + onUpdate: "CASCADE", }); } - static hooks(database: Sequelize) { - // Soft-cascade manual: al borrar una factura, marcamos items como borrados (paranoid). - /*CustomerInvoiceModel.addHook("afterDestroy", async (invoice, options) => { - if (!invoice?.id) return; - await CustomerInvoiceItemModel.destroy({ - where: { invoice_id: invoice.id }, - individualHooks: true, - transaction: options.transaction, - }); - });*/ - } + static hooks(database: Sequelize) {} } export default (database: Sequelize) => { CustomerInvoiceModel.init( { id: { - type: new DataTypes.UUID(), + type: DataTypes.UUID, primaryKey: true, }, @@ -112,6 +132,12 @@ export default (database: Sequelize) => { allowNull: false, }, + is_proforma: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + status: { type: new DataTypes.STRING(), allowNull: false, @@ -205,12 +231,12 @@ export default (database: Sequelize) => { defaultValue: 2, }, - tax_amount_value: { + taxes_amount_value: { type: new DataTypes.BIGINT(), // importante: evita problemas de precisi贸n con valores grandes allowNull: true, defaultValue: null, }, - tax_amount_scale: { + taxes_amount_scale: { type: new DataTypes.SMALLINT(), allowNull: false, defaultValue: 2, @@ -232,41 +258,49 @@ export default (database: Sequelize) => { type: DataTypes.UUID, allowNull: false, }, + customer_tin: { type: DataTypes.STRING, allowNull: true, defaultValue: null, }, + customer_name: { type: DataTypes.STRING, allowNull: true, defaultValue: null, }, + customer_street: { type: DataTypes.STRING, allowNull: true, defaultValue: null, }, + customer_street2: { type: DataTypes.STRING, allowNull: true, defaultValue: null, }, + customer_city: { type: DataTypes.STRING, allowNull: true, defaultValue: null, }, + customer_province: { type: DataTypes.STRING, allowNull: true, defaultValue: null, }, + customer_postal_code: { type: DataTypes.STRING, allowNull: true, defaultValue: null, }, + customer_country: { type: DataTypes.STRING, allowNull: true, @@ -285,11 +319,7 @@ export default (database: Sequelize) => { updatedAt: "updated_at", deletedAt: "deleted_at", - indexes: [ - { name: "company_idx", fields: ["company_id"], unique: false }, - { name: "idx_company_idx", fields: ["id", "company_id"], unique: true }, - { unique: true, fields: ["invoice_number"] }, - ], + indexes: [{ name: "company_idx", fields: ["company_id"], unique: false }], whereMergeStrategy: "and", // <- c贸mo tratar el merge de un scope diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts index 7863dd26..70e1d437 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts @@ -2,7 +2,7 @@ import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } fro 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 { Sequelize, Transaction } from "sequelize"; import { CustomerInvoice, ICustomerInvoiceRepository } from "../../domain"; import { ICustomerInvoiceMapper } from "../mappers/customer-invoice.mapper"; import { CustomerInvoiceModel } from "./customer-invoice.model"; @@ -11,11 +11,13 @@ export class CustomerInvoiceRepository extends SequelizeRepository implements ICustomerInvoiceRepository { - private readonly mapper!: ICustomerInvoiceMapper; + private readonly _database!: Sequelize; + private readonly _mapper!: ICustomerInvoiceMapper; - constructor(mapper: ICustomerInvoiceMapper) { + constructor(params: { mapper: ICustomerInvoiceMapper; database: Sequelize }) { super(); - this.mapper = mapper; + this._mapper = params.mapper; + this._database = params.database; } // Listado por tenant con criteria saneada @@ -64,9 +66,9 @@ export class CustomerInvoiceRepository transaction: Transaction ): Promise> { try { - const data = this.mapper.mapToPersistence(invoice); + const data = this._mapper.mapToPersistence(invoice); const [instance] = await CustomerInvoiceModel.upsert(data, { transaction, returning: true }); - const savedInvoice = this.mapper.mapToDomain(instance); + const savedInvoice = this._mapper.mapToDomain(instance); return savedInvoice; } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); @@ -121,7 +123,7 @@ export class CustomerInvoiceRepository return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString())); } - const customer = this.mapper.mapToDomain(row); + const customer = this._mapper.mapToDomain(row); return customer; } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); @@ -145,6 +147,7 @@ export class CustomerInvoiceRepository transaction: Transaction ): Promise, Error>> { try { + const { CustomerModel } = this._database.models; const converter = new CriteriaToSequelizeConverter(); const query = converter.convert(criteria); @@ -153,12 +156,22 @@ export class CustomerInvoiceRepository company_id: companyId.toString(), }; + query.include = [ + { + model: CustomerModel, + as: "currentCustomer", + required: false, // false => LEFT JOIN + }, + ]; + const instances = await CustomerInvoiceModel.findAll({ ...query, transaction, }); - return this.mapper.mapArrayToDomain(instances); + console.log(instances); + + return this._mapper.mapArrayToDomain(instances); } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); } diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoiceParticipant.mo.del.ts.bak b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoiceParticipant.mo.del.ts.bak index 540df678..54a1a176 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoiceParticipant.mo.del.ts.bak +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoiceParticipant.mo.del.ts.bak @@ -71,11 +71,11 @@ export default (sequelize: Sequelize) => { CustomerInvoiceParticipant_Model.init( { participant_id: { - type: new DataTypes.UUID(), + type: DataTypes.UUID, primaryKey: true, }, customerInvoice_id: { - type: new DataTypes.UUID(), + type: DataTypes.UUID, primaryKey: true, }, tin: { diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoiceParticipantAddress.mo.del.ts.bak b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoiceParticipantAddress.mo.del.ts.bak index 86dc4f78..a1ff1bcc 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoiceParticipantAddress.mo.del.ts.bak +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoiceParticipantAddress.mo.del.ts.bak @@ -44,11 +44,11 @@ export default (sequelize: Sequelize) => { CustomerInvoiceParticipantAddress_Model.init( { address_id: { - type: new DataTypes.UUID(), + type: DataTypes.UUID, primaryKey: true, }, participant_id: { - type: new DataTypes.UUID(), + type: DataTypes.UUID, primaryKey: true, }, type: { diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/index.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/index.ts index 8cfc4f63..684b5f52 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/index.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/index.ts @@ -1,11 +1,11 @@ -import customerInvoiceItemTaxesModelInit from "./customer-invoice-item-taxes.model"; +import customerInvoiceItemTaxesModelInit from "./customer-invoice-item-tax.model"; import customerInvoiceItemModelInit from "./customer-invoice-item.model"; -import customerInvoiceTaxesModelInit from "./customer-invoice-taxes.model"; +import customerInvoiceTaxesModelInit from "./customer-invoice-tax.model"; import customerInvoiceModelInit from "./customer-invoice.model"; -export * from "./customer-invoice-item-taxes.model"; +export * from "./customer-invoice-item-tax.model"; export * from "./customer-invoice-item.model"; -export * from "./customer-invoice-taxes.model"; +export * from "./customer-invoice-tax.model"; export * from "./customer-invoice.model"; export * from "./customer-invoice.repository"; diff --git a/modules/customers/src/api/index.ts b/modules/customers/src/api/index.ts index 11e17aac..8bde3a60 100644 --- a/modules/customers/src/api/index.ts +++ b/modules/customers/src/api/index.ts @@ -1,6 +1,8 @@ import { IModuleServer, ModuleParams } from "@erp/core/api"; import { customersRouter, models } from "./infrastructure"; +export * from "./infrastructure/sequelize"; + export const customersAPIModule: IModuleServer = { name: "customers", version: "1.0.0",