Facturas de cliente

This commit is contained in:
David Arranz 2025-09-09 17:48:12 +02:00
parent 2e4bb56458
commit 67c76c3185
24 changed files with 414 additions and 410 deletions

View File

@ -60,5 +60,8 @@
// other vscode settings // other vscode settings
"[handlebars]": { "[handlebars]": {
"editor.defaultFormatter": "vscode.html-language-features" "editor.defaultFormatter": "vscode.html-language-features"
},
"[sql]": {
"editor.defaultFormatter": "cweijan.vscode-mysql-client2"
} // <- your root font size here } // <- your root font size here
} }

View File

@ -1,10 +1,9 @@
import { DomainEntity } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils"; import { Collection, Result } from "@repo/rdx-utils";
import { Model } from "sequelize"; import { Model } from "sequelize";
export type MapperParamsType = Record<string, any>; export type MapperParamsType = Record<string, any>;
interface IDomainMapper<TModel extends Model, TEntity extends DomainEntity<any>> { interface IDomainMapper<TModel extends Model, TEntity> {
mapToDomain(source: TModel, params?: MapperParamsType): Result<TEntity, Error>; mapToDomain(source: TModel, params?: MapperParamsType): Result<TEntity, Error>;
mapArrayToDomain(source: TModel[], params?: MapperParamsType): Result<Collection<TEntity>, Error>; mapArrayToDomain(source: TModel[], params?: MapperParamsType): Result<Collection<TEntity>, Error>;
mapArrayAndCountToDomain( mapArrayAndCountToDomain(
@ -14,7 +13,7 @@ interface IDomainMapper<TModel extends Model, TEntity extends DomainEntity<any>>
): Result<Collection<TEntity>, Error>; ): Result<Collection<TEntity>, Error>;
} }
interface IPersistenceMapper<TModelAttributes, TEntity extends DomainEntity<any>> { interface IPersistenceMapper<TModelAttributes, TEntity> {
mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes; mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes;
mapCollectionToPersistence( mapCollectionToPersistence(
source: Collection<TEntity>, source: Collection<TEntity>,
@ -22,18 +21,12 @@ interface IPersistenceMapper<TModelAttributes, TEntity extends DomainEntity<any>
): TModelAttributes[]; ): TModelAttributes[];
} }
export interface ISequelizeMapper< export interface ISequelizeMapper<TModel extends Model, TModelAttributes, TEntity>
TModel extends Model, extends IDomainMapper<TModel, TEntity>,
TModelAttributes,
TEntity extends DomainEntity<any>,
> extends IDomainMapper<TModel, TEntity>,
IPersistenceMapper<TModelAttributes, TEntity> {} IPersistenceMapper<TModelAttributes, TEntity> {}
export abstract class SequelizeMapper< export abstract class SequelizeMapper<TModel extends Model, TModelAttributes, TEntity>
TModel extends Model, implements ISequelizeMapper<TModel, TModelAttributes, TEntity>
TModelAttributes,
TEntity extends DomainEntity<any>,
> implements ISequelizeMapper<TModel, TModelAttributes, TEntity>
{ {
public abstract mapToDomain(source: TModel, params?: MapperParamsType): Result<TEntity, Error>; public abstract mapToDomain(source: TModel, params?: MapperParamsType): Result<TEntity, Error>;

View File

@ -1,5 +1,6 @@
import { Sequelize, Transaction } from "sequelize"; import { Sequelize, Transaction } from "sequelize";
import { TransactionManager } from "../database"; import { TransactionManager } from "../database";
import { InfrastructureUnavailableError } from "../errors";
export class SequelizeTransactionManager extends TransactionManager { export class SequelizeTransactionManager extends TransactionManager {
protected _database: Sequelize | null = null; protected _database: Sequelize | null = null;
@ -26,4 +27,11 @@ export class SequelizeTransactionManager extends TransactionManager {
super(); super();
this._database = database; this._database = database;
} }
get database(): Sequelize {
if (!this._database) {
throw new InfrastructureUnavailableError("Database not available");
}
return this._database;
}
} }

View File

@ -20,6 +20,7 @@ import {
export interface CustomerInvoiceProps { export interface CustomerInvoiceProps {
companyId: UniqueID; companyId: UniqueID;
isProforma: boolean;
invoiceNumber: CustomerInvoiceNumber; invoiceNumber: CustomerInvoiceNumber;
status: CustomerInvoiceStatus; status: CustomerInvoiceStatus;
series: Maybe<CustomerInvoiceSerie>; series: Maybe<CustomerInvoiceSerie>;
@ -28,7 +29,7 @@ export interface CustomerInvoiceProps {
operationDate: Maybe<UtcDate>; operationDate: Maybe<UtcDate>;
customerId: UniqueID; customerId: UniqueID;
recipient: Maybe<InvoiceRecipient>; recipient: Maybe<InvoiceRecipient>;
notes: Maybe<TextValue>; notes: Maybe<TextValue>;
@ -56,7 +57,6 @@ export interface CustomerInvoiceProps {
//totalAmount: MoneyValue; //totalAmount: MoneyValue;
items: CustomerInvoiceItems; items: CustomerInvoiceItems;
} }
@ -79,17 +79,17 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
const customerInvoice = new CustomerInvoice(props, id); const customerInvoice = new CustomerInvoice(props, id);
// Reglas de negocio / validaciones // Reglas de negocio / validaciones
if (!customerInvoice.isDraft() && !customerInvoice.hasRecipient()) { if (!customerInvoice.isProforma && !customerInvoice.hasRecipient) {
return Result.fail( return Result.fail(
new DomainValidationError( new DomainValidationError(
"MISSING_CUSTOMER_DATA", "MISSING_CUSTOMER_DATA",
"customerData", "recipient",
"Customer data must be provided for non-draft invoices" "Customer data must be provided for non-proforma invoices"
) )
); );
} }
// 🔹 Disparar evento de dominio "CustomerInvoiceAuthenticatedEvent" // 🔹 Disparar evento de dominio "CustomerInvoiceAuthenticatedEvent"
//const { customerInvoice } = props; //const { customerInvoice } = props;
//user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString())); //user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString()));
@ -109,6 +109,10 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
return this.props.customerId; return this.props.customerId;
} }
public get isProforma(): boolean {
return this.props.isProforma;
}
public get status(): CustomerInvoiceStatus { public get status(): CustomerInvoiceStatus {
return this.props.status; return this.props.status;
} }
@ -174,12 +178,8 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
return this._items; return this._items;
} }
public isDraft() { get hasRecipient() {
return this.status.isDraft(); return this.recipient.isSome();
}
public hasRecipient() {
return this.recipient.isSome()
} }
/*get senderId(): UniqueID { /*get senderId(): UniqueID {

View File

@ -7,23 +7,22 @@ interface ICustomerInvoiceStatusProps {
} }
export enum INVOICE_STATUS { export enum INVOICE_STATUS {
DRAFT = "draft", DRAFT = "draft", // <- Proforma
EMITTED = "emitted", SENT = "sent", // <- Proforma
SENT = "sent", APPROVED = "approved", // <- Proforma
RECEIVED = "received", REJECTED = "rejected", // <- Proforma
REJECTED = "rejected", EMITTED = "emitted", // <- Factura y enviada a Veri*Factu
} }
export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusProps> { export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusProps> {
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 FIELD = "invoiceStatus";
private static readonly ERROR_CODE = "INVALID_INVOICE_STATUS"; private static readonly ERROR_CODE = "INVALID_INVOICE_STATUS";
private static readonly TRANSITIONS: Record<string, string[]> = { private static readonly TRANSITIONS: Record<string, string[]> = {
draft: [INVOICE_STATUS.EMITTED], draft: [INVOICE_STATUS.SENT],
emitted: [INVOICE_STATUS.SENT, INVOICE_STATUS.REJECTED, INVOICE_STATUS.DRAFT], sent: [INVOICE_STATUS.APPROVED, INVOICE_STATUS.REJECTED],
sent: [INVOICE_STATUS.RECEIVED, INVOICE_STATUS.REJECTED], approved: [INVOICE_STATUS.EMITTED],
received: [], rejected: [INVOICE_STATUS.DRAFT],
rejected: [],
}; };
static create(value: string): Result<CustomerInvoiceStatus, Error> { static create(value: string): Result<CustomerInvoiceStatus, Error> {
@ -44,9 +43,9 @@ export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusPro
: value === "sent" : value === "sent"
? CustomerInvoiceStatus.createSent() ? CustomerInvoiceStatus.createSent()
: value === "emitted" : value === "emitted"
? CustomerInvoiceStatus.createSent() ? CustomerInvoiceStatus.createEmitted()
: value === "" : value === "approved"
? CustomerInvoiceStatus.createReceived() ? CustomerInvoiceStatus.createApproved()
: CustomerInvoiceStatus.createDraft() : CustomerInvoiceStatus.createDraft()
); );
} }
@ -63,8 +62,8 @@ export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusPro
return new CustomerInvoiceStatus({ value: INVOICE_STATUS.SENT }); return new CustomerInvoiceStatus({ value: INVOICE_STATUS.SENT });
} }
public static createReceived(): CustomerInvoiceStatus { public static createApproved(): CustomerInvoiceStatus {
return new CustomerInvoiceStatus({ value: INVOICE_STATUS.RECEIVED }); return new CustomerInvoiceStatus({ value: INVOICE_STATUS.APPROVED });
} }
public static createRejected(): CustomerInvoiceStatus { public static createRejected(): CustomerInvoiceStatus {
@ -72,7 +71,7 @@ export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusPro
} }
isDraft(): boolean { isDraft(): boolean {
return this.props.value === INVOICE_STATUS.DRAFT return this.props.value === INVOICE_STATUS.DRAFT;
} }
getProps(): string { getProps(): string {

View File

@ -53,7 +53,7 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
const transactionManager = new SequelizeTransactionManager(database); const transactionManager = new SequelizeTransactionManager(database);
if (!_mapper) _mapper = new CustomerInvoiceMapper(); if (!_mapper) _mapper = new CustomerInvoiceMapper();
if (!_repo) _repo = new CustomerInvoiceRepository(_mapper); if (!_repo) _repo = new CustomerInvoiceRepository({ mapper: _mapper, database });
if (!_service) _service = new CustomerInvoiceService(_repo); if (!_service) _service = new CustomerInvoiceService(_repo);
if (!_catalogs) _catalogs = { taxes: spainTaxCatalogProvider }; if (!_catalogs) _catalogs = { taxes: spainTaxCatalogProvider };

View File

@ -97,7 +97,7 @@ export class CustomerInvoiceItemMapper
); );
// Creación del objeto de dominio // Creación del objeto de dominio
return CustomerInvoiceItem.create( const itemOrError = CustomerInvoiceItem.create(
{ {
languageCode: languageCode!, languageCode: languageCode!,
currencyCode: currencyCode!, currencyCode: currencyCode!,
@ -105,9 +105,16 @@ export class CustomerInvoiceItemMapper
quantity: quantity!, quantity: quantity!,
unitAmount: unitAmount!, unitAmount: unitAmount!,
discountPercentage: discountPercentage!, discountPercentage: discountPercentage!,
taxes: "",
}, },
itemId itemId
); );
if (itemOrError.isFailure) {
errors.push({ path: "item", message: itemOrError.error.message });
}
return itemOrError;
} }
public mapToPersistence( public mapToPersistence(

View File

@ -7,16 +7,9 @@ import {
extractOrPushError, extractOrPushError,
} from "@erp/core/api"; } from "@erp/core/api";
import { import {
City,
Country,
CurrencyCode, CurrencyCode,
LanguageCode, LanguageCode,
Name,
Percentage, Percentage,
PostalCode,
Province,
Street,
TINNumber,
TextValue, TextValue,
UniqueID, UniqueID,
UtcDate, UtcDate,
@ -34,6 +27,8 @@ import {
} from "../../domain"; } from "../../domain";
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../sequelize"; import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../sequelize";
import { CustomerInvoiceItemMapper } from "./customer-invoice-item.mapper"; import { CustomerInvoiceItemMapper } from "./customer-invoice-item.mapper";
import { InvoiceRecipientMapper } from "./invoice-recipient.mapper";
import { TaxesMapper } from "./taxes.mapper";
export interface ICustomerInvoiceMapper export interface ICustomerInvoiceMapper
extends ISequelizeMapper< extends ISequelizeMapper<
@ -47,10 +42,14 @@ export class CustomerInvoiceMapper
implements ICustomerInvoiceMapper implements ICustomerInvoiceMapper
{ {
private _itemsMapper: CustomerInvoiceItemMapper; private _itemsMapper: CustomerInvoiceItemMapper;
private _recipientMapper: InvoiceRecipientMapper;
private _taxesMapper: TaxesMapper;
constructor() { constructor() {
super(); super();
this._itemsMapper = new CustomerInvoiceItemMapper(); // Instanciar el mapper de items this._itemsMapper = new CustomerInvoiceItemMapper(); // Instanciar el mapper de items
this._recipientMapper = new InvoiceRecipientMapper();
this._taxesMapper = new TaxesMapper();
} }
public mapToDomain( public mapToDomain(
@ -67,6 +66,8 @@ export class CustomerInvoiceMapper
errors errors
); );
const isProforma = Boolean(source.is_proforma);
const status = extractOrPushError( const status = extractOrPushError(
CustomerInvoiceStatus.create(source.status), CustomerInvoiceStatus.create(source.status),
"status", "status",
@ -131,62 +132,25 @@ export class CustomerInvoiceMapper
errors errors
); );
const customerName = extractOrPushError( // Recipient (customer data) (snapshot)
maybeFromNullableVO(source.customer_name, (value) => Name.create(value)), const recipient = this._recipientMapper.mapToDomain(source, {
"customer_name", errors,
errors ...params,
); });
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
);
// Mapear los items de la factura // Mapear los items de la factura
const items = this._itemsMapper.mapArrayToDomain(source.items, { const itemsOrResult = this._itemsMapper.mapArrayToDomain(source.items, {
sourceParent: source, parent: source,
errors, errors,
...params, ...params,
}); });
// Mapear los impuestos // Mapear los impuestos
const taxesOrResult = this._taxesMapper.mapArrayToDomain(source.taxes, {
parent: source,
errors,
...params,
});
if (errors.length > 0) { if (errors.length > 0) {
return Result.fail( return Result.fail(
@ -196,6 +160,8 @@ export class CustomerInvoiceMapper
const invoiceProps: CustomerInvoiceProps = { const invoiceProps: CustomerInvoiceProps = {
companyId: companyId!, companyId: companyId!,
isProforma: isProforma,
status: status!, status: status!,
series: series!, series: series!,
invoiceNumber: invoiceNumber!, invoiceNumber: invoiceNumber!,
@ -203,6 +169,7 @@ export class CustomerInvoiceMapper
operationDate: operationDate!, operationDate: operationDate!,
customerId: customerId!, customerId: customerId!,
recipient: recipient,
notes: notes!, notes: notes!,
@ -211,9 +178,12 @@ export class CustomerInvoiceMapper
discountPercentage: discountPercentage!, discountPercentage: discountPercentage!,
taxes: taxesOrResult,
items: CustomerInvoiceItems.create({ items: CustomerInvoiceItems.create({
languageCode: languageCode!, languageCode: languageCode!,
currencyCode: currencyCode!, currencyCode: currencyCode!,
items: itemsOrResult.isSuccess ? itemsOrResult.data.getAll() : [],
}), }),
}; };
@ -229,27 +199,39 @@ export class CustomerInvoiceMapper
): CustomerInvoiceCreationAttributes { ): CustomerInvoiceCreationAttributes {
const items = this._itemsMapper.mapCollectionToPersistence(source.items, params); const items = this._itemsMapper.mapCollectionToPersistence(source.items, params);
const customer = source.recipient.match((recipient) => ({ const customer = source.recipient.match(
customer_id: recipient.id.toPrimitive(), (recipient) =>
customer_tin: recipient.tin.toPrimitive(), ({
customer_name: recipient.name.toPrimitive(), customer_id: recipient.id.toPrimitive(),
customer_street: toNullable(recipient.address.street, (street) => street.toPrimitive()), customer_tin: recipient.tin.toPrimitive(),
customer_street2: toNullable(recipient.address.street2, (street2) => street2.toPrimitive()), customer_name: recipient.name.toPrimitive(),
customer_city: toNullable(recipient.address.city, (city) => city.toPrimitive()), customer_street: toNullable(recipient.address.street, (street) => street.toPrimitive()),
customer_province: toNullable(recipient.address.province, (province) => province.toPrimitive()), customer_street2: toNullable(recipient.address.street2, (street2) =>
customer_postal_code: toNullable(recipient.address.postalCode, (postalCode) => postalCode.toPrimitive()), street2.toPrimitive()
customer_country: toNullable(recipient.address.country, (country) => country.toPrimitive()), ),
}) as any, () => ({ customer_city: toNullable(recipient.address.city, (city) => city.toPrimitive()),
customer_id: source.customerId.toPrimitive(), customer_province: toNullable(recipient.address.province, (province) =>
customer_tin: null, province.toPrimitive()
customer_name: null, ),
customer_street: null, customer_postal_code: toNullable(recipient.address.postalCode, (postalCode) =>
customer_street2: null, postalCode.toPrimitive()
customer_city: null, ),
customer_province: null, customer_country: toNullable(recipient.address.country, (country) =>
customer_postal_code: null, country.toPrimitive()
customer_country: null, ),
})) as any }) 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 { return {
id: source.id.toPrimitive(), id: source.id.toPrimitive(),
@ -289,7 +271,6 @@ export class CustomerInvoiceMapper
items, items,
...customer, ...customer,
}; };
} }
} }

View File

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

View File

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

View File

@ -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<InvoiceRecipient>()
: Maybe.some(recipientOrError.data);
}
public mapToPersistence(
source: InvoiceRecipient,
params?: MapperParamsType
): Partial<InferCreationAttributes<CustomerInvoiceModel, {}>> {
1;
const { index, sourceParent } = params as {
index: number;
sourceParent: CustomerInvoice;
};
}
}

View File

@ -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<CustomerInvoiceItemTaxModel, {}> {
/*const { index, sourceParent } = params as {
index: number;
sourceParent: CustomerInvoice;
};*/
}
}

