Facturas de cliente
This commit is contained in:
parent
b302870647
commit
db4a79422e
@ -1,6 +1,13 @@
|
|||||||
import { AggregateRoot, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
import {
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
AggregateRoot,
|
||||||
import { CustomerInvoiceCustomer, CustomerInvoiceItem, CustomerInvoiceItems } from "../entities";
|
CurrencyCode,
|
||||||
|
LanguageCode,
|
||||||
|
TextValue,
|
||||||
|
UniqueID,
|
||||||
|
UtcDate,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
import { CustomerInvoiceItems } from "../entities";
|
||||||
import {
|
import {
|
||||||
CustomerInvoiceNumber,
|
CustomerInvoiceNumber,
|
||||||
CustomerInvoiceSerie,
|
CustomerInvoiceSerie,
|
||||||
@ -8,20 +15,19 @@ import {
|
|||||||
} from "../value-objects";
|
} from "../value-objects";
|
||||||
|
|
||||||
export interface CustomerInvoiceProps {
|
export interface CustomerInvoiceProps {
|
||||||
invoiceNumber: CustomerInvoiceNumber;
|
companyId: UniqueID;
|
||||||
invoiceSeries: CustomerInvoiceSerie;
|
|
||||||
|
|
||||||
status: CustomerInvoiceStatus;
|
status: CustomerInvoiceStatus;
|
||||||
|
series: Maybe<CustomerInvoiceSerie>;
|
||||||
|
invoiceNumber: CustomerInvoiceNumber;
|
||||||
|
|
||||||
issueDate: UtcDate;
|
issueDate: UtcDate;
|
||||||
operationDate: UtcDate;
|
operationDate: Maybe<UtcDate>;
|
||||||
|
|
||||||
|
notes: Maybe<TextValue>;
|
||||||
|
|
||||||
//dueDate: UtcDate; // ? --> depende de la forma de pago
|
//dueDate: UtcDate; // ? --> depende de la forma de pago
|
||||||
|
|
||||||
//tax: Tax; // ? --> detalles?
|
//tax: Tax; // ? --> detalles?
|
||||||
currency: string;
|
|
||||||
|
|
||||||
//language: Language;
|
|
||||||
|
|
||||||
//purchareOrderNumber: string;
|
//purchareOrderNumber: string;
|
||||||
//notes: Note;
|
//notes: Note;
|
||||||
@ -31,53 +37,27 @@ export interface CustomerInvoiceProps {
|
|||||||
//paymentInstructions: Note;
|
//paymentInstructions: Note;
|
||||||
//paymentTerms: string;
|
//paymentTerms: string;
|
||||||
|
|
||||||
customer?: CustomerInvoiceCustomer;
|
languageCode: LanguageCode;
|
||||||
|
currencyCode: CurrencyCode;
|
||||||
|
|
||||||
|
//customer?: CustomerInvoiceCustomer;
|
||||||
items?: CustomerInvoiceItems;
|
items?: CustomerInvoiceItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICustomerInvoice {
|
export type CustomerInvoicePatchProps = Partial<Omit<CustomerInvoiceProps, "companyId">>;
|
||||||
id: UniqueID;
|
|
||||||
invoiceNumber: CustomerInvoiceNumber;
|
|
||||||
invoiceSeries: CustomerInvoiceSerie;
|
|
||||||
|
|
||||||
status: CustomerInvoiceStatus;
|
export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
|
||||||
|
private _items!: CustomerInvoiceItems;
|
||||||
issueDate: UtcDate;
|
|
||||||
operationDate: UtcDate;
|
|
||||||
|
|
||||||
//senderId: UniqueID;
|
|
||||||
|
|
||||||
customer?: CustomerInvoiceCustomer;
|
|
||||||
|
|
||||||
//dueDate
|
|
||||||
|
|
||||||
//tax: Tax;
|
|
||||||
//language: Language;
|
|
||||||
currency: string;
|
|
||||||
|
|
||||||
//purchareOrderNumber: string;
|
|
||||||
//notes: Note;
|
|
||||||
|
|
||||||
//paymentInstructions: Note;
|
|
||||||
//paymentTerms: string;
|
|
||||||
|
|
||||||
items: CustomerInvoiceItems;
|
|
||||||
|
|
||||||
calculateSubtotal: () => MoneyValue;
|
|
||||||
calculateTaxTotal: () => MoneyValue;
|
|
||||||
calculateTotal: () => MoneyValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CustomerInvoice
|
|
||||||
extends AggregateRoot<CustomerInvoiceProps>
|
|
||||||
implements ICustomerInvoice
|
|
||||||
{
|
|
||||||
private _items!: Collection<CustomerInvoiceItem>;
|
|
||||||
//protected _status: CustomerInvoiceStatus;
|
//protected _status: CustomerInvoiceStatus;
|
||||||
|
|
||||||
protected constructor(props: CustomerInvoiceProps, id?: UniqueID) {
|
protected constructor(props: CustomerInvoiceProps, id?: UniqueID) {
|
||||||
super(props, id);
|
super(props, id);
|
||||||
this._items = props.items || CustomerInvoiceItems.create();
|
this._items =
|
||||||
|
props.items ||
|
||||||
|
CustomerInvoiceItems.create({
|
||||||
|
languageCode: props.languageCode,
|
||||||
|
currencyCode: props.currencyCode,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
static create(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
||||||
@ -94,50 +74,55 @@ export class CustomerInvoice
|
|||||||
return Result.ok(customerInvoice);
|
return Result.ok(customerInvoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
get invoiceNumber() {
|
public update(partialInvoice: CustomerInvoicePatchProps): Result<CustomerInvoice, Error> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get companyId(): UniqueID {
|
||||||
|
return this.props.companyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get series(): Maybe<CustomerInvoiceSerie> {
|
||||||
|
return this.props.series;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get invoiceNumber() {
|
||||||
return this.props.invoiceNumber;
|
return this.props.invoiceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
get invoiceSeries() {
|
public get issueDate(): UtcDate {
|
||||||
return this.props.invoiceSeries;
|
return this.props.issueDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
get issueDate() {
|
public get operationDate(): Maybe<UtcDate> {
|
||||||
return this.props.issueDate;
|
return this.props.operationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get notes(): Maybe<TextValue> {
|
||||||
|
return this.props.notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get languageCode(): LanguageCode {
|
||||||
|
return this.props.languageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get currencyCode(): CurrencyCode {
|
||||||
|
return this.props.currencyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to get the complete list of line items
|
||||||
|
get lineItems(): CustomerInvoiceItems {
|
||||||
|
return this._items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*get senderId(): UniqueID {
|
/*get senderId(): UniqueID {
|
||||||
return this.props.senderId;
|
return this.props.senderId;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
get customer(): CustomerInvoiceCustomer | undefined {
|
/* get customer(): CustomerInvoiceCustomer | undefined {
|
||||||
return this.props.customer;
|
return this.props.customer;
|
||||||
}
|
|
||||||
|
|
||||||
get operationDate() {
|
|
||||||
return this.props.operationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*get language() {
|
|
||||||
return this.props.language;
|
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
get dueDate() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
get tax() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
get status() {
|
|
||||||
return this.props.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
get items() {
|
|
||||||
return this._items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*get purchareOrderNumber() {
|
/*get purchareOrderNumber() {
|
||||||
return this.props.purchareOrderNumber;
|
return this.props.purchareOrderNumber;
|
||||||
}
|
}
|
||||||
@ -158,19 +143,7 @@ export class CustomerInvoice
|
|||||||
return this.props.shipTo;
|
return this.props.shipTo;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
get currency() {
|
/*
|
||||||
return this.props.currency;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*get notes() {
|
|
||||||
return this.props.notes;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Method to get the complete list of line items
|
|
||||||
/*get lineItems(): CustomerInvoiceLineItem[] {
|
|
||||||
return this._lineItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
addLineItem(lineItem: CustomerInvoiceLineItem, position?: number): void {
|
addLineItem(lineItem: CustomerInvoiceLineItem, position?: number): void {
|
||||||
if (position === undefined) {
|
if (position === undefined) {
|
||||||
this._lineItems.push(lineItem);
|
this._lineItems.push(lineItem);
|
||||||
@ -179,7 +152,7 @@ export class CustomerInvoice
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
calculateSubtotal(): MoneyValue {
|
/*calculateSubtotal(): MoneyValue {
|
||||||
const customerInvoiceSubtotal = MoneyValue.create({
|
const customerInvoiceSubtotal = MoneyValue.create({
|
||||||
amount: 0,
|
amount: 0,
|
||||||
currency_code: this.props.currency,
|
currency_code: this.props.currency,
|
||||||
@ -189,10 +162,10 @@ export class CustomerInvoice
|
|||||||
return this._items.getAll().reduce((subtotal, item) => {
|
return this._items.getAll().reduce((subtotal, item) => {
|
||||||
return subtotal.add(item.calculateTotal());
|
return subtotal.add(item.calculateTotal());
|
||||||
}, customerInvoiceSubtotal);
|
}, customerInvoiceSubtotal);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// Method to calculate the total tax in the customerInvoice
|
// Method to calculate the total tax in the customerInvoice
|
||||||
calculateTaxTotal(): MoneyValue {
|
/*calculateTaxTotal(): MoneyValue {
|
||||||
const taxTotal = MoneyValue.create({
|
const taxTotal = MoneyValue.create({
|
||||||
amount: 0,
|
amount: 0,
|
||||||
currency_code: this.props.currency,
|
currency_code: this.props.currency,
|
||||||
@ -200,10 +173,10 @@ export class CustomerInvoice
|
|||||||
}).data;
|
}).data;
|
||||||
|
|
||||||
return taxTotal;
|
return taxTotal;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// Method to calculate the total customerInvoice amount, including taxes
|
// Method to calculate the total customerInvoice amount, including taxes
|
||||||
calculateTotal(): MoneyValue {
|
/*calculateTotal(): MoneyValue {
|
||||||
return this.calculateSubtotal().add(this.calculateTaxTotal());
|
return this.calculateSubtotal().add(this.calculateTaxTotal());
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { MoneyValue, Percentage, Quantity } from "@/core/common/domain";
|
import { CurrencyCode, LanguageCode, MoneyValue, Percentage, Quantity } from "@repo/rdx-ddd";
|
||||||
import { CustomerInvoiceItemDescription } from "../../value-objects";
|
import { CustomerInvoiceItemDescription } from "../../value-objects";
|
||||||
import { CustomerInvoiceItem } from "./customer-invoice-item";
|
import { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||||
|
|
||||||
@ -9,6 +9,8 @@ describe("CustomerInvoiceItem", () => {
|
|||||||
quantity: Quantity.create({ amount: 200, scale: 2 }),
|
quantity: Quantity.create({ amount: 200, scale: 2 }),
|
||||||
unitPrice: MoneyValue.create(50),
|
unitPrice: MoneyValue.create(50),
|
||||||
discount: Percentage.create(0),
|
discount: Percentage.create(0),
|
||||||
|
languageCode: LanguageCode.create("es"),
|
||||||
|
currencyCode: CurrencyCode.create("EUR"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = CustomerInvoiceItem.create(props);
|
const result = CustomerInvoiceItem.create(props);
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
import { CurrencyCode, DomainEntity, LanguageCode, UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
import {
|
||||||
|
CustomerInvoiceItemDescription,
|
||||||
|
CustomerInvoiceItemDiscount,
|
||||||
|
CustomerInvoiceItemQuantity,
|
||||||
|
CustomerInvoiceItemSubtotalPrice,
|
||||||
|
CustomerInvoiceItemTotalPrice,
|
||||||
|
CustomerInvoiceItemUnitPrice,
|
||||||
|
} from "../../value-objects";
|
||||||
|
|
||||||
|
export interface CustomerInvoiceItemProps {
|
||||||
|
description: Maybe<CustomerInvoiceItemDescription>;
|
||||||
|
quantity: Maybe<CustomerInvoiceItemQuantity>; // Cantidad de unidades
|
||||||
|
unitPrice: Maybe<CustomerInvoiceItemUnitPrice>; // Precio unitario en la moneda de la factura
|
||||||
|
discount: Maybe<CustomerInvoiceItemDiscount>; // % descuento
|
||||||
|
|
||||||
|
languageCode: LanguageCode;
|
||||||
|
currencyCode: CurrencyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps> {
|
||||||
|
private _subtotalPrice!: CustomerInvoiceItemSubtotalPrice;
|
||||||
|
private _totalPrice!: CustomerInvoiceItemTotalPrice;
|
||||||
|
|
||||||
|
public static create(
|
||||||
|
props: CustomerInvoiceItemProps,
|
||||||
|
id?: UniqueID
|
||||||
|
): Result<CustomerInvoiceItem, Error> {
|
||||||
|
const item = new CustomerInvoiceItem(props, id);
|
||||||
|
|
||||||
|
// Reglas de negocio / validaciones
|
||||||
|
// ...
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// 🔹 Disparar evento de dominio "CustomerInvoiceItemCreatedEvent"
|
||||||
|
//const { customerInvoice } = props;
|
||||||
|
//user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString()));
|
||||||
|
|
||||||
|
return Result.ok(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
get description(): Maybe<CustomerInvoiceItemDescription> {
|
||||||
|
return this.props.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
get quantity(): Maybe<CustomerInvoiceItemQuantity> {
|
||||||
|
return this.props.quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
get unitPrice(): Maybe<CustomerInvoiceItemUnitPrice> {
|
||||||
|
return this.props.unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
get subtotalPrice(): CustomerInvoiceItemSubtotalPrice {
|
||||||
|
if (!this._subtotalPrice) {
|
||||||
|
this._subtotalPrice = this.calculateSubtotal();
|
||||||
|
}
|
||||||
|
return this._subtotalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
get discount(): Maybe<CustomerInvoiceItemDiscount> {
|
||||||
|
return this.props.discount;
|
||||||
|
}
|
||||||
|
|
||||||
|
get totalPrice(): CustomerInvoiceItemTotalPrice {
|
||||||
|
if (!this._totalPrice) {
|
||||||
|
this._totalPrice = this.calculateTotal();
|
||||||
|
}
|
||||||
|
return this._totalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get languageCode(): LanguageCode {
|
||||||
|
return this.props.languageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get currencyCode(): CurrencyCode {
|
||||||
|
return this.props.currencyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): CustomerInvoiceItemProps {
|
||||||
|
return this.props;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive() {
|
||||||
|
return this.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateSubtotal(): CustomerInvoiceItemSubtotalPrice {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
|
||||||
|
/*const unitPrice = this.unitPrice.isSome()
|
||||||
|
? this.unitPrice.unwrap()
|
||||||
|
: CustomerInvoiceItemUnitPrice.zero();
|
||||||
|
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad*/
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateTotal(): CustomerInvoiceItemTotalPrice {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
//return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
||||||
|
import { Collection } from "@repo/rdx-utils";
|
||||||
|
import { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||||
|
|
||||||
|
export interface CustomerInvoiceItemsProps {
|
||||||
|
items?: CustomerInvoiceItem[];
|
||||||
|
languageCode: LanguageCode;
|
||||||
|
currencyCode: CurrencyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
||||||
|
private _languageCode!: LanguageCode;
|
||||||
|
private _currencyCode!: CurrencyCode;
|
||||||
|
|
||||||
|
constructor(props: CustomerInvoiceItemsProps) {
|
||||||
|
const { items, languageCode, currencyCode } = props;
|
||||||
|
super(items);
|
||||||
|
this._languageCode = languageCode;
|
||||||
|
this._currencyCode = currencyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(props: CustomerInvoiceItemsProps): CustomerInvoiceItems {
|
||||||
|
return new CustomerInvoiceItems(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,2 +1,2 @@
|
|||||||
export * from "./invoice-item";
|
export * from "./customer-invoice-item";
|
||||||
export * from "./invoice-items";
|
export * from "./customer-invoice-items";
|
||||||
|
|||||||
@ -1,107 +0,0 @@
|
|||||||
import { DomainEntity, UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
import {
|
|
||||||
CustomerInvoiceItemDescription,
|
|
||||||
CustomerInvoiceItemDiscount,
|
|
||||||
CustomerInvoiceItemQuantity,
|
|
||||||
CustomerInvoiceItemSubtotalPrice,
|
|
||||||
CustomerInvoiceItemTotalPrice,
|
|
||||||
CustomerInvoiceItemUnitPrice,
|
|
||||||
} from "../../value-objects";
|
|
||||||
|
|
||||||
export interface ICustomerInvoiceItemProps {
|
|
||||||
description: CustomerInvoiceItemDescription;
|
|
||||||
quantity: CustomerInvoiceItemQuantity; // Cantidad de unidades
|
|
||||||
unitPrice: CustomerInvoiceItemUnitPrice; // Precio unitario en la moneda de la factura
|
|
||||||
//subtotalPrice?: MoneyValue; // Precio unitario * Cantidad
|
|
||||||
discount: CustomerInvoiceItemDiscount; // % descuento
|
|
||||||
//totalPrice?: MoneyValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICustomerInvoiceItem {
|
|
||||||
id: UniqueID;
|
|
||||||
description: CustomerInvoiceItemDescription;
|
|
||||||
quantity: CustomerInvoiceItemQuantity;
|
|
||||||
unitPrice: CustomerInvoiceItemUnitPrice;
|
|
||||||
subtotalPrice: CustomerInvoiceItemSubtotalPrice;
|
|
||||||
discount: CustomerInvoiceItemDiscount;
|
|
||||||
totalPrice: CustomerInvoiceItemTotalPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CustomerInvoiceItem
|
|
||||||
extends DomainEntity<ICustomerInvoiceItemProps>
|
|
||||||
implements ICustomerInvoiceItem
|
|
||||||
{
|
|
||||||
private _subtotalPrice!: CustomerInvoiceItemSubtotalPrice;
|
|
||||||
private _totalPrice!: CustomerInvoiceItemTotalPrice;
|
|
||||||
|
|
||||||
public static create(
|
|
||||||
props: ICustomerInvoiceItemProps,
|
|
||||||
id?: UniqueID
|
|
||||||
): Result<CustomerInvoiceItem, Error> {
|
|
||||||
const item = new CustomerInvoiceItem(props, id);
|
|
||||||
|
|
||||||
// Reglas de negocio / validaciones
|
|
||||||
// ...
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// 🔹 Disparar evento de dominio "CustomerInvoiceItemCreatedEvent"
|
|
||||||
//const { customerInvoice } = props;
|
|
||||||
//user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString()));
|
|
||||||
|
|
||||||
return Result.ok(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
get description(): CustomerInvoiceItemDescription {
|
|
||||||
return this.props.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
get quantity(): CustomerInvoiceItemQuantity {
|
|
||||||
return this.props.quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
get unitPrice(): CustomerInvoiceItemUnitPrice {
|
|
||||||
return this.props.unitPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
get subtotalPrice(): CustomerInvoiceItemSubtotalPrice {
|
|
||||||
if (!this._subtotalPrice) {
|
|
||||||
this._subtotalPrice = this.calculateSubtotal();
|
|
||||||
}
|
|
||||||
return this._subtotalPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
get discount(): CustomerInvoiceItemDiscount {
|
|
||||||
return this.props.discount;
|
|
||||||
}
|
|
||||||
|
|
||||||
get totalPrice(): CustomerInvoiceItemTotalPrice {
|
|
||||||
if (!this._totalPrice) {
|
|
||||||
this._totalPrice = this.calculateTotal();
|
|
||||||
}
|
|
||||||
return this._totalPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue() {
|
|
||||||
return this.props;
|
|
||||||
}
|
|
||||||
|
|
||||||
toPrimitive() {
|
|
||||||
return {
|
|
||||||
description: this.description.toPrimitive(),
|
|
||||||
quantity: this.quantity.toPrimitive(),
|
|
||||||
unit_price: this.unitPrice.toPrimitive(),
|
|
||||||
subtotal_price: this.subtotalPrice.toPrimitive(),
|
|
||||||
discount: this.discount.toPrimitive(),
|
|
||||||
total_price: this.totalPrice.toPrimitive(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateSubtotal(): CustomerInvoiceItemSubtotalPrice {
|
|
||||||
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateTotal(): CustomerInvoiceItemTotalPrice {
|
|
||||||
return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Collection } from "@repo/rdx-utils";
|
|
||||||
import { CustomerInvoiceItem } from "./customer-invoice-item";
|
|
||||||
|
|
||||||
export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
|
||||||
public static create(items?: CustomerInvoiceItem[]): CustomerInvoiceItems {
|
|
||||||
return new CustomerInvoiceItems(items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,12 +3,15 @@ import { UniqueID } from "@repo/rdx-ddd";
|
|||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { CustomerInvoice } from "../aggregates";
|
import { CustomerInvoice } from "../aggregates";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interfaz del repositorio para el agregado `CustomerInvoice`.
|
||||||
|
* El escopado multitenant está representado por `companyId`.
|
||||||
|
*/
|
||||||
export interface ICustomerInvoiceRepository {
|
export interface ICustomerInvoiceRepository {
|
||||||
existsById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Persiste una nueva factura o actualiza una existente.
|
* Persiste una nueva factura o actualiza una existente.
|
||||||
|
* Retorna el objeto actualizado tras la operación.
|
||||||
*
|
*
|
||||||
* @param invoice - El agregado a guardar.
|
* @param invoice - El agregado a guardar.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
@ -17,34 +20,55 @@ export interface ICustomerInvoiceRepository {
|
|||||||
save(invoice: CustomerInvoice, transaction: any): Promise<Result<CustomerInvoice, Error>>;
|
save(invoice: CustomerInvoice, transaction: any): Promise<Result<CustomerInvoice, Error>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Comprueba si existe una factura con un `id` dentro de una `company`.
|
||||||
* Busca una factura por su identificador único.
|
|
||||||
* @param id - UUID de la factura.
|
|
||||||
* @param transaction - Transacción activa para la operación.
|
|
||||||
* @returns Result<CustomerInvoice, Error>
|
|
||||||
*/
|
*/
|
||||||
findById(id: UniqueID, transaction: any): Promise<Result<CustomerInvoice, Error>>;
|
existsByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: any
|
||||||
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recupera una factura por su ID y companyId.
|
||||||
|
* Devuelve un `NotFoundError` si no se encuentra.
|
||||||
|
*/
|
||||||
|
getByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: any
|
||||||
|
): Promise<Result<CustomerInvoice, Error>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Consulta facturas usando un objeto Criteria (filtros, orden, paginación).
|
* Consulta facturas dentro de una empresa usando un
|
||||||
|
* objeto Criteria (filtros, orden, paginación).
|
||||||
|
* El resultado está encapsulado en un objeto `Collection<T>`.
|
||||||
|
*
|
||||||
|
* @param companyId - ID de la empresa.
|
||||||
* @param criteria - Criterios de búsqueda.
|
* @param criteria - Criterios de búsqueda.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<CustomerInvoice[], Error>
|
* @returns Result<CustomerInvoice[], Error>
|
||||||
*
|
*
|
||||||
* @see Criteria
|
* @see Criteria
|
||||||
*/
|
*/
|
||||||
findByCriteria(
|
findByCriteriaInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction: any
|
transaction: any
|
||||||
): Promise<Result<Collection<CustomerInvoice>, Error>>;
|
): Promise<Result<Collection<CustomerInvoice>, Error>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Elimina o marca como eliminada una factura.
|
* Elimina o marca como eliminada una factura dentro de una empresa.
|
||||||
|
*
|
||||||
|
* @param companyId - ID de la empresa.
|
||||||
* @param id - UUID de la factura a eliminar.
|
* @param id - UUID de la factura a eliminar.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<void, Error>
|
* @returns Result<void, Error>
|
||||||
*/
|
*/
|
||||||
deleteById(id: UniqueID, transaction: any): Promise<Result<void, Error>>;
|
deleteByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: any
|
||||||
|
): Promise<Result<void, Error>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,10 +14,10 @@ export class CustomerInvoiceAddressType extends ValueObject<ICustomerInvoiceAddr
|
|||||||
private static readonly ALLOWED_TYPES = ["shipping", "billing"];
|
private static readonly ALLOWED_TYPES = ["shipping", "billing"];
|
||||||
|
|
||||||
static create(value: string): Result<CustomerInvoiceAddressType, Error> {
|
static create(value: string): Result<CustomerInvoiceAddressType, Error> {
|
||||||
if (!this.ALLOWED_TYPES.includes(value)) {
|
if (!CustomerInvoiceAddressType.ALLOWED_TYPES.includes(value)) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new Error(
|
new Error(
|
||||||
`Invalid address type: ${value}. Allowed types are: ${this.ALLOWED_TYPES.join(", ")}`
|
`Invalid address type: ${value}. Allowed types are: ${CustomerInvoiceAddressType.ALLOWED_TYPES.join(", ")}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,11 @@ import { ValueObject } from "@repo/rdx-ddd";
|
|||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
interface ICustomerInvoiceItemDescriptionProps {
|
interface CustomerInvoiceItemDescriptionProps {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CustomerInvoiceItemDescription extends ValueObject<ICustomerInvoiceItemDescriptionProps> {
|
export class CustomerInvoiceItemDescription extends ValueObject<CustomerInvoiceItemDescriptionProps> {
|
||||||
private static readonly MAX_LENGTH = 255;
|
private static readonly MAX_LENGTH = 255;
|
||||||
private static readonly FIELD = "invoiceItemDescription";
|
private static readonly FIELD = "invoiceItemDescription";
|
||||||
private static readonly ERROR_CODE = "INVALID_INVOICE_ITEM_DESCRIPTION";
|
private static readonly ERROR_CODE = "INVALID_INVOICE_ITEM_DESCRIPTION";
|
||||||
@ -23,10 +23,10 @@ export class CustomerInvoiceItemDescription extends ValueObject<ICustomerInvoice
|
|||||||
}
|
}
|
||||||
|
|
||||||
static create(value: string) {
|
static create(value: string) {
|
||||||
const result = CustomerInvoiceItemDescription.validate(value);
|
const valueIsValid = CustomerInvoiceItemDescription.validate(value);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!valueIsValid.success) {
|
||||||
const detail = result.error.message;
|
const detail = valueIsValid.error.message;
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new DomainValidationError(
|
new DomainValidationError(
|
||||||
CustomerInvoiceItemDescription.ERROR_CODE,
|
CustomerInvoiceItemDescription.ERROR_CODE,
|
||||||
|
|||||||
@ -11,4 +11,13 @@ export class CustomerInvoiceItemUnitPrice extends MoneyValue {
|
|||||||
};
|
};
|
||||||
return MoneyValue.create(props);
|
return MoneyValue.create(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static zero(currency_code: string, scale: number = CustomerInvoiceItemUnitPrice.DEFAULT_SCALE) {
|
||||||
|
const props: MoneyValueProps = {
|
||||||
|
amount: 0,
|
||||||
|
scale,
|
||||||
|
currency_code,
|
||||||
|
};
|
||||||
|
return MoneyValue.create(props);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,10 +23,10 @@ export class CustomerInvoiceNumber extends ValueObject<ICustomerInvoiceNumberPro
|
|||||||
}
|
}
|
||||||
|
|
||||||
static create(value: string) {
|
static create(value: string) {
|
||||||
const result = CustomerInvoiceNumber.validate(value);
|
const valueIsValid = CustomerInvoiceNumber.validate(value);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!valueIsValid.success) {
|
||||||
const detail = result.error.message;
|
const detail = valueIsValid.error.message;
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new DomainValidationError(
|
new DomainValidationError(
|
||||||
CustomerInvoiceNumber.ERROR_CODE,
|
CustomerInvoiceNumber.ERROR_CODE,
|
||||||
|
|||||||
@ -23,10 +23,10 @@ export class CustomerInvoiceSerie extends ValueObject<ICustomerInvoiceSerieProps
|
|||||||
}
|
}
|
||||||
|
|
||||||
static create(value: string) {
|
static create(value: string) {
|
||||||
const result = CustomerInvoiceSerie.validate(value);
|
const valueIsValid = CustomerInvoiceSerie.validate(value);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!valueIsValid.success) {
|
||||||
const detail = result.error.message;
|
const detail = valueIsValid.error.message;
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new DomainValidationError(
|
new DomainValidationError(
|
||||||
CustomerInvoiceSerie.ERROR_CODE,
|
CustomerInvoiceSerie.ERROR_CODE,
|
||||||
|
|||||||
@ -4,18 +4,18 @@ import { customerInvoicesRouter, models } from "./infrastructure";
|
|||||||
export const customerInvoicesAPIModule: IModuleServer = {
|
export const customerInvoicesAPIModule: IModuleServer = {
|
||||||
name: "customer-invoices",
|
name: "customer-invoices",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
dependencies: [],
|
dependencies: ["customers"],
|
||||||
|
|
||||||
async init(params: ModuleParams) {
|
async init(params: ModuleParams) {
|
||||||
// const contacts = getService<ContactsService>("contacts");
|
// const contacts = getService<ContactsService>("contacts");
|
||||||
const { logger } = params;
|
const { logger } = params;
|
||||||
customerInvoicesRouter(params);
|
customerInvoicesRouter(params);
|
||||||
logger.info("🚀 CustomerInvoices module initialized", { label: "customer-invoices" });
|
logger.info("🚀 CustomerInvoices module initialized", { label: this.name });
|
||||||
},
|
},
|
||||||
async registerDependencies(params) {
|
async registerDependencies(params) {
|
||||||
const { database, logger } = params;
|
const { database, logger } = params;
|
||||||
logger.info("🚀 CustomerInvoices module dependencies registered", {
|
logger.info("🚀 CustomerInvoices module dependencies registered", {
|
||||||
label: "customer-invoices",
|
label: this.name,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
models,
|
models,
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import {
|
|||||||
GetCustomerInvoiceUseCase,
|
GetCustomerInvoiceUseCase,
|
||||||
ListCustomerInvoicesAssembler,
|
ListCustomerInvoicesAssembler,
|
||||||
ListCustomerInvoicesUseCase,
|
ListCustomerInvoicesUseCase,
|
||||||
|
UpdateCustomerInvoiceAssembler,
|
||||||
|
UpdateCustomerInvoiceUseCase,
|
||||||
} from "../application";
|
} from "../application";
|
||||||
import { CustomerInvoiceService, ICustomerInvoiceService } from "../domain";
|
import { CustomerInvoiceService, ICustomerInvoiceService } from "../domain";
|
||||||
import { CustomerInvoiceMapper } from "./mappers";
|
import { CustomerInvoiceMapper } from "./mappers";
|
||||||
@ -22,13 +24,13 @@ type InvoiceDeps = {
|
|||||||
list: ListCustomerInvoicesAssembler;
|
list: ListCustomerInvoicesAssembler;
|
||||||
get: GetCustomerInvoiceAssembler;
|
get: GetCustomerInvoiceAssembler;
|
||||||
create: CreateCustomerInvoicesAssembler;
|
create: CreateCustomerInvoicesAssembler;
|
||||||
//update: UpdateCustomerInvoiceAssembler;
|
update: UpdateCustomerInvoiceAssembler;
|
||||||
};
|
};
|
||||||
build: {
|
build: {
|
||||||
list: () => ListCustomerInvoicesUseCase;
|
list: () => ListCustomerInvoicesUseCase;
|
||||||
get: () => GetCustomerInvoiceUseCase;
|
get: () => GetCustomerInvoiceUseCase;
|
||||||
create: () => CreateCustomerInvoiceUseCase;
|
create: () => CreateCustomerInvoiceUseCase;
|
||||||
//update: () => UpdateCustomerInvoiceUseCase;
|
update: () => UpdateCustomerInvoiceUseCase;
|
||||||
delete: () => DeleteCustomerInvoiceUseCase;
|
delete: () => DeleteCustomerInvoiceUseCase;
|
||||||
};
|
};
|
||||||
presenters: {
|
presenters: {
|
||||||
@ -54,7 +56,7 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
|||||||
list: new ListCustomerInvoicesAssembler(), // transforma domain → ListDTO
|
list: new ListCustomerInvoicesAssembler(), // transforma domain → ListDTO
|
||||||
get: new GetCustomerInvoiceAssembler(), // transforma domain → DetailDTO
|
get: new GetCustomerInvoiceAssembler(), // transforma domain → DetailDTO
|
||||||
create: new CreateCustomerInvoicesAssembler(), // transforma domain → CreatedDTO
|
create: new CreateCustomerInvoicesAssembler(), // transforma domain → CreatedDTO
|
||||||
//update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO
|
update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,8 +72,8 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
|||||||
get: () => new GetCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.get),
|
get: () => new GetCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.get),
|
||||||
create: () =>
|
create: () =>
|
||||||
new CreateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.create),
|
new CreateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.create),
|
||||||
/*update: () =>
|
update: () =>
|
||||||
new UpdateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.update),*/
|
new UpdateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.update),
|
||||||
delete: () => new DeleteCustomerInvoiceUseCase(_service!, transactionManager!),
|
delete: () => new DeleteCustomerInvoiceUseCase(_service!, transactionManager!),
|
||||||
},
|
},
|
||||||
presenters: {
|
presenters: {
|
||||||
|
|||||||
@ -1,9 +1,17 @@
|
|||||||
import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core/api";
|
import {
|
||||||
import { UniqueID, UtcDate } from "@repo/rdx-ddd";
|
ISequelizeMapper,
|
||||||
|
MapperParamsType,
|
||||||
|
SequelizeMapper,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
import { CurrencyCode, LanguageCode, UniqueID, UtcDate, maybeFromNullableVO } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import {
|
import {
|
||||||
CustomerInvoice,
|
CustomerInvoice,
|
||||||
CustomerInvoiceNumber,
|
CustomerInvoiceNumber,
|
||||||
|
CustomerInvoiceProps,
|
||||||
CustomerInvoiceSerie,
|
CustomerInvoiceSerie,
|
||||||
CustomerInvoiceStatus,
|
CustomerInvoiceStatus,
|
||||||
} from "../../domain";
|
} from "../../domain";
|
||||||
@ -32,50 +40,75 @@ export class CustomerInvoiceMapper
|
|||||||
source: CustomerInvoiceModel,
|
source: CustomerInvoiceModel,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<CustomerInvoice, Error> {
|
): Result<CustomerInvoice, Error> {
|
||||||
const idOrError = UniqueID.create(source.id);
|
const errors: ValidationErrorDetail[] = [];
|
||||||
const statusOrError = CustomerInvoiceStatus.create(source.invoice_status);
|
|
||||||
const customerInvoiceSeriesOrError = CustomerInvoiceSerie.create(source.invoice_series);
|
|
||||||
const customerInvoiceNumberOrError = CustomerInvoiceNumber.create(source.invoice_number);
|
|
||||||
const issueDateOrError = UtcDate.createFromISO(source.issue_date);
|
|
||||||
const operationDateOrError = UtcDate.createFromISO(source.operation_date);
|
|
||||||
|
|
||||||
const result = Result.combine([
|
const invoiceId = extractOrPushError(UniqueID.create(source.id), "id", errors);
|
||||||
idOrError,
|
const companyId = extractOrPushError(UniqueID.create(source.company_id), "company_id", errors);
|
||||||
statusOrError,
|
|
||||||
customerInvoiceSeriesOrError,
|
|
||||||
customerInvoiceNumberOrError,
|
|
||||||
issueDateOrError,
|
|
||||||
operationDateOrError,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (result.isFailure) {
|
const status = extractOrPushError(
|
||||||
return Result.fail(result.error);
|
CustomerInvoiceStatus.create(source.status),
|
||||||
|
"status",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
const series = extractOrPushError(CustomerInvoiceSerie.create(source.series), "series", errors);
|
||||||
|
const invoiceNumber = extractOrPushError(
|
||||||
|
CustomerInvoiceNumber.create(source.invoice_number),
|
||||||
|
"invoice_number",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
const issueDate = extractOrPushError(
|
||||||
|
UtcDate.createFromISO(source.issue_date),
|
||||||
|
"issue_date",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const operationDate = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.operation_date, (value) => UtcDate.createFromISO(value)),
|
||||||
|
"operation_date",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const languageCode = extractOrPushError(
|
||||||
|
LanguageCode.create(source.language_code),
|
||||||
|
"language_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const currencyCode = extractOrPushError(
|
||||||
|
CurrencyCode.create(source.currency_code),
|
||||||
|
"currency_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("Customer invoice props mapping failed", errors)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mapear los items de la factura
|
// Mapear los items de la factura
|
||||||
const itemsOrErrors = this.customerInvoiceItemMapper.mapArrayToDomain(source.items, {
|
/*const itemsOrErrors = this.customerInvoiceItemMapper.mapArrayToDomain(source.items, {
|
||||||
sourceParent: source,
|
sourceParent: source,
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (itemsOrErrors.isFailure) {
|
if (itemsOrErrors.isFailure) {
|
||||||
return Result.fail(itemsOrErrors.error);
|
return Result.fail(itemsOrErrors.error);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
const customerInvoiceCurrency = source.invoice_currency || "EUR";
|
const invoiceProps: CustomerInvoiceProps = {
|
||||||
|
status: status!,
|
||||||
|
series: series!,
|
||||||
|
invoiceNumber: invoiceNumber!,
|
||||||
|
issueDate: issueDate!,
|
||||||
|
operationDate: operationDate!,
|
||||||
|
|
||||||
return CustomerInvoice.create(
|
languageCode: languageCode!,
|
||||||
{
|
currencyCode: currencyCode!,
|
||||||
status: statusOrError.data,
|
//items: itemsOrErrors.data,
|
||||||
invoiceSeries: customerInvoiceSeriesOrError.data,
|
};
|
||||||
invoiceNumber: customerInvoiceNumberOrError.data,
|
|
||||||
issueDate: issueDateOrError.data,
|
return CustomerInvoice.create(invoiceProps, invoiceId);
|
||||||
operationDate: operationDateOrError.data,
|
|
||||||
currency: customerInvoiceCurrency,
|
|
||||||
items: itemsOrErrors.data,
|
|
||||||
},
|
|
||||||
idOrError.data
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapToPersistence(
|
public mapToPersistence(
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
CreationOptional,
|
|
||||||
DataTypes,
|
DataTypes,
|
||||||
InferAttributes,
|
InferAttributes,
|
||||||
InferCreationAttributes,
|
InferCreationAttributes,
|
||||||
@ -21,26 +20,26 @@ export class CustomerInvoiceItemModel extends Model<
|
|||||||
declare item_id: string;
|
declare item_id: string;
|
||||||
declare invoice_id: string;
|
declare invoice_id: string;
|
||||||
|
|
||||||
declare parent_id: CreationOptional<string>;
|
declare parent_id: string;
|
||||||
declare position: number;
|
declare position: number;
|
||||||
declare item_type: string;
|
declare item_type: string;
|
||||||
|
|
||||||
declare description: CreationOptional<string>;
|
declare description: string;
|
||||||
|
|
||||||
declare quantity_amount: CreationOptional<number>;
|
declare quantity_amount: number;
|
||||||
declare quantity_scale: CreationOptional<number>;
|
declare quantity_scale: number;
|
||||||
|
|
||||||
declare unit_price_amount: CreationOptional<number>;
|
declare unit_price_amount: number;
|
||||||
declare unit_price_scale: CreationOptional<number>;
|
declare unit_price_scale: number;
|
||||||
|
|
||||||
declare subtotal_amount: CreationOptional<number>;
|
declare subtotal_amount: number;
|
||||||
declare subtotal_scale: CreationOptional<number>;
|
declare subtotal_scale: number;
|
||||||
|
|
||||||
declare discount_amount: CreationOptional<number>;
|
declare discount_amount: number;
|
||||||
declare discount_scale: CreationOptional<number>;
|
declare discount_scale: number;
|
||||||
|
|
||||||
declare total_amount: CreationOptional<number>;
|
declare total_amount: number;
|
||||||
declare total_scale: CreationOptional<number>;
|
declare total_scale: number;
|
||||||
|
|
||||||
declare invoice: NonAttribute<CustomerInvoiceModel>;
|
declare invoice: NonAttribute<CustomerInvoiceModel>;
|
||||||
|
|
||||||
@ -65,14 +64,14 @@ export default (database: Sequelize) => {
|
|||||||
},
|
},
|
||||||
invoice_id: {
|
invoice_id: {
|
||||||
type: new DataTypes.UUID(),
|
type: new DataTypes.UUID(),
|
||||||
primaryKey: true,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
parent_id: {
|
parent_id: {
|
||||||
type: new DataTypes.UUID(),
|
type: new DataTypes.UUID(),
|
||||||
allowNull: true, // Puede ser nulo para elementos de nivel superior
|
allowNull: true, // Puede ser nulo para elementos de nivel superior
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
type: new DataTypes.MEDIUMINT(),
|
type: new DataTypes.MEDIUMINT().UNSIGNED,
|
||||||
autoIncrement: false,
|
autoIncrement: false,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
@ -84,6 +83,7 @@ export default (database: Sequelize) => {
|
|||||||
description: {
|
description: {
|
||||||
type: new DataTypes.TEXT(),
|
type: new DataTypes.TEXT(),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
quantity_amount: {
|
quantity_amount: {
|
||||||
@ -160,9 +160,17 @@ export default (database: Sequelize) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequelize: database,
|
sequelize: database,
|
||||||
underscored: true,
|
|
||||||
tableName: "customer_invoice_items",
|
tableName: "customer_invoice_items",
|
||||||
|
|
||||||
|
underscored: true,
|
||||||
|
|
||||||
|
indexes: [
|
||||||
|
{ name: "invoice_idx", fields: ["invoice_id"], unique: false },
|
||||||
|
{ name: "parent_idx", fields: ["parent_id"], unique: false },
|
||||||
|
],
|
||||||
|
|
||||||
|
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||||
|
|
||||||
defaultScope: {},
|
defaultScope: {},
|
||||||
|
|
||||||
scopes: {},
|
scopes: {},
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
CreationOptional,
|
|
||||||
DataTypes,
|
DataTypes,
|
||||||
InferAttributes,
|
InferAttributes,
|
||||||
InferCreationAttributes,
|
InferCreationAttributes,
|
||||||
@ -24,22 +23,23 @@ export class CustomerInvoiceModel extends Model<
|
|||||||
InferCreationAttributes<CustomerInvoiceModel, { omit: "items" }>
|
InferCreationAttributes<CustomerInvoiceModel, { omit: "items" }>
|
||||||
> {
|
> {
|
||||||
declare id: string;
|
declare id: string;
|
||||||
|
declare company_id: string;
|
||||||
|
|
||||||
declare invoice_status: string;
|
declare status: string;
|
||||||
declare invoice_series: CreationOptional<string>;
|
declare series: string;
|
||||||
declare invoice_number: CreationOptional<string>;
|
declare invoice_number: string;
|
||||||
declare issue_date: CreationOptional<string>;
|
declare issue_date: string;
|
||||||
declare operation_date: CreationOptional<string>;
|
declare operation_date: string;
|
||||||
declare invoice_language: string;
|
declare language_code: string;
|
||||||
declare invoice_currency: string;
|
declare currency_code: string;
|
||||||
|
|
||||||
// Subtotal
|
// Subtotal
|
||||||
declare subtotal_amount: CreationOptional<number>;
|
declare subtotal_amount: number;
|
||||||
declare subtotal_scale: CreationOptional<number>;
|
declare subtotal_scale: number;
|
||||||
|
|
||||||
// Total
|
// Total
|
||||||
declare total_amount: CreationOptional<number>;
|
declare total_amount: number;
|
||||||
declare total_scale: CreationOptional<number>;
|
declare total_scale: number;
|
||||||
|
|
||||||
// Relaciones
|
// Relaciones
|
||||||
declare items: NonAttribute<CustomerInvoiceItemModel[]>;
|
declare items: NonAttribute<CustomerInvoiceItemModel[]>;
|
||||||
@ -59,14 +59,14 @@ export class CustomerInvoiceModel extends Model<
|
|||||||
|
|
||||||
static hooks(database: Sequelize) {
|
static hooks(database: Sequelize) {
|
||||||
// Soft-cascade manual: al borrar una factura, marcamos items como borrados (paranoid).
|
// Soft-cascade manual: al borrar una factura, marcamos items como borrados (paranoid).
|
||||||
CustomerInvoiceModel.addHook("afterDestroy", async (invoice, options) => {
|
/*CustomerInvoiceModel.addHook("afterDestroy", async (invoice, options) => {
|
||||||
if (!invoice?.id) return;
|
if (!invoice?.id) return;
|
||||||
await CustomerInvoiceItemModel.destroy({
|
await CustomerInvoiceItemModel.destroy({
|
||||||
where: { invoiceId: invoice.id },
|
where: { invoice_id: invoice.id },
|
||||||
individualHooks: true,
|
individualHooks: true,
|
||||||
transaction: options.transaction,
|
transaction: options.transaction,
|
||||||
});
|
});
|
||||||
});
|
});*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,12 +78,18 @@ export default (database: Sequelize) => {
|
|||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
invoice_status: {
|
company_id: {
|
||||||
type: new DataTypes.STRING(),
|
type: DataTypes.UUID,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
invoice_series: {
|
status: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: "draft",
|
||||||
|
},
|
||||||
|
|
||||||
|
series: {
|
||||||
type: new DataTypes.STRING(),
|
type: new DataTypes.STRING(),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
@ -107,14 +113,16 @@ export default (database: Sequelize) => {
|
|||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
invoice_language: {
|
language_code: {
|
||||||
type: new DataTypes.STRING(),
|
type: DataTypes.STRING(2),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
defaultValue: "es",
|
||||||
},
|
},
|
||||||
|
|
||||||
invoice_currency: {
|
currency_code: {
|
||||||
type: new DataTypes.STRING(3), // ISO 4217
|
type: new DataTypes.STRING(3),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
defaultValue: "EUR",
|
||||||
},
|
},
|
||||||
|
|
||||||
subtotal_amount: {
|
subtotal_amount: {
|
||||||
@ -151,7 +159,11 @@ export default (database: Sequelize) => {
|
|||||||
updatedAt: "updated_at",
|
updatedAt: "updated_at",
|
||||||
deletedAt: "deleted_at",
|
deletedAt: "deleted_at",
|
||||||
|
|
||||||
indexes: [{ unique: true, fields: ["invoice_number"] }],
|
indexes: [
|
||||||
|
{ 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
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SequelizeRepository } from "@erp/core/api";
|
import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } from "@erp/core/api";
|
||||||
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";
|
||||||
@ -11,7 +11,6 @@ export class CustomerInvoiceRepository
|
|||||||
extends SequelizeRepository<CustomerInvoice>
|
extends SequelizeRepository<CustomerInvoice>
|
||||||
implements ICustomerInvoiceRepository
|
implements ICustomerInvoiceRepository
|
||||||
{
|
{
|
||||||
//private readonly model: typeof CustomerInvoiceModel;
|
|
||||||
private readonly mapper!: ICustomerInvoiceMapper;
|
private readonly mapper!: ICustomerInvoiceMapper;
|
||||||
|
|
||||||
constructor(mapper: ICustomerInvoiceMapper) {
|
constructor(mapper: ICustomerInvoiceMapper) {
|
||||||
@ -52,16 +51,6 @@ export class CustomerInvoiceRepository
|
|||||||
};
|
};
|
||||||
} */
|
} */
|
||||||
|
|
||||||
async existsById(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
|
|
||||||
try {
|
|
||||||
const result = await this._exists(CustomerInvoiceModel, "id", id.toString(), transaction);
|
|
||||||
|
|
||||||
return Result.ok(Boolean(result));
|
|
||||||
} catch (err: unknown) {
|
|
||||||
return Result.fail(translateSequelizeError(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Persiste una nueva factura o actualiza una existente.
|
* Persiste una nueva factura o actualiza una existente.
|
||||||
@ -76,29 +65,64 @@ export class CustomerInvoiceRepository
|
|||||||
): Promise<Result<CustomerInvoice, Error>> {
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
try {
|
try {
|
||||||
const data = this.mapper.mapToPersistence(invoice);
|
const data = this.mapper.mapToPersistence(invoice);
|
||||||
await CustomerInvoiceModel.upsert(data, { transaction });
|
const [instance] = await CustomerInvoiceModel.upsert(data, { transaction, returning: true });
|
||||||
return Result.ok(invoice);
|
const savedInvoice = this.mapper.mapToDomain(instance);
|
||||||
|
return savedInvoice;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return Result.fail(translateSequelizeError(err));
|
return Result.fail(translateSequelizeError(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comprueba si existe una factura con un `id` dentro de una `company`.
|
||||||
|
*
|
||||||
|
* @param companyId - Identificador UUID de la empresa a la que pertenece la factura.
|
||||||
|
* @param id - Identificador UUID de la factura.
|
||||||
|
* @param transaction - Transacción activa para la operación.
|
||||||
|
* @returns Result<boolean, Error>
|
||||||
|
*/
|
||||||
|
async existsByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
try {
|
||||||
|
const count = await CustomerInvoiceModel.count({
|
||||||
|
where: { id: id.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
return Result.ok(Boolean(count > 0));
|
||||||
|
} catch (error: any) {
|
||||||
|
return Result.fail(translateSequelizeError(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Busca una factura por su identificador único.
|
* Busca una factura por su identificador único.
|
||||||
|
*
|
||||||
|
* @param companyId - Identificador UUID de la empresa a la que pertenece la factura.
|
||||||
* @param id - UUID de la factura.
|
* @param id - UUID de la factura.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<CustomerInvoice, Error>
|
* @returns Result<CustomerInvoice, Error>
|
||||||
*/
|
*/
|
||||||
async findById(id: UniqueID, transaction: Transaction): Promise<Result<CustomerInvoice, Error>> {
|
async getByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: Transaction
|
||||||
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
try {
|
try {
|
||||||
const rawData = await this._findById(CustomerInvoiceModel, id.toString(), { transaction });
|
const row = await CustomerInvoiceModel.findOne({
|
||||||
|
where: { id: id.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
if (!rawData) {
|
if (!row) {
|
||||||
return Result.fail(new Error(`Invoice with id ${id} not found.`));
|
return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.mapper.mapToDomain(rawData);
|
const customer = this.mapper.mapToDomain(row);
|
||||||
|
return customer;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return Result.fail(translateSequelizeError(err));
|
return Result.fail(translateSequelizeError(err));
|
||||||
}
|
}
|
||||||
@ -107,13 +131,16 @@ export class CustomerInvoiceRepository
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Consulta facturas usando un objeto Criteria (filtros, orden, paginación).
|
* Consulta facturas usando un objeto Criteria (filtros, orden, paginación).
|
||||||
|
*
|
||||||
|
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
||||||
* @param criteria - Criterios de búsqueda.
|
* @param criteria - Criterios de búsqueda.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<CustomerInvoice[], Error>
|
* @returns Result<CustomerInvoice[], Error>
|
||||||
*
|
*
|
||||||
* @see Criteria
|
* @see Criteria
|
||||||
*/
|
*/
|
||||||
public async findByCriteria(
|
public async findByCriteriaInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
): Promise<Result<Collection<CustomerInvoice>, Error>> {
|
): Promise<Result<Collection<CustomerInvoice>, Error>> {
|
||||||
@ -121,6 +148,11 @@ export class CustomerInvoiceRepository
|
|||||||
const converter = new CriteriaToSequelizeConverter();
|
const converter = new CriteriaToSequelizeConverter();
|
||||||
const query = converter.convert(criteria);
|
const query = converter.convert(criteria);
|
||||||
|
|
||||||
|
query.where = {
|
||||||
|
...query.where,
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
const instances = await CustomerInvoiceModel.findAll({
|
const instances = await CustomerInvoiceModel.findAll({
|
||||||
...query,
|
...query,
|
||||||
transaction,
|
transaction,
|
||||||
@ -135,13 +167,23 @@ export class CustomerInvoiceRepository
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Elimina o marca como eliminada una factura.
|
* Elimina o marca como eliminada una factura.
|
||||||
|
*
|
||||||
|
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
||||||
* @param id - UUID de la factura a eliminar.
|
* @param id - UUID de la factura a eliminar.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<void, Error>
|
* @returns Result<void, Error>
|
||||||
*/
|
*/
|
||||||
async deleteById(id: UniqueID, transaction: any): Promise<Result<void, Error>> {
|
async deleteByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: any
|
||||||
|
): Promise<Result<void, Error>> {
|
||||||
try {
|
try {
|
||||||
await this._deleteById(CustomerInvoiceModel, id, false, transaction);
|
const deleted = await CustomerInvoiceModel.destroy({
|
||||||
|
where: { id: id.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
return Result.ok<void>();
|
return Result.ok<void>();
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return Result.fail(translateSequelizeError(err));
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import {
|
|||||||
PhoneNumber,
|
PhoneNumber,
|
||||||
PostalAddress,
|
PostalAddress,
|
||||||
PostalAddressPatchProps,
|
PostalAddressPatchProps,
|
||||||
PostalAddressSnapshot,
|
|
||||||
TINNumber,
|
TINNumber,
|
||||||
TaxCode,
|
TaxCode,
|
||||||
TextValue,
|
TextValue,
|
||||||
@ -41,32 +40,6 @@ export interface CustomerProps {
|
|||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomerSnapshot {
|
|
||||||
id: string;
|
|
||||||
companyId: string;
|
|
||||||
status: string;
|
|
||||||
reference: string | null;
|
|
||||||
|
|
||||||
isCompany: boolean;
|
|
||||||
name: string;
|
|
||||||
tradeName: string | null;
|
|
||||||
|
|
||||||
tin: string | null;
|
|
||||||
|
|
||||||
address: PostalAddressSnapshot; // snapshot serializable del VO PostalAddress
|
|
||||||
|
|
||||||
email: string | null;
|
|
||||||
phone: string | null;
|
|
||||||
fax: string | null;
|
|
||||||
website: string | null;
|
|
||||||
|
|
||||||
legalRecord: string | null;
|
|
||||||
defaultTaxes: string[];
|
|
||||||
|
|
||||||
languageCode: string;
|
|
||||||
currencyCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CustomerPatchProps = Partial<Omit<CustomerProps, "companyId" | "address">> & {
|
export type CustomerPatchProps = Partial<Omit<CustomerProps, "companyId" | "address">> & {
|
||||||
address?: PostalAddressPatchProps;
|
address?: PostalAddressPatchProps;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,8 +4,7 @@ import { Collection, Result } from "@repo/rdx-utils";
|
|||||||
import { Customer } from "../aggregates";
|
import { Customer } from "../aggregates";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contrato del repositorio de Customers.
|
* Interfaz del repositorio para el agregado `Customer`.
|
||||||
* Define la interfaz de persistencia para el agregado `Customer`.
|
|
||||||
* El escopado multitenant está representado por `companyId`.
|
* El escopado multitenant está representado por `companyId`.
|
||||||
*/
|
*/
|
||||||
export interface ICustomerRepository {
|
export interface ICustomerRepository {
|
||||||
@ -35,7 +34,8 @@ export interface ICustomerRepository {
|
|||||||
): Promise<Result<Customer, Error>>;
|
): Promise<Result<Customer, Error>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recupera múltiples customers dentro de una empresa según un criterio dinámico (búsqueda, paginación, etc.).
|
* Recupera múltiples customers dentro de una empresa
|
||||||
|
* según un criterio dinámico (búsqueda, paginación, etc.).
|
||||||
* El resultado está encapsulado en un objeto `Collection<T>`.
|
* El resultado está encapsulado en un objeto `Collection<T>`.
|
||||||
*/
|
*/
|
||||||
findByCriteriaInCompany(
|
findByCriteriaInCompany(
|
||||||
@ -47,6 +47,11 @@ export interface ICustomerRepository {
|
|||||||
/**
|
/**
|
||||||
* Elimina un Customer por su ID, dentro de una empresa.
|
* Elimina un Customer por su ID, dentro de una empresa.
|
||||||
* Retorna `void` si se elimina correctamente, o `NotFoundError` si no existía.
|
* Retorna `void` si se elimina correctamente, o `NotFoundError` si no existía.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
deleteByIdInCompany(companyId: UniqueID, id: UniqueID, transaction: any): Promise<Result<void>>;
|
deleteByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: any
|
||||||
|
): Promise<Result<void, Error>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,12 +10,12 @@ export const customersAPIModule: IModuleServer = {
|
|||||||
// const contacts = getService<ContactsService>("contacts");
|
// const contacts = getService<ContactsService>("contacts");
|
||||||
const { logger } = params;
|
const { logger } = params;
|
||||||
customersRouter(params);
|
customersRouter(params);
|
||||||
logger.info("🚀 Customers module initialized", { label: "customers" });
|
logger.info("🚀 Customers module initialized", { label: this.name });
|
||||||
},
|
},
|
||||||
async registerDependencies(params) {
|
async registerDependencies(params) {
|
||||||
const { database, logger } = params;
|
const { database, logger } = params;
|
||||||
logger.info("🚀 Customers module dependencies registered", {
|
logger.info("🚀 Customers module dependencies registered", {
|
||||||
label: "customers",
|
label: this.name,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
models,
|
models,
|
||||||
|
|||||||
@ -1,128 +0,0 @@
|
|||||||
import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core/api";
|
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
import { InferCreationAttributes } from "sequelize";
|
|
||||||
import {
|
|
||||||
Customer,
|
|
||||||
CustomerItem,
|
|
||||||
CustomerItemDescription,
|
|
||||||
CustomerItemDiscount,
|
|
||||||
CustomerItemQuantity,
|
|
||||||
CustomerItemUnitPrice,
|
|
||||||
} from "../../domain";
|
|
||||||
import { CustomerItemCreationAttributes, CustomerItemModel, CustomerModel } from "../sequelize";
|
|
||||||
|
|
||||||
export interface ICustomerItemMapper
|
|
||||||
extends ISequelizeMapper<CustomerItemModel, CustomerItemCreationAttributes, CustomerItem> {}
|
|
||||||
|
|
||||||
export class CustomerItemMapper
|
|
||||||
extends SequelizeMapper<CustomerItemModel, CustomerItemCreationAttributes, CustomerItem>
|
|
||||||
implements ICustomerItemMapper
|
|
||||||
{
|
|
||||||
public mapToDomain(
|
|
||||||
source: CustomerItemModel,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<CustomerItem, Error> {
|
|
||||||
const { sourceParent } = params as { sourceParent: CustomerModel };
|
|
||||||
|
|
||||||
// Validación y creación de ID único
|
|
||||||
const idOrError = UniqueID.create(source.item_id);
|
|
||||||
if (idOrError.isFailure) {
|
|
||||||
return Result.fail(idOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validación y creación de descripción
|
|
||||||
const descriptionOrError = CustomerItemDescription.create(source.description || "");
|
|
||||||
if (descriptionOrError.isFailure) {
|
|
||||||
return Result.fail(descriptionOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validación y creación de cantidad
|
|
||||||
const quantityOrError = CustomerItemQuantity.create({
|
|
||||||
amount: source.quantity_amount,
|
|
||||||
scale: source.quantity_scale,
|
|
||||||
});
|
|
||||||
if (quantityOrError.isFailure) {
|
|
||||||
return Result.fail(quantityOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validación y creación de precio unitario
|
|
||||||
const unitPriceOrError = CustomerItemUnitPrice.create({
|
|
||||||
amount: source.unit_price_amount,
|
|
||||||
scale: source.unit_price_scale,
|
|
||||||
currency_code: sourceParent.invoice_currency,
|
|
||||||
});
|
|
||||||
if (unitPriceOrError.isFailure) {
|
|
||||||
return Result.fail(unitPriceOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validación y creación de descuento
|
|
||||||
const discountOrError = CustomerItemDiscount.create({
|
|
||||||
amount: source.discount_amount || 0,
|
|
||||||
scale: source.discount_scale || 0,
|
|
||||||
});
|
|
||||||
if (discountOrError.isFailure) {
|
|
||||||
return Result.fail(discountOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combinación de resultados
|
|
||||||
const result = Result.combine([
|
|
||||||
idOrError,
|
|
||||||
descriptionOrError,
|
|
||||||
quantityOrError,
|
|
||||||
unitPriceOrError,
|
|
||||||
discountOrError,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (result.isFailure) {
|
|
||||||
return Result.fail(result.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creación del objeto de dominio
|
|
||||||
return CustomerItem.create(
|
|
||||||
{
|
|
||||||
description: descriptionOrError.data,
|
|
||||||
quantity: quantityOrError.data,
|
|
||||||
unitPrice: unitPriceOrError.data,
|
|
||||||
discount: discountOrError.data,
|
|
||||||
},
|
|
||||||
idOrError.data
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapToPersistence(
|
|
||||||
source: CustomerItem,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): InferCreationAttributes<CustomerItemModel, {}> {
|
|
||||||
const { index, sourceParent } = params as {
|
|
||||||
index: number;
|
|
||||||
sourceParent: Customer;
|
|
||||||
};
|
|
||||||
|
|
||||||
const lineData = {
|
|
||||||
parent_id: undefined,
|
|
||||||
invoice_id: sourceParent.id.toPrimitive(),
|
|
||||||
item_type: "simple",
|
|
||||||
position: index,
|
|
||||||
|
|
||||||
item_id: source.id.toPrimitive(),
|
|
||||||
description: source.description.toPrimitive(),
|
|
||||||
|
|
||||||
quantity_amount: source.quantity.toPrimitive().amount,
|
|
||||||
quantity_scale: source.quantity.toPrimitive().scale,
|
|
||||||
|
|
||||||
unit_price_amount: source.unitPrice.toPrimitive().amount,
|
|
||||||
unit_price_scale: source.unitPrice.toPrimitive().scale,
|
|
||||||
|
|
||||||
subtotal_amount: source.subtotalPrice.toPrimitive().amount,
|
|
||||||
subtotal_scale: source.subtotalPrice.toPrimitive().scale,
|
|
||||||
|
|
||||||
discount_amount: source.discount.toPrimitive().amount,
|
|
||||||
discount_scale: source.discount.toPrimitive().scale,
|
|
||||||
|
|
||||||
total_amount: source.totalPrice.toPrimitive().amount,
|
|
||||||
total_scale: source.totalPrice.toPrimitive().scale,
|
|
||||||
};
|
|
||||||
return lineData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -171,6 +171,7 @@ export default (database: Sequelize) => {
|
|||||||
sequelize: database,
|
sequelize: database,
|
||||||
tableName: "customers",
|
tableName: "customers",
|
||||||
|
|
||||||
|
underscored: true,
|
||||||
paranoid: true, // softs deletes
|
paranoid: true, // softs deletes
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,6 @@ export class CustomerRepository
|
|||||||
extends SequelizeRepository<Customer>
|
extends SequelizeRepository<Customer>
|
||||||
implements ICustomerRepository
|
implements ICustomerRepository
|
||||||
{
|
{
|
||||||
//private readonly model: typeof CustomerModel;
|
|
||||||
private readonly mapper!: ICustomerMapper;
|
private readonly mapper!: ICustomerMapper;
|
||||||
|
|
||||||
constructor(mapper: ICustomerMapper) {
|
constructor(mapper: ICustomerMapper) {
|
||||||
@ -149,7 +148,6 @@ export class CustomerRepository
|
|||||||
|
|
||||||
return Result.ok<void>();
|
return Result.ok<void>();
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
// , `Error deleting customer ${id} in company ${companyId}`
|
|
||||||
return Result.fail(translateSequelizeError(err));
|
return Result.fail(translateSequelizeError(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user