.
This commit is contained in:
parent
7def4f7dc5
commit
41edc1bf72
@ -31,7 +31,7 @@ export function createApp(): Application {
|
||||
});
|
||||
|
||||
app.use((req, _, next) => {
|
||||
logger.info(`▶️ Incoming request ${req.method} to ${req.path}`);
|
||||
logger.info(`▶️ Incoming request ${req.method} to ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
@ -49,7 +49,7 @@ export class EmailAddress extends ValueObject<EmailAddressProps> {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { Quantity } from "./quantity";
|
||||
import { ValueObject } from "./value-object";
|
||||
|
||||
const DEFAULT_SCALE = 2;
|
||||
const DEFAULT_CURRENCY_CODE = "EUR";
|
||||
|
||||
type CurrencyData = Currency;
|
||||
|
||||
@ -19,8 +20,8 @@ export type RoundingMode =
|
||||
|
||||
interface IMoneyValueProps {
|
||||
amount: number;
|
||||
scale: number;
|
||||
currency_code: string;
|
||||
scale?: number;
|
||||
currency_code?: string;
|
||||
}
|
||||
|
||||
interface IMoneyValue {
|
||||
@ -59,8 +60,8 @@ export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyV
|
||||
this.dinero = Object.freeze(
|
||||
DineroFactory({
|
||||
amount,
|
||||
precision: scale,
|
||||
currency: currency_code as Currency,
|
||||
precision: scale || DEFAULT_SCALE,
|
||||
currency: (currency_code as Currency) || DEFAULT_CURRENCY_CODE,
|
||||
})
|
||||
); // 🔒 Garantiza inmutabilidad
|
||||
}
|
||||
@ -81,6 +82,28 @@ export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyV
|
||||
return this.props;
|
||||
}
|
||||
|
||||
/** Serializa el VO a una cadena del tipo "EUR:123400:2" */
|
||||
toPersistence(): string {
|
||||
return `${this.currency()}:${this.value.getAmount()}:${this.getScale()}`;
|
||||
}
|
||||
|
||||
/** Reconstruye el VO desde la cadena persistida */
|
||||
static fromPersistence(value: string): MoneyValue {
|
||||
const [currencyCode, amountStr, scaleStr] = value.split(":");
|
||||
const amount = parseInt(amountStr, 10);
|
||||
const scale = parseInt(scaleStr, 10);
|
||||
const currency = getCurrencyByCode(currencyCode);
|
||||
return new MoneyValue(amount, scale, currency);
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return {
|
||||
amount: this.amount,
|
||||
scale: this.scale,
|
||||
currency_code: this.currency,
|
||||
};
|
||||
}
|
||||
|
||||
convertScale(newScale: number, roundingMode: RoundingMode = "HALF_UP"): MoneyValue {
|
||||
const _newDinero = this.dinero.convertPrecision(newScale, roundingMode);
|
||||
return new MoneyValue({
|
||||
@ -171,6 +194,12 @@ export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyV
|
||||
return this.dinero.hasSameAmount(comparator.dinero);
|
||||
}
|
||||
|
||||
/**
|
||||
* Devuelve una cadena con el importe formateado.
|
||||
* Ejemplo: 123456 -> €1,234.56
|
||||
* @param locale Código de idioma y país (ej. "es-ES")
|
||||
* @returns Importe formateado
|
||||
*/
|
||||
format(locale: string): string {
|
||||
const amount = this.amount;
|
||||
const currency = this.currency;
|
||||
|
||||
@ -56,7 +56,7 @@ export class Name extends ValueObject<INameProps> {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,6 +73,10 @@ export class Percentage extends ValueObject<IPercentageProps> implements IPercen
|
||||
return this.props;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
toNumber(): number {
|
||||
return this.amount / Math.pow(10, this.scale);
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ export class PhoneNumber extends ValueObject<PhoneNumberProps> {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
toPrimitive(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
|
||||
@ -99,6 +99,10 @@ export class PostalAddress extends ValueObject<IPostalAddressProps> {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `${this.props.street}, ${this.props.street2}, ${this.props.city}, ${this.props.postalCode}, ${this.props.state}, ${this.props.country}`;
|
||||
}
|
||||
|
||||
@ -17,6 +17,10 @@ interface IQuantity {
|
||||
toNumber(): number;
|
||||
toString(): string;
|
||||
|
||||
isZero(): boolean;
|
||||
isPositive(): boolean;
|
||||
isNegative(): boolean;
|
||||
|
||||
increment(anotherQuantity?: Quantity): Result<Quantity, Error>;
|
||||
decrement(anotherQuantity?: Quantity): Result<Quantity, Error>;
|
||||
hasSameScale(otherQuantity: Quantity): boolean;
|
||||
@ -58,6 +62,10 @@ export class Quantity extends ValueObject<IQuantityProps> implements IQuantity {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
toNumber(): number {
|
||||
return this.amount / Math.pow(10, this.scale);
|
||||
}
|
||||
@ -66,6 +74,18 @@ export class Quantity extends ValueObject<IQuantityProps> implements IQuantity {
|
||||
return this.toNumber().toFixed(this.scale);
|
||||
}
|
||||
|
||||
isZero(): boolean {
|
||||
return this.amount === 0;
|
||||
}
|
||||
|
||||
isPositive(): boolean {
|
||||
return this.amount > 0;
|
||||
}
|
||||
|
||||
isNegative(): boolean {
|
||||
return this.amount < 0;
|
||||
}
|
||||
|
||||
increment(anotherQuantity?: Quantity): Result<Quantity, Error> {
|
||||
if (!anotherQuantity) {
|
||||
return Quantity.create({
|
||||
|
||||
@ -43,7 +43,7 @@ export class Slug extends ValueObject<SlugProps> {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
toPrimitive(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ export class TINNumber extends ValueObject<TINNumberProps> {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
toPrimitive(): string {
|
||||
return this.props.value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ export class UniqueID extends ValueObject<string> {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
toPrimitive(): string {
|
||||
return this.props;
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,13 @@ export class UtcDate extends ValueObject<IUtcDateProps> {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Devuelve la fecha completa en formato UTC con hora. Ejemplo: 2025-12-31T23:59:59Z.
|
||||
*/
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Devuelve la fecha en formato UTC sin hora (YYYY-MM-DD).
|
||||
*/
|
||||
|
||||
@ -9,6 +9,8 @@ export abstract class ValueObject<T> {
|
||||
|
||||
abstract getValue(): any;
|
||||
|
||||
abstract toPrimitive(): any;
|
||||
|
||||
equals(other: ValueObject<T>): boolean {
|
||||
if (!(other instanceof ValueObject)) {
|
||||
return false;
|
||||
|
||||
20
apps/server/src/common/presentation/dto/error.dto.ts
Normal file
20
apps/server/src/common/presentation/dto/error.dto.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export interface IErrorDTO {
|
||||
detail?: string;
|
||||
instance?: string;
|
||||
status: number;
|
||||
title: string;
|
||||
type?: string;
|
||||
context: IErrorContextDTO;
|
||||
extra: IErrorExtraDTO;
|
||||
}
|
||||
|
||||
export interface IErrorContextDTO {
|
||||
user?: unknown;
|
||||
params?: Record<string, any>;
|
||||
query?: Record<string, any>;
|
||||
body?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface IErrorExtraDTO {
|
||||
errors: Record<string, any>[];
|
||||
}
|
||||
2
apps/server/src/common/presentation/dto/index.ts
Normal file
2
apps/server/src/common/presentation/dto/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./error.dto";
|
||||
export * from "./types.dto";
|
||||
15
apps/server/src/common/presentation/dto/types.dto.ts
Normal file
15
apps/server/src/common/presentation/dto/types.dto.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export interface IMoneyDTO {
|
||||
amount: number | null;
|
||||
scale: number;
|
||||
currency_code: string;
|
||||
}
|
||||
|
||||
export interface IPercentageDTO {
|
||||
amount: number | null;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
export interface IQuantityDTO {
|
||||
amount: number | null;
|
||||
scale: number;
|
||||
}
|
||||
@ -1 +1,2 @@
|
||||
export * from "./dto";
|
||||
export * from "./express";
|
||||
|
||||
@ -4,7 +4,7 @@ import { ITransactionManager } from "@common/infrastructure/database";
|
||||
import { logger } from "@common/infrastructure/logger";
|
||||
import { Account, IAccountService } from "@contexts/accounts/domain";
|
||||
|
||||
export class GetAccountsUseCase {
|
||||
export class GetAccountUseCase {
|
||||
constructor(
|
||||
private readonly accountService: IAccountService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
|
||||
@ -53,7 +53,7 @@ export class AccountStatus extends ValueObject<IAccountStatusProps> {
|
||||
return AccountStatus.create(nextStatus);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
toPrimitive(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,11 +68,11 @@ export class AccountMapper
|
||||
|
||||
public mapToPersistence(source: Account, params?: MapperParamsType): AccountCreationAttributes {
|
||||
return {
|
||||
id: source.id.toString(),
|
||||
id: source.id.toPrimitive(),
|
||||
is_freelancer: source.isFreelancer,
|
||||
name: source.name,
|
||||
trade_name: source.tradeName.getOrUndefined(),
|
||||
tin: source.tin.toString(),
|
||||
tin: source.tin.toPrimitive(),
|
||||
|
||||
street: source.address.street,
|
||||
city: source.address.city,
|
||||
@ -80,9 +80,9 @@ export class AccountMapper
|
||||
postal_code: source.address.postalCode,
|
||||
country: source.address.country,
|
||||
|
||||
email: source.email.toString(),
|
||||
phone: source.phone.toString(),
|
||||
fax: source.fax.isSome() ? source.fax.getOrUndefined()?.toString() : undefined,
|
||||
email: source.email.toPrimitive(),
|
||||
phone: source.phone.toPrimitive(),
|
||||
fax: source.fax.isSome() ? source.fax.getOrUndefined()?.toPrimitive() : undefined,
|
||||
website: source.website.getOrUndefined(),
|
||||
|
||||
legal_record: source.legalRecord,
|
||||
|
||||
@ -9,10 +9,7 @@ import {
|
||||
|
||||
export type AccountCreationAttributes = InferCreationAttributes<AccountModel, {}> & {};
|
||||
|
||||
export class AccountModel extends Model<
|
||||
InferAttributes<AccountModel>,
|
||||
InferCreationAttributes<AccountModel>
|
||||
> {
|
||||
export class AccountModel extends Model<InferAttributes<AccountModel>, AccountCreationAttributes> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
|
||||
@ -5,7 +5,7 @@ import { accountRepository } from "@contexts/accounts/infraestructure";
|
||||
import { CreateAccountController } from "./create-account.controller";
|
||||
import { createAccountPresenter } from "./create-account.presenter";
|
||||
|
||||
export const createAccountController = () => {
|
||||
export const buildCreateAccountController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const accountService = new AccountService(accountRepository);
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { UniqueID } from "@common/domain";
|
||||
import { ExpressController } from "@common/presentation";
|
||||
import { GetAccountsUseCase } from "@contexts/accounts/application";
|
||||
import { GetAccountUseCase } from "@contexts/accounts/application";
|
||||
import { IGetAccountPresenter } from "./get-account.presenter";
|
||||
|
||||
export class GetAccountController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly getAccount: GetAccountsUseCase,
|
||||
private readonly getAccount: GetAccountUseCase,
|
||||
private readonly presenter: IGetAccountPresenter
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -8,12 +8,12 @@ export interface IGetAccountPresenter {
|
||||
|
||||
export const getAccountPresenter: IGetAccountPresenter = {
|
||||
toDTO: (account: Account): IGetAccountResponseDTO => ({
|
||||
id: ensureString(account.id.toString()),
|
||||
id: ensureString(account.id.toPrimitive()),
|
||||
|
||||
is_freelancer: ensureBoolean(account.isFreelancer),
|
||||
name: ensureString(account.name),
|
||||
trade_name: ensureString(account.tradeName.getOrUndefined()),
|
||||
tin: ensureString(account.tin.toString()),
|
||||
tin: ensureString(account.tin.toPrimitive()),
|
||||
|
||||
street: ensureString(account.address.street),
|
||||
city: ensureString(account.address.city),
|
||||
@ -21,9 +21,9 @@ export const getAccountPresenter: IGetAccountPresenter = {
|
||||
postal_code: ensureString(account.address.postalCode),
|
||||
country: ensureString(account.address.country),
|
||||
|
||||
email: ensureString(account.email.toString()),
|
||||
phone: ensureString(account.phone.toString()),
|
||||
fax: ensureString(account.fax.getOrUndefined()?.toString()),
|
||||
email: ensureString(account.email.toPrimitive()),
|
||||
phone: ensureString(account.phone.toPrimitive()),
|
||||
fax: ensureString(account.fax.getOrUndefined()?.toPrimitive()),
|
||||
website: ensureString(account.website.getOrUndefined()),
|
||||
|
||||
legal_record: ensureString(account.legalRecord),
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { SequelizeTransactionManager } from "@common/infrastructure";
|
||||
import { GetAccountsUseCase } from "@contexts/accounts/application";
|
||||
import { GetAccountUseCase } from "@contexts/accounts/application";
|
||||
import { AccountService } from "@contexts/accounts/domain";
|
||||
import { accountRepository } from "@contexts/accounts/infraestructure";
|
||||
import { GetAccountController } from "./get-account.controller";
|
||||
import { getAccountPresenter } from "./get-account.presenter";
|
||||
|
||||
export const getAccountController = () => {
|
||||
export const buildGetAccountController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const accountService = new AccountService(accountRepository);
|
||||
|
||||
const useCase = new GetAccountsUseCase(accountService, transactionManager);
|
||||
const useCase = new GetAccountUseCase(accountService, transactionManager);
|
||||
const presenter = getAccountPresenter;
|
||||
|
||||
return new GetAccountController(useCase, presenter);
|
||||
|
||||
@ -5,7 +5,7 @@ import { accountRepository } from "@contexts/accounts/infraestructure";
|
||||
import { ListAccountsController } from "./list-accounts.controller";
|
||||
import { listAccountsPresenter } from "./list-accounts.presenter";
|
||||
|
||||
export const listAccountsController = () => {
|
||||
export const buildListAccountsController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const accountService = new AccountService(accountRepository);
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { accountRepository } from "@contexts/accounts/infraestructure";
|
||||
import { UpdateAccountController } from "./update-account.controller";
|
||||
import { updateAccountPresenter } from "./update-account.presenter";
|
||||
|
||||
export const updateAccountController = () => {
|
||||
export const buildUpdateAccountController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const accountService = new AccountService(accountRepository);
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { Result } from "@common/helpers";
|
||||
import { ITransactionManager } from "@common/infrastructure/database";
|
||||
import { logger } from "@common/infrastructure/logger";
|
||||
import { RegisterData } from "@contexts/auth/domain";
|
||||
import { IAuthService } from "@contexts/auth/domain/services";
|
||||
|
||||
@ -10,7 +12,12 @@ export class RegisterUseCase {
|
||||
|
||||
public async execute(registerData: RegisterData) {
|
||||
return await this.transactionManager.complete(async (transaction) => {
|
||||
return await this.authService.registerUser(registerData, transaction);
|
||||
try {
|
||||
return await this.authService.registerUser(registerData, transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import { userRepository } from "@contexts/auth/infraestructure";
|
||||
import { ListUsersController } from "./list-users.controller";
|
||||
import { listUsersPresenter } from "./list-users.presenter";
|
||||
|
||||
export const listUsersController = () => {
|
||||
export const buildListUsersController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const userService = new UserService(userRepository);
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { authenticatedUserRepository, tabContextRepository } from "@contexts/aut
|
||||
import { LoginController } from "./login.controller";
|
||||
import { loginPresenter } from "./login.presenter";
|
||||
|
||||
export const loginController = () => {
|
||||
export const buildLoginController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const authService = new AuthService(authenticatedUserRepository, tabContextRepository);
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { AuthService } from "@contexts/auth/domain/services";
|
||||
import { authenticatedUserRepository, tabContextRepository } from "@contexts/auth/infraestructure";
|
||||
import { LogoutController } from "./logout.controller";
|
||||
|
||||
export const logoutController = () => {
|
||||
export const buildLogoutController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const authService = new AuthService(authenticatedUserRepository, tabContextRepository);
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { authenticatedUserRepository, tabContextRepository } from "@contexts/aut
|
||||
import { RefreshTokenController } from "./refresh-token.controller";
|
||||
import { refreshTokenPresenter } from "./refresh-token.presenter";
|
||||
|
||||
export const refreshTokenController = () => {
|
||||
export const buildRefreshTokenController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const authService = new AuthService(authenticatedUserRepository, tabContextRepository);
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { authenticatedUserRepository, tabContextRepository } from "@contexts/aut
|
||||
import { RegisterController } from "./register.controller";
|
||||
import { registerPresenter } from "./register.presenter";
|
||||
|
||||
export const registerController = () => {
|
||||
export const buildRegisterController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const authService = new AuthService(authenticatedUserRepository, tabContextRepository);
|
||||
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
import { UniqueID } from "@common/domain";
|
||||
import { UniqueID, UtcDate } from "@common/domain";
|
||||
|
||||
import { Result } from "@common/helpers";
|
||||
import { ITransactionManager } from "@common/infrastructure/database";
|
||||
import { logger } from "@common/infrastructure/logger";
|
||||
import { IInvoiceProps, IInvoiceService, Invoice, InvoiceStatus } from "@contexts/invoices/domain";
|
||||
import { ICreateInvoiceRequestDTO } from "../presentation";
|
||||
import {
|
||||
IInvoiceProps,
|
||||
IInvoiceService,
|
||||
Invoice,
|
||||
InvoiceNumber,
|
||||
InvoiceSerie,
|
||||
InvoiceStatus,
|
||||
} from "@contexts/invoices/domain";
|
||||
import { ICreateInvoiceRequestDTO } from "../presentation/dto";
|
||||
|
||||
export class CreateInvoiceUseCase {
|
||||
constructor(
|
||||
@ -37,40 +44,71 @@ export class CreateInvoiceUseCase {
|
||||
private validateInvoiceData(dto: ICreateInvoiceRequestDTO): Result<IInvoiceProps, Error> {
|
||||
const errors: Error[] = [];
|
||||
|
||||
let invoice_status = InvoiceStatus.create(invoiceDTO.status).object;
|
||||
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: defaultCurrency.
|
||||
};
|
||||
|
||||
/*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(invoiceDTO.invoice_series).object;
|
||||
let invoice_series = InvoiceSeries.create(dto.invoice_series).object;
|
||||
if (invoice_series.isEmpty()) {
|
||||
invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||
invoice_series = InvoiceSeries.create(dto.invoice_series).object;
|
||||
}
|
||||
|
||||
let issue_date = InvoiceDate.create(invoiceDTO.issue_date).object;
|
||||
let issue_date = InvoiceDate.create(dto.issue_date).object;
|
||||
if (issue_date.isEmpty()) {
|
||||
issue_date = InvoiceDate.createCurrentDate().object;
|
||||
}
|
||||
|
||||
let operation_date = InvoiceDate.create(invoiceDTO.operation_date).object;
|
||||
let operation_date = InvoiceDate.create(dto.operation_date).object;
|
||||
if (operation_date.isEmpty()) {
|
||||
operation_date = InvoiceDate.createCurrentDate().object;
|
||||
}
|
||||
|
||||
let invoiceCurrency = Currency.createFromCode(invoiceDTO.currency).object;
|
||||
let invoiceCurrency = Currency.createFromCode(dto.currency).object;
|
||||
|
||||
if (invoiceCurrency.isEmpty()) {
|
||||
invoiceCurrency = Currency.createDefaultCode().object;
|
||||
}
|
||||
|
||||
let invoiceLanguage = Language.createFromCode(invoiceDTO.language_code).object;
|
||||
let invoiceLanguage = Language.createFromCode(dto.language_code).object;
|
||||
|
||||
if (invoiceLanguage.isEmpty()) {
|
||||
invoiceLanguage = Language.createDefaultCode().object;
|
||||
}
|
||||
|
||||
const items = new Collection<InvoiceItem>(
|
||||
invoiceDTO.items?.map(
|
||||
dto.items?.map(
|
||||
(item) =>
|
||||
InvoiceSimpleItem.create({
|
||||
description: Description.create(item.description).object,
|
||||
@ -104,6 +142,6 @@ export class CreateInvoiceUseCase {
|
||||
items,
|
||||
},
|
||||
invoiceId
|
||||
);
|
||||
);*/
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { Collection, Result } from "@common/helpers";
|
||||
import { ITransactionManager } from "@common/infrastructure/database";
|
||||
import { logger } from "@common/infrastructure/logger";
|
||||
import { Invoice } from "../domain";
|
||||
import { IInvoiceService, Invoice } from "../domain";
|
||||
|
||||
export class ListInvoicesUseCase {
|
||||
constructor(
|
||||
@ -1,13 +1,13 @@
|
||||
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
import { IInvoiceParticipantRepository } from "../../domain";
|
||||
import { InvoiceParticipant } from "../../domain/InvoiceParticipant/InvoiceParticipant";
|
||||
import { InvoiceCustomer } from "../../domain/entities/invoice-customer/invoice-customer";
|
||||
|
||||
export const participantFinder = async (
|
||||
participantId: UniqueID,
|
||||
adapter: IAdapter,
|
||||
repository: RepositoryBuilder<IInvoiceParticipantRepository>,
|
||||
): Promise<InvoiceParticipant | undefined> => {
|
||||
repository: RepositoryBuilder<IInvoiceParticipantRepository>
|
||||
): Promise<InvoiceCustomer | undefined> => {
|
||||
if (!participantId || (participantId && participantId.isNull())) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
@ -1,11 +1,13 @@
|
||||
import { AggregateRoot, MoneyValue, UniqueID, UtcDate } from "@common/domain";
|
||||
import { Collection, Result } from "@common/helpers";
|
||||
import { Currency } from "dinero.js";
|
||||
import { InvoiceStatus } from "../value-objects";
|
||||
import { InvoiceCustomer, InvoiceItem, InvoiceItems } from "../entities";
|
||||
import { InvoiceNumber, InvoiceSerie, InvoiceStatus } from "../value-objects";
|
||||
|
||||
export interface IInvoiceProps {
|
||||
invoiceNumber: InvoiceNumber;
|
||||
invoiceSeries: InvoiceSeries;
|
||||
invoiceSeries: InvoiceSerie;
|
||||
|
||||
status: InvoiceStatus;
|
||||
|
||||
issueDate: UtcDate;
|
||||
operationDate: UtcDate;
|
||||
@ -13,27 +15,26 @@ export interface IInvoiceProps {
|
||||
//dueDate: UtcDate; // ? --> depende de la forma de pago
|
||||
|
||||
//tax: Tax; // ? --> detalles?
|
||||
invoiceCurrency: Currency;
|
||||
invoiceCurrency: string;
|
||||
|
||||
language: Language;
|
||||
//language: Language;
|
||||
|
||||
//purchareOrderNumber: string;
|
||||
//notes: Note;
|
||||
|
||||
//senderId: UniqueID;
|
||||
|
||||
recipient: InvoiceParticipant;
|
||||
|
||||
//paymentInstructions: Note;
|
||||
//paymentTerms: string;
|
||||
|
||||
items: Collection<InvoiceItem>;
|
||||
customer?: InvoiceCustomer;
|
||||
items?: Collection<InvoiceItem>;
|
||||
}
|
||||
|
||||
export interface IInvoice {
|
||||
id: UniqueID;
|
||||
invoiceNumber: InvoiceNumber;
|
||||
invoiceSeries: InvoiceSeries;
|
||||
invoiceSeries: InvoiceSerie;
|
||||
|
||||
status: InvoiceStatus;
|
||||
|
||||
@ -42,13 +43,13 @@ export interface IInvoice {
|
||||
|
||||
//senderId: UniqueID;
|
||||
|
||||
recipient: InvoiceParticipant;
|
||||
customer?: InvoiceCustomer;
|
||||
|
||||
//dueDate
|
||||
|
||||
//tax: Tax;
|
||||
language: Language;
|
||||
currency: Currency;
|
||||
//language: Language;
|
||||
invoiceCurrency: string;
|
||||
|
||||
//purchareOrderNumber: string;
|
||||
//notes: Note;
|
||||
@ -56,7 +57,7 @@ export interface IInvoice {
|
||||
//paymentInstructions: Note;
|
||||
//paymentTerms: string;
|
||||
|
||||
items: Collection<InvoiceItem>;
|
||||
items: InvoiceItems;
|
||||
|
||||
calculateSubtotal: () => MoneyValue;
|
||||
calculateTaxTotal: () => MoneyValue;
|
||||
@ -64,8 +65,14 @@ export interface IInvoice {
|
||||
}
|
||||
|
||||
export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
|
||||
private _items: Collection<InvoiceItem>;
|
||||
protected _status: InvoiceStatus;
|
||||
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);
|
||||
@ -97,17 +104,17 @@ export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
|
||||
return this.props.senderId;
|
||||
}*/
|
||||
|
||||
get recipient(): InvoiceParticipant {
|
||||
return this.props.recipient;
|
||||
get customer(): InvoiceCustomer | undefined {
|
||||
return this.props.customer;
|
||||
}
|
||||
|
||||
get operationDate() {
|
||||
return this.props.operationDate;
|
||||
}
|
||||
|
||||
get language() {
|
||||
/*get language() {
|
||||
return this.props.language;
|
||||
}
|
||||
}*/
|
||||
|
||||
get dueDate() {
|
||||
return undefined;
|
||||
@ -118,7 +125,7 @@ export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this._status;
|
||||
return this.props.status;
|
||||
}
|
||||
|
||||
get items() {
|
||||
@ -145,7 +152,7 @@ export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
|
||||
return this.props.shipTo;
|
||||
}*/
|
||||
|
||||
get currency() {
|
||||
get invoiceCurrency() {
|
||||
return this.props.invoiceCurrency;
|
||||
}
|
||||
|
||||
@ -167,42 +174,30 @@ export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
|
||||
}*/
|
||||
|
||||
calculateSubtotal(): MoneyValue {
|
||||
let subtotal: MoneyValue | null = null;
|
||||
const invoiceSubtotal = MoneyValue.create({
|
||||
amount: 0,
|
||||
currency_code: this.props.invoiceCurrency,
|
||||
scale: 2,
|
||||
}).data;
|
||||
|
||||
for (const item of this._items.items) {
|
||||
if (!subtotal) {
|
||||
subtotal = item.calculateSubtotal();
|
||||
} else {
|
||||
subtotal = subtotal.add(item.calculateSubtotal());
|
||||
}
|
||||
}
|
||||
|
||||
return subtotal
|
||||
? subtotal.convertPrecision(2)
|
||||
: MoneyValue.create({
|
||||
amount: 0,
|
||||
currencyCode: this.props.invoiceCurrency.code,
|
||||
precision: 2,
|
||||
}).object;
|
||||
return this._items.getAll().reduce((subtotal, item) => {
|
||||
return subtotal.add(item.calculateTotal());
|
||||
}, invoiceSubtotal);
|
||||
}
|
||||
|
||||
// Method to calculate the total tax in the invoice
|
||||
calculateTaxTotal(): MoneyValue {
|
||||
let taxTotal = MoneyValue.create({
|
||||
const taxTotal = MoneyValue.create({
|
||||
amount: 0,
|
||||
currencyCode: this.props.invoiceCurrency.code,
|
||||
precision: 2,
|
||||
}).object;
|
||||
currency_code: this.props.invoiceCurrency,
|
||||
scale: 2,
|
||||
}).data;
|
||||
|
||||
for (const item of this._items.items) {
|
||||
taxTotal = taxTotal.add(item.calculateTaxAmount());
|
||||
}
|
||||
|
||||
return taxTotal.convertPrecision(2);
|
||||
return taxTotal;
|
||||
}
|
||||
|
||||
// Method to calculate the total invoice amount, including taxes
|
||||
calculateTotal(): MoneyValue {
|
||||
return this.calculateSubtotal().add(this.calculateTaxTotal()).convertPrecision(2);
|
||||
return this.calculateSubtotal().add(this.calculateTaxTotal());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./invoice-customer";
|
||||
export * from "./invoice-items";
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./invoice-address";
|
||||
export * from "./invoice-customer";
|
||||
@ -0,0 +1,78 @@
|
||||
import { EmailAddress, Name, PostalAddress, ValueObject } from "@common/domain";
|
||||
import { Result } from "@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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
import { DomainEntity, Name, TINNumber, UniqueID } from "@common/domain";
|
||||
import { Result } from "@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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./invoice-item";
|
||||
export * from "./invoice-items";
|
||||
@ -0,0 +1,83 @@
|
||||
import { MoneyValue, Percentage, Quantity } from "@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)
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,72 @@
|
||||
import { MoneyValue, Percentage, Quantity, ValueObject } from "@common/domain";
|
||||
import { Result } from "@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 {
|
||||
description: InvoiceItemDescription;
|
||||
quantity: Quantity;
|
||||
unitPrice: MoneyValue;
|
||||
subtotalPrice: MoneyValue;
|
||||
discount: Percentage;
|
||||
totalPrice: MoneyValue;
|
||||
}
|
||||
|
||||
export class InvoiceItem extends ValueObject<IInvoiceItemProps> implements IInvoiceItem {
|
||||
private _subtotalPrice!: MoneyValue;
|
||||
private _totalPrice!: MoneyValue;
|
||||
|
||||
public static create(props: IInvoiceItemProps): Result<InvoiceItem, Error> {
|
||||
return Result.ok(new InvoiceItem(props));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
calculateSubtotal(): MoneyValue {
|
||||
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad
|
||||
}
|
||||
|
||||
calculateTotal(): MoneyValue {
|
||||
return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
import { Collection } from "@common/helpers";
|
||||
import { InvoiceItem } from "./invoice-item";
|
||||
|
||||
export class InvoiceItems extends Collection<InvoiceItem> {
|
||||
public static create(items?: InvoiceItem[]): InvoiceItems {
|
||||
return new InvoiceItems(items);
|
||||
}
|
||||
}
|
||||
5
apps/server/src/contexts/invoices/domain/index.ts
Normal file
5
apps/server/src/contexts/invoices/domain/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./aggregates";
|
||||
export * from "./entities";
|
||||
export * from "./repositories";
|
||||
export * from "./services";
|
||||
export * from "./value-objects";
|
||||
@ -18,5 +18,5 @@ export interface IInvoiceService {
|
||||
transaction?: any
|
||||
): Promise<Result<Invoice, Error>>;
|
||||
|
||||
deleteInvoiceById(invoiceId: UniqueID, transaction?: any): Promise<Result<Invoice, Error>>;
|
||||
deleteInvoiceById(invoiceId: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
}
|
||||
@ -30,7 +30,7 @@ export class InvoiceService implements IInvoiceService {
|
||||
data: Partial<IInvoiceProps>,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Invoice, Error>> {
|
||||
// Verificar si la cuenta existe
|
||||
// Verificar si la factura existe
|
||||
const invoiceOrError = await this.repo.findById(invoiceId, transaction);
|
||||
if (invoiceOrError.isFailure) {
|
||||
return Result.fail(new Error("Invoice not found"));
|
||||
@ -54,7 +54,7 @@ export class InvoiceService implements IInvoiceService {
|
||||
data: IInvoiceProps,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Invoice, Error>> {
|
||||
// Verificar si la cuenta existe
|
||||
// Verificar si la factura existe
|
||||
const invoiceOrError = await this.repo.findById(invoiceId, transaction);
|
||||
if (invoiceOrError.isSuccess) {
|
||||
return Result.fail(new Error("Invoice exists"));
|
||||
@ -74,21 +74,7 @@ export class InvoiceService implements IInvoiceService {
|
||||
async deleteInvoiceById(
|
||||
invoiceId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Invoice, Error>> {
|
||||
// Verificar si la cuenta existe
|
||||
const invoiceOrError = await this.repo.findById(invoiceId, transaction);
|
||||
if (invoiceOrError.isFailure) {
|
||||
return Result.fail(new Error("Invoice not 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);
|
||||
): Promise<Result<boolean, Error>> {
|
||||
return this.repo.deleteById(invoiceId, transaction);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
export * from "./invoice-address-type";
|
||||
export * from "./invoice-item-description";
|
||||
export * from "./invoice-number";
|
||||
export * from "./invoice-serie";
|
||||
export * from "./invoice-status";
|
||||
@ -0,0 +1,38 @@
|
||||
import { ValueObject } from "@common/domain";
|
||||
import { Result } from "@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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
import { ValueObject } from "@common/domain";
|
||||
import { Maybe, Result } from "@common/helpers";
|
||||
import { z } from "zod";
|
||||
|
||||
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.errors[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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
import { ValueObject } from "@common/domain";
|
||||
import { Result } from "@common/helpers";
|
||||
import { z } from "zod";
|
||||
|
||||
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.errors[0].message));
|
||||
}
|
||||
return Result.ok(new InvoiceNumber({ value }));
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
import { ValueObject } from "@common/domain";
|
||||
import { Maybe, Result } from "@common/helpers";
|
||||
import { z } from "zod";
|
||||
|
||||
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.errors[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();
|
||||
}
|
||||
}
|
||||
@ -57,6 +57,10 @@ export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
canTransitionTo(nextStatus: string): boolean {
|
||||
return InvoiceStatus.TRANSITIONS[this.props.value].includes(nextStatus);
|
||||
}
|
||||
@ -1,12 +1,9 @@
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
SequelizeRepository,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { ISequelizeAdapter, SequelizeRepository } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Transaction } from "sequelize";
|
||||
import { InvoiceParticipant } from "../domain";
|
||||
import { InvoiceCustomer } from "../domain";
|
||||
import { IInvoiceParticipantMapper } from "./mappers";
|
||||
|
||||
export class InvoiceParticipantRepository extends SequelizeRepository<InvoiceParticipant> {
|
||||
export class InvoiceParticipantRepository extends SequelizeRepository<InvoiceCustomer> {
|
||||
protected mapper: IInvoiceParticipantMapper;
|
||||
|
||||
public constructor(props: {
|
||||
@ -1,19 +1,10 @@
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
SequelizeMapper,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
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.model";
|
||||
import {
|
||||
IContactAddressMapper,
|
||||
createContactAddressMapper,
|
||||
} from "./contactAddress.mapper";
|
||||
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> {}
|
||||
@ -22,10 +13,7 @@ class ContactMapper
|
||||
extends SequelizeMapper<Contact_Model, TCreationContact_Model, Contact>
|
||||
implements IContactMapper
|
||||
{
|
||||
public constructor(props: {
|
||||
addressMapper: IContactAddressMapper;
|
||||
context: IInvoicingContext;
|
||||
}) {
|
||||
public constructor(props: { addressMapper: IContactAddressMapper; context: IInvoicingContext }) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
@ -44,15 +32,9 @@ class ContactMapper
|
||||
);
|
||||
}
|
||||
|
||||
const billingAddress = this.props.addressMapper.mapToDomain(
|
||||
source.billingAddress!,
|
||||
params
|
||||
);
|
||||
const billingAddress = this.props.addressMapper.mapToDomain(source.billingAddress!, params);
|
||||
|
||||
const shippingAddress = this.props.addressMapper.mapToDomain(
|
||||
source.shippingAddress!,
|
||||
params
|
||||
);
|
||||
const shippingAddress = this.props.addressMapper.mapToDomain(source.shippingAddress!, params);
|
||||
|
||||
const props: IContactProps = {
|
||||
tin: this.mapsValue(source, "tin", TINNumber.create),
|
||||
@ -74,9 +56,7 @@ class ContactMapper
|
||||
}
|
||||
}
|
||||
|
||||
export const createContactMapper = (
|
||||
context: IInvoicingContext
|
||||
): IContactMapper =>
|
||||
export const createContactMapper = (context: IInvoicingContext): IContactMapper =>
|
||||
new ContactMapper({
|
||||
addressMapper: createContactAddressMapper(context),
|
||||
context,
|
||||
@ -0,0 +1 @@
|
||||
export * from "./invoice.mapper";
|
||||
@ -1,43 +1,23 @@
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
SequelizeMapper,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
|
||||
import { Description, Quantity, UniqueID, UnitPrice } from "@shared/contexts";
|
||||
import { Invoice } from "../../domain";
|
||||
import {
|
||||
IInvoiceSimpleItemProps,
|
||||
InvoiceItem,
|
||||
InvoiceSimpleItem,
|
||||
} from "../../domain/InvoiceItems";
|
||||
import { IInvoiceSimpleItemProps, InvoiceItem, InvoiceSimpleItem } from "../../domain/entities";
|
||||
import { IInvoicingContext } from "../InvoicingContext";
|
||||
import {
|
||||
InvoiceItem_Model,
|
||||
Invoice_Model,
|
||||
TCreationInvoiceItem_Model,
|
||||
} from "../sequelize";
|
||||
import { InvoiceItem_Model, InvoiceModel, TCreationInvoiceItem_Model } from "../sequelize";
|
||||
|
||||
export interface IInvoiceItemMapper
|
||||
extends ISequelizeMapper<
|
||||
InvoiceItem_Model,
|
||||
TCreationInvoiceItem_Model,
|
||||
InvoiceItem
|
||||
> {}
|
||||
extends ISequelizeMapper<InvoiceItem_Model, TCreationInvoiceItem_Model, InvoiceItem> {}
|
||||
|
||||
export const createInvoiceItemMapper = (
|
||||
context: IInvoicingContext,
|
||||
): IInvoiceItemMapper => new InvoiceItemMapper({ context });
|
||||
export const createInvoiceItemMapper = (context: IInvoicingContext): IInvoiceItemMapper =>
|
||||
new InvoiceItemMapper({ context });
|
||||
|
||||
class InvoiceItemMapper
|
||||
extends SequelizeMapper<
|
||||
InvoiceItem_Model,
|
||||
TCreationInvoiceItem_Model,
|
||||
InvoiceItem
|
||||
>
|
||||
extends SequelizeMapper<InvoiceItem_Model, TCreationInvoiceItem_Model, InvoiceItem>
|
||||
implements IInvoiceItemMapper
|
||||
{
|
||||
protected toDomainMappingImpl(
|
||||
source: InvoiceItem_Model,
|
||||
params: { sourceParent: Invoice_Model },
|
||||
params: { sourceParent: InvoiceModel }
|
||||
): InvoiceItem {
|
||||
const { sourceParent } = params;
|
||||
const id = this.mapsValue(source, "item_id", UniqueID.create);
|
||||
@ -50,7 +30,7 @@ class InvoiceItemMapper
|
||||
amount: unit_price,
|
||||
currencyCode: sourceParent.invoice_currency,
|
||||
precision: 4,
|
||||
}),
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
@ -65,7 +45,7 @@ class InvoiceItemMapper
|
||||
|
||||
protected toPersistenceMappingImpl(
|
||||
source: InvoiceItem,
|
||||
params: { index: number; sourceParent: Invoice },
|
||||
params: { index: number; sourceParent: Invoice }
|
||||
): TCreationInvoiceItem_Model {
|
||||
const { index, sourceParent } = params;
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
import { UniqueID, UtcDate } from "@common/domain";
|
||||
import { Result } from "@common/helpers";
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "@common/infrastructure/sequelize/sequelize-mapper";
|
||||
import { Invoice, InvoiceNumber, InvoiceSerie, InvoiceStatus } from "@contexts/invoices/domain/";
|
||||
import { InvoiceCreationAttributes, InvoiceModel } from "../sequelize";
|
||||
|
||||
export interface IInvoiceMapper
|
||||
extends ISequelizeMapper<InvoiceModel, InvoiceCreationAttributes, Invoice> {}
|
||||
|
||||
export class InvoiceMapper
|
||||
extends SequelizeMapper<InvoiceModel, InvoiceCreationAttributes, Invoice>
|
||||
implements IInvoiceMapper
|
||||
{
|
||||
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 subtotalOrError = MoneyValue.create({
|
||||
amount: source.subtotal,
|
||||
scale: 2,
|
||||
currency_code: source.invoice_currency,
|
||||
});
|
||||
|
||||
const totalOrError = MoneyValue.create({
|
||||
amount: source.total,
|
||||
scale: 2,
|
||||
currency_code: source.invoice_currency,
|
||||
});*/
|
||||
|
||||
const result = Result.combine([
|
||||
idOrError,
|
||||
statusOrError,
|
||||
invoiceSeriesOrError,
|
||||
invoiceNumberOrError,
|
||||
issueDateOrError,
|
||||
operationDateOrError,
|
||||
//subtotalOrError,
|
||||
//totalOrError,
|
||||
]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.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,
|
||||
|
||||
//currency: source.invoice_currency,
|
||||
//subtotal: subtotalOrError.data,
|
||||
//total: totalOrError.data,
|
||||
},
|
||||
idOrError.data
|
||||
);
|
||||
}
|
||||
|
||||
public mapToPersistence(source: Invoice, params?: MapperParamsType): InvoiceCreationAttributes {
|
||||
const subtotal = source.calculateSubtotal();
|
||||
const total = source.calculateTotal();
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const invoiceMapper: InvoiceMapper = new InvoiceMapper();
|
||||
export { invoiceMapper };
|
||||
@ -1,20 +1,14 @@
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
SequelizeMapper,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
|
||||
import { Name, TINNumber, UniqueID } from "@shared/contexts";
|
||||
import {
|
||||
IInvoiceParticipantProps,
|
||||
IInvoiceCustomerProps,
|
||||
Invoice,
|
||||
InvoiceParticipant,
|
||||
InvoiceCustomer,
|
||||
InvoiceParticipantBillingAddress,
|
||||
InvoiceParticipantShippingAddress,
|
||||
} from "../../domain";
|
||||
import { IInvoicingContext } from "../InvoicingContext";
|
||||
import {
|
||||
InvoiceParticipant_Model,
|
||||
TCreationInvoiceParticipant_Model,
|
||||
} from "../sequelize";
|
||||
import { InvoiceParticipant_Model, TCreationInvoiceParticipant_Model } from "../sequelize";
|
||||
import {
|
||||
IInvoiceParticipantAddressMapper,
|
||||
createInvoiceParticipantAddressMapper,
|
||||
@ -24,11 +18,11 @@ export interface IInvoiceParticipantMapper
|
||||
extends ISequelizeMapper<
|
||||
InvoiceParticipant_Model,
|
||||
TCreationInvoiceParticipant_Model,
|
||||
InvoiceParticipant
|
||||
InvoiceCustomer
|
||||
> {}
|
||||
|
||||
export const createInvoiceParticipantMapper = (
|
||||
context: IInvoicingContext,
|
||||
context: IInvoicingContext
|
||||
): IInvoiceParticipantMapper =>
|
||||
new InvoiceParticipantMapper({
|
||||
context,
|
||||
@ -39,7 +33,7 @@ class InvoiceParticipantMapper
|
||||
extends SequelizeMapper<
|
||||
InvoiceParticipant_Model,
|
||||
TCreationInvoiceParticipant_Model,
|
||||
InvoiceParticipant
|
||||
InvoiceCustomer
|
||||
>
|
||||
implements IInvoiceParticipantMapper
|
||||
{
|
||||
@ -66,24 +60,20 @@ class InvoiceParticipantMapper
|
||||
}
|
||||
*/
|
||||
const billingAddress = source.billingAddress
|
||||
? ((
|
||||
this.props.addressMapper as IInvoiceParticipantAddressMapper
|
||||
).mapToDomain(
|
||||
? ((this.props.addressMapper as IInvoiceParticipantAddressMapper).mapToDomain(
|
||||
source.billingAddress,
|
||||
params,
|
||||
params
|
||||
) as InvoiceParticipantBillingAddress)
|
||||
: undefined;
|
||||
|
||||
const shippingAddress = source.shippingAddress
|
||||
? ((
|
||||
this.props.addressMapper as IInvoiceParticipantAddressMapper
|
||||
).mapToDomain(
|
||||
? ((this.props.addressMapper as IInvoiceParticipantAddressMapper).mapToDomain(
|
||||
source.shippingAddress,
|
||||
params,
|
||||
params
|
||||
) as InvoiceParticipantShippingAddress)
|
||||
: undefined;
|
||||
|
||||
const props: IInvoiceParticipantProps = {
|
||||
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),
|
||||
@ -93,7 +83,7 @@ class InvoiceParticipantMapper
|
||||
};
|
||||
|
||||
const id = this.mapsValue(source, "participant_id", UniqueID.create);
|
||||
const participantOrError = InvoiceParticipant.create(props, id);
|
||||
const participantOrError = InvoiceCustomer.create(props, id);
|
||||
|
||||
if (participantOrError.isFailure) {
|
||||
throw participantOrError.error;
|
||||
@ -103,8 +93,8 @@ class InvoiceParticipantMapper
|
||||
}
|
||||
|
||||
protected toPersistenceMappingImpl(
|
||||
source: InvoiceParticipant,
|
||||
params: { sourceParent: Invoice },
|
||||
source: InvoiceCustomer,
|
||||
params: { sourceParent: Invoice }
|
||||
): TCreationInvoiceParticipant_Model {
|
||||
const { sourceParent } = params;
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
SequelizeMapper,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
|
||||
import {
|
||||
City,
|
||||
Country,
|
||||
@ -15,7 +12,7 @@ import {
|
||||
} from "@shared/contexts";
|
||||
import {
|
||||
IInvoiceParticipantAddressProps,
|
||||
InvoiceParticipant,
|
||||
InvoiceCustomer,
|
||||
InvoiceParticipantAddress,
|
||||
} from "../../domain";
|
||||
import { IInvoicingContext } from "../InvoicingContext";
|
||||
@ -33,8 +30,7 @@ export interface IInvoiceParticipantAddressMapper
|
||||
|
||||
export const createInvoiceParticipantAddressMapper = (
|
||||
context: IInvoicingContext
|
||||
): IInvoiceParticipantAddressMapper =>
|
||||
new InvoiceParticipantAddressMapper({ context });
|
||||
): IInvoiceParticipantAddressMapper => new InvoiceParticipantAddressMapper({ context });
|
||||
|
||||
class InvoiceParticipantAddressMapper
|
||||
extends SequelizeMapper<
|
||||
@ -44,10 +40,7 @@ class InvoiceParticipantAddressMapper
|
||||
>
|
||||
implements IInvoiceParticipantAddressMapper
|
||||
{
|
||||
protected toDomainMappingImpl(
|
||||
source: InvoiceParticipantAddress_Model,
|
||||
params: any
|
||||
) {
|
||||
protected toDomainMappingImpl(source: InvoiceParticipantAddress_Model, params: any) {
|
||||
const id = this.mapsValue(source, "address_id", UniqueID.create);
|
||||
|
||||
const props: IInvoiceParticipantAddressProps = {
|
||||
@ -73,7 +66,7 @@ class InvoiceParticipantAddressMapper
|
||||
|
||||
protected toPersistenceMappingImpl(
|
||||
source: InvoiceParticipantAddress,
|
||||
params: { sourceParent: InvoiceParticipant }
|
||||
params: { sourceParent: InvoiceCustomer }
|
||||
) {
|
||||
const { sourceParent } = params;
|
||||
|
||||
@ -8,10 +8,7 @@ import {
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
import {
|
||||
ContactAddress_Model,
|
||||
TCreationContactAddress_Attributes,
|
||||
} from "./contactAddress.model";
|
||||
import { ContactAddress_Model, TCreationContactAddress_Attributes } from "./contactAddress.mo.del";
|
||||
|
||||
export type TCreationContact_Model = InferCreationAttributes<
|
||||
Contact_Model,
|
||||
@ -22,14 +19,8 @@ export type TCreationContact_Model = InferCreationAttributes<
|
||||
};
|
||||
|
||||
export class Contact_Model extends Model<
|
||||
InferAttributes<
|
||||
Contact_Model,
|
||||
{ omit: "shippingAddress" | "billingAddress" }
|
||||
>,
|
||||
InferCreationAttributes<
|
||||
Contact_Model,
|
||||
{ omit: "shippingAddress" | "billingAddress" }
|
||||
>
|
||||
InferAttributes<Contact_Model, { omit: "shippingAddress" | "billingAddress" }>,
|
||||
InferCreationAttributes<Contact_Model, { omit: "shippingAddress" | "billingAddress" }>
|
||||
> {
|
||||
// To avoid table creation
|
||||
static async sync(): Promise<any> {
|
||||
@ -8,7 +8,7 @@ import {
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { Contact_Model } from "./contact.model";
|
||||
import { Contact_Model } from "./contact.mo.del";
|
||||
|
||||
export type TCreationContactAddress_Attributes = InferCreationAttributes<
|
||||
ContactAddress_Model,
|
||||
@ -1,4 +1,4 @@
|
||||
import { IInvoiceRepository } from "@contexts/invoicing/domain";
|
||||
import { IInvoiceRepository } from "@contexts/invoices/domain";
|
||||
import { invoiceRepository } from "./invoice.repository";
|
||||
|
||||
export * from "./invoice.model";
|
||||
@ -7,16 +7,28 @@ import {
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { Invoice_Model } from "./invoice.model";
|
||||
import { InvoiceModel } from "./invoice.model";
|
||||
|
||||
export type TCreationInvoiceItem_Model = InferCreationAttributes<
|
||||
InvoiceItem_Model,
|
||||
{ omit: "invoice" }
|
||||
{
|
||||
/*omit: "invoice"*/
|
||||
}
|
||||
>;
|
||||
|
||||
export class InvoiceItem_Model extends Model<
|
||||
InferAttributes<InvoiceItem_Model, { omit: "invoice" }>,
|
||||
InferCreationAttributes<InvoiceItem_Model, { omit: "invoice" }>
|
||||
InferAttributes<
|
||||
InvoiceItem_Model,
|
||||
{
|
||||
/*omit: "invoice"*/
|
||||
}
|
||||
>,
|
||||
InferCreationAttributes<
|
||||
InvoiceItem_Model,
|
||||
{
|
||||
/*omit: "invoice"*/
|
||||
}
|
||||
>
|
||||
> {
|
||||
static associate(connection: Sequelize) {
|
||||
const { Invoice_Model, InvoiceItem_Model } = connection.models;
|
||||
@ -34,12 +46,20 @@ export class InvoiceItem_Model extends Model<
|
||||
declare position: number;
|
||||
declare item_type: string;
|
||||
declare description: CreationOptional<string>;
|
||||
declare quantity: CreationOptional<number>;
|
||||
declare unit_price: CreationOptional<number>;
|
||||
declare subtotal: CreationOptional<number>;
|
||||
declare total: CreationOptional<number>;
|
||||
|
||||
declare invoice?: NonAttribute<Invoice_Model>;
|
||||
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 total_amount: CreationOptional<number>;
|
||||
declare total_scale: CreationOptional<number>;
|
||||
|
||||
declare invoice?: NonAttribute<InvoiceModel>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
@ -71,14 +91,29 @@ export default (sequelize: Sequelize) => {
|
||||
type: new DataTypes.TEXT(),
|
||||
allowNull: true,
|
||||
},
|
||||
quantity: {
|
||||
type: DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
},
|
||||
unit_price: {
|
||||
|
||||
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,
|
||||
@ -91,23 +126,37 @@ export default (sequelize: Sequelize) => {
|
||||
type: new DataTypes.DECIMAL(3, 2),
|
||||
allowNull: true,
|
||||
},*/
|
||||
subtotal: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
|
||||
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,
|
||||
},
|
||||
|
||||
/*tax_amount: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
},*/
|
||||
total: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
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",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return InvoiceItem_Model;
|
||||
@ -0,0 +1,141 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
export type InvoiceCreationAttributes = InferCreationAttributes<InvoiceModel, {}> & {};
|
||||
|
||||
export class InvoiceModel extends Model<InferAttributes<InvoiceModel>, InvoiceCreationAttributes> {
|
||||
static associate(connection: Sequelize) {
|
||||
/*const { Invoice_Model, InvoiceItem_Model, InvoiceParticipant_Model } = connection.models;
|
||||
|
||||
Invoice_Model.hasMany(InvoiceItem_Model, {
|
||||
as: "items",
|
||||
foreignKey: "invoice_id",
|
||||
onDelete: "CASCADE",
|
||||
});
|
||||
Invoice_Model.hasMany(InvoiceParticipant_Model, {
|
||||
as: "customer",
|
||||
foreignKey: "invoice_id",
|
||||
onDelete: "CASCADE",
|
||||
});*/
|
||||
}
|
||||
|
||||
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>;
|
||||
|
||||
//declare items: NonAttribute<InvoiceItem_Model[]>;
|
||||
//declare customer: NonAttribute<InvoiceParticipant_Model[]>;
|
||||
}
|
||||
|
||||
export default (sequelize: 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.DATE(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
operation_date: {
|
||||
type: new DataTypes.DATE(),
|
||||
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,
|
||||
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;
|
||||
};
|
||||
@ -7,11 +7,11 @@ import {
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { Invoice_Model } from "./invoice.model";
|
||||
import { InvoiceModel } from "./invoice.model";
|
||||
import {
|
||||
InvoiceParticipantAddress_Model,
|
||||
TCreationInvoiceParticipantAddress_Model,
|
||||
} from "./invoiceParticipantAddress.model";
|
||||
} from "./invoiceParticipantAddress.mo.del";
|
||||
|
||||
export type TCreationInvoiceParticipant_Model = InferCreationAttributes<
|
||||
InvoiceParticipant_Model,
|
||||
@ -32,11 +32,8 @@ export class InvoiceParticipant_Model extends Model<
|
||||
>
|
||||
> {
|
||||
static associate(connection: Sequelize) {
|
||||
const {
|
||||
Invoice_Model,
|
||||
InvoiceParticipantAddress_Model,
|
||||
InvoiceParticipant_Model,
|
||||
} = connection.models;
|
||||
const { Invoice_Model, InvoiceParticipantAddress_Model, InvoiceParticipant_Model } =
|
||||
connection.models;
|
||||
|
||||
InvoiceParticipant_Model.belongsTo(Invoice_Model, {
|
||||
as: "invoice",
|
||||
@ -67,7 +64,7 @@ export class InvoiceParticipant_Model extends Model<
|
||||
declare shippingAddress?: NonAttribute<InvoiceParticipantAddress_Model>;
|
||||
declare billingAddress?: NonAttribute<InvoiceParticipantAddress_Model>;
|
||||
|
||||
declare invoice?: NonAttribute<Invoice_Model>;
|
||||
declare invoice?: NonAttribute<InvoiceModel>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
@ -102,7 +99,7 @@ export default (sequelize: Sequelize) => {
|
||||
sequelize,
|
||||
tableName: "invoice_participants",
|
||||
timestamps: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return InvoiceParticipant_Model;
|
||||
@ -7,7 +7,7 @@ import {
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { InvoiceParticipant_Model } from "./invoiceParticipant.model";
|
||||
import { InvoiceParticipant_Model } from "./invoiceParticipant.mo.del";
|
||||
|
||||
export type TCreationInvoiceParticipantAddress_Model = InferCreationAttributes<
|
||||
InvoiceParticipantAddress_Model,
|
||||
@ -16,14 +16,10 @@ export type TCreationInvoiceParticipantAddress_Model = InferCreationAttributes<
|
||||
|
||||
export class InvoiceParticipantAddress_Model extends Model<
|
||||
InferAttributes<InvoiceParticipantAddress_Model, { omit: "participant" }>,
|
||||
InferCreationAttributes<
|
||||
InvoiceParticipantAddress_Model,
|
||||
{ omit: "participant" }
|
||||
>
|
||||
InferCreationAttributes<InvoiceParticipantAddress_Model, { omit: "participant" }>
|
||||
> {
|
||||
static associate(connection: Sequelize) {
|
||||
const { InvoiceParticipantAddress_Model, InvoiceParticipant_Model } =
|
||||
connection.models;
|
||||
const { InvoiceParticipantAddress_Model, InvoiceParticipant_Model } = connection.models;
|
||||
InvoiceParticipantAddress_Model.belongsTo(InvoiceParticipant_Model, {
|
||||
as: "participant",
|
||||
foreignKey: "participant_id",
|
||||
@ -91,7 +87,7 @@ export default (sequelize: Sequelize) => {
|
||||
{
|
||||
sequelize,
|
||||
tableName: "invoice_participant_addresses",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return InvoiceParticipantAddress_Model;
|
||||
@ -0,0 +1,45 @@
|
||||
import { UniqueID } from "@common/domain";
|
||||
import { ExpressController } from "@common/presentation";
|
||||
import { CreateInvoiceUseCase } from "../../../application";
|
||||
import { ICreateInvoiceRequestDTO } from "../../dto";
|
||||
import { ICreateInvoicePresenter } from "./presenter";
|
||||
|
||||
export class CreateInvoiceController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly createInvoice: CreateInvoiceUseCase,
|
||||
private readonly presenter: ICreateInvoicePresenter
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const createDTO: ICreateInvoiceRequestDTO = this.req.body;
|
||||
|
||||
// Validar ID
|
||||
const invoiceIdOrError = UniqueID.create(createDTO.id);
|
||||
if (invoiceIdOrError.isFailure) return this.invalidInputError("Invoice ID not valid");
|
||||
|
||||
const invoiceOrError = await this.createInvoice.execute(invoiceIdOrError.data, createDTO);
|
||||
|
||||
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,16 @@
|
||||
import { SequelizeTransactionManager } from "@common/infrastructure";
|
||||
import { CreateInvoiceUseCase } from "@contexts/invoices/application/create-invoice.use-case";
|
||||
import { InvoiceService } from "@contexts/invoices/domain";
|
||||
import { invoiceRepository } from "@contexts/invoices/intrastructure";
|
||||
import { CreateInvoiceController } from "./create-invoice.controller";
|
||||
import { createInvoicePresenter } from "./presenter";
|
||||
|
||||
export const buildCreateInvoiceController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const invoiceService = new InvoiceService(invoiceRepository);
|
||||
|
||||
const useCase = new CreateInvoiceUseCase(invoiceService, transactionManager);
|
||||
const presenter = createInvoicePresenter;
|
||||
|
||||
return new CreateInvoiceController(useCase, presenter);
|
||||
};
|
||||
@ -4,16 +4,16 @@ import { ICollection, IMoney_Response_DTO } from "@shared/contexts";
|
||||
|
||||
export const invoiceItemPresenter = (
|
||||
items: ICollection<InvoiceItem>,
|
||||
context: IInvoicingContext,
|
||||
context: IInvoicingContext
|
||||
) =>
|
||||
items.totalCount > 0
|
||||
? items.items.map((item: InvoiceItem) => ({
|
||||
description: item.description.toString(),
|
||||
quantity: item.quantity.toString(),
|
||||
unit_measure: "",
|
||||
unit_price: item.unitPrice.toObject() as IMoney_Response_DTO,
|
||||
subtotal: item.calculateSubtotal().toObject() as IMoney_Response_DTO,
|
||||
tax_amount: item.calculateTaxAmount().toObject() as IMoney_Response_DTO,
|
||||
total: item.calculateTotal().toObject() as IMoney_Response_DTO,
|
||||
unit_price: item.unitPrice.toPrimitive() as IMoney_Response_DTO,
|
||||
subtotal: item.calculateSubtotal().toPrimitive() as IMoney_Response_DTO,
|
||||
tax_amount: item.calculateTaxAmount().toPrimitive() as IMoney_Response_DTO,
|
||||
total: item.calculateTotal().toPrimitive() as IMoney_Response_DTO,
|
||||
}))
|
||||
: [];
|
||||
@ -0,0 +1,28 @@
|
||||
import { Invoice } from "@contexts/invoices/domain";
|
||||
import { ICreateInvoiceResponseDTO } from "../../../dto";
|
||||
|
||||
export interface ICreateInvoicePresenter {
|
||||
toDTO: (invoice: Invoice) => ICreateInvoiceResponseDTO;
|
||||
}
|
||||
|
||||
export const createInvoicePresenter: ICreateInvoicePresenter = {
|
||||
toDTO: (invoice: Invoice): ICreateInvoiceResponseDTO => ({
|
||||
id: invoice.id.toString(),
|
||||
|
||||
invoice_status: invoice.status.toString(),
|
||||
invoice_number: invoice.invoiceNumber.toString(),
|
||||
invoice_series: invoice.invoiceSeries.toString(),
|
||||
issue_date: invoice.issueDate.toDateString(),
|
||||
operation_date: invoice.operationDate.toDateString(),
|
||||
language_code: "es",
|
||||
currency: invoice.currency,
|
||||
subtotal: invoice.calculateSubtotal().toPrimitive(),
|
||||
total: invoice.calculateTotal().toPrimitive(),
|
||||
|
||||
//sender: {}, //await InvoiceParticipantPresenter(invoice.senderId, context),
|
||||
|
||||
//customer: InvoiceParticipantPresenter(invoice.recipient, context),
|
||||
|
||||
//items: invoiceItemPresenter(invoice.items, context),
|
||||
}),
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./create-invoice.presenter";
|
||||
@ -0,0 +1,12 @@
|
||||
import { ExpressController } from "@common/presentation";
|
||||
import { DeleteInvoiceUseCase } from "@contexts/invoices/application";
|
||||
|
||||
export class DeleteInvoiceController extends ExpressController {
|
||||
public constructor(private readonly deleteInvoice: DeleteInvoiceUseCase) {
|
||||
super();
|
||||
}
|
||||
|
||||
async executeImpl(): Promise<any> {
|
||||
return this.noContent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import { SequelizeTransactionManager } from "@common/infrastructure";
|
||||
import { DeleteInvoiceUseCase } from "@contexts/invoices/application";
|
||||
import { InvoiceService } from "@contexts/invoices/domain";
|
||||
import { invoiceRepository } from "@contexts/invoices/intrastructure";
|
||||
import { DeleteInvoiceController } from "./delete-invoice.controller";
|
||||
|
||||
export const buildDeleteInvoiceController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const invoiceService = new InvoiceService(invoiceRepository);
|
||||
|
||||
const useCase = new DeleteInvoiceUseCase(invoiceService, transactionManager);
|
||||
|
||||
return new DeleteInvoiceController(useCase);
|
||||
};
|
||||
@ -0,0 +1,44 @@
|
||||
import { UniqueID } from "@common/domain";
|
||||
import { ExpressController } from "@common/presentation";
|
||||
import { GetInvoiceUseCase } from "@contexts/invoices/application";
|
||||
import { IGetInvoicePresenter } from "./presenter";
|
||||
|
||||
export class GetInvoiceController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly getInvoice: GetInvoiceUseCase,
|
||||
private readonly presenter: IGetInvoicePresenter
|
||||
) {
|
||||
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.getInvoice.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,16 @@
|
||||
import { SequelizeTransactionManager } from "@common/infrastructure";
|
||||
import { GetInvoiceUseCase } from "@contexts/invoices/application";
|
||||
import { InvoiceService } from "@contexts/invoices/domain";
|
||||
import { invoiceRepository } from "@contexts/invoices/intrastructure";
|
||||
import { GetInvoiceController } from "./get-invoice.controller";
|
||||
import { getInvoicePresenter } from "./presenter";
|
||||
|
||||
export const buildGetInvoiceController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const invoiceService = new InvoiceService(invoiceRepository);
|
||||
|
||||
const useCase = new GetInvoiceUseCase(invoiceService, transactionManager);
|
||||
const presenter = getInvoicePresenter;
|
||||
|
||||
return new GetInvoiceController(useCase, presenter);
|
||||
};
|
||||
@ -4,16 +4,16 @@ import { ICollection, IMoney_Response_DTO } from "@shared/contexts";
|
||||
|
||||
export const invoiceItemPresenter = (
|
||||
items: ICollection<InvoiceItem>,
|
||||
context: IInvoicingContext,
|
||||
context: IInvoicingContext
|
||||
) =>
|
||||
items.totalCount > 0
|
||||
? items.items.map((item: InvoiceItem) => ({
|
||||
description: item.description.toString(),
|
||||
quantity: item.quantity.toString(),
|
||||
unit_measure: "",
|
||||
unit_price: item.unitPrice.toObject() as IMoney_Response_DTO,
|
||||
subtotal: item.calculateSubtotal().toObject() as IMoney_Response_DTO,
|
||||
tax_amount: item.calculateTaxAmount().toObject() as IMoney_Response_DTO,
|
||||
total: item.calculateTotal().toObject() as IMoney_Response_DTO,
|
||||
unit_price: item.unitPrice.toPrimitive() as IMoney_Response_DTO,
|
||||
subtotal: item.calculateSubtotal().toPrimitive() as IMoney_Response_DTO,
|
||||
tax_amount: item.calculateTaxAmount().toPrimitive() as IMoney_Response_DTO,
|
||||
total: item.calculateTotal().toPrimitive() as IMoney_Response_DTO,
|
||||
}))
|
||||
: [];
|
||||
@ -0,0 +1,46 @@
|
||||
import { Invoice } from "@contexts/invoices/domain";
|
||||
import { IGetInvoiceResponseDTO } from "../../../dto";
|
||||
|
||||
export interface IGetInvoicePresenter {
|
||||
toDTO: (invoice: Invoice) => IGetInvoiceResponseDTO;
|
||||
}
|
||||
|
||||
export const getInvoicePresenter: IGetInvoicePresenter = {
|
||||
toDTO: (invoice: Invoice): IGetInvoiceResponseDTO => ({
|
||||
id: invoice.id.toPrimitive(),
|
||||
|
||||
invoice_status: invoice.status.toString(),
|
||||
invoice_number: invoice.invoiceNumber.toString(),
|
||||
invoice_series: invoice.invoiceSeries.toString(),
|
||||
issue_date: invoice.issueDate.toISOString(),
|
||||
operation_date: invoice.operationDate.toISOString(),
|
||||
language_code: "ES",
|
||||
currency: invoice.currency,
|
||||
subtotal: invoice.calculateSubtotal().toPrimitive(),
|
||||
total: invoice.calculateTotal().toPrimitive(),
|
||||
|
||||
//sender: {}, //await InvoiceParticipantPresenter(invoice.senderId, context),
|
||||
|
||||
/*recipient: await InvoiceParticipantPresenter(invoice.recipient, context),
|
||||
items: invoiceItemPresenter(invoice.items, context),
|
||||
|
||||
payment_term: {
|
||||
payment_type: "",
|
||||
due_date: "",
|
||||
},
|
||||
|
||||
due_amount: {
|
||||
currency: invoice.currency.toString(),
|
||||
precision: 2,
|
||||
amount: 0,
|
||||
},
|
||||
|
||||
custom_fields: [],
|
||||
|
||||
metadata: {
|
||||
create_time: "",
|
||||
last_updated_time: "",
|
||||
delete_time: "",
|
||||
},*/
|
||||
}),
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user