Facturas de cliente
This commit is contained in:
parent
67c76c3185
commit
5879220fe9
@ -11,10 +11,10 @@ const DEFAULT_MIN_SCALE = 0;
|
||||
const DEFAULT_MAX_SCALE = 4;
|
||||
|
||||
export interface TaxProps {
|
||||
value: number;
|
||||
scale: number;
|
||||
name: string;
|
||||
code: string;
|
||||
code: string; // iva_21
|
||||
name: string; // 21% IVA
|
||||
value: number; // 2100
|
||||
scale: number; // 2
|
||||
}
|
||||
|
||||
export class Tax extends ValueObject<TaxProps> {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { JsonTaxCatalogProvider } from "@erp/core";
|
||||
import { ITransactionManager } from "@erp/core/api";
|
||||
import { Criteria } from "@repo/rdx-criteria/server";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
@ -16,7 +17,8 @@ export class ListCustomerInvoicesUseCase {
|
||||
constructor(
|
||||
private readonly service: CustomerInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
private readonly assembler: ListCustomerInvoicesAssembler
|
||||
private readonly assembler: ListCustomerInvoicesAssembler,
|
||||
private readonly taxCatalog: JsonTaxCatalogProvider
|
||||
) {}
|
||||
|
||||
public execute(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { DomainValidationError, Taxes } from "@erp/core/api";
|
||||
import { DomainValidationError } from "@erp/core/api";
|
||||
import {
|
||||
AggregateRoot,
|
||||
CurrencyCode,
|
||||
@ -11,6 +11,7 @@ import {
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoiceItems, InvoiceRecipient } from "../entities";
|
||||
import { InvoiceTaxes } from "../entities/invoice-taxes";
|
||||
import {
|
||||
CustomerInvoiceNumber,
|
||||
CustomerInvoiceSerie,
|
||||
@ -53,7 +54,7 @@ export interface CustomerInvoiceProps {
|
||||
discountPercentage: Percentage;
|
||||
//discountAmount: MoneyValue;
|
||||
|
||||
taxes: Taxes;
|
||||
taxes: InvoiceTaxes;
|
||||
|
||||
//totalAmount: MoneyValue;
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export * from "./customer-invoice-items";
|
||||
export * from "./invoice-customer";
|
||||
export * from "./invoice-recipient";
|
||||
|
||||
export * from "./invoice-taxes";
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export * from "./invoice-customer";
|
||||
@ -1,81 +0,0 @@
|
||||
import { EmailAddress, Name, PostalAddress, ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { PhoneNumber } from "libphonenumber-js";
|
||||
import { CustomerInvoiceAddressType } from "../../value-objects";
|
||||
|
||||
export interface ICustomerInvoiceAddressProps {
|
||||
type: CustomerInvoiceAddressType;
|
||||
title: Name;
|
||||
address: PostalAddress;
|
||||
email: EmailAddress;
|
||||
phone: PhoneNumber;
|
||||
}
|
||||
|
||||
export interface ICustomerInvoiceAddress {
|
||||
type: CustomerInvoiceAddressType;
|
||||
title: Name;
|
||||
address: PostalAddress;
|
||||
email: EmailAddress;
|
||||
phone: PhoneNumber;
|
||||
}
|
||||
|
||||
export class CustomerInvoiceAddress
|
||||
extends ValueObject<ICustomerInvoiceAddressProps>
|
||||
implements ICustomerInvoiceAddress
|
||||
{
|
||||
public static create(props: ICustomerInvoiceAddressProps) {
|
||||
return Result.ok(new CustomerInvoiceAddress(props));
|
||||
}
|
||||
|
||||
public static createShippingAddress(props: ICustomerInvoiceAddressProps) {
|
||||
return Result.ok(
|
||||
new CustomerInvoiceAddress({
|
||||
...props,
|
||||
type: CustomerInvoiceAddressType.create("shipping").data,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public static createBillingAddress(props: ICustomerInvoiceAddressProps) {
|
||||
return Result.ok(
|
||||
new CustomerInvoiceAddress({
|
||||
...props,
|
||||
type: CustomerInvoiceAddressType.create("billing").data,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get title(): Name {
|
||||
return this.props.title;
|
||||
}
|
||||
|
||||
get address(): PostalAddress {
|
||||
return this.props.address;
|
||||
}
|
||||
|
||||
get email(): EmailAddress {
|
||||
return this.props.email;
|
||||
}
|
||||
|
||||
get phone(): PhoneNumber {
|
||||
return this.props.phone;
|
||||
}
|
||||
|
||||
get type(): CustomerInvoiceAddressType {
|
||||
return this.props.type;
|
||||
}
|
||||
|
||||
getProps(): ICustomerInvoiceAddressProps {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return {
|
||||
type: this.type.toString(),
|
||||
title: this.title.toString(),
|
||||
address: this.address.toString(),
|
||||
email: this.email.toString(),
|
||||
phone: this.phone.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
import { DomainEntity, Name, TINNumber, UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoiceAddress } from "./customer-invoice-address";
|
||||
|
||||
export interface ICustomerInvoiceCustomerProps {
|
||||
tin: TINNumber;
|
||||
companyName: Name;
|
||||
firstName: Name;
|
||||
lastName: Name;
|
||||
|
||||
billingAddress?: CustomerInvoiceAddress;
|
||||
shippingAddress?: CustomerInvoiceAddress;
|
||||
}
|
||||
|
||||
export interface ICustomerInvoiceCustomer {
|
||||
id: UniqueID;
|
||||
tin: TINNumber;
|
||||
companyName: Name;
|
||||
firstName: Name;
|
||||
lastName: Name;
|
||||
|
||||
billingAddress?: CustomerInvoiceAddress;
|
||||
shippingAddress?: CustomerInvoiceAddress;
|
||||
}
|
||||
|
||||
export class CustomerInvoiceCustomer
|
||||
extends DomainEntity<ICustomerInvoiceCustomerProps>
|
||||
implements ICustomerInvoiceCustomer
|
||||
{
|
||||
public static create(
|
||||
props: ICustomerInvoiceCustomerProps,
|
||||
id?: UniqueID
|
||||
): Result<CustomerInvoiceCustomer, Error> {
|
||||
const participant = new CustomerInvoiceCustomer(props, id);
|
||||
return Result.ok<CustomerInvoiceCustomer>(participant);
|
||||
}
|
||||
|
||||
get tin(): TINNumber {
|
||||
return this.props.tin;
|
||||
}
|
||||
|
||||
get companyName(): Name {
|
||||
return this.props.companyName;
|
||||
}
|
||||
|
||||
get firstName(): Name {
|
||||
return this.props.firstName;
|
||||
}
|
||||
|
||||
get lastName(): Name {
|
||||
return this.props.lastName;
|
||||
}
|
||||
|
||||
get billingAddress() {
|
||||
return this.props.billingAddress;
|
||||
}
|
||||
|
||||
get shippingAddress() {
|
||||
return this.props.shippingAddress;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./invoice-tax";
|
||||
export * from "./invoice-taxes";
|
||||
@ -0,0 +1,55 @@
|
||||
import { Tax } from "@erp/core/api";
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { InvoiceAmount } from "../../value-objects/invoice-amount";
|
||||
|
||||
export interface InvoiceTaxProps {
|
||||
tax: Tax;
|
||||
taxableAmount: InvoiceAmount;
|
||||
taxesAmount: InvoiceAmount;
|
||||
}
|
||||
|
||||
export class InvoiceTax extends ValueObject<InvoiceTaxProps> {
|
||||
protected static validate(values: InvoiceTaxProps) {
|
||||
return Result.ok(values);
|
||||
}
|
||||
|
||||
static create(values: InvoiceTaxProps): Result<InvoiceTax, Error> {
|
||||
const valueIsValid = InvoiceTax.validate(values);
|
||||
|
||||
if (valueIsValid.isFailure) {
|
||||
return Result.fail(valueIsValid.error);
|
||||
}
|
||||
|
||||
return Result.ok(new InvoiceTax(values));
|
||||
}
|
||||
|
||||
public update(partial: Partial<InvoiceTaxProps>): Result<InvoiceTax, Error> {
|
||||
const updatedProps = {
|
||||
...this.props,
|
||||
...partial,
|
||||
} as InvoiceTaxProps;
|
||||
|
||||
return InvoiceTax.create(updatedProps);
|
||||
}
|
||||
|
||||
public get tax(): Tax {
|
||||
return this.props.tax;
|
||||
}
|
||||
|
||||
public get taxableAmount(): InvoiceAmount {
|
||||
return this.props.taxableAmount;
|
||||
}
|
||||
|
||||
public get taxesAmount(): InvoiceAmount {
|
||||
return this.props.taxesAmount;
|
||||
}
|
||||
|
||||
getProps(): InvoiceTaxProps {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getProps();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
import { InvoiceTax } from "./invoice-tax";
|
||||
|
||||
export interface InvoiceTaxesProps {
|
||||
items?: InvoiceTax[];
|
||||
}
|
||||
|
||||
export class InvoiceTaxes extends Collection<InvoiceTax> {
|
||||
constructor(props: InvoiceTaxesProps) {
|
||||
const { items = [] } = props;
|
||||
super(items);
|
||||
}
|
||||
|
||||
public static create(props: InvoiceTaxesProps): InvoiceTaxes {
|
||||
return new InvoiceTaxes(props);
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ export * from "./customer-invoice-item-description";
|
||||
export * from "./customer-invoice-number";
|
||||
export * from "./customer-invoice-serie";
|
||||
export * from "./customer-invoice-status";
|
||||
export * from "./invoice-amount";
|
||||
export * from "./item-amount";
|
||||
export * from "./item-discount";
|
||||
export * from "./item-quantity";
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||
|
||||
type InvoiceAmountProps = Pick<MoneyValueProps, "value" | "currency_code">;
|
||||
|
||||
export class InvoiceAmount extends MoneyValue {
|
||||
public static DEFAULT_SCALE = 2;
|
||||
|
||||
static create({ value, currency_code }: InvoiceAmountProps) {
|
||||
const props = {
|
||||
value: Number(value),
|
||||
scale: InvoiceAmount.DEFAULT_SCALE,
|
||||
currency_code,
|
||||
};
|
||||
return MoneyValue.create(props);
|
||||
}
|
||||
|
||||
static zero(currency_code: string) {
|
||||
const props = {
|
||||
value: 0,
|
||||
currency_code,
|
||||
};
|
||||
return InvoiceAmount.create(props);
|
||||
}
|
||||
}
|
||||
@ -52,10 +52,13 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
||||
const { database } = params;
|
||||
const transactionManager = new SequelizeTransactionManager(database);
|
||||
|
||||
if (!_mapper) _mapper = new CustomerInvoiceMapper();
|
||||
if (!_catalogs) _catalogs = { taxes: spainTaxCatalogProvider };
|
||||
if (!_mapper)
|
||||
_mapper = new CustomerInvoiceMapper({
|
||||
taxCatalog: _catalogs!.taxes,
|
||||
});
|
||||
if (!_repo) _repo = new CustomerInvoiceRepository({ mapper: _mapper, database });
|
||||
if (!_service) _service = new CustomerInvoiceService(_repo);
|
||||
if (!_catalogs) _catalogs = { taxes: spainTaxCatalogProvider };
|
||||
|
||||
if (!_assemblers) {
|
||||
_assemblers = {
|
||||
@ -75,8 +78,19 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
||||
catalogs: _catalogs,
|
||||
build: {
|
||||
list: () =>
|
||||
new ListCustomerInvoicesUseCase(_service!, transactionManager!, _assemblers!.list),
|
||||
get: () => new GetCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.get),
|
||||
new ListCustomerInvoicesUseCase(
|
||||
_service!,
|
||||
transactionManager!,
|
||||
_assemblers!.list,
|
||||
_catalogs!.taxes
|
||||
),
|
||||
get: () =>
|
||||
new GetCustomerInvoiceUseCase(
|
||||
_service!,
|
||||
transactionManager!,
|
||||
_assemblers!.get,
|
||||
_catalogs!.taxes
|
||||
),
|
||||
create: () =>
|
||||
new CreateCustomerInvoiceUseCase(
|
||||
_service!,
|
||||
@ -85,7 +99,12 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
||||
_catalogs!.taxes
|
||||
),
|
||||
update: () =>
|
||||
new UpdateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.update),
|
||||
new UpdateCustomerInvoiceUseCase(
|
||||
_service!,
|
||||
transactionManager!,
|
||||
_assemblers!.update,
|
||||
_catalogs!.taxes
|
||||
),
|
||||
delete: () => new DeleteCustomerInvoiceUseCase(_service!, transactionManager!),
|
||||
},
|
||||
presenters: {
|
||||
|
||||
@ -2,31 +2,23 @@ import {
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
ValidationErrorCollection,
|
||||
ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
} from "@erp/core/api";
|
||||
import {
|
||||
CurrencyCode,
|
||||
LanguageCode,
|
||||
UniqueID,
|
||||
maybeFromNullableVO,
|
||||
toNullable,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { UniqueID, maybeFromNullableVO, toNullable } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { InferCreationAttributes } from "sequelize";
|
||||
import {
|
||||
CustomerInvoice,
|
||||
CustomerInvoiceItem,
|
||||
CustomerInvoiceItemDescription,
|
||||
CustomerInvoiceProps,
|
||||
ItemAmount,
|
||||
ItemDiscount,
|
||||
ItemQuantity,
|
||||
} from "../../domain";
|
||||
import {
|
||||
CustomerInvoiceItemCreationAttributes,
|
||||
CustomerInvoiceItemModel,
|
||||
CustomerInvoiceModel,
|
||||
} from "../sequelize";
|
||||
import { CustomerInvoiceItemCreationAttributes, CustomerInvoiceItemModel } from "../sequelize";
|
||||
|
||||
export interface ICustomerInvoiceItemMapper
|
||||
extends ISequelizeMapper<
|
||||
@ -43,26 +35,16 @@ export class CustomerInvoiceItemMapper
|
||||
>
|
||||
implements ICustomerInvoiceItemMapper
|
||||
{
|
||||
public mapToDomain(
|
||||
source: CustomerInvoiceItemModel,
|
||||
params?: MapperParamsType
|
||||
): Result<CustomerInvoiceItem, Error> {
|
||||
const { sourceParent, errors } = params as {
|
||||
sourceParent: CustomerInvoiceModel;
|
||||
private mapAttributesToDomain(source: CustomerInvoiceItemModel, params?: MapperParamsType) {
|
||||
const { errors, index, attributes } = params as {
|
||||
index: number;
|
||||
errors: ValidationErrorDetail[];
|
||||
attributes: Partial<CustomerInvoiceProps>;
|
||||
};
|
||||
|
||||
const itemId = extractOrPushError(UniqueID.create(source.item_id), "item_id", errors);
|
||||
|
||||
const languageCode = extractOrPushError(
|
||||
LanguageCode.create(sourceParent.language_code),
|
||||
"language_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const currencyCode = extractOrPushError(
|
||||
CurrencyCode.create(sourceParent.currency_code),
|
||||
"currency_code",
|
||||
const itemId = extractOrPushError(
|
||||
UniqueID.create(source.item_id),
|
||||
`items[${index}].item_id`,
|
||||
errors
|
||||
);
|
||||
|
||||
@ -70,51 +52,87 @@ export class CustomerInvoiceItemMapper
|
||||
maybeFromNullableVO(source.description, (value) =>
|
||||
CustomerInvoiceItemDescription.create(value)
|
||||
),
|
||||
"description",
|
||||
`items[${index}].description`,
|
||||
errors
|
||||
);
|
||||
|
||||
const quantity = extractOrPushError(
|
||||
maybeFromNullableVO(source.quantity_value, (value) => ItemQuantity.create({ value })),
|
||||
"discount_percentage",
|
||||
`items[${index}].discount_percentage`,
|
||||
errors
|
||||
);
|
||||
|
||||
const unitAmount = extractOrPushError(
|
||||
maybeFromNullableVO(source.unit_amount_value, (value) =>
|
||||
ItemAmount.create({ value, currency_code: currencyCode!.code })
|
||||
ItemAmount.create({ value, currency_code: attributes.currencyCode!.code })
|
||||
),
|
||||
"unit_amount",
|
||||
`items[${index}].unit_amount`,
|
||||
errors
|
||||
);
|
||||
|
||||
return {
|
||||
itemId,
|
||||
languageCode: attributes.languageCode,
|
||||
currencyCode: attributes.currencyCode,
|
||||
description,
|
||||
quantity,
|
||||
|
||||
unitAmount,
|
||||
};
|
||||
}
|
||||
|
||||
public mapToDomain(
|
||||
source: CustomerInvoiceItemModel,
|
||||
params?: MapperParamsType
|
||||
): Result<CustomerInvoiceItem, Error> {
|
||||
const { errors, index, requireIncludes } = params as {
|
||||
index: number;
|
||||
requireIncludes: boolean;
|
||||
errors: ValidationErrorDetail[];
|
||||
attributes: Partial<CustomerInvoiceProps>;
|
||||
};
|
||||
|
||||
if (requireIncludes) {
|
||||
if (!source.taxes) {
|
||||
errors.push({
|
||||
path: `items[${index}].taxes`,
|
||||
message: "Taxes not included in query (requireIncludes=true)",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const attributes = this.mapAttributesToDomain(source, params);
|
||||
|
||||
const discountPercentage = extractOrPushError(
|
||||
maybeFromNullableVO(source.discount_percentage_value, (value) =>
|
||||
ItemDiscount.create({ value })
|
||||
),
|
||||
"discount_percentage",
|
||||
`items[${index}].discount_percentage`,
|
||||
errors
|
||||
);
|
||||
|
||||
// Creación del objeto de dominio
|
||||
const itemOrError = CustomerInvoiceItem.create(
|
||||
const createResult = CustomerInvoiceItem.create(
|
||||
{
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
description: description!,
|
||||
quantity: quantity!,
|
||||
unitAmount: unitAmount!,
|
||||
languageCode: attributes.languageCode!,
|
||||
currencyCode: attributes.currencyCode!,
|
||||
description: attributes.description!,
|
||||
quantity: attributes.quantity!,
|
||||
unitAmount: attributes.unitAmount!,
|
||||
discountPercentage: discountPercentage!,
|
||||
taxes: "",
|
||||
},
|
||||
itemId
|
||||
attributes.itemId
|
||||
);
|
||||
|
||||
if (itemOrError.isFailure) {
|
||||
errors.push({ path: "item", message: itemOrError.error.message });
|
||||
if (createResult.isFailure) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Invoice item entity creation failed", [
|
||||
{ path: `items[${index}]`, message: createResult.error.message },
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return itemOrError;
|
||||
return createResult;
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { JsonTaxCatalogProvider } from "@erp/core";
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
@ -25,6 +26,7 @@ import {
|
||||
CustomerInvoiceSerie,
|
||||
CustomerInvoiceStatus,
|
||||
} from "../../domain";
|
||||
import { InvoiceTaxes } from "../../domain/entities/invoice-taxes";
|
||||
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../sequelize";
|
||||
import { CustomerInvoiceItemMapper } from "./customer-invoice-item.mapper";
|
||||
import { InvoiceRecipientMapper } from "./invoice-recipient.mapper";
|
||||
@ -45,11 +47,103 @@ export class CustomerInvoiceMapper
|
||||
private _recipientMapper: InvoiceRecipientMapper;
|
||||
private _taxesMapper: TaxesMapper;
|
||||
|
||||
constructor() {
|
||||
constructor(params: {
|
||||
taxCatalog: JsonTaxCatalogProvider;
|
||||
}) {
|
||||
super();
|
||||
this._itemsMapper = new CustomerInvoiceItemMapper(); // Instanciar el mapper de items
|
||||
this._recipientMapper = new InvoiceRecipientMapper();
|
||||
this._taxesMapper = new TaxesMapper();
|
||||
this._taxesMapper = new TaxesMapper(params);
|
||||
}
|
||||
|
||||
private mapAttributesToDomain(source: CustomerInvoiceModel, params?: MapperParamsType) {
|
||||
const { errors } = params as {
|
||||
errors: ValidationErrorDetail[];
|
||||
};
|
||||
|
||||
const invoiceId = extractOrPushError(UniqueID.create(source.id), "id", errors);
|
||||
const companyId = extractOrPushError(UniqueID.create(source.company_id), "company_id", errors);
|
||||
|
||||
const customerId = extractOrPushError(
|
||||
UniqueID.create(source.customer_id),
|
||||
"customer_id",
|
||||
errors
|
||||
);
|
||||
|
||||
const isProforma = Boolean(source.is_proforma);
|
||||
|
||||
const status = extractOrPushError(
|
||||
CustomerInvoiceStatus.create(source.status),
|
||||
"status",
|
||||
errors
|
||||
);
|
||||
|
||||
const series = extractOrPushError(
|
||||
maybeFromNullableVO(source.series, (value) => CustomerInvoiceSerie.create(value)),
|
||||
"serie",
|
||||
errors
|
||||
);
|
||||
|
||||
const invoiceNumber = extractOrPushError(
|
||||
CustomerInvoiceNumber.create(source.invoice_number),
|
||||
"invoice_number",
|
||||
errors
|
||||
);
|
||||
|
||||
const invoiceDate = extractOrPushError(
|
||||
UtcDate.createFromISO(source.invoice_date),
|
||||
"invoice_date",
|
||||
errors
|
||||
);
|
||||
|
||||
const operationDate = extractOrPushError(
|
||||
maybeFromNullableVO(source.operation_date, (value) => UtcDate.createFromISO(value)),
|
||||
"operation_date",
|
||||
errors
|
||||
);
|
||||
|
||||
const notes = extractOrPushError(
|
||||
maybeFromNullableVO(source.notes, (value) => TextValue.create(value)),
|
||||
"notes",
|
||||
errors
|
||||
);
|
||||
|
||||
const languageCode = extractOrPushError(
|
||||
LanguageCode.create(source.language_code),
|
||||
"language_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const currencyCode = extractOrPushError(
|
||||
CurrencyCode.create(source.currency_code),
|
||||
"currency_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const discountPercentage = extractOrPushError(
|
||||
Percentage.create({
|
||||
value: source.discount_percentage_value,
|
||||
scale: source.discount_percentage_scale,
|
||||
}),
|
||||
"discount_percentage",
|
||||
errors
|
||||
);
|
||||
|
||||
return {
|
||||
invoiceId,
|
||||
companyId,
|
||||
customerId,
|
||||
isProforma,
|
||||
status,
|
||||
series,
|
||||
invoiceNumber,
|
||||
invoiceDate,
|
||||
operationDate,
|
||||
notes,
|
||||
languageCode,
|
||||
currencyCode,
|
||||
discountPercentage,
|
||||
};
|
||||
}
|
||||
|
||||
public mapToDomain(
|
||||
@ -59,135 +153,130 @@ export class CustomerInvoiceMapper
|
||||
try {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
|
||||
const invoiceId = extractOrPushError(UniqueID.create(source.id), "id", errors);
|
||||
const companyId = extractOrPushError(
|
||||
UniqueID.create(source.company_id),
|
||||
"company_id",
|
||||
errors
|
||||
);
|
||||
const attributes = this.mapAttributesToDomain(source, { errors, ...params });
|
||||
|
||||
const isProforma = Boolean(source.is_proforma);
|
||||
const requireIncludes = Boolean(params?.requireIncludes);
|
||||
if (requireIncludes) {
|
||||
if (!source.items) {
|
||||
errors.push({
|
||||
path: "items",
|
||||
message: "Items not included in query (requireIncludes=true)",
|
||||
});
|
||||
}
|
||||
if (!source.taxes) {
|
||||
errors.push({
|
||||
path: "taxes",
|
||||
message: "Taxes not included in query (requireIncludes=true)",
|
||||
});
|
||||
}
|
||||
|
||||
const status = extractOrPushError(
|
||||
CustomerInvoiceStatus.create(source.status),
|
||||
"status",
|
||||
errors
|
||||
);
|
||||
if (attributes.isProforma && !source.current_customer) {
|
||||
errors.push({
|
||||
path: "current_customer",
|
||||
message: "Current customer not included in query (requireIncludes=true)",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const series = extractOrPushError(
|
||||
maybeFromNullableVO(source.series, (value) => CustomerInvoiceSerie.create(value)),
|
||||
"serie",
|
||||
errors
|
||||
);
|
||||
|
||||
const invoiceNumber = extractOrPushError(
|
||||
CustomerInvoiceNumber.create(source.invoice_number),
|
||||
"invoice_number",
|
||||
errors
|
||||
);
|
||||
|
||||
const invoiceDate = extractOrPushError(
|
||||
UtcDate.createFromISO(source.invoice_date),
|
||||
"invoice_date",
|
||||
errors
|
||||
);
|
||||
|
||||
const operationDate = extractOrPushError(
|
||||
maybeFromNullableVO(source.operation_date, (value) => UtcDate.createFromISO(value)),
|
||||
"operation_date",
|
||||
errors
|
||||
);
|
||||
|
||||
const notes = extractOrPushError(
|
||||
maybeFromNullableVO(source.notes, (value) => TextValue.create(value)),
|
||||
"notes",
|
||||
errors
|
||||
);
|
||||
|
||||
const languageCode = extractOrPushError(
|
||||
LanguageCode.create(source.language_code),
|
||||
"language_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const currencyCode = extractOrPushError(
|
||||
CurrencyCode.create(source.currency_code),
|
||||
"currency_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const discountPercentage = extractOrPushError(
|
||||
Percentage.create({
|
||||
value: source.discount_percentage_value,
|
||||
scale: source.discount_percentage_scale,
|
||||
}),
|
||||
"discount_percentage",
|
||||
errors
|
||||
);
|
||||
|
||||
// Customer
|
||||
const customerId = extractOrPushError(
|
||||
UniqueID.create(source.customer_id),
|
||||
"customer_id",
|
||||
errors
|
||||
);
|
||||
|
||||
// Recipient (customer data) (snapshot)
|
||||
const recipient = this._recipientMapper.mapToDomain(source, {
|
||||
// 3) Recipient (snapshot en la factura o include)
|
||||
const recipientResult = this._recipientMapper.mapToDomain(source, {
|
||||
errors,
|
||||
attributes,
|
||||
...params,
|
||||
});
|
||||
|
||||
// Mapear los items de la factura
|
||||
const itemsOrResult = this._itemsMapper.mapArrayToDomain(source.items, {
|
||||
parent: source,
|
||||
if (recipientResult.isFailure) {
|
||||
errors.push({
|
||||
path: "recipient",
|
||||
message: recipientResult.error.message,
|
||||
});
|
||||
}
|
||||
|
||||
// 4) Items (colección)
|
||||
const itemsResults = this._itemsMapper.mapArrayToDomain(source.items, {
|
||||
requireIncludes,
|
||||
errors,
|
||||
attributes,
|
||||
...params,
|
||||
});
|
||||
|
||||
// Mapear los impuestos
|
||||
const taxesOrResult = this._taxesMapper.mapArrayToDomain(source.taxes, {
|
||||
parent: source,
|
||||
if (itemsResults.isFailure) {
|
||||
errors.push({
|
||||
path: "items",
|
||||
message: recipientResult.error.message,
|
||||
});
|
||||
}
|
||||
|
||||
// 5) Taxes (colección a nivel factura)
|
||||
const taxesResults = this._taxesMapper.mapArrayToDomain(source.taxes, {
|
||||
errors,
|
||||
attributes,
|
||||
...params,
|
||||
});
|
||||
|
||||
if (taxesResults.isFailure) {
|
||||
errors.push({
|
||||
path: "taxes",
|
||||
message: recipientResult.error.message,
|
||||
});
|
||||
}
|
||||
|
||||
// 6) Si hubo errores de mapeo, devolvemos colección de validación
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Customer invoice item props mapping failed", errors)
|
||||
new ValidationErrorCollection("Customer invoice mapping failed", errors)
|
||||
);
|
||||
}
|
||||
|
||||
// 7) Construcción del agregado (Dominio)
|
||||
|
||||
const recipient = recipientResult.data;
|
||||
|
||||
const taxes = InvoiceTaxes.create({
|
||||
items: taxesResults.data.getAll(),
|
||||
});
|
||||
|
||||
const items = CustomerInvoiceItems.create({
|
||||
languageCode: attributes.languageCode!,
|
||||
currencyCode: attributes.currencyCode!,
|
||||
items: itemsResults.data.getAll(),
|
||||
});
|
||||
|
||||
const invoiceProps: CustomerInvoiceProps = {
|
||||
companyId: companyId!,
|
||||
companyId: attributes.companyId!,
|
||||
|
||||
isProforma: isProforma,
|
||||
status: status!,
|
||||
series: series!,
|
||||
invoiceNumber: invoiceNumber!,
|
||||
invoiceDate: invoiceDate!,
|
||||
operationDate: operationDate!,
|
||||
isProforma: attributes.isProforma,
|
||||
status: attributes.status!,
|
||||
series: attributes.series!,
|
||||
invoiceNumber: attributes.invoiceNumber!,
|
||||
invoiceDate: attributes.invoiceDate!,
|
||||
operationDate: attributes.operationDate!,
|
||||
|
||||
customerId: customerId!,
|
||||
customerId: attributes.customerId!,
|
||||
recipient: recipient,
|
||||
|
||||
notes: notes!,
|
||||
notes: attributes.notes!,
|
||||
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
languageCode: attributes.languageCode!,
|
||||
currencyCode: attributes.currencyCode!,
|
||||
|
||||
discountPercentage: discountPercentage!,
|
||||
discountPercentage: attributes.discountPercentage!,
|
||||
|
||||
taxes: taxesOrResult,
|
||||
|
||||
items: CustomerInvoiceItems.create({
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
items: itemsOrResult.isSuccess ? itemsOrResult.data.getAll() : [],
|
||||
}),
|
||||
taxes,
|
||||
items,
|
||||
};
|
||||
|
||||
return CustomerInvoice.create(invoiceProps, invoiceId);
|
||||
const createResult = CustomerInvoice.create(invoiceProps, attributes.invoiceId);
|
||||
|
||||
if (createResult.isFailure) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Customer invoice entity creation failed", [
|
||||
{ path: "invoice", message: createResult.error.message },
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(createResult.data);
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(err as Error);
|
||||
}
|
||||
@ -262,9 +351,10 @@ export class CustomerInvoiceMapper
|
||||
discount_amount_scale: source.discountAmount.scale,
|
||||
|
||||
taxable_amount_value: source.taxableAmount.value,
|
||||
taxable_amount_scale: source.taxableAmount.value,
|
||||
tax_amount_value: source.taxAmount.value,
|
||||
tax_amount_scale: source.taxAmount.value,
|
||||
taxable_amount_scale: source.taxableAmount.scale,
|
||||
|
||||
taxes_amount_value: source.taxAmount.value,
|
||||
taxes_amount_scale: source.taxAmount.scale,
|
||||
|
||||
total_amount_value: 0, //total.amount,
|
||||
total_amount_scale: 2, //total.scale,
|
||||
|
||||
@ -9,69 +9,82 @@ import {
|
||||
maybeFromNullableVO,
|
||||
} from "@repo/rdx-ddd";
|
||||
|
||||
import { MapperParamsType, ValidationErrorDetail, extractOrPushError } from "@erp/core/api";
|
||||
import { Maybe, isNullishOrEmpty } from "@repo/rdx-utils";
|
||||
import {
|
||||
MapperParamsType,
|
||||
ValidationErrorCollection,
|
||||
ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
} from "@erp/core/api";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import { InferCreationAttributes } from "sequelize";
|
||||
import { CustomerInvoice, InvoiceRecipient } from "../../domain";
|
||||
import { CustomerInvoice, CustomerInvoiceProps, InvoiceRecipient } from "../../domain";
|
||||
import { CustomerInvoiceModel } from "../sequelize";
|
||||
|
||||
export class InvoiceRecipientMapper {
|
||||
public mapToDomain(source: CustomerInvoiceModel, params?: MapperParamsType) {
|
||||
const { errors } = params as {
|
||||
public mapToDomain(
|
||||
source: CustomerInvoiceModel,
|
||||
params?: MapperParamsType
|
||||
): Result<Maybe<InvoiceRecipient>, Error> {
|
||||
const { errors, attributes } = params as {
|
||||
errors: ValidationErrorDetail[];
|
||||
attributes: Partial<CustomerInvoiceProps>;
|
||||
};
|
||||
|
||||
const { isProforma } = attributes;
|
||||
|
||||
const _name = isProforma ? source.current_customer.name : source.customer_name;
|
||||
const _tin = isProforma ? source.current_customer.tin : source.customer_tin;
|
||||
const _street = isProforma ? source.current_customer.street : source.customer_street;
|
||||
const _street2 = isProforma ? source.current_customer.street2 : source.customer_street2;
|
||||
const _city = isProforma ? source.current_customer.city : source.customer_city;
|
||||
const _postal_code = isProforma
|
||||
? source.current_customer.postal_code
|
||||
: source.customer_postal_code;
|
||||
const _province = isProforma ? source.current_customer.province : source.customer_province;
|
||||
const _country = isProforma ? source.current_customer.country : source.customer_country;
|
||||
|
||||
// Customer (snapshot)
|
||||
const customerName = extractOrPushError(Name.create(_name), "customer_name", errors);
|
||||
|
||||
const customerName = extractOrPushError(
|
||||
Name.create(source.customer_name),
|
||||
"customer_name",
|
||||
errors
|
||||
);
|
||||
|
||||
const customerTin = extractOrPushError(
|
||||
TINNumber.create(source.customer_tin),
|
||||
"customer_tin",
|
||||
errors
|
||||
);
|
||||
const customerTin = extractOrPushError(TINNumber.create(_tin), "customer_tin", errors);
|
||||
|
||||
const customerStreet = extractOrPushError(
|
||||
maybeFromNullableVO(source.customer_street, (value) => Street.create(value)),
|
||||
maybeFromNullableVO(_street, (value) => Street.create(value)),
|
||||
"customer_street",
|
||||
errors
|
||||
);
|
||||
|
||||
const customerStreet2 = extractOrPushError(
|
||||
maybeFromNullableVO(source.customer_street2, (value) => Street.create(value)),
|
||||
maybeFromNullableVO(_street2, (value) => Street.create(value)),
|
||||
"customer_street2",
|
||||
errors
|
||||
);
|
||||
|
||||
const customerCity = extractOrPushError(
|
||||
maybeFromNullableVO(source.customer_city, (value) => City.create(value)),
|
||||
maybeFromNullableVO(_city, (value) => City.create(value)),
|
||||
"customer_city",
|
||||
errors
|
||||
);
|
||||
|
||||
const customerProvince = extractOrPushError(
|
||||
maybeFromNullableVO(source.customer_province, (value) => Province.create(value)),
|
||||
maybeFromNullableVO(_province, (value) => Province.create(value)),
|
||||
"customer_province",
|
||||
errors
|
||||
);
|
||||
|
||||
const customerPostalCode = extractOrPushError(
|
||||
maybeFromNullableVO(source.customer_postal_code, (value) => PostalCode.create(value)),
|
||||
maybeFromNullableVO(_postal_code, (value) => PostalCode.create(value)),
|
||||
"customer_postal_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const customerCountry = extractOrPushError(
|
||||
maybeFromNullableVO(source.customer_country, (value) => Country.create(value)),
|
||||
maybeFromNullableVO(_country, (value) => Country.create(value)),
|
||||
"customer_country",
|
||||
errors
|
||||
);
|
||||
|
||||
const recipientOrError = InvoiceRecipient.create({
|
||||
const createResult = InvoiceRecipient.create({
|
||||
name: customerName!,
|
||||
tin: customerTin!,
|
||||
street: customerStreet!,
|
||||
@ -82,16 +95,21 @@ export class InvoiceRecipientMapper {
|
||||
country: customerCountry!,
|
||||
});
|
||||
|
||||
return isNullishOrEmpty(recipientOrError)
|
||||
? Maybe.none<InvoiceRecipient>()
|
||||
: Maybe.some(recipientOrError.data);
|
||||
if (createResult.isFailure) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Invoice recipient entity creation failed", [
|
||||
{ path: "recipient", message: createResult.error.message },
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(Maybe.some(createResult.data));
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
source: InvoiceRecipient,
|
||||
params?: MapperParamsType
|
||||
): Partial<InferCreationAttributes<CustomerInvoiceModel, {}>> {
|
||||
1;
|
||||
const { index, sourceParent } = params as {
|
||||
index: number;
|
||||
sourceParent: CustomerInvoice;
|
||||
|
||||
@ -1,9 +1,104 @@
|
||||
import { MapperParamsType, Taxes } from "@erp/core/api";
|
||||
import { JsonTaxCatalogProvider } from "@erp/core";
|
||||
import {
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
Tax,
|
||||
Taxes,
|
||||
ValidationErrorCollection,
|
||||
ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
} from "@erp/core/api";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { InferCreationAttributes } from "sequelize";
|
||||
import { CustomerInvoiceItemTaxModel, CustomerInvoiceTaxModel } from "../sequelize";
|
||||
import { CustomerInvoiceProps, InvoiceAmount } from "../../domain";
|
||||
import { InvoiceTax } from "../../domain/entities/invoice-taxes";
|
||||
import {
|
||||
CustomerInvoiceItemTaxModel,
|
||||
CustomerInvoiceTaxCreationAttributes,
|
||||
CustomerInvoiceTaxModel,
|
||||
} from "../sequelize";
|
||||
|
||||
export class TaxesMapper {
|
||||
public mapArrayToDomain(taxes: CustomerInvoiceTaxModel[], params?: MapperParamsType) {}
|
||||
export class TaxesMapper extends SequelizeMapper<
|
||||
CustomerInvoiceTaxModel,
|
||||
CustomerInvoiceTaxCreationAttributes,
|
||||
InvoiceTax
|
||||
> {
|
||||
private _taxCatalog: JsonTaxCatalogProvider;
|
||||
|
||||
constructor(params: {
|
||||
taxCatalog: JsonTaxCatalogProvider;
|
||||
}) {
|
||||
super();
|
||||
const { taxCatalog } = params;
|
||||
this._taxCatalog = taxCatalog;
|
||||
}
|
||||
|
||||
public mapToDomain(
|
||||
source: CustomerInvoiceTaxModel,
|
||||
params?: MapperParamsType
|
||||
): Result<InvoiceTax, Error> {
|
||||
const { errors, index, attributes } = params as {
|
||||
index: number;
|
||||
requireIncludes: boolean;
|
||||
errors: ValidationErrorDetail[];
|
||||
attributes: Partial<CustomerInvoiceProps>;
|
||||
};
|
||||
|
||||
const tax = extractOrPushError(
|
||||
Tax.createFromCode(source.tax_code, this._taxCatalog),
|
||||
`taxes[${index}].tax_code`,
|
||||
errors
|
||||
);
|
||||
|
||||
const taxableAmount = extractOrPushError(
|
||||
InvoiceAmount.create({
|
||||
value: source.taxable_amount_value,
|
||||
currency_code: attributes.currencyCode?.code,
|
||||
}),
|
||||
`taxes[${index}].taxable_amount_value`,
|
||||
errors
|
||||
);
|
||||
|
||||
if (source.taxable_amount_scale !== InvoiceAmount.DEFAULT_SCALE) {
|
||||
errors.push({
|
||||
path: `taxes[${index}].taxable_amount_scale`,
|
||||
message: "Invalid taxable amount scale",
|
||||
});
|
||||
}
|
||||
|
||||
const taxesAmount = extractOrPushError(
|
||||
InvoiceAmount.create({
|
||||
value: source.taxes_amount_value,
|
||||
currency_code: attributes.currencyCode?.code,
|
||||
}),
|
||||
`taxes[${index}].taxes_amount_value`,
|
||||
errors
|
||||
);
|
||||
|
||||
if (source.taxes_amount_scale !== InvoiceAmount.DEFAULT_SCALE) {
|
||||
errors.push({
|
||||
path: `taxes[${index}].taxes_amount_scale`,
|
||||
message: "Invalid taxes amount scale",
|
||||
});
|
||||
}
|
||||
|
||||
// Creación del objeto de dominio
|
||||
const createResult = InvoiceTax.create({
|
||||
tax: tax!,
|
||||
taxableAmount: taxableAmount!,
|
||||
taxesAmount: taxesAmount!,
|
||||
});
|
||||
|
||||
if (createResult.isFailure) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Invoice taxes creation failed", [
|
||||
{ path: `taxes[${index}]`, message: createResult.error.message },
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return createResult;
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
source: Taxes,
|
||||
|
||||
@ -27,8 +27,8 @@ export class CustomerInvoiceItemTaxModel extends Model<
|
||||
declare taxable_amount_scale: number;
|
||||
|
||||
// Total tax amount / taxes total // 21,00 €
|
||||
declare tax_amount_value: number;
|
||||
declare tax_amount_scale: number;
|
||||
declare taxes_amount_value: number;
|
||||
declare taxes_amount_scale: number;
|
||||
|
||||
// Relaciones
|
||||
declare item: NonAttribute<CustomerInvoiceItem>;
|
||||
@ -71,18 +71,20 @@ export default (database: Sequelize) => {
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
taxable_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
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,
|
||||
|
||||
@ -27,8 +27,8 @@ export class CustomerInvoiceTaxModel extends Model<
|
||||
declare taxable_amount_scale: number;
|
||||
|
||||
// Total tax amount / taxes total // 21,00 €
|
||||
declare tax_amount_value: number;
|
||||
declare tax_amount_scale: number;
|
||||
declare taxes_amount_value: number;
|
||||
declare taxes_amount_scale: number;
|
||||
|
||||
// Relaciones
|
||||
declare invoice: NonAttribute<CustomerInvoice>;
|
||||
@ -70,18 +70,20 @@ export default (database: Sequelize) => {
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
taxable_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
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,
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
|
||||
export type CustomerInvoiceCreationAttributes = InferCreationAttributes<
|
||||
CustomerInvoiceModel,
|
||||
{ omit: "items" | "taxes" | "currentCustomer" }
|
||||
{ omit: "items" | "taxes" | "current_customer" }
|
||||
> & {
|
||||
items?: CustomerInvoiceItemCreationAttributes[];
|
||||
taxes?: CustomerInvoiceTaxCreationAttributes[];
|
||||
@ -81,7 +81,7 @@ export class CustomerInvoiceModel extends Model<
|
||||
// Relaciones
|
||||
declare items: NonAttribute<CustomerInvoiceItemModel[]>;
|
||||
declare taxes: NonAttribute<CustomerInvoiceTaxModel[]>;
|
||||
declare currentCustomer: NonAttribute<CustomerModel>;
|
||||
declare current_customer: NonAttribute<CustomerModel>;
|
||||
|
||||
static associate(database: Sequelize) {
|
||||
const {
|
||||
@ -92,7 +92,7 @@ export class CustomerInvoiceModel extends Model<
|
||||
} = database.models;
|
||||
|
||||
CustomerInvoiceModel.belongsTo(CustomerModel, {
|
||||
as: "currentCustomer",
|
||||
as: "current_customer",
|
||||
foreignKey: "customer_id",
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
@ -159,7 +159,7 @@ export class CustomerInvoiceRepository
|
||||
query.include = [
|
||||
{
|
||||
model: CustomerModel,
|
||||
as: "currentCustomer",
|
||||
as: "current_customer",
|
||||
required: false, // false => LEFT JOIN
|
||||
},
|
||||
];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user