.
This commit is contained in:
parent
d3fac62898
commit
0171f51c56
@ -1,5 +1,7 @@
|
||||
import { Result } from "@common/helpers";
|
||||
import DineroFactory, { Currency, Dinero } from "dinero.js";
|
||||
import { Percentage } from "./percentage";
|
||||
import { Quantity } from "./quantity";
|
||||
import { ValueObject } from "./value-object";
|
||||
|
||||
const DEFAULT_SCALE = 2;
|
||||
@ -30,14 +32,17 @@ interface IMoneyValue {
|
||||
convertScale(newScale: number): MoneyValue;
|
||||
add(addend: MoneyValue): MoneyValue;
|
||||
subtract(subtrahend: MoneyValue): MoneyValue;
|
||||
multiply(multiplier: number): MoneyValue;
|
||||
divide(divisor: number): MoneyValue;
|
||||
multiply(multiplier: number | Quantity, roundingMode?: RoundingMode): MoneyValue;
|
||||
divide(divisor: number, roundingMode?: RoundingMode): MoneyValue;
|
||||
percentage(percentage: number, roundingMode?: RoundingMode): MoneyValue;
|
||||
equalsTo(comparator: MoneyValue): boolean;
|
||||
greaterThan(comparator: MoneyValue): boolean;
|
||||
lessThan(comparator: MoneyValue): boolean;
|
||||
isZero(): boolean;
|
||||
isPositive(): boolean;
|
||||
isNegative(): boolean;
|
||||
hasSameCurrency(comparator: MoneyValue): boolean;
|
||||
hasSameAmount(comparator: MoneyValue): boolean;
|
||||
format(locale: string): string;
|
||||
}
|
||||
|
||||
@ -101,19 +106,36 @@ export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyV
|
||||
});
|
||||
}
|
||||
|
||||
multiply(multiplier: number): MoneyValue {
|
||||
multiply(multiplier: number | Quantity, roundingMode?: RoundingMode): MoneyValue {
|
||||
const _multiplier = typeof multiplier === "number" ? multiplier : multiplier.toNumber();
|
||||
|
||||
const _newDinero = this.dinero.multiply(_multiplier, roundingMode);
|
||||
return new MoneyValue({
|
||||
amount: this.dinero.multiply(multiplier).getAmount(),
|
||||
scale: this.scale,
|
||||
currency_code: this.currency,
|
||||
amount: _newDinero.getAmount(),
|
||||
scale: _newDinero.getPrecision(),
|
||||
currency_code: _newDinero.getCurrency(),
|
||||
});
|
||||
}
|
||||
|
||||
divide(divisor: number): MoneyValue {
|
||||
divide(divisor: number | Quantity, roundingMode?: RoundingMode): MoneyValue {
|
||||
const _divisor = typeof divisor === "number" ? divisor : divisor.toNumber();
|
||||
|
||||
const _newDinero = this.dinero.divide(_divisor, roundingMode);
|
||||
return new MoneyValue({
|
||||
amount: this.dinero.divide(divisor).getAmount(),
|
||||
scale: this.scale,
|
||||
currency_code: this.currency,
|
||||
amount: _newDinero.getAmount(),
|
||||
scale: _newDinero.getPrecision(),
|
||||
currency_code: _newDinero.getCurrency(),
|
||||
});
|
||||
}
|
||||
|
||||
percentage(percentage: number | Percentage, roundingMode?: RoundingMode): MoneyValue {
|
||||
const _percentage = typeof percentage === "number" ? percentage : percentage.toNumber();
|
||||
|
||||
const _newDinero = this.dinero.percentage(_percentage, roundingMode);
|
||||
return new MoneyValue({
|
||||
amount: _newDinero.getAmount(),
|
||||
scale: _newDinero.getPrecision(),
|
||||
currency_code: _newDinero.getCurrency(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -141,6 +163,14 @@ export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyV
|
||||
return this.amount < 0;
|
||||
}
|
||||
|
||||
hasSameCurrency(comparator: MoneyValue): boolean {
|
||||
return this.dinero.hasSameCurrency(comparator.dinero);
|
||||
}
|
||||
|
||||
hasSameAmount(comparator: MoneyValue): boolean {
|
||||
return this.dinero.hasSameAmount(comparator.dinero);
|
||||
}
|
||||
|
||||
format(locale: string): string {
|
||||
const amount = this.amount;
|
||||
const currency = this.currency;
|
||||
|
||||
@ -12,6 +12,13 @@ describe("UniqueID", () => {
|
||||
expect(result.data.toString()).toBe(id);
|
||||
});
|
||||
|
||||
test("should generate a UniqueID with a valid UUID", () => {
|
||||
const result = UniqueID.generate();
|
||||
|
||||
expect(result.isSuccess).toBe(true);
|
||||
expect(result.data.toString()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("should fail to create UniqueID with an invalid UUID", () => {
|
||||
const result = UniqueID.create("invalid-uuid");
|
||||
|
||||
|
||||
@ -19,8 +19,8 @@ export class UniqueID extends ValueObject<string> {
|
||||
: Result.fail(new Error(result.error.errors[0].message));
|
||||
}
|
||||
|
||||
static generate(): UniqueID {
|
||||
return new UniqueID(uuidv4());
|
||||
static generate(): Result<UniqueID, never> {
|
||||
return Result.ok(new UniqueID(uuidv4()));
|
||||
}
|
||||
|
||||
static validate(id: string) {
|
||||
|
||||
@ -20,12 +20,4 @@ export abstract class ValueObject<T> {
|
||||
|
||||
return shallowEqual(this.props, other.props);
|
||||
}
|
||||
|
||||
/*isEmpty(): boolean {
|
||||
return this.props === null || this.props === undefined;
|
||||
}*/
|
||||
|
||||
/*toString(): string {
|
||||
return this.props !== null && this.props !== undefined ? String(this.props) : "";
|
||||
}*/
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ export class CompanyMapper
|
||||
id: source.id.toString(),
|
||||
is_freelancer: source.isFreelancer,
|
||||
name: source.name,
|
||||
trade_name: source.tradeName.isSome() ? source.tradeName.getValue() : undefined,
|
||||
trade_name: source.tradeName.getOrUndefined(),
|
||||
tin: source.tin.toString(),
|
||||
|
||||
street: source.address.street,
|
||||
@ -83,15 +83,15 @@ export class CompanyMapper
|
||||
|
||||
email: source.email.toString(),
|
||||
phone: source.phone.toString(),
|
||||
fax: source.fax.isSome() ? source.fax.getValue()?.toString() : undefined,
|
||||
website: source.website.isSome() ? source.website.getValue() : undefined,
|
||||
fax: source.fax.isSome() ? source.fax.getOrUndefined()?.toString() : undefined,
|
||||
website: source.website.getOrUndefined(),
|
||||
|
||||
legal_record: source.legalRecord,
|
||||
default_tax: source.defaultTax,
|
||||
status: source.isActive ? "active" : "inactive",
|
||||
lang_code: source.langCode,
|
||||
currency_code: source.currencyCode,
|
||||
logo: source.logo.isSome() ? source.logo.getValue() : undefined,
|
||||
logo: source.logo.getOrUndefined(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ export const listCompaniesPresenter: IListCompaniesPresenter = {
|
||||
|
||||
is_freelancer: ensureBoolean(company.isFreelancer),
|
||||
name: ensureString(company.name),
|
||||
trade_name: ensureString(company.tradeName.getValue()),
|
||||
trade_name: ensureString(company.tradeName.getOrUndefined()),
|
||||
tin: ensureString(company.tin.toString()),
|
||||
|
||||
street: ensureString(company.address.street),
|
||||
@ -24,8 +24,8 @@ export const listCompaniesPresenter: IListCompaniesPresenter = {
|
||||
|
||||
email: ensureString(company.email.toString()),
|
||||
phone: ensureString(company.phone.toString()),
|
||||
fax: ensureString(company.fax.getValue()?.toString()),
|
||||
website: ensureString(company.website.getValue()),
|
||||
fax: ensureString(company.fax.getOrUndefined()?.toString()),
|
||||
website: ensureString(company.website.getOrUndefined()),
|
||||
|
||||
legal_record: ensureString(company.legalRecord),
|
||||
|
||||
@ -33,6 +33,6 @@ export const listCompaniesPresenter: IListCompaniesPresenter = {
|
||||
status: ensureString(company.isActive ? "active" : "inactive"),
|
||||
lang_code: ensureString(company.langCode),
|
||||
currency_code: ensureString(company.currencyCode),
|
||||
logo: ensureString(company.logo.getValue()),
|
||||
logo: ensureString(company.logo.getOrUndefined()),
|
||||
})),
|
||||
};
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { UniqueID } from "@common/domain";
|
||||
import { Result } from "@common/helpers";
|
||||
import { ITransactionManager } from "@common/infrastructure/database";
|
||||
import { ICustomerInvoiceService } from "@contexts/customer-billing/domain";
|
||||
import { CustomerInvoice } from "@contexts/customer-billing/domain/aggregates";
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./get-customer-invoice.use-case";
|
||||
@ -11,7 +11,7 @@ export class ListCustomersUseCase {
|
||||
|
||||
public execute(): Promise<Result<Collection<Customer>, Error>> {
|
||||
return this.transactionManager.complete((transaction) => {
|
||||
return this.customerService.findCustomers(transaction);
|
||||
return this.customerService.findCustomer(transaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
import { DomainEntity, MoneyValue, Percentage, UniqueID } from "@common/domain";
|
||||
import { Quantity } from "@common/domain/value-objects/quantity";
|
||||
import { Maybe, Result } from "@common/helpers";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
export * from "./customer-invoice-item";
|
||||
export * from "./tax";
|
||||
export * from "./tax-collection";
|
||||
@ -0,0 +1,33 @@
|
||||
import { Slug } from "@common/domain";
|
||||
import { Collection } from "@common/helpers";
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
import { DomainEntity, Percentage, Slug, UniqueID } from "@common/domain";
|
||||
import { Result } from "@common/helpers";
|
||||
|
||||
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,61 +0,0 @@
|
||||
import { DomainEntity, MoneyValue, Percentage, UniqueID } from "@common/domain";
|
||||
import { Quantity } from "@common/domain/value-objects/quantity";
|
||||
import { Maybe, Result } from "@common/helpers";
|
||||
|
||||
export interface ICustomerInvoiceItemProps {
|
||||
description: Maybe<string>; // Descripción del artículo o servicio
|
||||
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 ICustomerInvoiceItem {
|
||||
description: Maybe<string>;
|
||||
quantity: Quantity;
|
||||
unitPrice: MoneyValue;
|
||||
subtotalPrice: MoneyValue;
|
||||
discount: Percentage;
|
||||
totalPrice: MoneyValue;
|
||||
}
|
||||
|
||||
export class CustomerInvoiceItem
|
||||
extends DomainEntity<ICustomerInvoiceItemProps>
|
||||
implements ICustomerInvoiceItem
|
||||
{
|
||||
public static create(
|
||||
props: ICustomerInvoiceItemProps,
|
||||
id?: UniqueID
|
||||
): Result<CustomerInvoiceItem, Error> {
|
||||
return Result.ok(new CustomerInvoiceItem(props, id));
|
||||
}
|
||||
|
||||
get description(): Maybe<string> {
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
get quantity(): Quantity {
|
||||
return this.props.quantity;
|
||||
}
|
||||
|
||||
get unitPrice(): MoneyValue {
|
||||
return this.props.unitPrice;
|
||||
}
|
||||
|
||||
get subtotalPrice(): MoneyValue {
|
||||
return this.quantity.isNull() || this.unitPrice.isNull()
|
||||
? MoneyValue.create({ amount: null, scale: 2 }).object
|
||||
: this.unitPrice.multiply(this.quantity.toNumber());
|
||||
}
|
||||
|
||||
get discount(): Percentage {
|
||||
return this.props.discount;
|
||||
}
|
||||
|
||||
get totalPrice(): MoneyValue {
|
||||
return this.subtotalPrice.isNull()
|
||||
? MoneyValue.create({ amount: null, scale: 2 }).object
|
||||
: this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
import { UniqueID } from "@common/domain";
|
||||
import { Collection, Result } from "@common/helpers";
|
||||
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 +1,2 @@
|
||||
export * from "./customer-invoice-repository.interface";
|
||||
export * from "./customer-repository.interface";
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
import { UniqueID } from "@common/domain";
|
||||
import { Collection, Result } from "@common/helpers";
|
||||
import { CustomerInvoice } from "../aggregates";
|
||||
|
||||
export interface ICustomerInvoiceService {
|
||||
findCustomerInvoices(transaction?: any): Promise<Result<Collection<CustomerInvoice>, Error>>;
|
||||
findCustomerInvoiceById(invoiceId: UniqueID, transaction?: any): Promise<Result<CustomerInvoice>>;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
import { UniqueID } from "@common/domain";
|
||||
import { Collection, Result } from "@common/helpers";
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,6 @@ import { Collection, Result } from "@common/helpers";
|
||||
import { Customer } from "../aggregates";
|
||||
|
||||
export interface ICustomerService {
|
||||
findCustomers(transaction?: any): Promise<Result<Collection<Customer>, Error>>;
|
||||
findCustomerById(userId: UniqueID, transaction?: any): Promise<Result<Customer>>;
|
||||
findCustomer(transaction?: any): Promise<Result<Collection<Customer>, Error>>;
|
||||
findCustomerById(customerId: UniqueID, transaction?: any): Promise<Result<Customer>>;
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import { ICustomerService } from "./customer-service.interface";
|
||||
export class CustomerService implements ICustomerService {
|
||||
constructor(private readonly customerRepository: ICustomerRepository) {}
|
||||
|
||||
async findCustomers(transaction?: any): Promise<Result<Collection<Customer>, Error>> {
|
||||
async findCustomer(transaction?: any): Promise<Result<Collection<Customer>, Error>> {
|
||||
const customersOrError = await this.customerRepository.findAll(transaction);
|
||||
if (customersOrError.isFailure) {
|
||||
return Result.fail(customersOrError.error);
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export * from "./customer-invoice-service.interface";
|
||||
export * from "./customer-invoice.service";
|
||||
export * from "./customer-service.interface";
|
||||
export * from "./customer.service";
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
import { Result } from "@common/helpers";
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "@common/infrastructure/sequelize/sequelize-mapper";
|
||||
import { CustomerInvoice } from "@contexts/customer-billing/domain";
|
||||
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 tinOrError = TINNumber.create(source.tin);
|
||||
const emailOrError = EmailAddress.create(source.email);
|
||||
const phoneOrError = PhoneNumber.create(source.phone);
|
||||
const faxOrError = PhoneNumber.createNullable(source.fax);
|
||||
const postalAddressOrError = PostalAddress.create({
|
||||
street: source.street,
|
||||
city: source.city,
|
||||
state: source.state,
|
||||
postalCode: source.postal_code,
|
||||
country: source.country,
|
||||
});
|
||||
|
||||
const result = Result.combine([
|
||||
idOrError,
|
||||
tinOrError,
|
||||
emailOrError,
|
||||
phoneOrError,
|
||||
faxOrError,
|
||||
postalAddressOrError,
|
||||
]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return Customer.create(
|
||||
{
|
||||
isFreelancer: source.is_freelancer,
|
||||
reference: source.reference,
|
||||
name: source.name,
|
||||
tradeName: source.trade_name ? Maybe.Some(source.trade_name) : Maybe.None(),
|
||||
tin: tinOrError.data,
|
||||
address: postalAddressOrError.data,
|
||||
email: emailOrError.data,
|
||||
phone: phoneOrError.data,
|
||||
fax: faxOrError.data,
|
||||
website: source.website ? Maybe.Some(source.website) : Maybe.None(),
|
||||
legalRecord: source.legal_record,
|
||||
defaultTax: source.default_tax,
|
||||
status: source.status,
|
||||
langCode: source.lang_code,
|
||||
currencyCode: source.currency_code,
|
||||
},
|
||||
idOrError.data
|
||||
);*/
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
source: CustomerInvoice,
|
||||
params?: MapperParamsType
|
||||
): Result<CustomerInvoiceCreationAttributes, Error> {
|
||||
/*return Result.ok({
|
||||
id: source.id.toString(),
|
||||
reference: source.reference,
|
||||
is_freelancer: source.isFreelancer,
|
||||
name: source.name,
|
||||
trade_name: source.tradeName.isSome() ? source.tradeName.getValue() : undefined,
|
||||
tin: source.tin.toString(),
|
||||
|
||||
street: source.address.street,
|
||||
city: source.address.city,
|
||||
state: source.address.state,
|
||||
postal_code: source.address.postalCode,
|
||||
country: source.address.country,
|
||||
|
||||
email: source.email.toString(),
|
||||
phone: source.phone.toString(),
|
||||
fax: source.fax.isSome() ? source.fax.getValue()?.toString() : undefined,
|
||||
website: source.website.isSome() ? source.website.getValue() : undefined,
|
||||
|
||||
legal_record: source.legalRecord,
|
||||
default_tax: source.defaultTax,
|
||||
status: source.isActive ? "active" : "inactive",
|
||||
lang_code: source.langCode,
|
||||
currency_code: source.currencyCode,
|
||||
});*/
|
||||
}
|
||||
}
|
||||
|
||||
const customerInvoiceMapper: CustomerInvoiceMapper = new CustomerInvoiceMapper();
|
||||
export { customerInvoiceMapper };
|
||||
@ -73,7 +73,7 @@ export class CustomerMapper
|
||||
reference: source.reference,
|
||||
is_freelancer: source.isFreelancer,
|
||||
name: source.name,
|
||||
trade_name: source.tradeName.isSome() ? source.tradeName.getValue() : undefined,
|
||||
trade_name: source.tradeName.getOrUndefined(),
|
||||
tin: source.tin.toString(),
|
||||
|
||||
street: source.address.street,
|
||||
@ -84,8 +84,8 @@ export class CustomerMapper
|
||||
|
||||
email: source.email.toString(),
|
||||
phone: source.phone.toString(),
|
||||
fax: source.fax.isSome() ? source.fax.getValue()?.toString() : undefined,
|
||||
website: source.website.isSome() ? source.website.getValue() : undefined,
|
||||
fax: source.fax.isSome() ? source.fax.getOrUndefined()?.toString() : undefined,
|
||||
website: source.website.getOrUndefined(),
|
||||
|
||||
legal_record: source.legalRecord,
|
||||
default_tax: source.defaultTax,
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import { UniqueID } from "@common/domain";
|
||||
import { Collection, Result } from "@common/helpers";
|
||||
import { SequelizeRepository } from "@common/infrastructure";
|
||||
import { CustomerInvoice, ICustomerInvoiceRepository } from "@contexts/customer-billing/domain";
|
||||
import { Transaction } from "sequelize";
|
||||
import { customerInvoiceMapper, ICustomerInvoiceMapper } 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,9 +1,17 @@
|
||||
import { ICustomerRepository } from "@contexts/customer-billing/domain";
|
||||
import { ICustomerInvoiceRepository } from "@contexts/customer-billing/domain/";
|
||||
import { customerRepository } from "./customer.repository";
|
||||
|
||||
export * from "./customer.model";
|
||||
export * from "./customer.repository";
|
||||
|
||||
export * from "./customer-invoice.model";
|
||||
export * from "./customer-invoice.repository";
|
||||
|
||||
export const createCustomerRepository = (): ICustomerRepository => {
|
||||
return customerRepository;
|
||||
};
|
||||
|
||||
export const createCustomerInvoiceRepository = (): ICustomerInvoiceRepository => {
|
||||
return customerRepository;
|
||||
};
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
import { UniqueID } from "@common/domain";
|
||||
import { ExpressController } from "@common/presentation";
|
||||
import { GetCustomerInvoiceUseCase } from "@contexts/customer-billing/application";
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
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;
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
import { SequelizeTransactionManager } from "@common/infrastructure";
|
||||
import { GetCustomerInvoiceUseCase } from "@contexts/customer-billing/application/";
|
||||
import { CustomerInvoiceService } from "@contexts/customer-billing/domain";
|
||||
import { customerInvoiceRepository } from "@contexts/customer-billing/infraestructure";
|
||||
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);
|
||||
};
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./get";
|
||||
export * from "./list";
|
||||
@ -1,2 +1,15 @@
|
||||
export * from "./list-customer-invoices.controller";
|
||||
export * from "./list-customer-invoices.presenter";
|
||||
import { SequelizeTransactionManager } from "@common/infrastructure";
|
||||
import { CustomerInvoiceService } from "@contexts/customer-billing/domain";
|
||||
import { customerInvoiceRepository } from "@contexts/customer-billing/infraestructure";
|
||||
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,5 +1,6 @@
|
||||
import { Collection, ensureBoolean, ensureNumber, ensureString } from "@common/helpers";
|
||||
|
||||
import { CustomerInvoice } from "@contexts/customer-billing/domain";
|
||||
import { IListCustomerInvoicesResponseDTO } from "../../../dto";
|
||||
|
||||
export interface IListCustomerInvoicesPresenter {
|
||||
|
||||
@ -25,3 +25,5 @@ export interface IListCustomerInvoicesResponseDTO {
|
||||
lang_code: string;
|
||||
currency_code: string;
|
||||
}
|
||||
|
||||
export interface IGetCustomerInvoiceResponseDTO {}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ListCustomerInvoicesSchema = z.object({});
|
||||
export const GetCustomerInvoiceSchema = z.object({});
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import { validateRequestDTO } from "@common/presentation";
|
||||
import { checkTabContext, checkUser } from "@contexts/auth/infraestructure";
|
||||
import { ListCustomerInvoicesSchema } from "@contexts/customer-billing/presentation";
|
||||
import {
|
||||
GetCustomerInvoiceSchema,
|
||||
ListCustomerInvoicesSchema,
|
||||
} from "@contexts/customer-billing/presentation";
|
||||
import {
|
||||
getCustomerInvoiceController,
|
||||
listCustomerInvoicesController,
|
||||
} from "@contexts/customer-billing/presentation/controllers";
|
||||
import { NextFunction, Request, Response, Router } from "express";
|
||||
|
||||
export const customerInvoicesRouter = (appRouter: Router) => {
|
||||
@ -16,5 +23,15 @@ export const customerInvoicesRouter = (appRouter: Router) => {
|
||||
}
|
||||
);
|
||||
|
||||
routes.get(
|
||||
"/:invoiceId",
|
||||
validateRequestDTO(GetCustomerInvoiceSchema),
|
||||
checkTabContext,
|
||||
checkUser,
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
getCustomerInvoiceController().execute(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
appRouter.use("/customer-invoices", routes);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user