View File

@ -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<CustomerInvoiceItemTaxModel, {}> {
/*const { index, sourceParent } = params as {
index: number;
sourceParent: CustomerInvoice;
};*/
}
}

View File

@ -57,7 +57,7 @@ export default (sequelize: Sequelize) => {
Contact_Model.init( Contact_Model.init(
{ {
id: { id: {
type: new DataTypes.UUID(), type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
tin: { tin: {

View File

@ -54,7 +54,7 @@ export default (sequelize: Sequelize) => {
type: DataTypes.UUID, type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
customer_id: new DataTypes.UUID(), customer_id: DataTypes.UUID,
type: DataTypes.STRING(), type: DataTypes.STRING(),
street: DataTypes.STRING(), street: DataTypes.STRING(),
postal_code: DataTypes.STRING(), postal_code: DataTypes.STRING(),

View File

@ -8,14 +8,14 @@ import {
} from "sequelize"; } from "sequelize";
import { CustomerInvoiceItem } from "../../domain"; import { CustomerInvoiceItem } from "../../domain";
export type CustomerInvoiceItemTaxesCreationAttributes = InferCreationAttributes< export type CustomerInvoiceItemTaxCreationAttributes = InferCreationAttributes<
CustomerInvoiceItemTaxesModel, CustomerInvoiceItemTaxModel,
{} { omit: "item" }
>; >;
export class CustomerInvoiceItemTaxesModel extends Model< export class CustomerInvoiceItemTaxModel extends Model<
InferAttributes<CustomerInvoiceItemTaxesModel>, InferAttributes<CustomerInvoiceItemTaxModel>,
InferCreationAttributes<CustomerInvoiceItemTaxesModel> InferCreationAttributes<CustomerInvoiceItemTaxModel>
> { > {
declare tax_id: string; declare tax_id: string;
declare item_id: string; declare item_id: string;
@ -36,11 +36,12 @@ export class CustomerInvoiceItemTaxesModel extends Model<
static associate(database: Sequelize) { static associate(database: Sequelize) {
const { CustomerInvoiceItemModel } = database.models; const { CustomerInvoiceItemModel } = database.models;
CustomerInvoiceItemTaxesModel.belongsTo(CustomerInvoiceItemModel, { CustomerInvoiceItemTaxModel.belongsTo(CustomerInvoiceItemModel, {
as: "item", as: "item",
targetKey: "item_id", targetKey: "item_id",
foreignKey: "item_id", foreignKey: "item_id",
onDelete: "CASCADE", onDelete: "CASCADE",
onUpdate: "CASCADE",
}); });
} }
@ -48,10 +49,10 @@ export class CustomerInvoiceItemTaxesModel extends Model<
} }
export default (database: Sequelize) => { export default (database: Sequelize) => {
CustomerInvoiceItemTaxesModel.init( CustomerInvoiceItemTaxModel.init(
{ {
tax_id: { tax_id: {
type: new DataTypes.UUID(), type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
@ -89,7 +90,7 @@ export default (database: Sequelize) => {
}, },
{ {
sequelize: database, sequelize: database,
tableName: "customer_invoices_item_taxes", tableName: "customer_invoice_item_taxes",
underscored: true, underscored: true,
@ -103,5 +104,5 @@ export default (database: Sequelize) => {
} }
); );
return CustomerInvoiceItemTaxesModel; return CustomerInvoiceItemTaxModel;
}; };

View File

@ -6,16 +6,22 @@ import {
NonAttribute, NonAttribute,
Sequelize, Sequelize,
} from "sequelize"; } from "sequelize";
import {
CustomerInvoiceItemTaxCreationAttributes,
CustomerInvoiceItemTaxModel,
} from "./customer-invoice-item-tax.model";
import { CustomerInvoiceModel } from "./customer-invoice.model"; import { CustomerInvoiceModel } from "./customer-invoice.model";
export type CustomerInvoiceItemCreationAttributes = InferCreationAttributes< export type CustomerInvoiceItemCreationAttributes = InferCreationAttributes<
CustomerInvoiceItemModel, CustomerInvoiceItemModel,
{ omit: "invoice" } { omit: "invoice" | "taxes" }
>; > & {
taxes?: CustomerInvoiceItemTaxCreationAttributes[];
};
export class CustomerInvoiceItemModel extends Model< export class CustomerInvoiceItemModel extends Model<
InferAttributes<CustomerInvoiceItemModel>, InferAttributes<CustomerInvoiceItemModel>,
InferCreationAttributes<CustomerInvoiceItemModel, { omit: "invoice" }> InferCreationAttributes<CustomerInvoiceItemModel, { omit: "invoice" | "taxes" }>
> { > {
declare item_id: string; declare item_id: string;
declare invoice_id: string; declare invoice_id: string;
@ -55,15 +61,27 @@ export class CustomerInvoiceItemModel extends Model<
declare total_amount_scale: number; declare total_amount_scale: number;
declare invoice: NonAttribute<CustomerInvoiceModel>; declare invoice: NonAttribute<CustomerInvoiceModel>;
declare taxes: NonAttribute<CustomerInvoiceItemTaxModel[]>;
static associate(database: Sequelize) { static associate(database: Sequelize) {
const { CustomerInvoiceModel, CustomerInvoiceItemModel } = database.models; const { CustomerInvoiceModel, CustomerInvoiceItemModel, CustomerInvoiceItemTaxModel } =
database.models;
CustomerInvoiceItemModel.belongsTo(CustomerInvoiceModel, { CustomerInvoiceItemModel.belongsTo(CustomerInvoiceModel, {
as: "customerInvoice", as: "invoice",
targetKey: "id", targetKey: "id",
foreignKey: "invoice_id", foreignKey: "invoice_id",
onDelete: "CASCADE", 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( CustomerInvoiceItemModel.init(
{ {
item_id: { item_id: {
type: new DataTypes.UUID(), type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
invoice_id: { invoice_id: {
type: new DataTypes.UUID(), type: DataTypes.UUID,
allowNull: false, allowNull: false,
}, },
@ -195,7 +213,7 @@ export default (database: Sequelize) => {
underscored: true, underscored: true,
indexes: [{ name: "invoice_idx", fields: ["invoice_id"], unique: false }], indexes: [],
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope whereMergeStrategy: "and", // <- cómo tratar el merge de un scope

View File

@ -8,14 +8,14 @@ import {
} from "sequelize"; } from "sequelize";
import { CustomerInvoice } from "../../domain"; import { CustomerInvoice } from "../../domain";
export type CustomerInvoiceTaxesCreationAttributes = InferCreationAttributes< export type CustomerInvoiceTaxCreationAttributes = InferCreationAttributes<
CustomerInvoiceTaxesModel, CustomerInvoiceTaxModel,
{} { omit: "invoice" }
>; >;
export class CustomerInvoiceTaxesModel extends Model< export class CustomerInvoiceTaxModel extends Model<
InferAttributes<CustomerInvoiceTaxesModel>, InferAttributes<CustomerInvoiceTaxModel>,
InferCreationAttributes<CustomerInvoiceTaxesModel> InferCreationAttributes<CustomerInvoiceTaxModel>
> { > {
declare tax_id: string; declare tax_id: string;
declare invoice_id: string; declare invoice_id: string;
@ -36,7 +36,7 @@ export class CustomerInvoiceTaxesModel extends Model<
static associate(database: Sequelize) { static associate(database: Sequelize) {
const { CustomerInvoiceModel } = database.models; const { CustomerInvoiceModel } = database.models;
CustomerInvoiceTaxesModel.belongsTo(CustomerInvoiceModel, { CustomerInvoiceTaxModel.belongsTo(CustomerInvoiceModel, {
as: "invoice", as: "invoice",
targetKey: "id", targetKey: "id",
foreignKey: "invoice_id", foreignKey: "invoice_id",
@ -48,10 +48,10 @@ export class CustomerInvoiceTaxesModel extends Model<
} }
export default (database: Sequelize) => { export default (database: Sequelize) => {
CustomerInvoiceTaxesModel.init( CustomerInvoiceTaxModel.init(
{ {
tax_id: { tax_id: {
type: new DataTypes.UUID(), type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
@ -89,11 +89,17 @@ export default (database: Sequelize) => {
}, },
{ {
sequelize: database, sequelize: database,
tableName: "customer_invoices_taxes", tableName: "customer_invoice_taxes",
underscored: true, underscored: true,
indexes: [], indexes: [
{
name: "invoice_id_idx",
fields: ["invoice_id"],
unique: false,
},
],
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
@ -103,5 +109,5 @@ export default (database: Sequelize) => {
} }
); );
return CustomerInvoiceTaxesModel; return CustomerInvoiceTaxModel;
}; };

View File

@ -11,20 +11,28 @@ import {
CustomerInvoiceItemModel, CustomerInvoiceItemModel,
} from "./customer-invoice-item.model"; } from "./customer-invoice-item.model";
import { CustomerModel } from "@erp/customers/api";
import {
CustomerInvoiceTaxCreationAttributes,
CustomerInvoiceTaxModel,
} from "./customer-invoice-tax.model";
export type CustomerInvoiceCreationAttributes = InferCreationAttributes< export type CustomerInvoiceCreationAttributes = InferCreationAttributes<
CustomerInvoiceModel, CustomerInvoiceModel,
{ omit: "items" } { omit: "items" | "taxes" | "currentCustomer" }
> & { > & {
items?: CustomerInvoiceItemCreationAttributes[]; items?: CustomerInvoiceItemCreationAttributes[];
taxes?: CustomerInvoiceTaxCreationAttributes[];
}; };
export class CustomerInvoiceModel extends Model< export class CustomerInvoiceModel extends Model<
InferAttributes<CustomerInvoiceModel>, InferAttributes<CustomerInvoiceModel>,
InferCreationAttributes<CustomerInvoiceModel, { omit: "items" }> InferCreationAttributes<CustomerInvoiceModel, { omit: "items" | "taxes" }>
> { > {
declare id: string; declare id: string;
declare company_id: string; declare company_id: string;
declare is_proforma: boolean;
declare status: string; declare status: string;
declare series: string; declare series: string;
declare invoice_number: string; declare invoice_number: string;
@ -51,9 +59,9 @@ export class CustomerInvoiceModel extends Model<
declare taxable_amount_value: number; declare taxable_amount_value: number;
declare taxable_amount_scale: number; declare taxable_amount_scale: number;
// Total tax amount / taxes total // Total taxes amount / taxes total
declare tax_amount_value: number; declare taxes_amount_value: number;
declare tax_amount_scale: number; declare taxes_amount_scale: number;
// Total // Total
declare total_amount_value: number; declare total_amount_value: number;
@ -72,38 +80,50 @@ export class CustomerInvoiceModel extends Model<
// Relaciones // Relaciones
declare items: NonAttribute<CustomerInvoiceItemModel[]>; declare items: NonAttribute<CustomerInvoiceItemModel[]>;
//declare customer: NonAttribute<CustomerInvoiceParticipant_Model[]>; declare taxes: NonAttribute<CustomerInvoiceTaxModel[]>;
declare currentCustomer: NonAttribute<CustomerModel>;
static associate(database: Sequelize) { 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, { CustomerInvoiceModel.hasMany(CustomerInvoiceItemModel, {
as: "items", as: "items",
foreignKey: "invoice_id", foreignKey: "invoice_id",
sourceKey: "id", sourceKey: "id",
onDelete: "CASCADE",
constraints: true, 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) { 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,
});
});*/
}
} }
export default (database: Sequelize) => { export default (database: Sequelize) => {
CustomerInvoiceModel.init( CustomerInvoiceModel.init(
{ {
id: { id: {
type: new DataTypes.UUID(), type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
@ -112,6 +132,12 @@ export default (database: Sequelize) => {
allowNull: false, allowNull: false,
}, },
is_proforma: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
status: { status: {
type: new DataTypes.STRING(), type: new DataTypes.STRING(),
allowNull: false, allowNull: false,
@ -205,12 +231,12 @@ export default (database: Sequelize) => {
defaultValue: 2, defaultValue: 2,
}, },
tax_amount_value: { taxes_amount_value: {
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
}, },
tax_amount_scale: { taxes_amount_scale: {
type: new DataTypes.SMALLINT(), type: new DataTypes.SMALLINT(),
allowNull: false, allowNull: false,
defaultValue: 2, defaultValue: 2,
@ -232,41 +258,49 @@ export default (database: Sequelize) => {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,
}, },
customer_tin: { customer_tin: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
}, },
customer_name: { customer_name: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
}, },
customer_street: { customer_street: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
}, },
customer_street2: { customer_street2: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
}, },
customer_city: { customer_city: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
}, },
customer_province: { customer_province: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
}, },
customer_postal_code: { customer_postal_code: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
}, },
customer_country: { customer_country: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
@ -285,11 +319,7 @@ export default (database: Sequelize) => {
updatedAt: "updated_at", updatedAt: "updated_at",
deletedAt: "deleted_at", deletedAt: "deleted_at",
indexes: [ indexes: [{ name: "company_idx", fields: ["company_id"], unique: false }],
{ name: "company_idx", fields: ["company_id"], unique: false },
{ name: "idx_company_idx", fields: ["id", "company_id"], unique: true },
{ unique: true, fields: ["invoice_number"] },
],
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope whereMergeStrategy: "and", // <- cómo tratar el merge de un scope

View File

@ -2,7 +2,7 @@ import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } fro
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server"; import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd"; import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils"; import { Collection, Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize"; import { Sequelize, Transaction } from "sequelize";
import { CustomerInvoice, ICustomerInvoiceRepository } from "../../domain"; import { CustomerInvoice, ICustomerInvoiceRepository } from "../../domain";
import { ICustomerInvoiceMapper } from "../mappers/customer-invoice.mapper"; import { ICustomerInvoiceMapper } from "../mappers/customer-invoice.mapper";
import { CustomerInvoiceModel } from "./customer-invoice.model"; import { CustomerInvoiceModel } from "./customer-invoice.model";
@ -11,11 +11,13 @@ export class CustomerInvoiceRepository
extends SequelizeRepository<CustomerInvoice> extends SequelizeRepository<CustomerInvoice>
implements ICustomerInvoiceRepository implements ICustomerInvoiceRepository
{ {
private readonly mapper!: ICustomerInvoiceMapper; private readonly _database!: Sequelize;
private readonly _mapper!: ICustomerInvoiceMapper;
constructor(mapper: ICustomerInvoiceMapper) { constructor(params: { mapper: ICustomerInvoiceMapper; database: Sequelize }) {
super(); super();
this.mapper = mapper; this._mapper = params.mapper;
this._database = params.database;
} }
// Listado por tenant con criteria saneada // Listado por tenant con criteria saneada
@ -64,9 +66,9 @@ export class CustomerInvoiceRepository
transaction: Transaction transaction: Transaction
): Promise<Result<CustomerInvoice, Error>> { ): Promise<Result<CustomerInvoice, Error>> {
try { try {
const data = this.mapper.mapToPersistence(invoice); const data = this._mapper.mapToPersistence(invoice);
const [instance] = await CustomerInvoiceModel.upsert(data, { transaction, returning: true }); const [instance] = await CustomerInvoiceModel.upsert(data, { transaction, returning: true });
const savedInvoice = this.mapper.mapToDomain(instance); const savedInvoice = this._mapper.mapToDomain(instance);
return savedInvoice; return savedInvoice;
} catch (err: unknown) { } catch (err: unknown) {
return Result.fail(translateSequelizeError(err)); return Result.fail(translateSequelizeError(err));
@ -121,7 +123,7 @@ export class CustomerInvoiceRepository
return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString())); return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString()));
} }
const customer = this.mapper.mapToDomain(row); const customer = this._mapper.mapToDomain(row);
return customer; return customer;
} catch (err: unknown) { } catch (err: unknown) {
return Result.fail(translateSequelizeError(err)); return Result.fail(translateSequelizeError(err));
@ -145,6 +147,7 @@ export class CustomerInvoiceRepository
transaction: Transaction transaction: Transaction
): Promise<Result<Collection<CustomerInvoice>, Error>> { ): Promise<Result<Collection<CustomerInvoice>, Error>> {
try { try {
const { CustomerModel } = this._database.models;
const converter = new CriteriaToSequelizeConverter(); const converter = new CriteriaToSequelizeConverter();
const query = converter.convert(criteria); const query = converter.convert(criteria);
@ -153,12 +156,22 @@ export class CustomerInvoiceRepository
company_id: companyId.toString(), company_id: companyId.toString(),
}; };
query.include = [
{
model: CustomerModel,
as: "currentCustomer",
required: false, // false => LEFT JOIN
},
];
const instances = await CustomerInvoiceModel.findAll({ const instances = await CustomerInvoiceModel.findAll({
...query, ...query,
transaction, transaction,
}); });
return this.mapper.mapArrayToDomain(instances); console.log(instances);
return this._mapper.mapArrayToDomain(instances);
} catch (err: unknown) { } catch (err: unknown) {
return Result.fail(translateSequelizeError(err)); return Result.fail(translateSequelizeError(err));
} }

View File

@ -71,11 +71,11 @@ export default (sequelize: Sequelize) => {
CustomerInvoiceParticipant_Model.init( CustomerInvoiceParticipant_Model.init(
{ {
participant_id: { participant_id: {
type: new DataTypes.UUID(), type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
customerInvoice_id: { customerInvoice_id: {
type: new DataTypes.UUID(), type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
tin: { tin: {

View File

@ -44,11 +44,11 @@ export default (sequelize: Sequelize) => {
CustomerInvoiceParticipantAddress_Model.init( CustomerInvoiceParticipantAddress_Model.init(
{ {
address_id: { address_id: {
type: new DataTypes.UUID(), type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
participant_id: { participant_id: {
type: new DataTypes.UUID(), type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
type: { type: {

View File

@ -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 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"; 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-item.model";
export * from "./customer-invoice-taxes.model"; export * from "./customer-invoice-tax.model";
export * from "./customer-invoice.model"; export * from "./customer-invoice.model";
export * from "./customer-invoice.repository"; export * from "./customer-invoice.repository";

View File

@ -1,6 +1,8 @@
import { IModuleServer, ModuleParams } from "@erp/core/api"; import { IModuleServer, ModuleParams } from "@erp/core/api";
import { customersRouter, models } from "./infrastructure"; import { customersRouter, models } from "./infrastructure";
export * from "./infrastructure/sequelize";
export const customersAPIModule: IModuleServer = { export const customersAPIModule: IModuleServer = {
name: "customers", name: "customers",
version: "1.0.0", version: "1.0.0",