Limpieza
This commit is contained in:
parent
af7d3dcf28
commit
c7b42fa8ba
@ -1,18 +0,0 @@
|
||||
import { ICustomerInvoiceService } from "@/contexts/customer-billing/domain";
|
||||
import { CustomerInvoice } from "@/contexts/customer-billing/domain/aggregates";
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
export class GetCustomerInvoiceUseCase {
|
||||
constructor(
|
||||
private readonly invoiceService: ICustomerInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(invoiceId: UniqueID): Promise<Result<CustomerInvoice, Error>> {
|
||||
return this.transactionManager.complete((transaction) => {
|
||||
return this.invoiceService.findCustomerInvoiceById(invoiceId, transaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./get-customer-invoice.use-case";
|
||||
export * from "./list-customer-invoices-use-case";
|
||||
@ -1,16 +0,0 @@
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoice, ICustomerInvoiceService } from "../domain";
|
||||
|
||||
export class ListCustomerInvoicesUseCase {
|
||||
constructor(
|
||||
private readonly invoiceService: ICustomerInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(): Promise<Result<Collection<CustomerInvoice>, Error>> {
|
||||
return this.transactionManager.complete((transaction) => {
|
||||
return this.invoiceService.findCustomerInvoices(transaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
import { AggregateRoot, UniqueID, UtcDate } from "@/core/common/domain";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import { Customer, CustomerInvoiceItem } from "../entities";
|
||||
import { InvoiceStatus } from "../value-objetcs";
|
||||
|
||||
export interface ICustomerInvoiceProps {
|
||||
status: InvoiceStatus;
|
||||
issueDate: UtcDate;
|
||||
invoiceNumber: string;
|
||||
invoiceType: string;
|
||||
invoiceCustomerReference: Maybe<string>;
|
||||
|
||||
customer: Customer;
|
||||
items: CustomerInvoiceItem[];
|
||||
}
|
||||
|
||||
export interface ICustomerInvoice {
|
||||
id: UniqueID;
|
||||
status: InvoiceStatus;
|
||||
issueDate: UtcDate;
|
||||
invoiceNumber: string;
|
||||
invoiceType: string;
|
||||
invoiceCustomerReference: Maybe<string>;
|
||||
|
||||
customer: Customer;
|
||||
items: CustomerInvoiceItem[];
|
||||
|
||||
//send();
|
||||
//accept();
|
||||
}
|
||||
|
||||
export class CustomerInvoice
|
||||
extends AggregateRoot<ICustomerInvoiceProps>
|
||||
implements ICustomerInvoice
|
||||
{
|
||||
id: UniqueID;
|
||||
static create(props: ICustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
||||
const invoice = new CustomerInvoice(props, id);
|
||||
|
||||
// Reglas de negocio / validaciones
|
||||
// ...
|
||||
// ...
|
||||
|
||||
// 🔹 Disparar evento de dominio "CustomerAuthenticatedEvent"
|
||||
//const { customer } = props;
|
||||
//user.addDomainEvent(new CustomerAuthenticatedEvent(id, customer.toString()));
|
||||
|
||||
return Result.ok(invoice);
|
||||
}
|
||||
|
||||
get status(): InvoiceStatus {
|
||||
return this.props.status;
|
||||
}
|
||||
|
||||
get issueDate(): UtcDate {
|
||||
return this.props.issueDate;
|
||||
}
|
||||
|
||||
get invoiceNumber(): string {
|
||||
return this.props.invoiceNumber;
|
||||
}
|
||||
|
||||
get invoiceType(): string {
|
||||
return this.props.invoiceType;
|
||||
}
|
||||
|
||||
get invoiceCustomerReference(): Maybe<string> {
|
||||
return this.props.invoiceCustomerReference;
|
||||
}
|
||||
|
||||
get customer(): Customer {
|
||||
return this.props.customer;
|
||||
}
|
||||
|
||||
get items(): CustomerInvoiceItem[] {
|
||||
return this.props.items;
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./customer-invoice";
|
||||
@ -1,107 +0,0 @@
|
||||
import { DomainEntity, MoneyValue, Percentage, UniqueID } from "@/core/common/domain";
|
||||
import { Quantity } from "@/core/common/domain/value-objects/quantity";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
export interface ICustomerInvoiceItemProps {
|
||||
description: Maybe<string>; // Descripción del artículo o servicio
|
||||
quantity: Maybe<Quantity>; // Cantidad de unidades
|
||||
unitPrice: Maybe<MoneyValue>; // Precio unitario en la moneda de la factura
|
||||
// subtotalPrice: MoneyValue; // Precio unitario * Cantidad
|
||||
discount: Maybe<Percentage>; // % descuento
|
||||
// totalPrice: MoneyValue;
|
||||
}
|
||||
|
||||
export interface ICustomerInvoiceItem {
|
||||
description: Maybe<string>;
|
||||
quantity: Maybe<Quantity>;
|
||||
unitPrice: Maybe<MoneyValue>;
|
||||
subtotalPrice: Maybe<MoneyValue>;
|
||||
discount: Maybe<Percentage>;
|
||||
totalPrice: Maybe<MoneyValue>;
|
||||
|
||||
isEmptyLine(): boolean;
|
||||
}
|
||||
|
||||
export class CustomerInvoiceItem
|
||||
extends DomainEntity<ICustomerInvoiceItemProps>
|
||||
implements ICustomerInvoiceItem
|
||||
{
|
||||
private readonly _subtotalPrice!: Maybe<MoneyValue>;
|
||||
private readonly _totalPrice!: Maybe<MoneyValue>;
|
||||
|
||||
static validate(props: ICustomerInvoiceItemProps) {
|
||||
return Result.ok(props);
|
||||
}
|
||||
|
||||
static create(
|
||||
props: ICustomerInvoiceItemProps,
|
||||
id?: UniqueID
|
||||
): Result<CustomerInvoiceItem, Error> {
|
||||
const validation = CustomerInvoiceItem.validate(props);
|
||||
if (!validation.isSuccess) {
|
||||
Result.fail(new Error("Invalid invoice line data"));
|
||||
}
|
||||
|
||||
return Result.ok(new CustomerInvoiceItem(props, id));
|
||||
}
|
||||
|
||||
private constructor(props: ICustomerInvoiceItemProps, id?: UniqueID) {
|
||||
super(props, id);
|
||||
this._subtotalPrice = this.calculateSubtotal();
|
||||
this._totalPrice = this.calculateTotal();
|
||||
}
|
||||
|
||||
isEmptyLine(): boolean {
|
||||
return this.quantity.isNone() && this.unitPrice.isNone() && this.discount.isNone();
|
||||
}
|
||||
|
||||
calculateSubtotal(): Maybe<MoneyValue> {
|
||||
if (this.quantity.isNone() || this.unitPrice.isNone()) {
|
||||
return Maybe.none();
|
||||
}
|
||||
|
||||
const _quantity = this.quantity.getOrUndefined()!;
|
||||
const _unitPrice = this.unitPrice.getOrUndefined()!;
|
||||
const _subtotal = _unitPrice.multiply(_quantity);
|
||||
|
||||
return Maybe.some(_subtotal);
|
||||
}
|
||||
|
||||
calculateTotal(): Maybe<MoneyValue> {
|
||||
const subtotal = this.calculateSubtotal();
|
||||
|
||||
if (subtotal.isNone()) {
|
||||
return Maybe.none();
|
||||
}
|
||||
|
||||
const _subtotal = subtotal.getOrUndefined()!;
|
||||
const _discount = this.discount.getOrUndefined()!;
|
||||
const _total = _subtotal.subtract(_subtotal.percentage(_discount));
|
||||
|
||||
return Maybe.some(_total);
|
||||
}
|
||||
|
||||
get description(): Maybe<string> {
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
get quantity(): Maybe<Quantity> {
|
||||
return this.props.quantity;
|
||||
}
|
||||
|
||||
get unitPrice(): Maybe<MoneyValue> {
|
||||
return this.props.unitPrice;
|
||||
}
|
||||
|
||||
get subtotalPrice(): Maybe<MoneyValue> {
|
||||
return this._subtotalPrice;
|
||||
}
|
||||
|
||||
get discount(): Maybe<Percentage> {
|
||||
return this.props.discount;
|
||||
}
|
||||
|
||||
get totalPrice(): Maybe<MoneyValue> {
|
||||
return this._totalPrice;
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
import { AggregateRoot, PostalAddress, TINNumber, UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
export interface ICustomerProps {
|
||||
name: string;
|
||||
tin: TINNumber;
|
||||
address: PostalAddress;
|
||||
}
|
||||
|
||||
export interface ICustomer {
|
||||
id: UniqueID;
|
||||
|
||||
name: string;
|
||||
tin: TINNumber;
|
||||
address: PostalAddress;
|
||||
}
|
||||
|
||||
export class Customer extends AggregateRoot<ICustomerProps> implements ICustomer {
|
||||
id: UniqueID;
|
||||
static create(props: ICustomerProps, id?: UniqueID): Result<Customer, Error> {
|
||||
const customer = new Customer(props, id);
|
||||
|
||||
// Reglas de negocio / validaciones
|
||||
// ...
|
||||
// ...
|
||||
|
||||
// 🔹 Disparar evento de dominio "CustomerAuthenticatedEvent"
|
||||
//const { customer } = props;
|
||||
//user.addDomainEvent(new CustomerAuthenticatedEvent(id, customer.toString()));
|
||||
|
||||
return Result.ok(customer);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.props.name;
|
||||
}
|
||||
|
||||
get tin(): TINNumber {
|
||||
return this.props.tin;
|
||||
}
|
||||
|
||||
get address(): PostalAddress {
|
||||
return this.props.address;
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "./customer";
|
||||
export * from "./customer-invoice-item";
|
||||
export * from "./tax";
|
||||
export * from "./tax-collection";
|
||||
@ -1,33 +0,0 @@
|
||||
import { Slug } from "@/core/common/domain";
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
import { Tax } from "./tax";
|
||||
|
||||
export class TaxCollection extends Collection<Tax> {
|
||||
constructor(items: Tax[] = []) {
|
||||
super(items);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Agrega un impuesto a la colección garantizando que el slug sea único. */
|
||||
add(tax: Tax): void {
|
||||
if (this.exists(tax.slug)) {
|
||||
throw new Error(`(El impuesto con slug "${tax.slug.toString()}" ya existe.`);
|
||||
}
|
||||
this.add(tax);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Verifica si un slug ya existe en la colección. */
|
||||
exists(slug: Slug): boolean {
|
||||
return this.some((tax) => tax.slug.equals(slug));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Encuentra un impuesto por su slug. */
|
||||
findBySlug(slug: Slug): Tax | undefined {
|
||||
return this.find((tax) => tax.slug.equals(slug));
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import { DomainEntity, Percentage, Slug, UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
interface ITaxProps {
|
||||
slug: Slug;
|
||||
name: string;
|
||||
taxValue: Percentage;
|
||||
}
|
||||
|
||||
interface ITax {
|
||||
slug: Slug;
|
||||
name: string;
|
||||
taxValue: Percentage;
|
||||
}
|
||||
|
||||
export class Tax extends DomainEntity<ITaxProps> implements ITax {
|
||||
static create(props: ITaxProps, id?: UniqueID): Result<Tax, Error> {
|
||||
return Result.ok(new Tax(props, id));
|
||||
}
|
||||
|
||||
get slug(): Slug {
|
||||
return this.props.slug;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.props.name;
|
||||
}
|
||||
|
||||
get taxValue(): Percentage {
|
||||
return this.props.taxValue;
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "./aggregates";
|
||||
export * from "./entities";
|
||||
export * from "./repositories";
|
||||
export * from "./services";
|
||||
@ -1,8 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoice } from "../aggregates";
|
||||
|
||||
export interface ICustomerInvoiceRepository {
|
||||
findAll(transaction?: any): Promise<Result<Collection<CustomerInvoice>, Error>>;
|
||||
findById(id: UniqueID, transaction?: any): Promise<Result<CustomerInvoice, Error>>;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./customer-invoice-repository.interface";
|
||||
@ -1,8 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoice } from "../aggregates";
|
||||
|
||||
export interface ICustomerInvoiceService {
|
||||
findCustomerInvoices(transaction?: any): Promise<Result<Collection<CustomerInvoice>, Error>>;
|
||||
findCustomerInvoiceById(invoiceId: UniqueID, transaction?: any): Promise<Result<CustomerInvoice>>;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoice } from "../aggregates";
|
||||
import { ICustomerInvoiceRepository } from "../repositories";
|
||||
import { ICustomerInvoiceService } from "./customer-invoice-service.interface";
|
||||
|
||||
export class CustomerInvoiceService implements ICustomerInvoiceService {
|
||||
constructor(private readonly invoiceRepository: ICustomerInvoiceRepository) {}
|
||||
|
||||
async findCustomerInvoices(
|
||||
transaction?: any
|
||||
): Promise<Result<Collection<CustomerInvoice>, Error>> {
|
||||
const invoicesOrError = await this.invoiceRepository.findAll(transaction);
|
||||
if (invoicesOrError.isFailure) {
|
||||
return Result.fail(invoicesOrError.error);
|
||||
}
|
||||
|
||||
return Result.ok(invoicesOrError.data);
|
||||
}
|
||||
|
||||
async findCustomerInvoiceById(
|
||||
invoiceId: UniqueID,
|
||||
transaction?: any
|
||||
): Promise<Result<CustomerInvoice>> {
|
||||
return await this.invoiceRepository.findById(invoiceId, transaction);
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./customer-invoice-service.interface";
|
||||
export * from "./customer-invoice.service";
|
||||
@ -1 +0,0 @@
|
||||
export * from "./invoice-status";
|
||||
@ -1,56 +0,0 @@
|
||||
import { InvoiceStatus } from "./invoice-status";
|
||||
|
||||
// Pruebas para InvoiceStatus
|
||||
describe("InvoiceStatus", () => {
|
||||
test("Debe crear estados válidos", () => {
|
||||
const result1 = InvoiceStatus.create("borrador");
|
||||
const result2 = InvoiceStatus.create("enviada");
|
||||
const result3 = InvoiceStatus.create("cerrada");
|
||||
|
||||
expect(result1.isSuccess).toBe(true);
|
||||
expect(result1.data.getValue()).toBe("borrador");
|
||||
expect(result1.data.toString()).toBe("borrador");
|
||||
|
||||
expect(result2.isSuccess).toBe(true);
|
||||
expect(result2.data.getValue()).toBe("enviada");
|
||||
expect(result2.data.toString()).toBe("enviada");
|
||||
|
||||
expect(result3.isSuccess).toBe(true);
|
||||
expect(result3.data.getValue()).toBe("cerrada");
|
||||
expect(result3.data.toString()).toBe("cerrada");
|
||||
});
|
||||
|
||||
test("Debe lanzar error al crear un estado inválido", () => {
|
||||
const result = InvoiceStatus.create("inexistente");
|
||||
expect(result.isFailure).toBe(true);
|
||||
});
|
||||
|
||||
test("Debe permitir transiciones válidas #1", () => {
|
||||
const result = InvoiceStatus.create("borrador");
|
||||
expect(result.isSuccess).toBe(true);
|
||||
expect(result.data.canTransitionTo("enviada")).toBe(true);
|
||||
});
|
||||
|
||||
test("Debe permitir transiciones válidas #2", () => {
|
||||
const result = InvoiceStatus.create("borrador");
|
||||
expect(result.isSuccess).toBe(true);
|
||||
|
||||
const new_state = result.data.transitionTo("enviada");
|
||||
expect(new_state.isSuccess).toBe(true);
|
||||
expect(new_state.data.toString()).toBe("enviada");
|
||||
});
|
||||
|
||||
test("Debe impedir transiciones inválidas", () => {
|
||||
const result = InvoiceStatus.create("cerrada");
|
||||
expect(result.isSuccess).toBe(true);
|
||||
expect(result.data.canTransitionTo("enviada")).toBe(false);
|
||||
});
|
||||
|
||||
test("Debe lanzar error al intentar transición inválida", () => {
|
||||
const result = InvoiceStatus.create("borrador");
|
||||
expect(result.isSuccess).toBe(true);
|
||||
const new_state = result.data.transitionTo("cerrada");
|
||||
expect(new_state.isSuccess).toBe(false);
|
||||
expect(new_state.error.message).toContain("Transición no permitida de borrador a cerrada");
|
||||
});
|
||||
});
|
||||
@ -1,56 +0,0 @@
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
interface IInvoiceStatusProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
|
||||
private static readonly ALLOWED_STATUSES = [
|
||||
"borrador",
|
||||
"enviada",
|
||||
"aceptada",
|
||||
"registrada",
|
||||
"rechazada",
|
||||
"cerrada",
|
||||
"error",
|
||||
];
|
||||
|
||||
private static readonly TRANSITIONS: Record<string, string[]> = {
|
||||
borrador: ["enviada"],
|
||||
enviada: ["aceptada", "registrada", "rechazada", "error"],
|
||||
aceptada: ["registrada"],
|
||||
registrada: ["cerrada"],
|
||||
rechazada: ["cerrada"],
|
||||
error: ["borrador"],
|
||||
cerrada: [],
|
||||
};
|
||||
|
||||
static create(value: string): Result<InvoiceStatus, Error> {
|
||||
if (!this.ALLOWED_STATUSES.includes(value)) {
|
||||
return Result.fail(new Error(`Estado de factura no válido: ${value}`));
|
||||
}
|
||||
return Result.ok(new InvoiceStatus({ value }));
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
canTransitionTo(nextStatus: string): boolean {
|
||||
return InvoiceStatus.TRANSITIONS[this.props.value].includes(nextStatus);
|
||||
}
|
||||
|
||||
transitionTo(nextStatus: string): Result<InvoiceStatus, Error> {
|
||||
if (!this.canTransitionTo(nextStatus)) {
|
||||
return Result.fail(
|
||||
new Error(`Transición no permitida de ${this.props.value} a ${nextStatus}`)
|
||||
);
|
||||
}
|
||||
return InvoiceStatus.create(nextStatus);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./mappers";
|
||||
export * from "./sequelize";
|
||||
@ -1,111 +0,0 @@
|
||||
import { Customer, CustomerInvoice } from "@/contexts/customer-billing/domain";
|
||||
import { InvoiceStatus } from "@/contexts/customer-billing/domain/value-objetcs";
|
||||
import { PostalAddress, TINNumber, UniqueID, UtcDate } from "@/core/common/domain";
|
||||
import {
|
||||
type ISequelizeMapper,
|
||||
type MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "@/core/common/infrastructure/sequelize/sequelize-mapper";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
CustomerInvoiceCreationAttributes,
|
||||
CustomerInvoiceModel,
|
||||
} from "../sequelize/customer-invoice.model";
|
||||
|
||||
export interface ICustomerInvoiceMapper
|
||||
extends ISequelizeMapper<
|
||||
CustomerInvoiceModel,
|
||||
CustomerInvoiceCreationAttributes,
|
||||
CustomerInvoice
|
||||
> {}
|
||||
|
||||
export class CustomerInvoiceMapper
|
||||
extends SequelizeMapper<CustomerInvoiceModel, CustomerInvoiceCreationAttributes, CustomerInvoice>
|
||||
implements ICustomerInvoiceMapper
|
||||
{
|
||||
public mapToDomain(
|
||||
source: CustomerInvoiceModel,
|
||||
params?: MapperParamsType
|
||||
): Result<CustomerInvoice, Error> {
|
||||
const idOrError = UniqueID.create(source.id);
|
||||
const statusOrError = InvoiceStatus.create(source.status);
|
||||
const issueDateOrError = UtcDate.create(source.issue_date);
|
||||
|
||||
const result = Result.combine([idOrError, statusOrError, issueDateOrError]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
// Customer
|
||||
const customerIdOrError = UniqueID.create(source.customer_id);
|
||||
const tinOrError = TINNumber.create(source.customer_tin);
|
||||
const postalAddressOrError = PostalAddress.create({
|
||||
street: source.customer_street,
|
||||
street2: source.customer_street2,
|
||||
city: source.customer_city,
|
||||
state: source.customer_state,
|
||||
postalCode: source.customer_postal_code,
|
||||
country: source.customer_country,
|
||||
});
|
||||
|
||||
const check2 = Result.combine([idOrError, tinOrError, postalAddressOrError]);
|
||||
|
||||
if (check2.isFailure) {
|
||||
return Result.fail(check2.error);
|
||||
}
|
||||
|
||||
const customerOrError = Customer.create(
|
||||
{
|
||||
name: source.customer_name,
|
||||
tin: tinOrError.data,
|
||||
address: postalAddressOrError.data,
|
||||
},
|
||||
customerIdOrError.data
|
||||
);
|
||||
|
||||
return CustomerInvoice.create(
|
||||
{
|
||||
status: statusOrError.data,
|
||||
issueDate: issueDateOrError.data,
|
||||
invoiceNumber: source.invoice_number,
|
||||
invoiceType: source.invoice_type,
|
||||
invoiceCustomerReference: Maybe.fromNullable(source.invoice_customer_reference),
|
||||
customer: customerOrError.data,
|
||||
items: [],
|
||||
},
|
||||
idOrError.data
|
||||
);
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
source: CustomerInvoice,
|
||||
params?: MapperParamsType
|
||||
): Result<CustomerInvoiceCreationAttributes, Error> {
|
||||
return Result.ok({
|
||||
id: source.id.toString(),
|
||||
status: source.status.toString(),
|
||||
|
||||
issue_date: source.issueDate.toDateString(),
|
||||
invoice_number: source.invoiceNumber,
|
||||
invoice_type: source.invoiceType,
|
||||
invoice_customer_reference: source.invoiceCustomerReference.getOrUndefined(),
|
||||
|
||||
lang_code: "es",
|
||||
currency_code: "EUR",
|
||||
|
||||
customer_id: source.customer.id.toString(),
|
||||
customer_name: source.customer.name,
|
||||
customer_tin: source.customer.tin.toString(),
|
||||
customer_street: source.customer.address.street,
|
||||
customer_street2: source.customer.address.street2.getOrUndefined(),
|
||||
customer_city: source.customer.address.city,
|
||||
customer_postal_code: source.customer.address.postalCode,
|
||||
customer_state: source.customer.address.state,
|
||||
customer_country: source.customer.address.country,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const customerInvoiceMapper: CustomerInvoiceMapper = new CustomerInvoiceMapper();
|
||||
export { customerInvoiceMapper };
|
||||
@ -1 +0,0 @@
|
||||
export * from "./customer-invoice.mapper";
|
||||
@ -1,107 +0,0 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { CustomerInvoiceModel } from "./customer-invoice.model";
|
||||
|
||||
export type CustomerInvoiceItemCreationAttributes = InferCreationAttributes<
|
||||
CustomerInvoiceItemModel,
|
||||
{ omit: "invoice" }
|
||||
>;
|
||||
|
||||
export class CustomerInvoiceItemModel extends Model<
|
||||
InferAttributes<CustomerInvoiceItemModel, { omit: "invoice" }>,
|
||||
InferCreationAttributes<CustomerInvoiceItemModel, { omit: "invoice" }>
|
||||
> {
|
||||
static associate(connection: Sequelize) {
|
||||
const { CustomerInvoiceModel, CustomerInvoiceItemModel } = connection.models;
|
||||
|
||||
CustomerInvoiceItemModel.belongsTo(CustomerInvoiceModel, {
|
||||
as: "invoice",
|
||||
foreignKey: "id",
|
||||
onDelete: "CASCADE",
|
||||
});
|
||||
}
|
||||
|
||||
declare customer_invoice_id: string;
|
||||
declare item_id: string;
|
||||
declare id_article: CreationOptional<number>;
|
||||
declare position: number;
|
||||
declare description: CreationOptional<string>;
|
||||
declare quantity: CreationOptional<number>;
|
||||
declare unit_price: CreationOptional<number>;
|
||||
declare subtotal_price: CreationOptional<number>;
|
||||
declare discount: CreationOptional<number>;
|
||||
declare total_price: CreationOptional<number>;
|
||||
|
||||
declare invoice: NonAttribute<CustomerInvoiceModel>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
CustomerInvoiceItemModel.init(
|
||||
{
|
||||
item_id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
customer_invoice_id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
id_article: {
|
||||
type: DataTypes.BIGINT().UNSIGNED,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
position: {
|
||||
type: new DataTypes.MEDIUMINT(),
|
||||
autoIncrement: false,
|
||||
allowNull: false,
|
||||
},
|
||||
description: {
|
||||
type: new DataTypes.TEXT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
quantity: {
|
||||
type: DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
unit_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
subtotal_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
discount: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
total_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "customer_invoice_items",
|
||||
timestamps: false,
|
||||
|
||||
indexes: [],
|
||||
}
|
||||
);
|
||||
|
||||
return CustomerInvoiceItemModel;
|
||||
};
|
||||
@ -1,263 +0,0 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { CustomerInvoiceItemModel } from "./customer-invoice-item.model";
|
||||
|
||||
export type CustomerInvoiceCreationAttributes = InferCreationAttributes<
|
||||
CustomerInvoiceModel,
|
||||
{ omit: "items" }
|
||||
> & {
|
||||
// creo que no es necesario
|
||||
//items: CustomerInvoiceItemCreationAttributes[];
|
||||
//customer_id: string;
|
||||
};
|
||||
|
||||
export class CustomerInvoiceModel extends Model<
|
||||
InferAttributes<CustomerInvoiceModel, { omit: "items" }>,
|
||||
InferCreationAttributes<CustomerInvoiceModel, { omit: "items" }>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
|
||||
static associate(connection: Sequelize) {
|
||||
const { CustomerInvoiceModel, CustomerInvoiceItemModel, CustomerModel } = connection.models;
|
||||
|
||||
CustomerInvoiceModel.hasMany(CustomerInvoiceItemModel, {
|
||||
as: "items",
|
||||
foreignKey: "customer_invoice_id",
|
||||
onDelete: "CASCADE",
|
||||
});
|
||||
}
|
||||
|
||||
declare id: string;
|
||||
declare status: string;
|
||||
|
||||
declare issue_date: string;
|
||||
declare invoice_number: string;
|
||||
declare invoice_type: string;
|
||||
declare invoice_customer_reference?: CreationOptional<string>;
|
||||
|
||||
declare lang_code: string;
|
||||
declare currency_code: string;
|
||||
|
||||
declare customer_id: string;
|
||||
declare customer_tin: string;
|
||||
declare customer_name: string;
|
||||
|
||||
declare customer_street: string;
|
||||
declare customer_street2?: CreationOptional<string>;
|
||||
declare customer_city: string;
|
||||
declare customer_state: string;
|
||||
declare customer_postal_code: string;
|
||||
declare customer_country: string;
|
||||
|
||||
declare subtotal_price?: CreationOptional<number>;
|
||||
|
||||
declare discount?: CreationOptional<number>;
|
||||
declare discount_price?: CreationOptional<number>;
|
||||
|
||||
declare before_tax_price?: CreationOptional<number>;
|
||||
|
||||
declare tax?: CreationOptional<number>;
|
||||
declare tax_price?: CreationOptional<number>;
|
||||
|
||||
declare total_price?: CreationOptional<number>;
|
||||
|
||||
declare notes?: CreationOptional<string>;
|
||||
|
||||
declare items: NonAttribute<CustomerInvoiceItemModel[]>;
|
||||
|
||||
declare integrity_hash?: CreationOptional<string>;
|
||||
declare previous_invoice_id?: CreationOptional<string>;
|
||||
declare signed_at?: CreationOptional<string>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
CustomerInvoiceModel.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
status: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
issue_date: {
|
||||
type: new DataTypes.DATEONLY(),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
invoice_number: {
|
||||
type: DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
invoice_customer_reference: {
|
||||
type: new DataTypes.STRING(),
|
||||
},
|
||||
|
||||
invoice_type: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
lang_code: {
|
||||
type: DataTypes.STRING(2),
|
||||
allowNull: false,
|
||||
defaultValue: "es",
|
||||
},
|
||||
|
||||
currency_code: {
|
||||
type: new DataTypes.STRING(3),
|
||||
allowNull: false,
|
||||
defaultValue: "EUR",
|
||||
},
|
||||
|
||||
customer_id: {
|
||||
type: new DataTypes.UUID(),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
customer_name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
customer_tin: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
customer_street: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
customer_street2: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
customer_city: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
customer_state: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
customer_postal_code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
customer_country: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
subtotal_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
discount: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
discount_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
before_tax_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
tax: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
tax_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
total_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
notes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
integrity_hash: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
comment: "Hash criptográfico para asegurar integridad",
|
||||
},
|
||||
previous_invoice_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
comment: "Referencia a la factura anterior (si aplica)",
|
||||
},
|
||||
signed_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
comment: "Fecha en que la factura fue firmada digitalmente",
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "customer_invoices",
|
||||
|
||||
paranoid: true, // softs deletes
|
||||
timestamps: true,
|
||||
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at",
|
||||
deletedAt: "deleted_at",
|
||||
|
||||
indexes: [
|
||||
{ name: "status_idx", fields: ["status"] },
|
||||
{ name: "invoice_number_idx", fields: ["invoice_number"] },
|
||||
{ name: "deleted_at_idx", fields: ["deleted_at"] },
|
||||
{ name: "signed_at_idx", fields: ["signed_at"] },
|
||||
],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
return CustomerInvoiceModel;
|
||||
};
|
||||
@ -1,64 +0,0 @@
|
||||
import { CustomerInvoice, ICustomerInvoiceRepository } from "@/contexts/customer-billing/domain";
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { SequelizeRepository } from "@/core/common/infrastructure";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Transaction } from "sequelize";
|
||||
import {
|
||||
type ICustomerInvoiceMapper,
|
||||
customerInvoiceMapper,
|
||||
} from "../mappers/customer-invoice.mapper";
|
||||
import { CustomerInvoiceModel } from "./customer-invoice.model";
|
||||
|
||||
class CustomerInvoiceRepository
|
||||
extends SequelizeRepository<CustomerInvoice>
|
||||
implements ICustomerInvoiceRepository
|
||||
{
|
||||
private readonly _mapper!: ICustomerInvoiceMapper;
|
||||
|
||||
/**
|
||||
* 🔹 Función personalizada para mapear errores de unicidad en autenticación
|
||||
*/
|
||||
private _customErrorMapper(error: Error): string | null {
|
||||
if (error.name === "SequelizeUniqueConstraintError") {
|
||||
return "Customer invoice with this email already exists";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(mapper: ICustomerInvoiceMapper) {
|
||||
super();
|
||||
this._mapper = mapper;
|
||||
}
|
||||
|
||||
async findAll(transaction?: Transaction): Promise<Result<Collection<CustomerInvoice>, Error>> {
|
||||
try {
|
||||
const rawCustomerInvoices: any = await this._findAll(CustomerInvoiceModel, {}, transaction);
|
||||
|
||||
if (!rawCustomerInvoices === true) {
|
||||
return Result.fail(new Error("Customer with email not exists"));
|
||||
}
|
||||
|
||||
return this._mapper.mapArrayToDomain(rawCustomerInvoices);
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: UniqueID, transaction?: Transaction): Promise<Result<CustomerInvoice, Error>> {
|
||||
try {
|
||||
const rawInvoice: any = await this._getById(CustomerInvoiceModel, id, {}, transaction);
|
||||
|
||||
if (!rawInvoice === true) {
|
||||
return Result.fail(new Error(`Customer with id ${id.toString()} not exists`));
|
||||
}
|
||||
|
||||
return this._mapper.mapToDomain(rawInvoice);
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const customerInvoiceRepository = new CustomerInvoiceRepository(customerInvoiceMapper);
|
||||
export { customerInvoiceRepository };
|
||||
@ -1,175 +0,0 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
export type CustomerCreationAttributes = InferCreationAttributes<CustomerModel, {}> & {};
|
||||
|
||||
export class CustomerModel extends Model<
|
||||
InferAttributes<CustomerModel>,
|
||||
InferCreationAttributes<CustomerModel>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
|
||||
declare id: string;
|
||||
declare reference: CreationOptional<string>;
|
||||
|
||||
declare is_companyr: boolean;
|
||||
declare name: string;
|
||||
declare trade_name: CreationOptional<string>;
|
||||
declare tin: string;
|
||||
|
||||
declare street: string;
|
||||
declare city: string;
|
||||
declare state: string;
|
||||
declare postal_code: string;
|
||||
declare country: string;
|
||||
|
||||
declare email: string;
|
||||
declare phone: string;
|
||||
declare fax: CreationOptional<string>;
|
||||
declare website: CreationOptional<string>;
|
||||
|
||||
declare legal_record: string;
|
||||
|
||||
declare default_tax: number;
|
||||
declare status: string;
|
||||
declare lang_code: string;
|
||||
declare currency_code: string;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
CustomerModel.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
},
|
||||
reference: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
is_companyr: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
trade_name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
tin: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
street: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
city: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
state: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
postal_code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
country: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
isEmail: true,
|
||||
},
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
fax: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
website: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
validate: {
|
||||
isUrl: true,
|
||||
},
|
||||
},
|
||||
legal_record: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
default_tax: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 2100,
|
||||
},
|
||||
|
||||
lang_code: {
|
||||
type: DataTypes.STRING(2),
|
||||
allowNull: false,
|
||||
defaultValue: "es",
|
||||
},
|
||||
|
||||
currency_code: {
|
||||
type: new DataTypes.STRING(3),
|
||||
allowNull: false,
|
||||
defaultValue: "EUR",
|
||||
},
|
||||
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: "active",
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "customers",
|
||||
|
||||
paranoid: true, // softs deletes
|
||||
timestamps: true,
|
||||
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at",
|
||||
deletedAt: "deleted_at",
|
||||
|
||||
indexes: [
|
||||
{ name: "email_idx", fields: ["email"], unique: true },
|
||||
{ name: "reference_idx", fields: ["reference"], unique: true },
|
||||
],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
return CustomerModel;
|
||||
};
|
||||
@ -1,15 +0,0 @@
|
||||
import { ICustomerInvoiceRepository } from "@/contexts/customer-billing/domain/";
|
||||
import { customerInvoiceRepository } from "./customer-invoice.repository";
|
||||
|
||||
export * from "./customer-invoice.model";
|
||||
export * from "./customer.model";
|
||||
|
||||
export * from "./customer-invoice.repository";
|
||||
|
||||
/*export const createCustomerRepository = (): ICustomerRepository => {
|
||||
return customerRepository;
|
||||
};*/
|
||||
|
||||
export const createCustomerInvoiceRepository = (): ICustomerInvoiceRepository => {
|
||||
return customerInvoiceRepository;
|
||||
};
|
||||
@ -1,44 +0,0 @@
|
||||
import { GetCustomerInvoiceUseCase } from "@/contexts/customer-billing/application";
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { ExpressController } from "@/core/common/presentation";
|
||||
import { IGetCustomerInvoicePresenter } from "./get-customer-invoice.presenter";
|
||||
|
||||
export class GetCustomerInvoiceController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly getCustomerInvoice: GetCustomerInvoiceUseCase,
|
||||
private readonly presenter: IGetCustomerInvoicePresenter
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const { invoiceId } = this.req.params;
|
||||
|
||||
// Validar ID
|
||||
const invoiceIdOrError = UniqueID.create(invoiceId);
|
||||
if (invoiceIdOrError.isFailure) return this.invalidInputError("Invoice ID not valid");
|
||||
|
||||
const invoiceOrError = await this.getCustomerInvoice.execute(invoiceIdOrError.data);
|
||||
|
||||
if (invoiceOrError.isFailure) {
|
||||
return this.handleError(invoiceOrError.error);
|
||||
}
|
||||
|
||||
return this.ok(this.presenter.toDTO(invoiceOrError.data));
|
||||
}
|
||||
|
||||
private handleError(error: Error) {
|
||||
const message = error.message;
|
||||
|
||||
if (
|
||||
message.includes("Database connection lost") ||
|
||||
message.includes("Database request timed out")
|
||||
) {
|
||||
return this.unavailableError(
|
||||
"Database service is currently unavailable. Please try again later."
|
||||
);
|
||||
}
|
||||
|
||||
return this.conflictError(message);
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
import { CustomerInvoice } from "@/contexts/customer-billing/domain";
|
||||
import { IGetCustomerInvoiceResponseDTO } from "@/contexts/customer-billing/presentation/dto";
|
||||
|
||||
export interface IGetCustomerInvoicePresenter {
|
||||
toDTO: (invoice: CustomerInvoice) => IGetCustomerInvoiceResponseDTO;
|
||||
}
|
||||
|
||||
export const getCustomerInvoicesPresenter: IGetCustomerInvoicePresenter = {
|
||||
toDTO: (invoice: CustomerInvoice): IGetCustomerInvoiceResponseDTO => {
|
||||
return {} as IGetCustomerInvoiceResponseDTO;
|
||||
},
|
||||
};
|
||||
@ -1,16 +0,0 @@
|
||||
import { GetCustomerInvoiceUseCase } from "@/contexts/customer-billing/application/";
|
||||
import { CustomerInvoiceService } from "@/contexts/customer-billing/domain";
|
||||
import { customerInvoiceRepository } from "@/contexts/customer-billing/infraestructure";
|
||||
import { SequelizeTransactionManager } from "@/core/common/infrastructure";
|
||||
import { GetCustomerInvoiceController } from "./get-customer-invoice.controller";
|
||||
import { getCustomerInvoicesPresenter } from "./get-customer-invoice.presenter";
|
||||
|
||||
export const getCustomerInvoiceController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const customerInvoiceService = new CustomerInvoiceService(customerInvoiceRepository);
|
||||
|
||||
const useCase = new GetCustomerInvoiceUseCase(customerInvoiceService, transactionManager);
|
||||
const presenter = getCustomerInvoicesPresenter;
|
||||
|
||||
return new GetCustomerInvoiceController(useCase, presenter);
|
||||
};
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./get";
|
||||
export * from "./list";
|
||||
@ -1,16 +0,0 @@
|
||||
import { CustomerInvoiceService } from "@/contexts/customer-billing/domain";
|
||||
import { customerInvoiceRepository } from "@/contexts/customer-billing/infraestructure";
|
||||
import { SequelizeTransactionManager } from "@/core/common/infrastructure";
|
||||
import { ListCustomerInvoicesUseCase } from "../../../../application";
|
||||
import { ListCustomerInvoicesController } from "./list-customer-invoices.controller";
|
||||
import { listCustomerInvoicesPresenter } from "./list-customer-invoices.presenter";
|
||||
|
||||
export const listCustomerInvoicesController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const customerInvoiceService = new CustomerInvoiceService(customerInvoiceRepository);
|
||||
|
||||
const useCase = new ListCustomerInvoicesUseCase(customerInvoiceService, transactionManager);
|
||||
const presenter = listCustomerInvoicesPresenter;
|
||||
|
||||
return new ListCustomerInvoicesController(useCase, presenter);
|
||||
};
|
||||
@ -1,37 +0,0 @@
|
||||
import { ListCustomerInvoicesUseCase } from "@/contexts/customer-billing/application";
|
||||
import { ExpressController } from "@/core/common/presentation";
|
||||
import { IListCustomerInvoicesPresenter } from "./list-customer-invoices.presenter";
|
||||
|
||||
export class ListCustomerInvoicesController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly listCustomerInvoices: ListCustomerInvoicesUseCase,
|
||||
private readonly presenter: IListCustomerInvoicesPresenter
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const customersOrError = await this.listCustomerInvoices.execute();
|
||||
|
||||
if (customersOrError.isFailure) {
|
||||
return this.handleError(customersOrError.error);
|
||||
}
|
||||
|
||||
return this.ok(this.presenter.toDTO(customersOrError.data));
|
||||
}
|
||||
|
||||
private handleError(error: Error) {
|
||||
const message = error.message;
|
||||
|
||||
if (
|
||||
message.includes("Database connection lost") ||
|
||||
message.includes("Database request timed out")
|
||||
) {
|
||||
return this.unavailableError(
|
||||
"Database service is currently unavailable. Please try again later."
|
||||
);
|
||||
}
|
||||
|
||||
return this.conflictError(message);
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import { Collection, ensureString } from "@repo/rdx-utils";
|
||||
|
||||
import { CustomerInvoice } from "@/contexts/customer-billing/domain";
|
||||
import { IListCustomerInvoicesResponseDTO } from "../../../dto";
|
||||
|
||||
export interface IListCustomerInvoicesPresenter {
|
||||
toDTO: (customers: Collection<CustomerInvoice>) => IListCustomerInvoicesResponseDTO[];
|
||||
}
|
||||
|
||||
export const listCustomerInvoicesPresenter: IListCustomerInvoicesPresenter = {
|
||||
toDTO: (invoice: Collection<CustomerInvoice>): IListCustomerInvoicesResponseDTO[] =>
|
||||
invoice.map((customer) => ({
|
||||
id: ensureString(customer.id.toString()),
|
||||
/*reference: ensureString(customer.),
|
||||
|
||||
is_companyr: ensureBoolean(customer.isFreelancer),
|
||||
name: ensureString(customer.name),
|
||||
trade_name: ensureString(customer.tradeName.getValue()),
|
||||
tin: ensureString(customer.tin.toString()),
|
||||
|
||||
street: ensureString(customer.address.street),
|
||||
city: ensureString(customer.address.city),
|
||||
state: ensureString(customer.address.state),
|
||||
postal_code: ensureString(customer.address.postalCode),
|
||||
country: ensureString(customer.address.country),
|
||||
|
||||
email: ensureString(customer.email.toString()),
|
||||
phone: ensureString(customer.phone.toString()),
|
||||
fax: ensureString(customer.fax.getValue()?.toString()),
|
||||
website: ensureString(customer.website.getValue()),
|
||||
|
||||
legal_record: ensureString(customer.legalRecord),
|
||||
|
||||
default_tax: ensureNumber(customer.defaultTax),
|
||||
status: ensureString(customer.isActive ? "active" : "inactive"),
|
||||
lang_code: ensureString(customer.langCode),
|
||||
currency_code: ensureString(customer.currencyCode),*/
|
||||
})),
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export * from "./customer-invoices";
|
||||
@ -1 +0,0 @@
|
||||
export type IListCustomerInvoicesRequestDTO = {}
|
||||
@ -1,29 +0,0 @@
|
||||
export interface IListCustomerInvoicesResponseDTO {
|
||||
id: string;
|
||||
/*reference: string;
|
||||
|
||||
is_companyr: boolean;
|
||||
name: string;
|
||||
trade_name: string;
|
||||
tin: string;
|
||||
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
|
||||
email: string;
|
||||
phone: string;
|
||||
fax: string;
|
||||
website: string;
|
||||
|
||||
legal_record: string;
|
||||
|
||||
default_tax: number;
|
||||
status: string;
|
||||
lang_code: string;
|
||||
currency_code: string;*/
|
||||
}
|
||||
|
||||
export type IGetCustomerInvoiceResponseDTO = {};
|
||||
@ -1,4 +0,0 @@
|
||||
import * as z from "zod/v4";
|
||||
|
||||
export const ListCustomerInvoicesSchema = z.object({});
|
||||
export const GetCustomerInvoiceSchema = z.object({});
|
||||
@ -1,3 +0,0 @@
|
||||
export * from "./customer-invoices.request.dto";
|
||||
export * from "./customer-invoices.response.dto";
|
||||
export * from "./customer-invoices.validation.dto";
|
||||
@ -1 +0,0 @@
|
||||
export type IListCustomersRequestDTO = {}
|
||||
@ -1,27 +0,0 @@
|
||||
export interface IListCustomersResponseDTO {
|
||||
id: string;
|
||||
reference: string;
|
||||
|
||||
is_companyr: boolean;
|
||||
name: string;
|
||||
trade_name: string;
|
||||
tin: string;
|
||||
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
|
||||
email: string;
|
||||
phone: string;
|
||||
fax: string;
|
||||
website: string;
|
||||
|
||||
legal_record: string;
|
||||
|
||||
default_tax: number;
|
||||
status: string;
|
||||
lang_code: string;
|
||||
currency_code: string;
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
import * as z from "zod/v4";
|
||||
|
||||
export const ListCustomersSchema = z.object({});
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "./customer-invoices";
|
||||
export * from "./customers.request.dto";
|
||||
export * from "./customers.response.dto";
|
||||
export * from "./customers.validation.dto";
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./controllers";
|
||||
export * from "./dto";
|
||||
@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "@modules/invoices",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": ["dist/**"],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"lint": "eslint src/",
|
||||
"lint:fix": "eslint src/ --fix",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "jest"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@repo/jest-presets/node"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rdx/utils": "workspace:*",
|
||||
"@repo/jest-presets": "workspace:*",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/dinero.js": "^1.9.4",
|
||||
"jest": "^29.7.0",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export default () => {};
|
||||
@ -1 +0,0 @@
|
||||
export * from "./client";
|
||||
@ -1,147 +0,0 @@
|
||||
import { UniqueID, UtcDate } from "core/common/domain";
|
||||
|
||||
import {
|
||||
IInvoiceProps,
|
||||
IInvoiceService,
|
||||
Invoice,
|
||||
InvoiceNumber,
|
||||
InvoiceSerie,
|
||||
InvoiceStatus,
|
||||
} from "@contexts/invoices/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
import { ITransactionManager } from "core/common/infrastructure/database";
|
||||
import { logger } from "core/common/infrastructure/logger";
|
||||
import { ICreateInvoiceRequestDTO } from "../presentation/dto";
|
||||
|
||||
export class CreateInvoiceUseCase {
|
||||
constructor(
|
||||
private readonly invoiceService: IInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(
|
||||
invoiceID: UniqueID,
|
||||
dto: ICreateInvoiceRequestDTO
|
||||
): Promise<Result<Invoice, Error>> {
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
const validOrErrors = this.validateInvoiceData(dto);
|
||||
if (validOrErrors.isFailure) {
|
||||
return Result.fail(validOrErrors.error);
|
||||
}
|
||||
|
||||
const data = validOrErrors.data;
|
||||
|
||||
// Update invoice with dto
|
||||
return await this.invoiceService.createInvoice(invoiceID, data, transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private validateInvoiceData(dto: ICreateInvoiceRequestDTO): Result<IInvoiceProps, Error> {
|
||||
const errors: Error[] = [];
|
||||
|
||||
const invoiceNumerOrError = InvoiceNumber.create(dto.invoice_number);
|
||||
const invoiceSeriesOrError = InvoiceSerie.create(dto.invoice_series);
|
||||
const issueDateOrError = UtcDate.create(dto.issue_date);
|
||||
const operationDateOrError = UtcDate.create(dto.operation_date);
|
||||
|
||||
const result = Result.combine([
|
||||
invoiceNumerOrError,
|
||||
invoiceSeriesOrError,
|
||||
issueDateOrError,
|
||||
operationDateOrError,
|
||||
]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
const validatedData: IInvoiceProps = {
|
||||
status: InvoiceStatus.createDraft(),
|
||||
invoiceNumber: invoiceNumerOrError.data,
|
||||
invoiceSeries: invoiceSeriesOrError.data,
|
||||
issueDate: issueDateOrError.data,
|
||||
operationDate: operationDateOrError.data,
|
||||
invoiceCurrency: dto.currency_code,
|
||||
};
|
||||
|
||||
/*if (errors.length > 0) {
|
||||
const message = errors.map((err) => err.message).toString();
|
||||
return Result.fail(new Error(message));
|
||||
}*/
|
||||
return Result.ok(validatedData);
|
||||
|
||||
/*let invoice_status = InvoiceStatus.create(dto.status).object;
|
||||
if (invoice_status.isEmpty()) {
|
||||
invoice_status = InvoiceStatus.createDraft();
|
||||
}
|
||||
|
||||
let invoice_series = InvoiceSeries.create(dto.invoice_series).object;
|
||||
if (invoice_series.isEmpty()) {
|
||||
invoice_series = InvoiceSeries.create(dto.invoice_series).object;
|
||||
}
|
||||
|
||||
let issue_date = InvoiceDate.create(dto.issue_date).object;
|
||||
if (issue_date.isEmpty()) {
|
||||
issue_date = InvoiceDate.createCurrentDate().object;
|
||||
}
|
||||
|
||||
let operation_date = InvoiceDate.create(dto.operation_date).object;
|
||||
if (operation_date.isEmpty()) {
|
||||
operation_date = InvoiceDate.createCurrentDate().object;
|
||||
}
|
||||
|
||||
let invoiceCurrency = Currency.createFromCode(dto.currency).object;
|
||||
|
||||
if (invoiceCurrency.isEmpty()) {
|
||||
invoiceCurrency = Currency.createDefaultCode().object;
|
||||
}
|
||||
|
||||
let invoiceLanguage = Language.createFromCode(dto.language_code).object;
|
||||
|
||||
if (invoiceLanguage.isEmpty()) {
|
||||
invoiceLanguage = Language.createDefaultCode().object;
|
||||
}
|
||||
|
||||
const items = new Collection<InvoiceItem>(
|
||||
dto.items?.map(
|
||||
(item) =>
|
||||
InvoiceSimpleItem.create({
|
||||
description: Description.create(item.description).object,
|
||||
quantity: Quantity.create(item.quantity).object,
|
||||
unitPrice: UnitPrice.create({
|
||||
amount: item.unit_price.amount,
|
||||
currencyCode: item.unit_price.currency,
|
||||
precision: item.unit_price.precision,
|
||||
}).object,
|
||||
}).object
|
||||
)
|
||||
);
|
||||
|
||||
if (!invoice_status.isDraft()) {
|
||||
throw Error("Error al crear una factura que no es borrador");
|
||||
}
|
||||
|
||||
return DraftInvoice.create(
|
||||
{
|
||||
invoiceSeries: invoice_series,
|
||||
issueDate: issue_date,
|
||||
operationDate: operation_date,
|
||||
invoiceCurrency,
|
||||
language: invoiceLanguage,
|
||||
invoiceNumber: InvoiceNumber.create(undefined).object,
|
||||
//notes: Note.create(invoiceDTO.notes).object,
|
||||
|
||||
//senderId: UniqueID.create(null).object,
|
||||
recipient,
|
||||
|
||||
items,
|
||||
},
|
||||
invoiceId
|
||||
);*/
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { UniqueID } from "core/common/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
import { ITransactionManager } from "core/common/infrastructure/database";
|
||||
import { logger } from "core/common/infrastructure/logger";
|
||||
import { IInvoiceService, Invoice } from "../domain";
|
||||
|
||||
export class DeleteInvoiceUseCase {
|
||||
constructor(
|
||||
private readonly invoiceService: IInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(invoiceID: UniqueID): Promise<Result<Invoice, Error>> {
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
return await this.invoiceService.deleteInvoiceById(invoiceID, transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { UniqueID } from "core/common/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
import { ITransactionManager } from "core/common/infrastructure/database";
|
||||
import { logger } from "core/common/infrastructure/logger";
|
||||
import { IInvoiceService, Invoice } from "../domain";
|
||||
|
||||
export class GetInvoiceUseCase {
|
||||
constructor(
|
||||
private readonly invoiceService: IInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(invoiceID: UniqueID): Promise<Result<Invoice, Error>> {
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
return await this.invoiceService.findInvoiceById(invoiceID, transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
export * from "./create-invoice.use-case";
|
||||
export * from "./delete-invoice.use-case";
|
||||
export * from "./get-invoice.use-case";
|
||||
export * from "./list-invoices.use-case";
|
||||
export * from "./update-invoice.use-case";
|
||||
@ -1,22 +0,0 @@
|
||||
import { Collection, Result } from "core/common/helpers";
|
||||
import { ITransactionManager } from "core/common/infrastructure/database";
|
||||
import { logger } from "core/common/infrastructure/logger";
|
||||
import { IInvoiceService, Invoice } from "../domain";
|
||||
|
||||
export class ListInvoicesUseCase {
|
||||
constructor(
|
||||
private readonly invoiceService: IInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(): Promise<Result<Collection<Invoice>, Error>> {
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
return await this.invoiceService.findInvoices(transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./participantAddressFinder";
|
||||
export * from "./participantFinder";
|
||||
@ -1,70 +0,0 @@
|
||||
import {
|
||||
ApplicationServiceError,
|
||||
IApplicationServiceError,
|
||||
} from "@/contexts/common/application/services/ApplicationServiceError";
|
||||
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
||||
import { Result, UniqueID } from "@shared/contexts";
|
||||
import { NullOr } from "@shared/utilities";
|
||||
import {
|
||||
IInvoiceParticipantAddress,
|
||||
IInvoiceParticipantAddressRepository,
|
||||
} from "../../domain";
|
||||
|
||||
export const participantAddressFinder = async (
|
||||
addressId: UniqueID,
|
||||
adapter: IAdapter,
|
||||
repository: RepositoryBuilder<IInvoiceParticipantAddressRepository>,
|
||||
) => {
|
||||
if (addressId.isNull()) {
|
||||
return Result.fail<IApplicationServiceError>(
|
||||
ApplicationServiceError.create(
|
||||
ApplicationServiceError.INVALID_REQUEST_PARAM,
|
||||
`Participant address ID required`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const transaction = adapter.startTransaction();
|
||||
let address: NullOr<IInvoiceParticipantAddress> = null;
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
address = await repository({ transaction: t }).getById(addressId);
|
||||
});
|
||||
|
||||
if (address === null) {
|
||||
return Result.fail<IApplicationServiceError>(
|
||||
ApplicationServiceError.create(
|
||||
ApplicationServiceError.NOT_FOUND_ERROR,
|
||||
"",
|
||||
{
|
||||
id: addressId.toString(),
|
||||
entity: "participant address",
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok<IInvoiceParticipantAddress>(address);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as Error;
|
||||
|
||||
if (repository().isRepositoryError(_error)) {
|
||||
return Result.fail<IApplicationServiceError>(
|
||||
ApplicationServiceError.create(
|
||||
ApplicationServiceError.REPOSITORY_ERROR,
|
||||
_error.message,
|
||||
_error,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Result.fail<IApplicationServiceError>(
|
||||
ApplicationServiceError.create(
|
||||
ApplicationServiceError.UNEXCEPTED_ERROR,
|
||||
_error.message,
|
||||
_error,
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
import { IInvoiceParticipantRepository } from "../../domain";
|
||||
import { InvoiceCustomer } from "../../domain/entities/invoice-customer/invoice-customer";
|
||||
|
||||
export const participantFinder = async (
|
||||
participantId: UniqueID,
|
||||
adapter: IAdapter,
|
||||
repository: RepositoryBuilder<IInvoiceParticipantRepository>
|
||||
): Promise<InvoiceCustomer | undefined> => {
|
||||
if (!participantId || (participantId && participantId.isNull())) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const participant = await adapter
|
||||
.startTransaction()
|
||||
.complete((t) => repository({ transaction: t }).getById(participantId));
|
||||
|
||||
return Promise.resolve(participant ? participant : undefined);
|
||||
};
|
||||
@ -1,398 +0,0 @@
|
||||
import { UniqueID } from "core/common/domain";
|
||||
|
||||
import { Result } from "core/common/helpers";
|
||||
import { ITransactionManager } from "core/common/infrastructure/database";
|
||||
import { logger } from "core/common/infrastructure/logger";
|
||||
import { IUpdateInvoiceRequestDTO } from "../presentation/dto";
|
||||
|
||||
export class CreateInvoiceUseCase {
|
||||
constructor(
|
||||
private readonly invoiceService: IInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(
|
||||
invoiceID: UniqueID,
|
||||
dto: Partial<IUpdateInvoiceRequestDTO>
|
||||
): Promise<Result<Invoice, Error>> {
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
const validOrErrors = this.validateInvoiceData(dto);
|
||||
if (validOrErrors.isFailure) {
|
||||
return Result.fail(validOrErrors.error);
|
||||
}
|
||||
|
||||
const data = validOrErrors.data;
|
||||
|
||||
// Update invoice with dto
|
||||
return await this.invoiceService.updateInvoiceById(invoiceID, data, transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private validateInvoiceData(
|
||||
dto: Partial<IUpdateInvoiceRequestDTO>
|
||||
): Result<Partial<IInvoiceProps>, Error> {
|
||||
const errors: Error[] = [];
|
||||
const validatedData: Partial<IInvoiceProps> = {};
|
||||
|
||||
// Create invoice
|
||||
let invoice_status = InvoiceStatus.create(invoiceDTO.status).object;
|
||||
if (invoice_status.isEmpty()) {
|
||||
invoice_status = InvoiceStatus.createDraft();
|
||||
}
|
||||
|
||||
let invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||
if (invoice_series.isEmpty()) {
|
||||
invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||
}
|
||||
|
||||
let issue_date = InvoiceDate.create(invoiceDTO.issue_date).object;
|
||||
if (issue_date.isEmpty()) {
|
||||
issue_date = InvoiceDate.createCurrentDate().object;
|
||||
}
|
||||
|
||||
let operation_date = InvoiceDate.create(invoiceDTO.operation_date).object;
|
||||
if (operation_date.isEmpty()) {
|
||||
operation_date = InvoiceDate.createCurrentDate().object;
|
||||
}
|
||||
|
||||
let invoiceCurrency = Currency.createFromCode(invoiceDTO.currency).object;
|
||||
|
||||
if (invoiceCurrency.isEmpty()) {
|
||||
invoiceCurrency = Currency.createDefaultCode().object;
|
||||
}
|
||||
|
||||
let invoiceLanguage = Language.createFromCode(invoiceDTO.language_code).object;
|
||||
|
||||
if (invoiceLanguage.isEmpty()) {
|
||||
invoiceLanguage = Language.createDefaultCode().object;
|
||||
}
|
||||
|
||||
const items = new Collection<InvoiceItem>(
|
||||
invoiceDTO.items?.map(
|
||||
(item) =>
|
||||
InvoiceSimpleItem.create({
|
||||
description: Description.create(item.description).object,
|
||||
quantity: Quantity.create(item.quantity).object,
|
||||
unitPrice: UnitPrice.create({
|
||||
amount: item.unit_price.amount,
|
||||
currencyCode: item.unit_price.currency,
|
||||
precision: item.unit_price.precision,
|
||||
}).object,
|
||||
}).object
|
||||
)
|
||||
);
|
||||
|
||||
if (!invoice_status.isDraft()) {
|
||||
throw Error("Error al crear una factura que no es borrador");
|
||||
}
|
||||
|
||||
return DraftInvoice.create(
|
||||
{
|
||||
invoiceSeries: invoice_series,
|
||||
issueDate: issue_date,
|
||||
operationDate: operation_date,
|
||||
invoiceCurrency,
|
||||
language: invoiceLanguage,
|
||||
invoiceNumber: InvoiceNumber.create(undefined).object,
|
||||
//notes: Note.create(invoiceDTO.notes).object,
|
||||
|
||||
//senderId: UniqueID.create(null).object,
|
||||
recipient,
|
||||
|
||||
items,
|
||||
},
|
||||
invoiceId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type UpdateInvoiceResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<Invoice, never>; // Success!
|
||||
|
||||
export class UpdateInvoiceUseCase2
|
||||
implements
|
||||
IUseCase<{ id: UniqueID; data: IUpdateInvoice_DTO }, Promise<UpdateInvoiceResponseOrError>>
|
||||
{
|
||||
private _context: IInvoicingContext;
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(context: IInvoicingContext) {
|
||||
this._context = context;
|
||||
this._adapter = context.adapter;
|
||||
this._repositoryManager = context.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepository<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
private handleValidationFailure(
|
||||
validationError: Error,
|
||||
message?: string
|
||||
): Result<never, IUseCaseError> {
|
||||
return Result.fail<IUseCaseError>(
|
||||
UseCaseError.create(
|
||||
UseCaseError.INVALID_INPUT_DATA,
|
||||
message ? message : validationError.message,
|
||||
validationError
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async execute(request: {
|
||||
id: UniqueID;
|
||||
data: IUpdateInvoice_DTO;
|
||||
}): Promise<UpdateInvoiceResponseOrError> {
|
||||
const { id, data: invoiceDTO } = request;
|
||||
|
||||
// Validaciones
|
||||
const invoiceDTOOrError = ensureUpdateInvoice_DTOIsValid(invoiceDTO);
|
||||
if (invoiceDTOOrError.isFailure) {
|
||||
return this.handleValidationFailure(invoiceDTOOrError.error);
|
||||
}
|
||||
|
||||
const transaction = this._adapter.startTransaction();
|
||||
|
||||
const invoiceRepoBuilder = this.getRepository<IInvoiceRepository>("Invoice");
|
||||
|
||||
let invoice: Invoice | null = null;
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
invoice = await invoiceRepoBuilder({ transaction: t }).getById(id);
|
||||
});
|
||||
|
||||
if (invoice === null) {
|
||||
return Result.fail<IUseCaseError>(
|
||||
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, `Invoice not found`, {
|
||||
id: request.id.toString(),
|
||||
entity: "invoice",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok<Invoice>(invoice);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as Error;
|
||||
if (invoiceRepoBuilder().isRepositoryError(_error)) {
|
||||
return this.handleRepositoryError(error as BaseError, invoiceRepoBuilder());
|
||||
} else {
|
||||
return this.handleUnexceptedError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Recipient validations
|
||||
/*const recipientIdOrError = ensureParticipantIdIsValid(
|
||||
invoiceDTO?.recipient?.id,
|
||||
);
|
||||
if (recipientIdOrError.isFailure) {
|
||||
return this.handleValidationFailure(
|
||||
recipientIdOrError.error,
|
||||
"Recipient ID not valid",
|
||||
);
|
||||
}
|
||||
const recipientId = recipientIdOrError.object;
|
||||
|
||||
const recipientBillingIdOrError = ensureParticipantAddressIdIsValid(
|
||||
invoiceDTO?.recipient?.billing_address_id,
|
||||
);
|
||||
if (recipientBillingIdOrError.isFailure) {
|
||||
return this.handleValidationFailure(
|
||||
recipientBillingIdOrError.error,
|
||||
"Recipient billing address ID not valid",
|
||||
);
|
||||
}
|
||||
const recipientBillingId = recipientBillingIdOrError.object;
|
||||
|
||||
const recipientShippingIdOrError = ensureParticipantAddressIdIsValid(
|
||||
invoiceDTO?.recipient?.shipping_address_id,
|
||||
);
|
||||
if (recipientShippingIdOrError.isFailure) {
|
||||
return this.handleValidationFailure(
|
||||
recipientShippingIdOrError.error,
|
||||
"Recipient shipping address ID not valid",
|
||||
);
|
||||
}
|
||||
const recipientShippingId = recipientShippingIdOrError.object;
|
||||
|
||||
const recipientContact = await this.findContact(
|
||||
recipientId,
|
||||
recipientBillingId,
|
||||
recipientShippingId,
|
||||
);
|
||||
|
||||
if (!recipientContact) {
|
||||
return this.handleValidationFailure(
|
||||
new Error(`Recipient with ID ${recipientId.toString()} does not exist`),
|
||||
);
|
||||
}
|
||||
|
||||
// Crear invoice
|
||||
const invoiceOrError = await this.tryUpdateInvoiceInstance(
|
||||
invoiceDTO,
|
||||
invoiceIdOrError.object,
|
||||
//senderId,
|
||||
//senderBillingId,
|
||||
//senderShippingId,
|
||||
recipientContact,
|
||||
);
|
||||
|
||||
if (invoiceOrError.isFailure) {
|
||||
const { error: domainError } = invoiceOrError;
|
||||
let errorCode = "";
|
||||
let message = "";
|
||||
|
||||
switch (domainError.code) {
|
||||
case Invoice.ERROR_CUSTOMER_WITHOUT_NAME:
|
||||
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||
message =
|
||||
"El cliente debe ser una compañía o tener nombre y apellidos.";
|
||||
break;
|
||||
|
||||
default:
|
||||
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
||||
message = "";
|
||||
break;
|
||||
}
|
||||
|
||||
return Result.fail<IUseCaseError>(
|
||||
UseCaseError.create(errorCode, message, domainError),
|
||||
);
|
||||
}
|
||||
|
||||
return this.saveInvoice(invoiceOrError.object);
|
||||
*/
|
||||
}
|
||||
|
||||
private async tryUpdateInvoiceInstance(invoiceDTO, invoiceId, recipient) {
|
||||
// Create invoice
|
||||
let invoice_status = InvoiceStatus.create(invoiceDTO.status).object;
|
||||
if (invoice_status.isEmpty()) {
|
||||
invoice_status = InvoiceStatus.createDraft();
|
||||
}
|
||||
|
||||
let invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||
if (invoice_series.isEmpty()) {
|
||||
invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||
}
|
||||
|
||||
let issue_date = InvoiceDate.create(invoiceDTO.issue_date).object;
|
||||
if (issue_date.isEmpty()) {
|
||||
issue_date = InvoiceDate.createCurrentDate().object;
|
||||
}
|
||||
|
||||
let operation_date = InvoiceDate.create(invoiceDTO.operation_date).object;
|
||||
if (operation_date.isEmpty()) {
|
||||
operation_date = InvoiceDate.createCurrentDate().object;
|
||||
}
|
||||
|
||||
let invoiceCurrency = Currency.createFromCode(invoiceDTO.currency).object;
|
||||
|
||||
if (invoiceCurrency.isEmpty()) {
|
||||
invoiceCurrency = Currency.createDefaultCode().object;
|
||||
}
|
||||
|
||||
let invoiceLanguage = Language.createFromCode(invoiceDTO.language_code).object;
|
||||
|
||||
if (invoiceLanguage.isEmpty()) {
|
||||
invoiceLanguage = Language.createDefaultCode().object;
|
||||
}
|
||||
|
||||
const items = new Collection<InvoiceItem>(
|
||||
invoiceDTO.items?.map(
|
||||
(item) =>
|
||||
InvoiceSimpleItem.create({
|
||||
description: Description.create(item.description).object,
|
||||
quantity: Quantity.create(item.quantity).object,
|
||||
unitPrice: UnitPrice.create({
|
||||
amount: item.unit_price.amount,
|
||||
currencyCode: item.unit_price.currency,
|
||||
precision: item.unit_price.precision,
|
||||
}).object,
|
||||
}).object
|
||||
)
|
||||
);
|
||||
|
||||
if (!invoice_status.isDraft()) {
|
||||
throw Error("Error al crear una factura que no es borrador");
|
||||
}
|
||||
|
||||
return DraftInvoice.create(
|
||||
{
|
||||
invoiceSeries: invoice_series,
|
||||
issueDate: issue_date,
|
||||
operationDate: operation_date,
|
||||
invoiceCurrency,
|
||||
language: invoiceLanguage,
|
||||
invoiceNumber: InvoiceNumber.create(undefined).object,
|
||||
//notes: Note.create(invoiceDTO.notes).object,
|
||||
|
||||
//senderId: UniqueID.create(null).object,
|
||||
recipient,
|
||||
|
||||
items,
|
||||
},
|
||||
invoiceId
|
||||
);
|
||||
}
|
||||
|
||||
private async findContact(
|
||||
contactId: UniqueID,
|
||||
billingAddressId: UniqueID,
|
||||
shippingAddressId: UniqueID
|
||||
) {
|
||||
const contactRepoBuilder = this.getRepository<IContactRepository>("Contact");
|
||||
|
||||
const contact = await contactRepoBuilder().getById2(
|
||||
contactId,
|
||||
billingAddressId,
|
||||
shippingAddressId
|
||||
);
|
||||
|
||||
return contact;
|
||||
}
|
||||
|
||||
private async saveInvoice(invoice: DraftInvoice) {
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const invoiceRepoBuilder = this.getRepository<IInvoiceRepository>("Invoice");
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
const invoiceRepo = invoiceRepoBuilder({ transaction: t });
|
||||
await invoiceRepo.save(invoice);
|
||||
});
|
||||
|
||||
return Result.ok<DraftInvoice>(invoice);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as Error;
|
||||
if (invoiceRepoBuilder().isRepositoryError(_error)) {
|
||||
return this.handleRepositoryError(error as BaseError, invoiceRepoBuilder());
|
||||
} else {
|
||||
return this.handleUnexceptedError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleUnexceptedError(error): Result<never, IUseCaseError> {
|
||||
return Result.fail<IUseCaseError>(
|
||||
UseCaseError.create(UseCaseError.UNEXCEPTED_ERROR, error.message, error)
|
||||
);
|
||||
}
|
||||
|
||||
private handleRepositoryError(
|
||||
error: BaseError,
|
||||
repository: IInvoiceRepository
|
||||
): Result<never, IUseCaseError> {
|
||||
const { message, details } = repository.handleRepositoryError(error);
|
||||
return Result.fail<IUseCaseError>(
|
||||
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, message, details)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./invoice";
|
||||
@ -1,203 +0,0 @@
|
||||
import { AggregateRoot, MoneyValue, UniqueID, UtcDate } from "core/common/domain";
|
||||
import { Collection, Result } from "core/common/helpers";
|
||||
import { InvoiceCustomer, InvoiceItem, InvoiceItems } from "../entities";
|
||||
import { InvoiceNumber, InvoiceSerie, InvoiceStatus } from "../value-objects";
|
||||
|
||||
export interface IInvoiceProps {
|
||||
invoiceNumber: InvoiceNumber;
|
||||
invoiceSeries: InvoiceSerie;
|
||||
|
||||
status: InvoiceStatus;
|
||||
|
||||
issueDate: UtcDate;
|
||||
operationDate: UtcDate;
|
||||
|
||||
//dueDate: UtcDate; // ? --> depende de la forma de pago
|
||||
|
||||
//tax: Tax; // ? --> detalles?
|
||||
invoiceCurrency: string;
|
||||
|
||||
//language: Language;
|
||||
|
||||
//purchareOrderNumber: string;
|
||||
//notes: Note;
|
||||
|
||||
//senderId: UniqueID;
|
||||
|
||||
//paymentInstructions: Note;
|
||||
//paymentTerms: string;
|
||||
|
||||
customer?: InvoiceCustomer;
|
||||
items?: InvoiceItems;
|
||||
}
|
||||
|
||||
export interface IInvoice {
|
||||
id: UniqueID;
|
||||
invoiceNumber: InvoiceNumber;
|
||||
invoiceSeries: InvoiceSerie;
|
||||
|
||||
status: InvoiceStatus;
|
||||
|
||||
issueDate: UtcDate;
|
||||
operationDate: UtcDate;
|
||||
|
||||
//senderId: UniqueID;
|
||||
|
||||
customer?: InvoiceCustomer;
|
||||
|
||||
//dueDate
|
||||
|
||||
//tax: Tax;
|
||||
//language: Language;
|
||||
invoiceCurrency: string;
|
||||
|
||||
//purchareOrderNumber: string;
|
||||
//notes: Note;
|
||||
|
||||
//paymentInstructions: Note;
|
||||
//paymentTerms: string;
|
||||
|
||||
items: InvoiceItems;
|
||||
|
||||
calculateSubtotal: () => MoneyValue;
|
||||
calculateTaxTotal: () => MoneyValue;
|
||||
calculateTotal: () => MoneyValue;
|
||||
}
|
||||
|
||||
export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
|
||||
private _items!: Collection<InvoiceItem>;
|
||||
//protected _status: InvoiceStatus;
|
||||
|
||||
protected constructor(props: IInvoiceProps, id?: UniqueID) {
|
||||
super(props, id);
|
||||
|
||||
this._items = props.items || InvoiceItems.create();
|
||||
}
|
||||
|
||||
static create(props: IInvoiceProps, id?: UniqueID): Result<Invoice, Error> {
|
||||
const invoice = new Invoice(props, id);
|
||||
|
||||
// Reglas de negocio / validaciones
|
||||
// ...
|
||||
// ...
|
||||
|
||||
// 🔹 Disparar evento de dominio "InvoiceAuthenticatedEvent"
|
||||
//const { invoice } = props;
|
||||
//user.addDomainEvent(new InvoiceAuthenticatedEvent(id, invoice.toString()));
|
||||
|
||||
return Result.ok(invoice);
|
||||
}
|
||||
|
||||
get invoiceNumber() {
|
||||
return this.props.invoiceNumber;
|
||||
}
|
||||
|
||||
get invoiceSeries() {
|
||||
return this.props.invoiceSeries;
|
||||
}
|
||||
|
||||
get issueDate() {
|
||||
return this.props.issueDate;
|
||||
}
|
||||
|
||||
/*get senderId(): UniqueID {
|
||||
return this.props.senderId;
|
||||
}*/
|
||||
|
||||
get customer(): InvoiceCustomer | undefined {
|
||||
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() {
|
||||
return this.props.purchareOrderNumber;
|
||||
}
|
||||
|
||||
get paymentInstructions() {
|
||||
return this.props.paymentInstructions;
|
||||
}
|
||||
|
||||
get paymentTerms() {
|
||||
return this.props.paymentTerms;
|
||||
}
|
||||
|
||||
get billTo() {
|
||||
return this.props.billTo;
|
||||
}
|
||||
|
||||
get shipTo() {
|
||||
return this.props.shipTo;
|
||||
}*/
|
||||
|
||||
get invoiceCurrency() {
|
||||
return this.props.invoiceCurrency;
|
||||
}
|
||||
|
||||
/*get notes() {
|
||||
return this.props.notes;
|
||||
}*/
|
||||
|
||||
// Method to get the complete list of line items
|
||||
/*get lineItems(): InvoiceLineItem[] {
|
||||
return this._lineItems;
|
||||
}
|
||||
|
||||
addLineItem(lineItem: InvoiceLineItem, position?: number): void {
|
||||
if (position === undefined) {
|
||||
this._lineItems.push(lineItem);
|
||||
} else {
|
||||
this._lineItems.splice(position, 0, lineItem);
|
||||
}
|
||||
}*/
|
||||
|
||||
calculateSubtotal(): MoneyValue {
|
||||
const invoiceSubtotal = MoneyValue.create({
|
||||
amount: 0,
|
||||
currency_code: this.props.invoiceCurrency,
|
||||
scale: 2,
|
||||
}).data;
|
||||
|
||||
return this._items.getAll().reduce((subtotal, item) => {
|
||||
return subtotal.add(item.calculateTotal());
|
||||
}, invoiceSubtotal);
|
||||
}
|
||||
|
||||
// Method to calculate the total tax in the invoice
|
||||
calculateTaxTotal(): MoneyValue {
|
||||
const taxTotal = MoneyValue.create({
|
||||
amount: 0,
|
||||
currency_code: this.props.invoiceCurrency,
|
||||
scale: 2,
|
||||
}).data;
|
||||
|
||||
return taxTotal;
|
||||
}
|
||||
|
||||
// Method to calculate the total invoice amount, including taxes
|
||||
calculateTotal(): MoneyValue {
|
||||
return this.calculateSubtotal().add(this.calculateTaxTotal());
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./invoice-customer";
|
||||
export * from "./invoice-items";
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./invoice-address";
|
||||
export * from "./invoice-customer";
|
||||
@ -1,78 +0,0 @@
|
||||
import { EmailAddress, Name, PostalAddress, ValueObject } from "core/common/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
import { PhoneNumber } from "libphonenumber-js";
|
||||
import { InvoiceAddressType } from "../../value-objects";
|
||||
|
||||
export interface IInvoiceAddressProps {
|
||||
type: InvoiceAddressType;
|
||||
title: Name;
|
||||
address: PostalAddress;
|
||||
email: EmailAddress;
|
||||
phone: PhoneNumber;
|
||||
}
|
||||
|
||||
export interface IInvoiceAddress {
|
||||
type: InvoiceAddressType;
|
||||
title: Name;
|
||||
address: PostalAddress;
|
||||
email: EmailAddress;
|
||||
phone: PhoneNumber;
|
||||
}
|
||||
|
||||
export class InvoiceAddress extends ValueObject<IInvoiceAddressProps> implements IInvoiceAddress {
|
||||
public static create(props: IInvoiceAddressProps) {
|
||||
return Result.ok(new this(props));
|
||||
}
|
||||
|
||||
public static createShippingAddress(props: IInvoiceAddressProps) {
|
||||
return Result.ok(
|
||||
new this({
|
||||
...props,
|
||||
type: InvoiceAddressType.create("shipping").data,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public static createBillingAddress(props: IInvoiceAddressProps) {
|
||||
return Result.ok(
|
||||
new this({
|
||||
...props,
|
||||
type: InvoiceAddressType.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(): InvoiceAddressType {
|
||||
return this.props.type;
|
||||
}
|
||||
|
||||
getValue(): IInvoiceAddressProps {
|
||||
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 "core/common/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
import { InvoiceAddress } from "./invoice-address";
|
||||
|
||||
export interface IInvoiceCustomerProps {
|
||||
tin: TINNumber;
|
||||
companyName: Name;
|
||||
firstName: Name;
|
||||
lastName: Name;
|
||||
|
||||
billingAddress?: InvoiceAddress;
|
||||
shippingAddress?: InvoiceAddress;
|
||||
}
|
||||
|
||||
export interface IInvoiceCustomer {
|
||||
id: UniqueID;
|
||||
tin: TINNumber;
|
||||
companyName: Name;
|
||||
firstName: Name;
|
||||
lastName: Name;
|
||||
|
||||
billingAddress?: InvoiceAddress;
|
||||
shippingAddress?: InvoiceAddress;
|
||||
}
|
||||
|
||||
export class InvoiceCustomer
|
||||
extends DomainEntity<IInvoiceCustomerProps>
|
||||
implements IInvoiceCustomer
|
||||
{
|
||||
public static create(
|
||||
props: IInvoiceCustomerProps,
|
||||
id?: UniqueID
|
||||
): Result<InvoiceCustomer, Error> {
|
||||
const participant = new InvoiceCustomer(props, id);
|
||||
return Result.ok<InvoiceCustomer>(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;
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./invoice-item";
|
||||
export * from "./invoice-items";
|
||||
@ -1,83 +0,0 @@
|
||||
import { MoneyValue, Percentage, Quantity } from "core/common/domain";
|
||||
import { InvoiceItemDescription } from "../../value-objects";
|
||||
import { InvoiceItem } from "./invoice-item";
|
||||
|
||||
describe("InvoiceItem", () => {
|
||||
it("debería calcular correctamente el subtotal (unitPrice * quantity)", () => {
|
||||
const props = {
|
||||
description: InvoiceItemDescription.create("Producto A"),
|
||||
quantity: Quantity.create({ amount: 200, scale: 2 }),
|
||||
unitPrice: MoneyValue.create(50),
|
||||
discount: Percentage.create(0),
|
||||
};
|
||||
|
||||
const result = InvoiceItem.create(props);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const invoiceItem = result.unwrap();
|
||||
expect(invoiceItem.subtotalPrice.value).toBe(100); // 50 * 2
|
||||
});
|
||||
|
||||
it("debería calcular correctamente el total con descuento", () => {
|
||||
const props = {
|
||||
description: new InvoiceItemDescription("Producto B"),
|
||||
quantity: new Quantity(3),
|
||||
unitPrice: new MoneyValue(30),
|
||||
discount: new Percentage(10), // 10%
|
||||
};
|
||||
|
||||
const result = InvoiceItem.create(props);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const invoiceItem = result.unwrap();
|
||||
expect(invoiceItem.totalPrice.value).toBe(81); // (30 * 3) - 10% de (30 * 3)
|
||||
});
|
||||
|
||||
it("debería devolver los valores correctos de las propiedades", () => {
|
||||
const props = {
|
||||
description: new InvoiceItemDescription("Producto C"),
|
||||
quantity: new Quantity(1),
|
||||
unitPrice: new MoneyValue(100),
|
||||
discount: new Percentage(5),
|
||||
};
|
||||
|
||||
const result = InvoiceItem.create(props);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const invoiceItem = result.unwrap();
|
||||
expect(invoiceItem.description.value).toBe("Producto C");
|
||||
expect(invoiceItem.quantity.value).toBe(1);
|
||||
expect(invoiceItem.unitPrice.value).toBe(100);
|
||||
expect(invoiceItem.discount.value).toBe(5);
|
||||
});
|
||||
|
||||
it("debería manejar correctamente un descuento del 0%", () => {
|
||||
const props = {
|
||||
description: new InvoiceItemDescription("Producto D"),
|
||||
quantity: new Quantity(4),
|
||||
unitPrice: new MoneyValue(25),
|
||||
discount: new Percentage(0),
|
||||
};
|
||||
|
||||
const result = InvoiceItem.create(props);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const invoiceItem = result.unwrap();
|
||||
expect(invoiceItem.totalPrice.value).toBe(100); // 25 * 4
|
||||
});
|
||||
|
||||
it("debería manejar correctamente un descuento del 100%", () => {
|
||||
const props = {
|
||||
description: new InvoiceItemDescription("Producto E"),
|
||||
quantity: new Quantity(2),
|
||||
unitPrice: new MoneyValue(50),
|
||||
discount: new Percentage(100),
|
||||
};
|
||||
|
||||
const result = InvoiceItem.create(props);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const invoiceItem = result.unwrap();
|
||||
expect(invoiceItem.totalPrice.value).toBe(0); // (50 * 2) - 100% de (50 * 2)
|
||||
});
|
||||
});
|
||||
@ -1,94 +0,0 @@
|
||||
import { DomainEntity, MoneyValue, Percentage, Quantity, UniqueID } from "core/common/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
import { InvoiceItemDescription } from "../../value-objects";
|
||||
|
||||
export interface IInvoiceItemProps {
|
||||
description: InvoiceItemDescription;
|
||||
quantity: Quantity; // Cantidad de unidades
|
||||
unitPrice: MoneyValue; // Precio unitario en la moneda de la factura
|
||||
//subtotalPrice?: MoneyValue; // Precio unitario * Cantidad
|
||||
discount: Percentage; // % descuento
|
||||
//totalPrice?: MoneyValue;
|
||||
}
|
||||
|
||||
export interface IInvoiceItem {
|
||||
id: UniqueID;
|
||||
description: InvoiceItemDescription;
|
||||
quantity: Quantity;
|
||||
unitPrice: MoneyValue;
|
||||
subtotalPrice: MoneyValue;
|
||||
discount: Percentage;
|
||||
totalPrice: MoneyValue;
|
||||
}
|
||||
|
||||
export class InvoiceItem extends DomainEntity<IInvoiceItemProps> implements IInvoiceItem {
|
||||
private _subtotalPrice!: MoneyValue;
|
||||
private _totalPrice!: MoneyValue;
|
||||
|
||||
public static create(props: IInvoiceItemProps, id?: UniqueID): Result<InvoiceItem, Error> {
|
||||
const item = new InvoiceItem(props, id);
|
||||
|
||||
// Reglas de negocio / validaciones
|
||||
// ...
|
||||
// ...
|
||||
|
||||
// 🔹 Disparar evento de dominio "InvoiceItemCreatedEvent"
|
||||
//const { invoice } = props;
|
||||
//user.addDomainEvent(new InvoiceAuthenticatedEvent(id, invoice.toString()));
|
||||
|
||||
return Result.ok(item);
|
||||
}
|
||||
|
||||
get description(): InvoiceItemDescription {
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
get quantity(): Quantity {
|
||||
return this.props.quantity;
|
||||
}
|
||||
|
||||
get unitPrice(): MoneyValue {
|
||||
return this.props.unitPrice;
|
||||
}
|
||||
|
||||
get subtotalPrice(): MoneyValue {
|
||||
if (!this._subtotalPrice) {
|
||||
this._subtotalPrice = this.calculateSubtotal();
|
||||
}
|
||||
return this._subtotalPrice;
|
||||
}
|
||||
|
||||
get discount(): Percentage {
|
||||
return this.props.discount;
|
||||
}
|
||||
|
||||
get totalPrice(): MoneyValue {
|
||||
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(): MoneyValue {
|
||||
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad
|
||||
}
|
||||
|
||||
calculateTotal(): MoneyValue {
|
||||
return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
import { Collection } from "core/common/helpers";
|
||||
import { InvoiceItem } from "./invoice-item";
|
||||
|
||||
export class InvoiceItems extends Collection<InvoiceItem> {
|
||||
public static create(items?: InvoiceItem[]): InvoiceItems {
|
||||
return new InvoiceItems(items);
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
export * from "./aggregates";
|
||||
export * from "./entities";
|
||||
export * from "./repositories";
|
||||
export * from "./services";
|
||||
export * from "./value-objects";
|
||||
@ -1 +0,0 @@
|
||||
export * from "./invoice-repository.interface";
|
||||
@ -1,12 +0,0 @@
|
||||
import { UniqueID } from "core/common/domain";
|
||||
import { Collection, Result } from "core/common/helpers";
|
||||
import { Invoice } from "../aggregates";
|
||||
|
||||
export interface IInvoiceRepository {
|
||||
findAll(transaction?: any): Promise<Result<Collection<Invoice>, Error>>;
|
||||
getById(id: UniqueID, transaction?: any): Promise<Result<Invoice, Error>>;
|
||||
deleteByIdInCompany(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
|
||||
create(invoice: Invoice, transaction?: any): Promise<void>;
|
||||
update(invoice: Invoice, transaction?: any): Promise<void>;
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./invoice-service.interface";
|
||||
export * from "./invoice.service";
|
||||
@ -1,22 +0,0 @@
|
||||
import { UniqueID } from "core/common/domain";
|
||||
import { Collection, Result } from "core/common/helpers";
|
||||
import { IInvoiceProps, Invoice } from "../aggregates";
|
||||
|
||||
export interface IInvoiceService {
|
||||
findInvoices(transaction?: any): Promise<Result<Collection<Invoice>, Error>>;
|
||||
findInvoiceById(invoiceId: UniqueID, transaction?: any): Promise<Result<Invoice>>;
|
||||
|
||||
updateInvoiceById(
|
||||
invoiceId: UniqueID,
|
||||
data: Partial<IInvoiceProps>,
|
||||
transaction?: any
|
||||
): Promise<Result<Invoice, Error>>;
|
||||
|
||||
createInvoice(
|
||||
invoiceId: UniqueID,
|
||||
data: IInvoiceProps,
|
||||
transaction?: any
|
||||
): Promise<Result<Invoice, Error>>;
|
||||
|
||||
deleteInvoiceById(invoiceId: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
import { UniqueID } from "core/common/domain";
|
||||
import { Collection, Result } from "core/common/helpers";
|
||||
import { Transaction } from "sequelize";
|
||||
import { IInvoiceProps, Invoice } from "../aggregates";
|
||||
import { IInvoiceRepository } from "../repositories";
|
||||
import { IInvoiceService } from "./invoice-service.interface";
|
||||
|
||||
export class InvoiceService implements IInvoiceService {
|
||||
constructor(private readonly repo: IInvoiceRepository) {}
|
||||
deleteInvoiceById(invoiceId: UniqueID, transaction?: any): Promise<Result<boolean, Error>> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
async findInvoices(transaction?: Transaction): Promise<Result<Collection<Invoice>, Error>> {
|
||||
const invoicesOrError = await this.repo.findAll(transaction);
|
||||
if (invoicesOrError.isFailure) {
|
||||
return Result.fail(invoicesOrError.error);
|
||||
}
|
||||
|
||||
// Solo devolver usuarios activos
|
||||
//const allInvoices = invoicesOrError.data.filter((invoice) => invoice.isActive);
|
||||
//return Result.ok(new Collection(allInvoices));
|
||||
|
||||
return invoicesOrError;
|
||||
}
|
||||
|
||||
async findInvoiceById(invoiceId: UniqueID, transaction?: Transaction): Promise<Result<Invoice>> {
|
||||
return await this.repo.getById(invoiceId, transaction);
|
||||
}
|
||||
|
||||
async updateInvoiceById(
|
||||
invoiceId: UniqueID,
|
||||
data: Partial<IInvoiceProps>,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Invoice, Error>> {
|
||||
// Verificar si la factura existe
|
||||
const invoiceOrError = await this.repo.getById(invoiceId, transaction);
|
||||
if (invoiceOrError.isFailure) {
|
||||
return Result.fail(new Error("Invoice not found"));
|
||||
}
|
||||
|
||||
const updatedInvoiceOrError = Invoice.update(invoiceOrError.data, data);
|
||||
if (updatedInvoiceOrError.isFailure) {
|
||||
return Result.fail(
|
||||
new Error(`Error updating invoice: ${updatedInvoiceOrError.error.message}`)
|
||||
);
|
||||
}
|
||||
|
||||
const updateInvoice = updatedInvoiceOrError.data;
|
||||
|
||||
await this.repo.update(updateInvoice, transaction);
|
||||
return Result.ok(updateInvoice);
|
||||
}
|
||||
|
||||
async createInvoice(
|
||||
invoiceId: UniqueID,
|
||||
data: IInvoiceProps,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Invoice, Error>> {
|
||||
// Verificar si la factura existe
|
||||
const invoiceOrError = await this.repo.getById(invoiceId, transaction);
|
||||
if (invoiceOrError.isSuccess) {
|
||||
return Result.fail(new Error("Invoice exists"));
|
||||
}
|
||||
|
||||
const newInvoiceOrError = Invoice.create(data, invoiceId);
|
||||
if (newInvoiceOrError.isFailure) {
|
||||
return Result.fail(new Error(`Error creating invoice: ${newInvoiceOrError.error.message}`));
|
||||
}
|
||||
|
||||
const newInvoice = newInvoiceOrError.data;
|
||||
|
||||
await this.repo.create(newInvoice, transaction);
|
||||
return Result.ok(newInvoice);
|
||||
}
|
||||
|
||||
async deleteInvoiceByIdInCompnay(
|
||||
companyId: UniqueID,
|
||||
invoiceId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<boolean, Error>> {
|
||||
return this.repo.deleteByIdInCompany(companyId, invoiceId, transaction);
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
export * from "./invoice-address-type";
|
||||
export * from "./invoice-item-description";
|
||||
export * from "./invoice-number";
|
||||
export * from "./invoice-serie";
|
||||
export * from "./invoice-status";
|
||||
@ -1,38 +0,0 @@
|
||||
import { ValueObject } from "core/common/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
|
||||
interface IInvoiceAddressTypeProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export enum INVOICE_ADDRESS_TYPE {
|
||||
SHIPPING = "shipping",
|
||||
BILLING = "billing",
|
||||
}
|
||||
|
||||
export class InvoiceAddressType extends ValueObject<IInvoiceAddressTypeProps> {
|
||||
private static readonly ALLOWED_TYPES = ["shipping", "billing"];
|
||||
|
||||
static create(value: string): Result<InvoiceAddressType, Error> {
|
||||
if (!this.ALLOWED_TYPES.includes(value)) {
|
||||
return Result.fail(
|
||||
new Error(
|
||||
`Invalid address type: ${value}. Allowed types are: ${this.ALLOWED_TYPES.join(", ")}`
|
||||
)
|
||||
);
|
||||
}
|
||||
return Result.ok(new InvoiceAddressType({ value }));
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
toPrimitive(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
import { ValueObject } from "core/common/domain";
|
||||
import { Maybe, Result } from "core/common/helpers";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
interface IInvoiceItemDescriptionProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class InvoiceItemDescription extends ValueObject<IInvoiceItemDescriptionProps> {
|
||||
private static readonly MAX_LENGTH = 255;
|
||||
|
||||
protected static validate(value: string) {
|
||||
const schema = z
|
||||
.string()
|
||||
.trim()
|
||||
.max(InvoiceItemDescription.MAX_LENGTH, {
|
||||
message: `Description must be at most ${InvoiceItemDescription.MAX_LENGTH} characters long`,
|
||||
});
|
||||
return schema.safeParse(value);
|
||||
}
|
||||
|
||||
static create(value: string) {
|
||||
const valueIsValid = InvoiceItemDescription.validate(value);
|
||||
|
||||
if (!valueIsValid.success) {
|
||||
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||
}
|
||||
return Result.ok(new InvoiceItemDescription({ value }));
|
||||
}
|
||||
|
||||
static createNullable(value?: string): Result<Maybe<InvoiceItemDescription>, Error> {
|
||||
if (!value || value.trim() === "") {
|
||||
return Result.ok(Maybe.none<InvoiceItemDescription>());
|
||||
}
|
||||
|
||||
return InvoiceItemDescription.create(value).map((value) => Maybe.some(value));
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
import { ValueObject } from "core/common/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
interface IInvoiceNumberProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class InvoiceNumber extends ValueObject<IInvoiceNumberProps> {
|
||||
private static readonly MAX_LENGTH = 255;
|
||||
|
||||
protected static validate(value: string) {
|
||||
const schema = z
|
||||
.string()
|
||||
.trim()
|
||||
.max(InvoiceNumber.MAX_LENGTH, {
|
||||
message: `Name must be at most ${InvoiceNumber.MAX_LENGTH} characters long`,
|
||||
});
|
||||
return schema.safeParse(value);
|
||||
}
|
||||
|
||||
static create(value: string) {
|
||||
const valueIsValid = InvoiceNumber.validate(value);
|
||||
|
||||
if (!valueIsValid.success) {
|
||||
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||
}
|
||||
return Result.ok(new InvoiceNumber({ value }));
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
import { ValueObject } from "core/common/domain";
|
||||
import { Maybe, Result } from "core/common/helpers";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
interface IInvoiceSerieProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class InvoiceSerie extends ValueObject<IInvoiceSerieProps> {
|
||||
private static readonly MAX_LENGTH = 255;
|
||||
|
||||
protected static validate(value: string) {
|
||||
const schema = z
|
||||
.string()
|
||||
.trim()
|
||||
.max(InvoiceSerie.MAX_LENGTH, {
|
||||
message: `Name must be at most ${InvoiceSerie.MAX_LENGTH} characters long`,
|
||||
});
|
||||
return schema.safeParse(value);
|
||||
}
|
||||
|
||||
static create(value: string) {
|
||||
const valueIsValid = InvoiceSerie.validate(value);
|
||||
|
||||
if (!valueIsValid.success) {
|
||||
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||
}
|
||||
return Result.ok(new InvoiceSerie({ value }));
|
||||
}
|
||||
|
||||
static createNullable(value?: string): Result<Maybe<InvoiceSerie>, Error> {
|
||||
if (!value || value.trim() === "") {
|
||||
return Result.ok(Maybe.none<InvoiceSerie>());
|
||||
}
|
||||
|
||||
return InvoiceSerie.create(value).map((value) => Maybe.some(value));
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
import { ValueObject } from "core/common/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
|
||||
interface IInvoiceStatusProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export enum INVOICE_STATUS {
|
||||
DRAFT = "draft",
|
||||
EMITTED = "emitted",
|
||||
SENT = "sent",
|
||||
REJECTED = "rejected",
|
||||
}
|
||||
export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
|
||||
private static readonly ALLOWED_STATUSES = ["draft", "emitted", "sent", "rejected"];
|
||||
|
||||
private static readonly TRANSITIONS: Record<string, string[]> = {
|
||||
draft: [INVOICE_STATUS.EMITTED],
|
||||
emitted: [INVOICE_STATUS.SENT, INVOICE_STATUS.REJECTED, INVOICE_STATUS.DRAFT],
|
||||
sent: [INVOICE_STATUS.REJECTED],
|
||||
rejected: [],
|
||||
};
|
||||
|
||||
static create(value: string): Result<InvoiceStatus, Error> {
|
||||
if (!this.ALLOWED_STATUSES.includes(value)) {
|
||||
return Result.fail(new Error(`Estado de la factura no válido: ${value}`));
|
||||
}
|
||||
|
||||
return Result.ok(
|
||||
value === "rejected"
|
||||
? InvoiceStatus.createRejected()
|
||||
: value === "sent"
|
||||
? InvoiceStatus.createSent()
|
||||
: value === "emitted"
|
||||
? InvoiceStatus.createSent()
|
||||
: InvoiceStatus.createDraft()
|
||||
);
|
||||
}
|
||||
|
||||
public static createDraft(): InvoiceStatus {
|
||||
return new InvoiceStatus({ value: INVOICE_STATUS.DRAFT });
|
||||
}
|
||||
|
||||
public static createEmitted(): InvoiceStatus {
|
||||
return new InvoiceStatus({ value: INVOICE_STATUS.EMITTED });
|
||||
}
|
||||
|
||||
public static createSent(): InvoiceStatus {
|
||||
return new InvoiceStatus({ value: INVOICE_STATUS.SENT });
|
||||
}
|
||||
|
||||
public static createRejected(): InvoiceStatus {
|
||||
return new InvoiceStatus({ value: INVOICE_STATUS.REJECTED });
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
canTransitionTo(nextStatus: string): boolean {
|
||||
return InvoiceStatus.TRANSITIONS[this.props.value].includes(nextStatus);
|
||||
}
|
||||
|
||||
transitionTo(nextStatus: string): Result<InvoiceStatus, Error> {
|
||||
if (!this.canTransitionTo(nextStatus)) {
|
||||
return Result.fail(
|
||||
new Error(`Transición no permitida de ${this.props.value} a ${nextStatus}`)
|
||||
);
|
||||
}
|
||||
return InvoiceStatus.create(nextStatus);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
/* import { getService } from "@apps/server/src/core/service-registry"; */
|
||||
import { IPackageServer } from "@packages/package";
|
||||
import { invoicesRouter } from "./intrastructure";
|
||||
import initInvoiceModel from "./intrastructure/sequelize/invoice.model";
|
||||
|
||||
export const InvoicesPackage: IPackageServer = {
|
||||
metadata: {
|
||||
name: "invoices",
|
||||
version: "1.0.0",
|
||||
dependencies: ["contacts"],
|
||||
},
|
||||
init(app) {
|
||||
// const contacts = getService<ContactsService>("contacts");
|
||||
invoicesRouter(app);
|
||||
},
|
||||
registerDependencies() {
|
||||
return {
|
||||
models: [(sequelize) => initInvoiceModel(sequelize)],
|
||||
services: {
|
||||
getInvoice: () => {},
|
||||
/*...*/
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -1,77 +0,0 @@
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
SequelizeRepository,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
import { Transaction } from "sequelize";
|
||||
import { Contact, IContactRepository } from "../domain/Contact";
|
||||
import { IContactMapper } from "./mappers/contact.mapper";
|
||||
|
||||
export class ContactRepository
|
||||
extends SequelizeRepository<Contact>
|
||||
implements IContactRepository
|
||||
{
|
||||
protected mapper: IContactMapper;
|
||||
|
||||
public constructor(props: {
|
||||
mapper: IContactMapper;
|
||||
adapter: ISequelizeAdapter;
|
||||
transaction: Transaction;
|
||||
}) {
|
||||
const { adapter, mapper, transaction } = props;
|
||||
super({ adapter, transaction });
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public async getById2(
|
||||
id: UniqueID,
|
||||
billingAddressId: UniqueID,
|
||||
shippingAddressId: UniqueID,
|
||||
) {
|
||||
const Contact_Model = this.adapter.getModel("Contact_Model");
|
||||
const ContactAddress_Model = this.adapter.getModel("ContactAddress_Model");
|
||||
|
||||
const rawContact: any = await Contact_Model.findOne({
|
||||
where: { id: id.toString() },
|
||||
include: [
|
||||
{
|
||||
model: ContactAddress_Model,
|
||||
as: "billingAddress",
|
||||
where: {
|
||||
id: billingAddressId.toString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
model: ContactAddress_Model,
|
||||
as: "shippingAddress",
|
||||
where: {
|
||||
id: shippingAddressId.toString(),
|
||||
},
|
||||
},
|
||||
],
|
||||
transaction: this.transaction,
|
||||
});
|
||||
|
||||
if (!rawContact === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapper.mapToDomain(rawContact);
|
||||
}
|
||||
|
||||
public async getById(id: UniqueID): Promise<Contact | null> {
|
||||
const rawContact: any = await this._getById("Contact_Model", id, {
|
||||
include: [{ all: true }],
|
||||
});
|
||||
|
||||
if (!rawContact === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapper.mapToDomain(rawContact);
|
||||
}
|
||||
|
||||
public async exists(id: UniqueID): Promise<boolean> {
|
||||
return this._exists("Customer", "id", id.toString());
|
||||
}
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
import { SequelizeRepository } from "@/contexts/common/infrastructure/sequelize/SequelizeRepository";
|
||||
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||
import { Transaction } from "sequelize";
|
||||
import { IInvoiceRepository, Invoice } from "../domain";
|
||||
import { IInvoiceMapper } from "./mappers";
|
||||
|
||||
export type QueryParams = {
|
||||
pagination: Record<string, unknown>;
|
||||
filters: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export class InvoiceRepository extends SequelizeRepository<Invoice> implements IInvoiceRepository {
|
||||
protected mapper: IInvoiceMapper;
|
||||
|
||||
public constructor(props: {
|
||||
mapper: IInvoiceMapper;
|
||||
adapter: ISequelizeAdapter;
|
||||
transaction: Transaction;
|
||||
}) {
|
||||
const { adapter, mapper, transaction } = props;
|
||||
super({ adapter, transaction });
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public async getById(id: UniqueID): Promise<Invoice | null> {
|
||||
const rawContact: any = await this._getById("Invoice_Model", id, {
|
||||
include: [
|
||||
{ association: "items" },
|
||||
{
|
||||
association: "participants",
|
||||
include: [{ association: "shippingAddress" }, { association: "billingAddress" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!rawContact === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapper.mapToDomain(rawContact);
|
||||
}
|
||||
|
||||
public async findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<Invoice>> {
|
||||
const { rows, count } = await this._findAll("Invoice_Model", queryCriteria, {
|
||||
include: [
|
||||
{
|
||||
association: "participants",
|
||||
separate: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return this.mapper.mapArrayAndCountToDomain(rows, count);
|
||||
}
|
||||
|
||||
public async save(invoice: Invoice): Promise<void> {
|
||||
const { items, participants, ...invoiceData } = this.mapper.mapToPersistence(invoice);
|
||||
|
||||
await this.adapter
|
||||
.getModel("Invoice_Model")
|
||||
.create(invoiceData, { transaction: this.transaction });
|
||||
|
||||
await this.adapter
|
||||
.getModel("InvoiceItem_Model")
|
||||
.bulkCreate(items, { transaction: this.transaction });
|
||||
|
||||
await this.adapter
|
||||
.getModel("InvoiceParticipant_Model")
|
||||
.bulkCreate(participants, { transaction: this.transaction });
|
||||
|
||||
await this.adapter
|
||||
.getModel("InvoiceParticipantAddress_Model")
|
||||
.bulkCreate([participants[0].billingAddress, participants[0].shippingAddress], {
|
||||
transaction: this.transaction,
|
||||
});
|
||||
}
|
||||
|
||||
public removeById(id: UniqueID): Promise<void> {
|
||||
return this._removeById("Invoice_Model", id);
|
||||
}
|
||||
|
||||
public async exists(id: UniqueID): Promise<boolean> {
|
||||
return this._exists("Invoice_Model", "id", id.toString());
|
||||
}
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
import { ISequelizeAdapter, SequelizeRepository } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Transaction } from "sequelize";
|
||||
import { InvoiceCustomer } from "../domain";
|
||||
import { IInvoiceParticipantMapper } from "./mappers";
|
||||
|
||||
export class InvoiceParticipantRepository extends SequelizeRepository<InvoiceCustomer> {
|
||||
protected mapper: IInvoiceParticipantMapper;
|
||||
|
||||
public constructor(props: {
|
||||
mapper: IInvoiceParticipantMapper;
|
||||
adapter: ISequelizeAdapter;
|
||||
transaction: Transaction;
|
||||
}) {
|
||||
const { adapter, mapper, transaction } = props;
|
||||
super({ adapter, transaction });
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
/*public async getParticipantById(
|
||||
id: UniqueID,
|
||||
): Promise<InvoiceParticipant | null> {
|
||||
const rawParticipant: any = await this._getById(
|
||||
"InvoiceParticipant_Model",
|
||||
id,
|
||||
{
|
||||
include: [{ all: true }],
|
||||
raw: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (!rawParticipant === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapper.mapToDomain(rawParticipant);
|
||||
}
|
||||
|
||||
public async getContactById(id: UniqueID): Promise<any | null> {
|
||||
const rawContact: any = await this._getById("Customer", id, {
|
||||
include: [{ all: true }],
|
||||
raw: true,
|
||||
});
|
||||
|
||||
if (!rawContact === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapper.mapToDomain(rawContact);
|
||||
}
|
||||
|
||||
public async exists(id: UniqueID): Promise<boolean> {
|
||||
return this._exists("Customer", "id", id.toString());
|
||||
}*/
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
SequelizeRepository,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
import { Transaction } from "sequelize";
|
||||
import { InvoiceParticipantAddress } from "../domain";
|
||||
import { IInvoiceParticipantAddressMapper } from "./mappers";
|
||||
|
||||
export class InvoiceParticipantAddressRepository extends SequelizeRepository<InvoiceParticipantAddress> {
|
||||
protected mapper: IInvoiceParticipantAddressMapper;
|
||||
|
||||
public constructor(props: {
|
||||
mapper: IInvoiceParticipantAddressMapper;
|
||||
adapter: ISequelizeAdapter;
|
||||
transaction: Transaction;
|
||||
}) {
|
||||
const { adapter, mapper, transaction } = props;
|
||||
super({ adapter, transaction });
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public async getById(
|
||||
id: UniqueID,
|
||||
): Promise<InvoiceParticipantAddress | null> {
|
||||
const rawParticipant: any = await this._getById(
|
||||
"InvoiceParticipantAddress_Model",
|
||||
id,
|
||||
{
|
||||
include: [{ all: true }],
|
||||
},
|
||||
);
|
||||
|
||||
if (!rawParticipant === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapper.mapToDomain(rawParticipant);
|
||||
}
|
||||
|
||||
public async exists(id: UniqueID): Promise<boolean> {
|
||||
return this._exists("CustomerAddress", "id", id.toString());
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
import {
|
||||
IRepositoryManager,
|
||||
RepositoryManager,
|
||||
} from "@/contexts/common/domain";
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
createSequelizeAdapter,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { InvoicingServices, TInvoicingServices } from "../application";
|
||||
|
||||
export interface IInvoicingContext {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
services: TInvoicingServices;
|
||||
}
|
||||
|
||||
class InvoicingContext {
|
||||
private static instance: InvoicingContext | null = null;
|
||||
public static getInstance(): InvoicingContext {
|
||||
if (!InvoicingContext.instance) {
|
||||
InvoicingContext.instance = new InvoicingContext();
|
||||
}
|
||||
|
||||
return InvoicingContext.instance;
|
||||
}
|
||||
|
||||
private context: IInvoicingContext;
|
||||
|
||||
private constructor() {
|
||||
this.context = {
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
services: InvoicingServices,
|
||||
};
|
||||
}
|
||||
|
||||
public getContext(): IInvoicingContext {
|
||||
return this.context;
|
||||
}
|
||||
}
|
||||
|
||||
const sharedInvoicingContext = InvoicingContext.getInstance().getContext();
|
||||
export { sharedInvoicingContext };
|
||||
@ -1 +0,0 @@
|
||||
export * from "./invoices.routes";
|
||||
@ -1,65 +0,0 @@
|
||||
import { validateAndParseBody } from "@repo/shared";
|
||||
import { Express } from "express";
|
||||
import {
|
||||
buildCreateInvoiceController,
|
||||
buildGetInvoiceController,
|
||||
buildListInvoicesController,
|
||||
ICreateInvoiceRequestSchema,
|
||||
} from "../../presentation";
|
||||
|
||||
import { NextFunction, Request, Response, Router } from "express";
|
||||
|
||||
export const invoicesRouter = (app: Express) => {
|
||||
const routes: Router = Router({ mergeParams: true });
|
||||
|
||||
routes.get(
|
||||
"/",
|
||||
//checkTabContext,
|
||||
//checkUser,
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
buildListInvoicesController().execute(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
routes.get(
|
||||
"/:invoiceId",
|
||||
//checkTabContext,
|
||||
//checkUser,
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
buildGetInvoiceController().execute(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
routes.post(
|
||||
"/",
|
||||
validateAndParseBody(ICreateInvoiceRequestSchema, { sanitize: false }),
|
||||
//checkTabContext,
|
||||
//checkUser,
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
buildCreateInvoiceController().execute(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
routes.put(
|
||||
"/:invoiceId",
|
||||
validateAndParseBody(IUpdateInvoiceRequestSchema),
|
||||
checkTabContext,
|
||||
//checkUser,
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
buildUpdateInvoiceController().execute(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
routes.delete(
|
||||
"/:invoiceId",
|
||||
validateAndParseBody(IDeleteInvoiceRequestSchema),
|
||||
checkTabContext,
|
||||
//checkUser,
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
buildDeleteInvoiceController().execute(req, res, next);
|
||||
}
|
||||
);*/
|
||||
|
||||
app.use("/invoices", routes);
|
||||
};
|
||||
@ -1,3 +0,0 @@
|
||||
export * from "./express";
|
||||
export * from "./mappers";
|
||||
export * from "./sequelize";
|
||||
@ -1,63 +0,0 @@
|
||||
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
|
||||
import { Name, TINNumber, UniqueID } from "@shared/contexts";
|
||||
|
||||
import { Contact, IContactProps } from "../../domain";
|
||||
import { IInvoicingContext } from "../InvoicingContext";
|
||||
import { Contact_Model, TCreationContact_Model } from "../sequelize/contact.mo.del";
|
||||
import { IContactAddressMapper, createContactAddressMapper } from "./contactAddress.mapper";
|
||||
|
||||
export interface IContactMapper
|
||||
extends ISequelizeMapper<Contact_Model, TCreationContact_Model, Contact> {}
|
||||
|
||||
class ContactMapper
|
||||
extends SequelizeMapper<Contact_Model, TCreationContact_Model, Contact>
|
||||
implements IContactMapper
|
||||
{
|
||||
public constructor(props: { addressMapper: IContactAddressMapper; context: IInvoicingContext }) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected toDomainMappingImpl(source: Contact_Model, params: any): Contact {
|
||||
if (!source.billingAddress) {
|
||||
this.handleRequiredFieldError(
|
||||
"billingAddress",
|
||||
new Error("Missing participant's billing address")
|
||||
);
|
||||
}
|
||||
|
||||
if (!source.shippingAddress) {
|
||||
this.handleRequiredFieldError(
|
||||
"shippingAddress",
|
||||
new Error("Missing participant's shipping address")
|
||||
);
|
||||
}
|
||||
|
||||
const billingAddress = this.props.addressMapper.mapToDomain(source.billingAddress!, params);
|
||||
|
||||
const shippingAddress = this.props.addressMapper.mapToDomain(source.shippingAddress!, params);
|
||||
|
||||
const props: IContactProps = {
|
||||
tin: this.mapsValue(source, "tin", TINNumber.create),
|
||||
firstName: this.mapsValue(source, "first_name", Name.create),
|
||||
lastName: this.mapsValue(source, "last_name", Name.create),
|
||||
companyName: this.mapsValue(source, "company_name", Name.create),
|
||||
billingAddress,
|
||||
shippingAddress,
|
||||
};
|
||||
|
||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||
const contactOrError = Contact.create(props, id);
|
||||
|
||||
if (contactOrError.isFailure) {
|
||||
throw contactOrError.error;
|
||||
}
|
||||
|
||||
return contactOrError.object;
|
||||
}
|
||||
}
|
||||
|
||||
export const createContactMapper = (context: IInvoicingContext): IContactMapper =>
|
||||
new ContactMapper({
|
||||
addressMapper: createContactAddressMapper(context),
|
||||
context,
|
||||
});
|
||||
@ -1,65 +0,0 @@
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
SequelizeMapper,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import {
|
||||
City,
|
||||
Country,
|
||||
Email,
|
||||
Note,
|
||||
Phone,
|
||||
PostalCode,
|
||||
Province,
|
||||
Street,
|
||||
UniqueID,
|
||||
} from "@shared/contexts";
|
||||
import { ContactAddress, IContactAddressProps } from "../../domain";
|
||||
import { IInvoicingContext } from "../InvoicingContext";
|
||||
import {
|
||||
ContactAddress_Model,
|
||||
TCreationContactAddress_Attributes,
|
||||
} from "../sequelize";
|
||||
|
||||
export interface IContactAddressMapper
|
||||
extends ISequelizeMapper<
|
||||
ContactAddress_Model,
|
||||
TCreationContactAddress_Attributes,
|
||||
ContactAddress
|
||||
> {}
|
||||
|
||||
export const createContactAddressMapper = (
|
||||
context: IInvoicingContext
|
||||
): IContactAddressMapper => new ContactAddressMapper({ context });
|
||||
|
||||
class ContactAddressMapper
|
||||
extends SequelizeMapper<
|
||||
ContactAddress_Model,
|
||||
TCreationContactAddress_Attributes,
|
||||
ContactAddress
|
||||
>
|
||||
implements IContactAddressMapper
|
||||
{
|
||||
protected toDomainMappingImpl(source: ContactAddress_Model, params: any) {
|
||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||
|
||||
const props: IContactAddressProps = {
|
||||
type: source.type,
|
||||
street: this.mapsValue(source, "street", Street.create),
|
||||
city: this.mapsValue(source, "city", City.create),
|
||||
province: this.mapsValue(source, "province", Province.create),
|
||||
postalCode: this.mapsValue(source, "postal_code", PostalCode.create),
|
||||
country: this.mapsValue(source, "country", Country.create),
|
||||
email: this.mapsValue(source, "email", Email.create),
|
||||
phone: this.mapsValue(source, "phone", Phone.create),
|
||||
notes: this.mapsValue(source, "notes", Note.create),
|
||||
};
|
||||
|
||||
const addressOrError = ContactAddress.create(props, id);
|
||||
|
||||
if (addressOrError.isFailure) {
|
||||
throw addressOrError.error;
|
||||
}
|
||||
|
||||
return addressOrError.object;
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./invoice.mapper";
|
||||
@ -1,104 +0,0 @@
|
||||
import { Invoice, InvoiceItem, InvoiceItemDescription } from "@contexts/invoices/domain/";
|
||||
import { MoneyValue, Percentage, Quantity, UniqueID } from "core/common/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "core/common/infrastructure/sequelize/sequelize-mapper";
|
||||
import { InferCreationAttributes } from "sequelize";
|
||||
import { InvoiceItemCreationAttributes, InvoiceItemModel, InvoiceModel } from "../sequelize";
|
||||
|
||||
export interface IInvoiceItemMapper
|
||||
extends ISequelizeMapper<InvoiceItemModel, InvoiceItemCreationAttributes, InvoiceItem> {}
|
||||
|
||||
export class InvoiceItemMapper
|
||||
extends SequelizeMapper<InvoiceItemModel, InvoiceItemCreationAttributes, InvoiceItem>
|
||||
implements IInvoiceItemMapper
|
||||
{
|
||||
public mapToDomain(
|
||||
source: InvoiceItemModel,
|
||||
params?: MapperParamsType
|
||||
): Result<InvoiceItem, Error> {
|
||||
const { sourceParent } = params as { sourceParent: InvoiceModel };
|
||||
|
||||
const idOrError = UniqueID.create(source.item_id);
|
||||
|
||||
const descriptionOrError = InvoiceItemDescription.create(source.description);
|
||||
|
||||
const quantityOrError = Quantity.create({
|
||||
amount: source.quantity_amount,
|
||||
scale: source.quantity_scale,
|
||||
});
|
||||
|
||||
const unitPriceOrError = MoneyValue.create({
|
||||
amount: source.unit_price_amount,
|
||||
scale: source.unit_price_scale,
|
||||
currency_code: sourceParent.invoice_currency,
|
||||
});
|
||||
|
||||
const discountOrError = Percentage.create({
|
||||
amount: source.discount_amount,
|
||||
scale: source.discount_scale,
|
||||
});
|
||||
|
||||
const result = Result.combine([
|
||||
idOrError,
|
||||
descriptionOrError,
|
||||
quantityOrError,
|
||||
unitPriceOrError,
|
||||
discountOrError,
|
||||
]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return InvoiceItem.create(
|
||||
{
|
||||
description: descriptionOrError.data,
|
||||
quantity: quantityOrError.data,
|
||||
unitPrice: unitPriceOrError.data,
|
||||
discount: discountOrError.data,
|
||||
},
|
||||
idOrError.data
|
||||
//sourceParent
|
||||
);
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
source: InvoiceItem,
|
||||
params?: MapperParamsType
|
||||
): InferCreationAttributes<InvoiceItemModel, {}> {
|
||||
const { index, sourceParent } = params as {
|
||||
index: number;
|
||||
sourceParent: Invoice;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,101 +0,0 @@
|
||||
import { Invoice, InvoiceNumber, InvoiceSerie, InvoiceStatus } from "@contexts/invoices/domain/";
|
||||
import { UniqueID, UtcDate } from "core/common/domain";
|
||||
import { Result } from "core/common/helpers";
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "core/common/infrastructure/sequelize/sequelize-mapper";
|
||||
import { InvoiceCreationAttributes, InvoiceModel } from "../sequelize";
|
||||
import { InvoiceItemMapper } from "./invoice-item.mapper"; // Importar el mapper de items
|
||||
|
||||
export interface IInvoiceMapper
|
||||
extends ISequelizeMapper<InvoiceModel, InvoiceCreationAttributes, Invoice> {}
|
||||
|
||||
export class InvoiceMapper
|
||||
extends SequelizeMapper<InvoiceModel, InvoiceCreationAttributes, Invoice>
|
||||
implements IInvoiceMapper
|
||||
{
|
||||
private invoiceItemMapper: InvoiceItemMapper;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.invoiceItemMapper = new InvoiceItemMapper(); // Instanciar el mapper de items
|
||||
}
|
||||
|
||||
public mapToDomain(source: InvoiceModel, params?: MapperParamsType): Result<Invoice, Error> {
|
||||
const idOrError = UniqueID.create(source.id);
|
||||
const statusOrError = InvoiceStatus.create(source.invoice_status);
|
||||
const invoiceSeriesOrError = InvoiceSerie.create(source.invoice_series);
|
||||
const invoiceNumberOrError = InvoiceNumber.create(source.invoice_number);
|
||||
const issueDateOrError = UtcDate.create(source.issue_date);
|
||||
const operationDateOrError = UtcDate.create(source.operation_date);
|
||||
|
||||
const result = Result.combine([
|
||||
idOrError,
|
||||
statusOrError,
|
||||
invoiceSeriesOrError,
|
||||
invoiceNumberOrError,
|
||||
issueDateOrError,
|
||||
operationDateOrError,
|
||||
]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
// Mapear los items de la factura
|
||||
const itemsOrErrors = this.invoiceItemMapper.mapArrayToDomain(source.items, {
|
||||
sourceParent: source,
|
||||
...params,
|
||||
});
|
||||
|
||||
if (itemsOrErrors.isFailure) {
|
||||
return Result.fail(itemsOrErrors.error);
|
||||
}
|
||||
|
||||
const invoiceCurrency = source.invoice_currency || "EUR";
|
||||
|
||||
return Invoice.create(
|
||||
{
|
||||
status: statusOrError.data,
|
||||
invoiceSeries: invoiceSeriesOrError.data,
|
||||
invoiceNumber: invoiceNumberOrError.data,
|
||||
issueDate: issueDateOrError.data,
|
||||
operationDate: operationDateOrError.data,
|
||||
invoiceCurrency,
|
||||
items: itemsOrErrors.data,
|
||||
},
|
||||
idOrError.data
|
||||
);
|
||||
}
|
||||
|
||||
public mapToPersistence(source: Invoice, params?: MapperParamsType): InvoiceCreationAttributes {
|
||||
const subtotal = source.calculateSubtotal();
|
||||
const total = source.calculateTotal();
|
||||
|
||||
const items = this.invoiceItemMapper.mapCollectionToPersistence(source.items, params);
|
||||
|
||||
return {
|
||||
id: source.id.toString(),
|
||||
invoice_status: source.status.toPrimitive(),
|
||||
invoice_series: source.invoiceSeries.toPrimitive(),
|
||||
invoice_number: source.invoiceNumber.toPrimitive(),
|
||||
issue_date: source.issueDate.toPrimitive(),
|
||||
operation_date: source.operationDate.toPrimitive(),
|
||||
invoice_language: "es",
|
||||
invoice_currency: source.invoiceCurrency || "EUR",
|
||||
|
||||
subtotal_amount: subtotal.amount,
|
||||
subtotal_scale: subtotal.scale,
|
||||
|
||||
total_amount: total.amount,
|
||||
total_scale: total.scale,
|
||||
|
||||
items,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const invoiceMapper: InvoiceMapper = new InvoiceMapper();
|
||||
export { invoiceMapper };
|
||||
@ -1,119 +0,0 @@
|
||||
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
|
||||
import { Name, TINNumber, UniqueID } from "@shared/contexts";
|
||||
import {
|
||||
IInvoiceCustomerProps,
|
||||
Invoice,
|
||||
InvoiceCustomer,
|
||||
InvoiceParticipantBillingAddress,
|
||||
InvoiceParticipantShippingAddress,
|
||||
} from "../../domain";
|
||||
import { IInvoicingContext } from "../InvoicingContext";
|
||||
import { InvoiceParticipant_Model, TCreationInvoiceParticipant_Model } from "../sequelize";
|
||||
import {
|
||||
IInvoiceParticipantAddressMapper,
|
||||
createInvoiceParticipantAddressMapper,
|
||||
} from "./invoiceParticipantAddress.mapper";
|
||||
|
||||
export interface IInvoiceParticipantMapper
|
||||
extends ISequelizeMapper<
|
||||
InvoiceParticipant_Model,
|
||||
TCreationInvoiceParticipant_Model,
|
||||
InvoiceCustomer
|
||||
> {}
|
||||
|
||||
export const createInvoiceParticipantMapper = (
|
||||
context: IInvoicingContext
|
||||
): IInvoiceParticipantMapper =>
|
||||
new InvoiceParticipantMapper({
|
||||
context,
|
||||
addressMapper: createInvoiceParticipantAddressMapper(context),
|
||||
});
|
||||
|
||||
class InvoiceParticipantMapper
|
||||
extends SequelizeMapper<
|
||||
InvoiceParticipant_Model,
|
||||
TCreationInvoiceParticipant_Model,
|
||||
InvoiceCustomer
|
||||
>
|
||||
implements IInvoiceParticipantMapper
|
||||
{
|
||||
public constructor(props: {
|
||||
addressMapper: IInvoiceParticipantAddressMapper;
|
||||
context: IInvoicingContext;
|
||||
}) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected toDomainMappingImpl(source: InvoiceParticipant_Model, params: any) {
|
||||
/*if (!source.billingAddress) {
|
||||
this.handleRequiredFieldError(
|
||||
"billingAddress",
|
||||
new Error("Missing participant's billing address"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!source.shippingAddress) {
|
||||
this.handleRequiredFieldError(
|
||||
"shippingAddress",
|
||||
new Error("Missing participant's shipping address"),
|
||||
);
|
||||
}
|
||||
*/
|
||||
const billingAddress = source.billingAddress
|
||||
? ((this.props.addressMapper as IInvoiceParticipantAddressMapper).mapToDomain(
|
||||
source.billingAddress,
|
||||
params
|
||||
) as InvoiceParticipantBillingAddress)
|
||||
: undefined;
|
||||
|
||||
const shippingAddress = source.shippingAddress
|
||||
? ((this.props.addressMapper as IInvoiceParticipantAddressMapper).mapToDomain(
|
||||
source.shippingAddress,
|
||||
params
|
||||
) as InvoiceParticipantShippingAddress)
|
||||
: undefined;
|
||||
|
||||
const props: IInvoiceCustomerProps = {
|
||||
tin: this.mapsValue(source, "tin", TINNumber.create),
|
||||
firstName: this.mapsValue(source, "first_name", Name.create),
|
||||
lastName: this.mapsValue(source, "last_name", Name.create),
|
||||
companyName: this.mapsValue(source, "company_name", Name.create),
|
||||
billingAddress,
|
||||
shippingAddress,
|
||||
};
|
||||
|
||||
const id = this.mapsValue(source, "participant_id", UniqueID.create);
|
||||
const participantOrError = InvoiceCustomer.create(props, id);
|
||||
|
||||
if (participantOrError.isFailure) {
|
||||
throw participantOrError.error;
|
||||
}
|
||||
|
||||
return participantOrError.object;
|
||||
}
|
||||
|
||||
protected toPersistenceMappingImpl(
|
||||
source: InvoiceCustomer,
|
||||
params: { sourceParent: Invoice }
|
||||
): TCreationInvoiceParticipant_Model {
|
||||
const { sourceParent } = params;
|
||||
|
||||
return {
|
||||
invoice_id: sourceParent.id.toPrimitive(),
|
||||
|
||||
participant_id: source.id.toPrimitive(),
|
||||
tin: source.tin.toPrimitive(),
|
||||
first_name: source.firstName.toPrimitive(),
|
||||
last_name: source.lastName.toPrimitive(),
|
||||
company_name: source.companyName.toPrimitive(),
|
||||
|
||||
billingAddress: (
|
||||
this.props.addressMapper as IInvoiceParticipantAddressMapper
|
||||
).mapToPersistence(source.billingAddress!, { sourceParent: source }),
|
||||
|
||||
shippingAddress: (
|
||||
this.props.addressMapper as IInvoiceParticipantAddressMapper
|
||||
).mapToPersistence(source.shippingAddress!, { sourceParent: source }),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
|
||||
import {
|
||||
City,
|
||||
Country,
|
||||
Email,
|
||||
Note,
|
||||
Phone,
|
||||
PostalCode,
|
||||
Province,
|
||||
Street,
|
||||
UniqueID,
|
||||
} from "@shared/contexts";
|
||||
import {
|
||||
IInvoiceParticipantAddressProps,
|
||||
InvoiceCustomer,
|
||||
InvoiceParticipantAddress,
|
||||
} from "../../domain";
|
||||
import { IInvoicingContext } from "../InvoicingContext";
|
||||
import {
|
||||
InvoiceParticipantAddress_Model,
|
||||
TCreationInvoiceParticipantAddress_Model,
|
||||
} from "../sequelize";
|
||||
|
||||
export interface IInvoiceParticipantAddressMapper
|
||||
extends ISequelizeMapper<
|
||||
InvoiceParticipantAddress_Model,
|
||||
TCreationInvoiceParticipantAddress_Model,
|
||||
InvoiceParticipantAddress
|
||||
> {}
|
||||
|
||||
export const createInvoiceParticipantAddressMapper = (
|
||||
context: IInvoicingContext
|
||||
): IInvoiceParticipantAddressMapper => new InvoiceParticipantAddressMapper({ context });
|
||||
|
||||
class InvoiceParticipantAddressMapper
|
||||
extends SequelizeMapper<
|
||||
InvoiceParticipantAddress_Model,
|
||||
TCreationInvoiceParticipantAddress_Model,
|
||||
InvoiceParticipantAddress
|
||||
>
|
||||
implements IInvoiceParticipantAddressMapper
|
||||
{
|
||||
protected toDomainMappingImpl(source: InvoiceParticipantAddress_Model, params: any) {
|
||||
const id = this.mapsValue(source, "address_id", UniqueID.create);
|
||||
|
||||
const props: IInvoiceParticipantAddressProps = {
|
||||
type: source.type,
|
||||
street: this.mapsValue(source, "street", Street.create),
|
||||
city: this.mapsValue(source, "city", City.create),
|
||||
province: this.mapsValue(source, "province", Province.create),
|
||||
postalCode: this.mapsValue(source, "postal_code", PostalCode.create),
|
||||
country: this.mapsValue(source, "country", Country.create),
|
||||
email: this.mapsValue(source, "email", Email.create),
|
||||
phone: this.mapsValue(source, "phone", Phone.create),
|
||||
notes: this.mapsValue(source, "notes", Note.create),
|
||||
};
|
||||
|
||||
const addressOrError = InvoiceParticipantAddress.create(props, id);
|
||||
|
||||
if (addressOrError.isFailure) {
|
||||
throw addressOrError.error;
|
||||
}
|
||||
|
||||
return addressOrError.object;
|
||||
}
|
||||
|
||||
protected toPersistenceMappingImpl(
|
||||
source: InvoiceParticipantAddress,
|
||||
params: { sourceParent: InvoiceCustomer }
|
||||
) {
|
||||
const { sourceParent } = params;
|
||||
|
||||
return {
|
||||
address_id: source.id.toPrimitive(),
|
||||
participant_id: sourceParent.id.toPrimitive(),
|
||||
type: String(source.type),
|
||||
title: source.title,
|
||||
street: source.street.toPrimitive(),
|
||||
city: source.city.toPrimitive(),
|
||||
postal_code: source.postalCode.toPrimitive(),
|
||||
province: source.province.toPrimitive(),
|
||||
country: source.country.toPrimitive(),
|
||||
email: source.email.toPrimitive(),
|
||||
phone: source.phone.toPrimitive(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
import { ContactAddress_Model, TCreationContactAddress_Attributes } from "./contactAddress.mo.del";
|
||||
|
||||
export type TCreationContact_Model = InferCreationAttributes<
|
||||
Contact_Model,
|
||||
{ omit: "shippingAddress" | "billingAddress" }
|
||||
> & {
|
||||
billingAddress: TCreationContactAddress_Attributes;
|
||||
shippingAddress: TCreationContactAddress_Attributes;
|
||||
};
|
||||
|
||||
export class Contact_Model extends Model<
|
||||
InferAttributes<Contact_Model, { omit: "shippingAddress" | "billingAddress" }>,
|
||||
InferCreationAttributes<Contact_Model, { omit: "shippingAddress" | "billingAddress" }>
|
||||
> {
|
||||
// To avoid table creation
|
||||
static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
static associate(connection: Sequelize) {
|
||||
const { Contact_Model, ContactAddress_Model } = connection.models;
|
||||
|
||||
Contact_Model.hasOne(ContactAddress_Model, {
|
||||
as: "shippingAddress",
|
||||
foreignKey: "customer_id",
|
||||
onDelete: "CASCADE",
|
||||
});
|
||||
|
||||
Contact_Model.hasOne(ContactAddress_Model, {
|
||||
as: "billingAddress",
|
||||
foreignKey: "customer_id",
|
||||
onDelete: "CASCADE",
|
||||
});
|
||||
}
|
||||
|
||||
declare id: string;
|
||||
declare tin: CreationOptional<string>;
|
||||
declare company_name: CreationOptional<string>;
|
||||
declare first_name: CreationOptional<string>;
|
||||
declare last_name: CreationOptional<string>;
|
||||
|
||||
declare shippingAddress?: NonAttribute<ContactAddress_Model>;
|
||||
declare billingAddress?: NonAttribute<ContactAddress_Model>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
Contact_Model.init(
|
||||
{
|
||||
id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
tin: {
|
||||
type: new DataTypes.STRING(),
|
||||
},
|
||||
company_name: {
|
||||
type: new DataTypes.STRING(),
|
||||
},
|
||||
first_name: {
|
||||
type: new DataTypes.STRING(),
|
||||
},
|
||||
last_name: {
|
||||
type: new DataTypes.STRING(),
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "customers",
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
|
||||
return Contact_Model;
|
||||
};
|
||||
@ -1,75 +0,0 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
ForeignKey,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { Contact_Model } from "./contact.mo.del";
|
||||
|
||||
export type TCreationContactAddress_Attributes = InferCreationAttributes<
|
||||
ContactAddress_Model,
|
||||
{ omit: "customer" }
|
||||
>;
|
||||
|
||||
export class ContactAddress_Model extends Model<
|
||||
InferAttributes<ContactAddress_Model, { omit: "customer" }>,
|
||||
TCreationContactAddress_Attributes
|
||||
> {
|
||||
// To avoid table creation
|
||||
static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
static associate(connection: Sequelize) {
|
||||
const { Contact_Model, ContactAddress_Model } = connection.models;
|
||||
|
||||
ContactAddress_Model.belongsTo(Contact_Model, {
|
||||
as: "customer",
|
||||
foreignKey: "customer_id",
|
||||
});
|
||||
}
|
||||
|
||||
declare id: string;
|
||||
declare customer_id: ForeignKey<Contact_Model["id"]>;
|
||||
declare type: string;
|
||||
declare street: CreationOptional<string>;
|
||||
declare postal_code: CreationOptional<string>;
|
||||
declare city: CreationOptional<string>;
|
||||
declare province: CreationOptional<string>;
|
||||
declare country: CreationOptional<string>;
|
||||
declare phone: CreationOptional<string>;
|
||||
declare email: CreationOptional<string>;
|
||||
|
||||
declare customer?: NonAttribute<Contact_Model>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
ContactAddress_Model.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
},
|
||||
customer_id: new DataTypes.UUID(),
|
||||
type: DataTypes.STRING(),
|
||||
street: DataTypes.STRING(),
|
||||
postal_code: DataTypes.STRING(),
|
||||
city: DataTypes.STRING,
|
||||
province: DataTypes.STRING,
|
||||
country: DataTypes.STRING,
|
||||
email: DataTypes.STRING,
|
||||
phone: DataTypes.STRING,
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "customer_addresses",
|
||||
timestamps: false,
|
||||
}
|
||||
);
|
||||
|
||||
return ContactAddress_Model;
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
import { IInvoiceRepository } from "@contexts/invoices/domain";
|
||||
import { invoiceRepository } from "./invoice.repository";
|
||||
|
||||
export * from "./invoice-item.model";
|
||||
export * from "./invoice.model";
|
||||
|
||||
export * from "./invoice.repository";
|
||||
|
||||
export const createInvoiceRepository = (): IInvoiceRepository => {
|
||||
return invoiceRepository;
|
||||
};
|
||||
@ -1,165 +0,0 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
} from "sequelize";
|
||||
|
||||
export type InvoiceItemCreationAttributes = InferCreationAttributes<InvoiceItemModel, {}> & {};
|
||||
|
||||
export class InvoiceItemModel extends Model<
|
||||
InferAttributes<InvoiceItemModel>,
|
||||
InvoiceItemCreationAttributes
|
||||
> {
|
||||
static associate(connection: Sequelize) {
|
||||
/*const { Invoice_Model, InvoiceItem_Model } = connection.models;
|
||||
|
||||
InvoiceItem_Model.belongsTo(Invoice_Model, {
|
||||
as: "invoice",
|
||||
foreignKey: "invoice_id",
|
||||
onDelete: "CASCADE",
|
||||
});*/
|
||||
}
|
||||
|
||||
declare item_id: string;
|
||||
declare invoice_id: string;
|
||||
|
||||
declare parent_id: CreationOptional<string>;
|
||||
declare position: number;
|
||||
declare item_type: string;
|
||||
|
||||
declare description: CreationOptional<string>;
|
||||
|
||||
declare quantity_amount: CreationOptional<number>;
|
||||
declare quantity_scale: CreationOptional<number>;
|
||||
|
||||
declare unit_price_amount: CreationOptional<number>;
|
||||
declare unit_price_scale: CreationOptional<number>;
|
||||
|
||||
declare subtotal_amount: CreationOptional<number>;
|
||||
declare subtotal_scale: CreationOptional<number>;
|
||||
|
||||
declare discount_amount: CreationOptional<number>;
|
||||
declare discount_scale: CreationOptional<number>;
|
||||
|
||||
declare total_amount: CreationOptional<number>;
|
||||
declare total_scale: CreationOptional<number>;
|
||||
|
||||
//declare invoice?: NonAttribute<InvoiceModel>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
InvoiceItemModel.init(
|
||||
{
|
||||
item_id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
invoice_id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
parent_id: {
|
||||
type: new DataTypes.UUID(),
|
||||
allowNull: true, // Puede ser nulo para elementos de nivel superior
|
||||
},
|
||||
position: {
|
||||
type: new DataTypes.MEDIUMINT(),
|
||||
autoIncrement: false,
|
||||
allowNull: false,
|
||||
},
|
||||
item_type: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
defaultValue: "simple",
|
||||
},
|
||||
description: {
|
||||
type: new DataTypes.TEXT(),
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
quantity_amount: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
quantity_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
unit_price_amount: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
unit_price_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
/*tax_slug: {
|
||||
type: new DataTypes.DECIMAL(3, 2),
|
||||
allowNull: true,
|
||||
},
|
||||
tax_rate: {
|
||||
type: new DataTypes.DECIMAL(3, 2),
|
||||
allowNull: true,
|
||||
},
|
||||
tax_equalization: {
|
||||
type: new DataTypes.DECIMAL(3, 2),
|
||||
allowNull: true,
|
||||
},*/
|
||||
|
||||
subtotal_amount: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
subtotal_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
discount_amount: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
discount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
/*tax_amount: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
},*/
|
||||
total_amount: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
total_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "invoice_items",
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
|
||||
return InvoiceItemModel;
|
||||
};
|
||||
@ -1,144 +0,0 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { InvoiceItemCreationAttributes, InvoiceItemModel } from "./invoice-item.model";
|
||||
|
||||
export type InvoiceCreationAttributes = InferCreationAttributes<InvoiceModel, { omit: "items" }> & {
|
||||
items?: InvoiceItemCreationAttributes[];
|
||||
};
|
||||
|
||||
export class InvoiceModel extends Model<
|
||||
InferAttributes<InvoiceModel>,
|
||||
InferCreationAttributes<InvoiceModel, { omit: "items" }>
|
||||
> {
|
||||
declare id: string;
|
||||
|
||||
declare invoice_status: string;
|
||||
declare invoice_series: CreationOptional<string>;
|
||||
declare invoice_number: CreationOptional<string>;
|
||||
declare issue_date: CreationOptional<string>;
|
||||
declare operation_date: CreationOptional<string>;
|
||||
declare invoice_language: string;
|
||||
declare invoice_currency: string;
|
||||
|
||||
// Subtotal
|
||||
declare subtotal_amount: CreationOptional<number>;
|
||||
declare subtotal_scale: CreationOptional<number>;
|
||||
|
||||
// Total
|
||||
declare total_amount: CreationOptional<number>;
|
||||
declare total_scale: CreationOptional<number>;
|
||||
|
||||
// Relaciones
|
||||
declare items: NonAttribute<InvoiceItemModel[]>;
|
||||
//declare customer: NonAttribute<InvoiceParticipant_Model[]>;
|
||||
|
||||
static associate(database: Sequelize) {
|
||||
const { InvoiceModel, InvoiceItemModel } = database.models;
|
||||
|
||||
InvoiceModel.hasMany(InvoiceItemModel, {
|
||||
as: "items",
|
||||
foreignKey: "invoice_id",
|
||||
onDelete: "CASCADE",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default (database: Sequelize) => {
|
||||
InvoiceModel.init(
|
||||
{
|
||||
id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
invoice_status: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
invoice_series: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
invoice_number: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
issue_date: {
|
||||
type: new DataTypes.DATEONLY(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
operation_date: {
|
||||
type: new DataTypes.DATEONLY(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
invoice_language: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
invoice_currency: {
|
||||
type: new DataTypes.STRING(3), // ISO 4217
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
subtotal_amount: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
subtotal_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
total_amount: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
total_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize: database,
|
||||
tableName: "invoices",
|
||||
|
||||
paranoid: true, // softs deletes
|
||||
timestamps: true,
|
||||
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at",
|
||||
deletedAt: "deleted_at",
|
||||
|
||||
indexes: [{ unique: true, fields: ["invoice_number"] }],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
|
||||
return InvoiceModel;
|
||||
};
|
||||
@ -1,112 +0,0 @@
|
||||
import { Invoice } from "@contexts/invoices/domain";
|
||||
import { IInvoiceRepository } from "@contexts/invoices/domain/repositories/invoice-repository.interface";
|
||||
import { UniqueID } from "core/common/domain";
|
||||
import { Collection, Result } from "core/common/helpers";
|
||||
import { SequelizeRepository } from "core/common/infrastructure";
|
||||
import { Transaction } from "sequelize";
|
||||
import { IInvoiceMapper, invoiceMapper } from "../mappers/invoice.mapper";
|
||||
import { InvoiceItemModel } from "./invoice-item.model";
|
||||
import { InvoiceModel } from "./invoice.model";
|
||||
|
||||
class InvoiceRepository extends SequelizeRepository<Invoice> implements IInvoiceRepository {
|
||||
private readonly _mapper!: IInvoiceMapper;
|
||||
|
||||
/**
|
||||
* 🔹 Función personalizada para mapear errores de unicidad en autenticación
|
||||
*/
|
||||
private _customErrorMapper(error: Error): string | null {
|
||||
if (error.name === "SequelizeUniqueConstraintError") {
|
||||
return "Invoice with this email already exists";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(mapper: IInvoiceMapper) {
|
||||
super();
|
||||
this._mapper = mapper;
|
||||
}
|
||||
|
||||
async invoiceExists(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
|
||||
try {
|
||||
const _invoice = await this._getById(InvoiceModel, id, {}, transaction);
|
||||
|
||||
return Result.ok(Boolean(id.equals(_invoice.id)));
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async findAll(transaction?: Transaction): Promise<Result<Collection<Invoice>, Error>> {
|
||||
try {
|
||||
const rawInvoices: any = await this._findAll(
|
||||
InvoiceModel,
|
||||
{
|
||||
include: [
|
||||
{
|
||||
model: InvoiceItemModel,
|
||||
as: "items",
|
||||
},
|
||||
],
|
||||
},
|
||||
transaction
|
||||
);
|
||||
|
||||
if (!rawInvoices === true) {
|
||||
return Result.fail(new Error("Invoice with email not exists"));
|
||||
}
|
||||
|
||||
return this._mapper.mapArrayToDomain(rawInvoices);
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async getById(id: UniqueID, transaction?: Transaction): Promise<Result<Invoice, Error>> {
|
||||
try {
|
||||
const rawInvoice: any = await this._getById(
|
||||
InvoiceModel,
|
||||
id,
|
||||
{
|
||||
include: [
|
||||
{
|
||||
model: InvoiceItemModel,
|
||||
as: "items",
|
||||
},
|
||||
],
|
||||
},
|
||||
transaction
|
||||
);
|
||||
|
||||
if (!rawInvoice === true) {
|
||||
return Result.fail(new Error(`Invoice with id ${id.toString()} not exists`));
|
||||
}
|
||||
|
||||
return this._mapper.mapToDomain(rawInvoice);
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteById(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
|
||||
try {
|
||||
this._deleteById(InvoiceModel, id);
|
||||
return Result.ok<boolean>(true);
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async create(invoice: Invoice, transaction?: Transaction): Promise<void> {
|
||||
const invoiceData = this._mapper.mapToPersistence(invoice);
|
||||
await this._save(InvoiceModel, invoice.id, invoiceData, {}, transaction);
|
||||
}
|
||||
|
||||
async update(invoice: Invoice, transaction?: Transaction): Promise<void> {
|
||||
const invoiceData = this._mapper.mapToPersistence(invoice);
|
||||
await this._save(InvoiceModel, invoice.id, invoiceData, {}, transaction);
|
||||
}
|
||||
}
|
||||
|
||||
const invoiceRepository = new InvoiceRepository(invoiceMapper);
|
||||
export { invoiceRepository };
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user