diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 31e87534..a8608ac6 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -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(); }); diff --git a/apps/server/src/common/domain/value-objects/email-address.ts b/apps/server/src/common/domain/value-objects/email-address.ts index 10648159..6c26238c 100644 --- a/apps/server/src/common/domain/value-objects/email-address.ts +++ b/apps/server/src/common/domain/value-objects/email-address.ts @@ -49,7 +49,7 @@ export class EmailAddress extends ValueObject { return this.props.value; } - toString(): string { + toPrimitive() { return this.getValue(); } } diff --git a/apps/server/src/common/domain/value-objects/money-value.ts b/apps/server/src/common/domain/value-objects/money-value.ts index 300bdb08..ec1dea5b 100644 --- a/apps/server/src/common/domain/value-objects/money-value.ts +++ b/apps/server/src/common/domain/value-objects/money-value.ts @@ -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 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 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 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; diff --git a/apps/server/src/common/domain/value-objects/name.ts b/apps/server/src/common/domain/value-objects/name.ts index 0924d207..005502dc 100644 --- a/apps/server/src/common/domain/value-objects/name.ts +++ b/apps/server/src/common/domain/value-objects/name.ts @@ -56,7 +56,7 @@ export class Name extends ValueObject { return this.props.value; } - toString(): string { + toPrimitive() { return this.getValue(); } } diff --git a/apps/server/src/common/domain/value-objects/percentage.ts b/apps/server/src/common/domain/value-objects/percentage.ts index 94f3bb43..eecc2d8d 100644 --- a/apps/server/src/common/domain/value-objects/percentage.ts +++ b/apps/server/src/common/domain/value-objects/percentage.ts @@ -73,6 +73,10 @@ export class Percentage extends ValueObject implements IPercen return this.props; } + toPrimitive() { + return this.getValue(); + } + toNumber(): number { return this.amount / Math.pow(10, this.scale); } diff --git a/apps/server/src/common/domain/value-objects/phone-number.ts b/apps/server/src/common/domain/value-objects/phone-number.ts index 33b5df9a..777b2ed5 100644 --- a/apps/server/src/common/domain/value-objects/phone-number.ts +++ b/apps/server/src/common/domain/value-objects/phone-number.ts @@ -41,7 +41,7 @@ export class PhoneNumber extends ValueObject { return this.props.value; } - toString(): string { + toPrimitive(): string { return this.getValue(); } diff --git a/apps/server/src/common/domain/value-objects/postal-address.ts b/apps/server/src/common/domain/value-objects/postal-address.ts index acea64d6..6505dffa 100644 --- a/apps/server/src/common/domain/value-objects/postal-address.ts +++ b/apps/server/src/common/domain/value-objects/postal-address.ts @@ -99,6 +99,10 @@ export class PostalAddress extends ValueObject { 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}`; } diff --git a/apps/server/src/common/domain/value-objects/quantity.ts b/apps/server/src/common/domain/value-objects/quantity.ts index ed5e25da..f07b9216 100644 --- a/apps/server/src/common/domain/value-objects/quantity.ts +++ b/apps/server/src/common/domain/value-objects/quantity.ts @@ -17,6 +17,10 @@ interface IQuantity { toNumber(): number; toString(): string; + isZero(): boolean; + isPositive(): boolean; + isNegative(): boolean; + increment(anotherQuantity?: Quantity): Result; decrement(anotherQuantity?: Quantity): Result; hasSameScale(otherQuantity: Quantity): boolean; @@ -58,6 +62,10 @@ export class Quantity extends ValueObject 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 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 { if (!anotherQuantity) { return Quantity.create({ diff --git a/apps/server/src/common/domain/value-objects/slug.ts b/apps/server/src/common/domain/value-objects/slug.ts index c832452b..3d6e9633 100644 --- a/apps/server/src/common/domain/value-objects/slug.ts +++ b/apps/server/src/common/domain/value-objects/slug.ts @@ -43,7 +43,7 @@ export class Slug extends ValueObject { return this.props.value; } - toString(): string { + toPrimitive(): string { return this.getValue(); } } diff --git a/apps/server/src/common/domain/value-objects/tin-number.ts b/apps/server/src/common/domain/value-objects/tin-number.ts index 286c1538..e6f6b602 100644 --- a/apps/server/src/common/domain/value-objects/tin-number.ts +++ b/apps/server/src/common/domain/value-objects/tin-number.ts @@ -45,7 +45,7 @@ export class TINNumber extends ValueObject { return this.props.value; } - toString(): string { + toPrimitive(): string { return this.props.value; } } diff --git a/apps/server/src/common/domain/value-objects/unique-id.ts b/apps/server/src/common/domain/value-objects/unique-id.ts index 74116189..eab93bee 100644 --- a/apps/server/src/common/domain/value-objects/unique-id.ts +++ b/apps/server/src/common/domain/value-objects/unique-id.ts @@ -36,7 +36,7 @@ export class UniqueID extends ValueObject { return this.props; } - toString(): string { + toPrimitive(): string { return this.props; } } diff --git a/apps/server/src/common/domain/value-objects/utc-date.ts b/apps/server/src/common/domain/value-objects/utc-date.ts index c6472588..72d2db9a 100644 --- a/apps/server/src/common/domain/value-objects/utc-date.ts +++ b/apps/server/src/common/domain/value-objects/utc-date.ts @@ -44,6 +44,13 @@ export class UtcDate extends ValueObject { 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). */ diff --git a/apps/server/src/common/domain/value-objects/value-object.ts b/apps/server/src/common/domain/value-objects/value-object.ts index 8804b617..d356a178 100644 --- a/apps/server/src/common/domain/value-objects/value-object.ts +++ b/apps/server/src/common/domain/value-objects/value-object.ts @@ -9,6 +9,8 @@ export abstract class ValueObject { abstract getValue(): any; + abstract toPrimitive(): any; + equals(other: ValueObject): boolean { if (!(other instanceof ValueObject)) { return false; diff --git a/apps/server/src/common/presentation/dto/error.dto.ts b/apps/server/src/common/presentation/dto/error.dto.ts new file mode 100644 index 00000000..effa8922 --- /dev/null +++ b/apps/server/src/common/presentation/dto/error.dto.ts @@ -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; + query?: Record; + body?: Record; +} + +export interface IErrorExtraDTO { + errors: Record[]; +} diff --git a/apps/server/src/common/presentation/dto/index.ts b/apps/server/src/common/presentation/dto/index.ts new file mode 100644 index 00000000..6961457b --- /dev/null +++ b/apps/server/src/common/presentation/dto/index.ts @@ -0,0 +1,2 @@ +export * from "./error.dto"; +export * from "./types.dto"; diff --git a/apps/server/src/common/presentation/dto/types.dto.ts b/apps/server/src/common/presentation/dto/types.dto.ts new file mode 100644 index 00000000..e3c67db3 --- /dev/null +++ b/apps/server/src/common/presentation/dto/types.dto.ts @@ -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; +} diff --git a/apps/server/src/common/presentation/index.ts b/apps/server/src/common/presentation/index.ts index 6b5f6511..cc1f9dc0 100644 --- a/apps/server/src/common/presentation/index.ts +++ b/apps/server/src/common/presentation/index.ts @@ -1 +1,2 @@ +export * from "./dto"; export * from "./express"; diff --git a/apps/server/src/contexts/accounts/application/get-account.use-case.ts b/apps/server/src/contexts/accounts/application/get-account.use-case.ts index c5f001ab..2a09b246 100644 --- a/apps/server/src/contexts/accounts/application/get-account.use-case.ts +++ b/apps/server/src/contexts/accounts/application/get-account.use-case.ts @@ -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 diff --git a/apps/server/src/contexts/accounts/domain/value-objects/account-status.ts b/apps/server/src/contexts/accounts/domain/value-objects/account-status.ts index cea3d6d5..afd5b435 100644 --- a/apps/server/src/contexts/accounts/domain/value-objects/account-status.ts +++ b/apps/server/src/contexts/accounts/domain/value-objects/account-status.ts @@ -53,7 +53,7 @@ export class AccountStatus extends ValueObject { return AccountStatus.create(nextStatus); } - toString(): string { + toPrimitive(): string { return this.getValue(); } } diff --git a/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts b/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts index 1f349242..3a65f0eb 100644 --- a/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts +++ b/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts @@ -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, diff --git a/apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts b/apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts index 0f6476d1..e41381f5 100644 --- a/apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts +++ b/apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts @@ -9,10 +9,7 @@ import { export type AccountCreationAttributes = InferCreationAttributes & {}; -export class AccountModel extends Model< - InferAttributes, - InferCreationAttributes -> { +export class AccountModel extends Model, AccountCreationAttributes> { // To avoid table creation /*static async sync(): Promise { return Promise.resolve(); diff --git a/apps/server/src/contexts/accounts/presentation/controllers/create-account/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/create-account/index.ts index ef5d2184..5379b52b 100644 --- a/apps/server/src/contexts/accounts/presentation/controllers/create-account/index.ts +++ b/apps/server/src/contexts/accounts/presentation/controllers/create-account/index.ts @@ -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); diff --git a/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.controller.ts b/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.controller.ts index 0dc89f71..23cc378f 100644 --- a/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.controller.ts +++ b/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.controller.ts @@ -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(); diff --git a/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.presenter.ts b/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.presenter.ts index 6c8adf4c..0db6796f 100644 --- a/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.presenter.ts +++ b/apps/server/src/contexts/accounts/presentation/controllers/get-account/get-account.presenter.ts @@ -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), diff --git a/apps/server/src/contexts/accounts/presentation/controllers/get-account/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/get-account/index.ts index 5cabe3a7..9c04d7a6 100644 --- a/apps/server/src/contexts/accounts/presentation/controllers/get-account/index.ts +++ b/apps/server/src/contexts/accounts/presentation/controllers/get-account/index.ts @@ -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); diff --git a/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts index 4441e30c..69acd22f 100644 --- a/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts +++ b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts @@ -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); diff --git a/apps/server/src/contexts/accounts/presentation/controllers/update-account/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/update-account/index.ts index 9081a2d7..6bb45e0c 100644 --- a/apps/server/src/contexts/accounts/presentation/controllers/update-account/index.ts +++ b/apps/server/src/contexts/accounts/presentation/controllers/update-account/index.ts @@ -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); diff --git a/apps/server/src/contexts/auth/application/register/register.use-case.ts b/apps/server/src/contexts/auth/application/register/register.use-case.ts index e3d4d3da..2fecc868 100644 --- a/apps/server/src/contexts/auth/application/register/register.use-case.ts +++ b/apps/server/src/contexts/auth/application/register/register.use-case.ts @@ -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); + } }); } } diff --git a/apps/server/src/contexts/auth/presentation/controllers/listUsers/index.ts b/apps/server/src/contexts/auth/presentation/controllers/listUsers/index.ts index 9b390787..6ffabfa7 100644 --- a/apps/server/src/contexts/auth/presentation/controllers/listUsers/index.ts +++ b/apps/server/src/contexts/auth/presentation/controllers/listUsers/index.ts @@ -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); diff --git a/apps/server/src/contexts/auth/presentation/controllers/login/index.ts b/apps/server/src/contexts/auth/presentation/controllers/login/index.ts index bb1aead2..ba6e8531 100644 --- a/apps/server/src/contexts/auth/presentation/controllers/login/index.ts +++ b/apps/server/src/contexts/auth/presentation/controllers/login/index.ts @@ -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); diff --git a/apps/server/src/contexts/auth/presentation/controllers/logout/index.ts b/apps/server/src/contexts/auth/presentation/controllers/logout/index.ts index ad42ca9c..9eee68c4 100644 --- a/apps/server/src/contexts/auth/presentation/controllers/logout/index.ts +++ b/apps/server/src/contexts/auth/presentation/controllers/logout/index.ts @@ -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); diff --git a/apps/server/src/contexts/auth/presentation/controllers/refreshToken/index.ts b/apps/server/src/contexts/auth/presentation/controllers/refreshToken/index.ts index 1ca699c9..0bd45b4c 100644 --- a/apps/server/src/contexts/auth/presentation/controllers/refreshToken/index.ts +++ b/apps/server/src/contexts/auth/presentation/controllers/refreshToken/index.ts @@ -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); diff --git a/apps/server/src/contexts/auth/presentation/controllers/register/index.ts b/apps/server/src/contexts/auth/presentation/controllers/register/index.ts index 691099ca..2d6750d3 100644 --- a/apps/server/src/contexts/auth/presentation/controllers/register/index.ts +++ b/apps/server/src/contexts/auth/presentation/controllers/register/index.ts @@ -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); diff --git a/apps/server/src/contexts/invoicing/application/create-invoice.use-case.ts b/apps/server/src/contexts/invoices/application/create-invoice.use-case.ts similarity index 60% rename from apps/server/src/contexts/invoicing/application/create-invoice.use-case.ts rename to apps/server/src/contexts/invoices/application/create-invoice.use-case.ts index 539c3681..89605d40 100644 --- a/apps/server/src/contexts/invoicing/application/create-invoice.use-case.ts +++ b/apps/server/src/contexts/invoices/application/create-invoice.use-case.ts @@ -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 { 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( - invoiceDTO.items?.map( + dto.items?.map( (item) => InvoiceSimpleItem.create({ description: Description.create(item.description).object, @@ -104,6 +142,6 @@ export class CreateInvoiceUseCase { items, }, invoiceId - ); + );*/ } } diff --git a/apps/server/src/contexts/invoicing/application/delete-invoice.use-case.ts b/apps/server/src/contexts/invoices/application/delete-invoice.use-case.ts similarity index 100% rename from apps/server/src/contexts/invoicing/application/delete-invoice.use-case.ts rename to apps/server/src/contexts/invoices/application/delete-invoice.use-case.ts diff --git a/apps/server/src/contexts/invoicing/application/get-invoice.use-case.ts b/apps/server/src/contexts/invoices/application/get-invoice.use-case.ts similarity index 100% rename from apps/server/src/contexts/invoicing/application/get-invoice.use-case.ts rename to apps/server/src/contexts/invoices/application/get-invoice.use-case.ts diff --git a/apps/server/src/contexts/invoicing/application/index.ts b/apps/server/src/contexts/invoices/application/index.ts similarity index 100% rename from apps/server/src/contexts/invoicing/application/index.ts rename to apps/server/src/contexts/invoices/application/index.ts diff --git a/apps/server/src/contexts/invoicing/application/list-invoices.use-case.ts b/apps/server/src/contexts/invoices/application/list-invoices.use-case.ts similarity index 92% rename from apps/server/src/contexts/invoicing/application/list-invoices.use-case.ts rename to apps/server/src/contexts/invoices/application/list-invoices.use-case.ts index e70a8fac..beb50526 100644 --- a/apps/server/src/contexts/invoicing/application/list-invoices.use-case.ts +++ b/apps/server/src/contexts/invoices/application/list-invoices.use-case.ts @@ -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( diff --git a/apps/server/src/contexts/invoicing/application/services/index.ts b/apps/server/src/contexts/invoices/application/services/index.ts similarity index 100% rename from apps/server/src/contexts/invoicing/application/services/index.ts rename to apps/server/src/contexts/invoices/application/services/index.ts diff --git a/apps/server/src/contexts/invoicing/application/services/participantAddressFinder.ts b/apps/server/src/contexts/invoices/application/services/participantAddressFinder.ts similarity index 100% rename from apps/server/src/contexts/invoicing/application/services/participantAddressFinder.ts rename to apps/server/src/contexts/invoices/application/services/participantAddressFinder.ts diff --git a/apps/server/src/contexts/invoicing/application/services/participantFinder.ts b/apps/server/src/contexts/invoices/application/services/participantFinder.ts similarity index 74% rename from apps/server/src/contexts/invoicing/application/services/participantFinder.ts rename to apps/server/src/contexts/invoices/application/services/participantFinder.ts index 2fa96b00..b73ad1c2 100644 --- a/apps/server/src/contexts/invoicing/application/services/participantFinder.ts +++ b/apps/server/src/contexts/invoices/application/services/participantFinder.ts @@ -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, -): Promise => { + repository: RepositoryBuilder +): Promise => { if (!participantId || (participantId && participantId.isNull())) { return Promise.resolve(undefined); } diff --git a/apps/server/src/contexts/invoicing/application/update-invoice.use-case.ts b/apps/server/src/contexts/invoices/application/update-invoice.use-case.ts similarity index 100% rename from apps/server/src/contexts/invoicing/application/update-invoice.use-case.ts rename to apps/server/src/contexts/invoices/application/update-invoice.use-case.ts diff --git a/apps/server/src/contexts/invoicing/domain/aggregates/index.ts b/apps/server/src/contexts/invoices/domain/aggregates/index.ts similarity index 100% rename from apps/server/src/contexts/invoicing/domain/aggregates/index.ts rename to apps/server/src/contexts/invoices/domain/aggregates/index.ts diff --git a/apps/server/src/contexts/invoicing/domain/aggregates/invoice.ts b/apps/server/src/contexts/invoices/domain/aggregates/invoice.ts similarity index 69% rename from apps/server/src/contexts/invoicing/domain/aggregates/invoice.ts rename to apps/server/src/contexts/invoices/domain/aggregates/invoice.ts index 6be556ca..6061bc56 100644 --- a/apps/server/src/contexts/invoicing/domain/aggregates/invoice.ts +++ b/apps/server/src/contexts/invoices/domain/aggregates/invoice.ts @@ -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; + customer?: InvoiceCustomer; + items?: Collection; } 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; + items: InvoiceItems; calculateSubtotal: () => MoneyValue; calculateTaxTotal: () => MoneyValue; @@ -64,8 +65,14 @@ export interface IInvoice { } export class Invoice extends AggregateRoot implements IInvoice { - private _items: Collection; - protected _status: InvoiceStatus; + private _items!: Collection; + //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 { const invoice = new Invoice(props, id); @@ -97,17 +104,17 @@ export class Invoice extends AggregateRoot 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 implements IInvoice { } get status() { - return this._status; + return this.props.status; } get items() { @@ -145,7 +152,7 @@ export class Invoice extends AggregateRoot implements IInvoice { return this.props.shipTo; }*/ - get currency() { + get invoiceCurrency() { return this.props.invoiceCurrency; } @@ -167,42 +174,30 @@ export class Invoice extends AggregateRoot 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()); } } diff --git a/apps/server/src/contexts/invoices/domain/entities/index.ts b/apps/server/src/contexts/invoices/domain/entities/index.ts new file mode 100644 index 00000000..4c02277f --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/entities/index.ts @@ -0,0 +1,2 @@ +export * from "./invoice-customer"; +export * from "./invoice-items"; diff --git a/apps/server/src/contexts/invoices/domain/entities/invoice-customer/index.ts b/apps/server/src/contexts/invoices/domain/entities/invoice-customer/index.ts new file mode 100644 index 00000000..1f5a7b22 --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/entities/invoice-customer/index.ts @@ -0,0 +1,2 @@ +export * from "./invoice-address"; +export * from "./invoice-customer"; diff --git a/apps/server/src/contexts/invoices/domain/entities/invoice-customer/invoice-address.ts b/apps/server/src/contexts/invoices/domain/entities/invoice-customer/invoice-address.ts new file mode 100644 index 00000000..4bc09f98 --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/entities/invoice-customer/invoice-address.ts @@ -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 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(), + }; + } +} diff --git a/apps/server/src/contexts/invoices/domain/entities/invoice-customer/invoice-customer.ts b/apps/server/src/contexts/invoices/domain/entities/invoice-customer/invoice-customer.ts new file mode 100644 index 00000000..e875ca5d --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/entities/invoice-customer/invoice-customer.ts @@ -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 + implements IInvoiceCustomer +{ + public static create( + props: IInvoiceCustomerProps, + id?: UniqueID + ): Result { + const participant = new InvoiceCustomer(props, id); + return Result.ok(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; + } +} diff --git a/apps/server/src/contexts/invoices/domain/entities/invoice-items/index.ts b/apps/server/src/contexts/invoices/domain/entities/invoice-items/index.ts new file mode 100644 index 00000000..f95bd889 --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/entities/invoice-items/index.ts @@ -0,0 +1,2 @@ +export * from "./invoice-item"; +export * from "./invoice-items"; diff --git a/apps/server/src/contexts/invoices/domain/entities/invoice-items/invoice-item.test.ts b/apps/server/src/contexts/invoices/domain/entities/invoice-items/invoice-item.test.ts new file mode 100644 index 00000000..87fc8105 --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/entities/invoice-items/invoice-item.test.ts @@ -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) + }); +}); diff --git a/apps/server/src/contexts/invoices/domain/entities/invoice-items/invoice-item.ts b/apps/server/src/contexts/invoices/domain/entities/invoice-items/invoice-item.ts new file mode 100644 index 00000000..5a14a32a --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/entities/invoice-items/invoice-item.ts @@ -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 implements IInvoiceItem { + private _subtotalPrice!: MoneyValue; + private _totalPrice!: MoneyValue; + + public static create(props: IInvoiceItemProps): Result { + 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())); + } +} diff --git a/apps/server/src/contexts/invoices/domain/entities/invoice-items/invoice-items.ts b/apps/server/src/contexts/invoices/domain/entities/invoice-items/invoice-items.ts new file mode 100644 index 00000000..00c596cb --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/entities/invoice-items/invoice-items.ts @@ -0,0 +1,8 @@ +import { Collection } from "@common/helpers"; +import { InvoiceItem } from "./invoice-item"; + +export class InvoiceItems extends Collection { + public static create(items?: InvoiceItem[]): InvoiceItems { + return new InvoiceItems(items); + } +} diff --git a/apps/server/src/contexts/invoices/domain/index.ts b/apps/server/src/contexts/invoices/domain/index.ts new file mode 100644 index 00000000..2c5c423d --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/index.ts @@ -0,0 +1,5 @@ +export * from "./aggregates"; +export * from "./entities"; +export * from "./repositories"; +export * from "./services"; +export * from "./value-objects"; diff --git a/apps/server/src/contexts/invoicing/domain/repositories/index.ts b/apps/server/src/contexts/invoices/domain/repositories/index.ts similarity index 100% rename from apps/server/src/contexts/invoicing/domain/repositories/index.ts rename to apps/server/src/contexts/invoices/domain/repositories/index.ts diff --git a/apps/server/src/contexts/invoicing/domain/repositories/invoice-repository.interface.ts b/apps/server/src/contexts/invoices/domain/repositories/invoice-repository.interface.ts similarity index 100% rename from apps/server/src/contexts/invoicing/domain/repositories/invoice-repository.interface.ts rename to apps/server/src/contexts/invoices/domain/repositories/invoice-repository.interface.ts diff --git a/apps/server/src/contexts/invoicing/domain/services/index.ts b/apps/server/src/contexts/invoices/domain/services/index.ts similarity index 100% rename from apps/server/src/contexts/invoicing/domain/services/index.ts rename to apps/server/src/contexts/invoices/domain/services/index.ts diff --git a/apps/server/src/contexts/invoicing/domain/services/invoice-service.interface.ts b/apps/server/src/contexts/invoices/domain/services/invoice-service.interface.ts similarity index 95% rename from apps/server/src/contexts/invoicing/domain/services/invoice-service.interface.ts rename to apps/server/src/contexts/invoices/domain/services/invoice-service.interface.ts index 03ea031e..ceebc0d4 100644 --- a/apps/server/src/contexts/invoicing/domain/services/invoice-service.interface.ts +++ b/apps/server/src/contexts/invoices/domain/services/invoice-service.interface.ts @@ -18,5 +18,5 @@ export interface IInvoiceService { transaction?: any ): Promise>; - deleteInvoiceById(invoiceId: UniqueID, transaction?: any): Promise>; + deleteInvoiceById(invoiceId: UniqueID, transaction?: any): Promise>; } diff --git a/apps/server/src/contexts/invoicing/domain/services/invoice.service.ts b/apps/server/src/contexts/invoices/domain/services/invoice.service.ts similarity index 79% rename from apps/server/src/contexts/invoicing/domain/services/invoice.service.ts rename to apps/server/src/contexts/invoices/domain/services/invoice.service.ts index 59b44c64..e64854fb 100644 --- a/apps/server/src/contexts/invoicing/domain/services/invoice.service.ts +++ b/apps/server/src/contexts/invoices/domain/services/invoice.service.ts @@ -30,7 +30,7 @@ export class InvoiceService implements IInvoiceService { data: Partial, transaction?: Transaction ): Promise> { - // 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> { - // 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> { - // 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> { + return this.repo.deleteById(invoiceId, transaction); } } diff --git a/apps/server/src/contexts/invoices/domain/value-objects/index.ts b/apps/server/src/contexts/invoices/domain/value-objects/index.ts new file mode 100644 index 00000000..4169e9d8 --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/value-objects/index.ts @@ -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"; diff --git a/apps/server/src/contexts/invoices/domain/value-objects/invoice-address-type.ts b/apps/server/src/contexts/invoices/domain/value-objects/invoice-address-type.ts new file mode 100644 index 00000000..82ab14e4 --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/value-objects/invoice-address-type.ts @@ -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 { + private static readonly ALLOWED_TYPES = ["shipping", "billing"]; + + static create(value: string): Result { + 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(); + } +} diff --git a/apps/server/src/contexts/invoices/domain/value-objects/invoice-item-description.ts b/apps/server/src/contexts/invoices/domain/value-objects/invoice-item-description.ts new file mode 100644 index 00000000..e58759be --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/value-objects/invoice-item-description.ts @@ -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 { + 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, Error> { + if (!value || value.trim() === "") { + return Result.ok(Maybe.none()); + } + + return InvoiceItemDescription.create(value!).map((value) => Maybe.some(value)); + } + + getValue(): string { + return this.props.value; + } + + toString(): string { + return this.getValue(); + } +} diff --git a/apps/server/src/contexts/invoices/domain/value-objects/invoice-number.ts b/apps/server/src/contexts/invoices/domain/value-objects/invoice-number.ts new file mode 100644 index 00000000..eb436fa0 --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/value-objects/invoice-number.ts @@ -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 { + 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(); + } +} diff --git a/apps/server/src/contexts/invoices/domain/value-objects/invoice-serie.ts b/apps/server/src/contexts/invoices/domain/value-objects/invoice-serie.ts new file mode 100644 index 00000000..971e5f00 --- /dev/null +++ b/apps/server/src/contexts/invoices/domain/value-objects/invoice-serie.ts @@ -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 { + 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, Error> { + if (!value || value.trim() === "") { + return Result.ok(Maybe.none()); + } + + return InvoiceSerie.create(value!).map((value) => Maybe.some(value)); + } + + getValue(): string { + return this.props.value; + } + + toString(): string { + return this.getValue(); + } +} diff --git a/apps/server/src/contexts/invoicing/domain/value-objects/invoice-status.ts b/apps/server/src/contexts/invoices/domain/value-objects/invoice-status.ts similarity index 97% rename from apps/server/src/contexts/invoicing/domain/value-objects/invoice-status.ts rename to apps/server/src/contexts/invoices/domain/value-objects/invoice-status.ts index 19f8fb7f..bca8bc03 100644 --- a/apps/server/src/contexts/invoicing/domain/value-objects/invoice-status.ts +++ b/apps/server/src/contexts/invoices/domain/value-objects/invoice-status.ts @@ -57,6 +57,10 @@ export class InvoiceStatus extends ValueObject { return this.props.value; } + toPrimitive() { + return this.getValue(); + } + canTransitionTo(nextStatus: string): boolean { return InvoiceStatus.TRANSITIONS[this.props.value].includes(nextStatus); } diff --git a/apps/server/src/contexts/invoicing/intrastructure/Contact.repository.ts b/apps/server/src/contexts/invoices/intrastructure/Contact.repository.ts similarity index 100% rename from apps/server/src/contexts/invoicing/intrastructure/Contact.repository.ts rename to apps/server/src/contexts/invoices/intrastructure/Contact.repository.ts diff --git a/apps/server/src/contexts/invoicing/intrastructure/Invoice.repository.ts b/apps/server/src/contexts/invoices/intrastructure/Invoice.repository.ts similarity index 100% rename from apps/server/src/contexts/invoicing/intrastructure/Invoice.repository.ts rename to apps/server/src/contexts/invoices/intrastructure/Invoice.repository.ts diff --git a/apps/server/src/contexts/invoicing/intrastructure/InvoiceParticipant.repository.ts b/apps/server/src/contexts/invoices/intrastructure/InvoiceParticipant.repository.ts similarity index 87% rename from apps/server/src/contexts/invoicing/intrastructure/InvoiceParticipant.repository.ts rename to apps/server/src/contexts/invoices/intrastructure/InvoiceParticipant.repository.ts index de77b304..32e9b106 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/InvoiceParticipant.repository.ts +++ b/apps/server/src/contexts/invoices/intrastructure/InvoiceParticipant.repository.ts @@ -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 { +export class InvoiceParticipantRepository extends SequelizeRepository { protected mapper: IInvoiceParticipantMapper; public constructor(props: { diff --git a/apps/server/src/contexts/invoicing/intrastructure/InvoiceParticipantAddress.repository.ts b/apps/server/src/contexts/invoices/intrastructure/InvoiceParticipantAddress.repository.ts similarity index 100% rename from apps/server/src/contexts/invoicing/intrastructure/InvoiceParticipantAddress.repository.ts rename to apps/server/src/contexts/invoices/intrastructure/InvoiceParticipantAddress.repository.ts diff --git a/apps/server/src/contexts/invoicing/intrastructure/InvoicingContext.ts b/apps/server/src/contexts/invoices/intrastructure/InvoicingContext.ts similarity index 100% rename from apps/server/src/contexts/invoicing/intrastructure/InvoicingContext.ts rename to apps/server/src/contexts/invoices/intrastructure/InvoicingContext.ts diff --git a/apps/server/src/contexts/invoicing/intrastructure/index.ts b/apps/server/src/contexts/invoices/intrastructure/index.ts similarity index 100% rename from apps/server/src/contexts/invoicing/intrastructure/index.ts rename to apps/server/src/contexts/invoices/intrastructure/index.ts diff --git a/apps/server/src/contexts/invoicing/intrastructure/mappers/contact.mapper.ts b/apps/server/src/contexts/invoices/intrastructure/mappers/contact.mapper.ts similarity index 74% rename from apps/server/src/contexts/invoicing/intrastructure/mappers/contact.mapper.ts rename to apps/server/src/contexts/invoices/intrastructure/mappers/contact.mapper.ts index 8c10523d..8fdf7ee9 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/mappers/contact.mapper.ts +++ b/apps/server/src/contexts/invoices/intrastructure/mappers/contact.mapper.ts @@ -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 {} @@ -22,10 +13,7 @@ class ContactMapper extends SequelizeMapper 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, diff --git a/apps/server/src/contexts/invoicing/intrastructure/mappers/contactAddress.mapper.ts b/apps/server/src/contexts/invoices/intrastructure/mappers/contactAddress.mapper.ts similarity index 100% rename from apps/server/src/contexts/invoicing/intrastructure/mappers/contactAddress.mapper.ts rename to apps/server/src/contexts/invoices/intrastructure/mappers/contactAddress.mapper.ts diff --git a/apps/server/src/contexts/invoices/intrastructure/mappers/index.ts b/apps/server/src/contexts/invoices/intrastructure/mappers/index.ts new file mode 100644 index 00000000..722a3c8b --- /dev/null +++ b/apps/server/src/contexts/invoices/intrastructure/mappers/index.ts @@ -0,0 +1 @@ +export * from "./invoice.mapper"; diff --git a/apps/server/src/contexts/invoicing/intrastructure/mappers/invoiceItem.mapper.ts b/apps/server/src/contexts/invoices/intrastructure/mappers/invoice-item.mapper.ts similarity index 69% rename from apps/server/src/contexts/invoicing/intrastructure/mappers/invoiceItem.mapper.ts rename to apps/server/src/contexts/invoices/intrastructure/mappers/invoice-item.mapper.ts index ee275418..e87d8a97 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/mappers/invoiceItem.mapper.ts +++ b/apps/server/src/contexts/invoices/intrastructure/mappers/invoice-item.mapper.ts @@ -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 {} -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 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; diff --git a/apps/server/src/contexts/invoices/intrastructure/mappers/invoice.mapper.ts b/apps/server/src/contexts/invoices/intrastructure/mappers/invoice.mapper.ts new file mode 100644 index 00000000..897322d0 --- /dev/null +++ b/apps/server/src/contexts/invoices/intrastructure/mappers/invoice.mapper.ts @@ -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 {} + +export class InvoiceMapper + extends SequelizeMapper + implements IInvoiceMapper +{ + public mapToDomain(source: InvoiceModel, params?: MapperParamsType): Result { + 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 }; diff --git a/apps/server/src/contexts/invoicing/intrastructure/mappers/invoiceParticipant.mapper.ts b/apps/server/src/contexts/invoices/intrastructure/mappers/invoiceParticipant.mapper.ts similarity index 80% rename from apps/server/src/contexts/invoicing/intrastructure/mappers/invoiceParticipant.mapper.ts rename to apps/server/src/contexts/invoices/intrastructure/mappers/invoiceParticipant.mapper.ts index fdb319ad..aa6944f3 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/mappers/invoiceParticipant.mapper.ts +++ b/apps/server/src/contexts/invoices/intrastructure/mappers/invoiceParticipant.mapper.ts @@ -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; diff --git a/apps/server/src/contexts/invoicing/intrastructure/mappers/invoiceParticipantAddress.mapper.ts b/apps/server/src/contexts/invoices/intrastructure/mappers/invoiceParticipantAddress.mapper.ts similarity index 87% rename from apps/server/src/contexts/invoicing/intrastructure/mappers/invoiceParticipantAddress.mapper.ts rename to apps/server/src/contexts/invoices/intrastructure/mappers/invoiceParticipantAddress.mapper.ts index 33686df0..1e898f64 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/mappers/invoiceParticipantAddress.mapper.ts +++ b/apps/server/src/contexts/invoices/intrastructure/mappers/invoiceParticipantAddress.mapper.ts @@ -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; diff --git a/apps/server/src/contexts/invoicing/intrastructure/sequelize/contact.model.ts b/apps/server/src/contexts/invoices/intrastructure/sequelize/contact.mo.del.ts similarity index 85% rename from apps/server/src/contexts/invoicing/intrastructure/sequelize/contact.model.ts rename to apps/server/src/contexts/invoices/intrastructure/sequelize/contact.mo.del.ts index 32da79c0..d5b11b17 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/sequelize/contact.model.ts +++ b/apps/server/src/contexts/invoices/intrastructure/sequelize/contact.mo.del.ts @@ -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, + InferCreationAttributes > { // To avoid table creation static async sync(): Promise { diff --git a/apps/server/src/contexts/invoicing/intrastructure/sequelize/contactAddress.model.ts b/apps/server/src/contexts/invoices/intrastructure/sequelize/contactAddress.mo.del.ts similarity index 97% rename from apps/server/src/contexts/invoicing/intrastructure/sequelize/contactAddress.model.ts rename to apps/server/src/contexts/invoices/intrastructure/sequelize/contactAddress.mo.del.ts index 9f101dc0..49d7e269 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/sequelize/contactAddress.model.ts +++ b/apps/server/src/contexts/invoices/intrastructure/sequelize/contactAddress.mo.del.ts @@ -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, diff --git a/apps/server/src/contexts/invoicing/intrastructure/sequelize/index.ts b/apps/server/src/contexts/invoices/intrastructure/sequelize/index.ts similarity index 77% rename from apps/server/src/contexts/invoicing/intrastructure/sequelize/index.ts rename to apps/server/src/contexts/invoices/intrastructure/sequelize/index.ts index 2db96ef4..7fefbbdb 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/sequelize/index.ts +++ b/apps/server/src/contexts/invoices/intrastructure/sequelize/index.ts @@ -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"; diff --git a/apps/server/src/contexts/invoicing/intrastructure/sequelize/invoiceItem.model.ts b/apps/server/src/contexts/invoices/intrastructure/sequelize/invoice-item.mo.del.ts similarity index 56% rename from apps/server/src/contexts/invoicing/intrastructure/sequelize/invoiceItem.model.ts rename to apps/server/src/contexts/invoices/intrastructure/sequelize/invoice-item.mo.del.ts index f2ba4801..cc3b2b00 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/sequelize/invoiceItem.model.ts +++ b/apps/server/src/contexts/invoices/intrastructure/sequelize/invoice-item.mo.del.ts @@ -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, - InferCreationAttributes + 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; - declare quantity: CreationOptional; - declare unit_price: CreationOptional; - declare subtotal: CreationOptional; - declare total: CreationOptional; - declare invoice?: NonAttribute; + declare quantity_amount: CreationOptional; + declare quantity_scale: CreationOptional; + + declare unit_price_amount: CreationOptional; + declare unit_price_scale: CreationOptional; + + declare subtotal_amount: CreationOptional; + declare subtotal_scale: CreationOptional; + + declare total_amount: CreationOptional; + declare total_scale: CreationOptional; + + declare invoice?: NonAttribute; } 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; diff --git a/apps/server/src/contexts/invoices/intrastructure/sequelize/invoice.model.ts b/apps/server/src/contexts/invoices/intrastructure/sequelize/invoice.model.ts new file mode 100644 index 00000000..9ed80e9c --- /dev/null +++ b/apps/server/src/contexts/invoices/intrastructure/sequelize/invoice.model.ts @@ -0,0 +1,141 @@ +import { + CreationOptional, + DataTypes, + InferAttributes, + InferCreationAttributes, + Model, + Sequelize, +} from "sequelize"; + +export type InvoiceCreationAttributes = InferCreationAttributes & {}; + +export class InvoiceModel extends Model, 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; + declare invoice_number: CreationOptional; + declare issue_date: CreationOptional; + declare operation_date: CreationOptional; + declare invoice_language: string; + declare invoice_currency: string; + + // Subtotal + declare subtotal_amount: CreationOptional; + declare subtotal_scale: CreationOptional; + + // Total + declare total_amount: CreationOptional; + declare total_scale: CreationOptional; + + //declare items: NonAttribute; + //declare customer: NonAttribute; +} + +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; +}; diff --git a/apps/server/src/contexts/invoicing/intrastructure/sequelize/invoice.repository.ts b/apps/server/src/contexts/invoices/intrastructure/sequelize/invoice.repository.ts similarity index 100% rename from apps/server/src/contexts/invoicing/intrastructure/sequelize/invoice.repository.ts rename to apps/server/src/contexts/invoices/intrastructure/sequelize/invoice.repository.ts diff --git a/apps/server/src/contexts/invoicing/intrastructure/sequelize/invoiceParticipant.model.ts b/apps/server/src/contexts/invoices/intrastructure/sequelize/invoiceParticipant.mo.del.ts similarity index 90% rename from apps/server/src/contexts/invoicing/intrastructure/sequelize/invoiceParticipant.model.ts rename to apps/server/src/contexts/invoices/intrastructure/sequelize/invoiceParticipant.mo.del.ts index 78df602a..151b22c3 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/sequelize/invoiceParticipant.model.ts +++ b/apps/server/src/contexts/invoices/intrastructure/sequelize/invoiceParticipant.mo.del.ts @@ -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; declare billingAddress?: NonAttribute; - declare invoice?: NonAttribute; + declare invoice?: NonAttribute; } export default (sequelize: Sequelize) => { @@ -102,7 +99,7 @@ export default (sequelize: Sequelize) => { sequelize, tableName: "invoice_participants", timestamps: false, - }, + } ); return InvoiceParticipant_Model; diff --git a/apps/server/src/contexts/invoicing/intrastructure/sequelize/invoiceParticipantAddress.model.ts b/apps/server/src/contexts/invoices/intrastructure/sequelize/invoiceParticipantAddress.mo.del.ts similarity index 94% rename from apps/server/src/contexts/invoicing/intrastructure/sequelize/invoiceParticipantAddress.model.ts rename to apps/server/src/contexts/invoices/intrastructure/sequelize/invoiceParticipantAddress.mo.del.ts index def690c6..216e348a 100644 --- a/apps/server/src/contexts/invoicing/intrastructure/sequelize/invoiceParticipantAddress.model.ts +++ b/apps/server/src/contexts/invoices/intrastructure/sequelize/invoiceParticipantAddress.mo.del.ts @@ -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, - InferCreationAttributes< - InvoiceParticipantAddress_Model, - { omit: "participant" } - > + InferCreationAttributes > { 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; diff --git a/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/create-invoice.controller.ts b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/create-invoice.controller.ts new file mode 100644 index 00000000..b201062a --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/create-invoice.controller.ts @@ -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); + } +} diff --git a/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/index.ts b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/index.ts new file mode 100644 index 00000000..d81fa3e2 --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/index.ts @@ -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); +}; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/InvoiceItem.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/InvoiceItem.presenter.ts similarity index 62% rename from apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/InvoiceItem.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/InvoiceItem.presenter.ts index 6024c716..3177298b 100644 --- a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/InvoiceItem.presenter.ts +++ b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/InvoiceItem.presenter.ts @@ -4,16 +4,16 @@ import { ICollection, IMoney_Response_DTO } from "@shared/contexts"; export const invoiceItemPresenter = ( items: ICollection, - 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, })) : []; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/InvoiceParticipant.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/InvoiceParticipant.presenter.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/InvoiceParticipant.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/InvoiceParticipant.presenter.ts diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/InvoiceParticipantAddress.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/InvoiceParticipantAddress.presenter.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/InvoiceParticipantAddress.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/InvoiceParticipantAddress.presenter.ts diff --git a/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/create-invoice.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/create-invoice.presenter.ts new file mode 100644 index 00000000..69fb0e2a --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/create-invoice.presenter.ts @@ -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), + }), +}; diff --git a/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/index.ts b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/index.ts new file mode 100644 index 00000000..3677365d --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/create-invoice/presenter/index.ts @@ -0,0 +1 @@ +export * from "./create-invoice.presenter"; diff --git a/apps/server/src/contexts/invoices/presentation/controllers/delete-invoice/delete-invoice.controller.ts b/apps/server/src/contexts/invoices/presentation/controllers/delete-invoice/delete-invoice.controller.ts new file mode 100644 index 00000000..b9623453 --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/delete-invoice/delete-invoice.controller.ts @@ -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 { + return this.noContent(); + } +} diff --git a/apps/server/src/contexts/invoices/presentation/controllers/delete-invoice/index.ts b/apps/server/src/contexts/invoices/presentation/controllers/delete-invoice/index.ts new file mode 100644 index 00000000..572923e0 --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/delete-invoice/index.ts @@ -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); +}; diff --git a/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/get-invoice.controller.ts b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/get-invoice.controller.ts new file mode 100644 index 00000000..1d219f32 --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/get-invoice.controller.ts @@ -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); + } +} diff --git a/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/index.ts b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/index.ts new file mode 100644 index 00000000..156b2325 --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/index.ts @@ -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); +}; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/InvoiceItem.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/InvoiceItem.presenter.ts similarity index 62% rename from apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/InvoiceItem.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/InvoiceItem.presenter.ts index 6024c716..3177298b 100644 --- a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/InvoiceItem.presenter.ts +++ b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/InvoiceItem.presenter.ts @@ -4,16 +4,16 @@ import { ICollection, IMoney_Response_DTO } from "@shared/contexts"; export const invoiceItemPresenter = ( items: ICollection, - 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, })) : []; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/InvoiceParticipant.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/InvoiceParticipant.presenter.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/InvoiceParticipant.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/InvoiceParticipant.presenter.ts diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/InvoiceParticipantAddress.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/InvoiceParticipantAddress.presenter.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/InvoiceParticipantAddress.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/InvoiceParticipantAddress.presenter.ts diff --git a/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/get-invoice.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/get-invoice.presenter.ts new file mode 100644 index 00000000..d7f1f30c --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/get-invoice.presenter.ts @@ -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: "", + },*/ + }), +}; diff --git a/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/index.ts b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/index.ts new file mode 100644 index 00000000..60624c19 --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/get-invoice/presenter/index.ts @@ -0,0 +1 @@ +export * from "./get-invoice.presenter"; diff --git a/apps/server/src/contexts/invoices/presentation/controllers/index.ts b/apps/server/src/contexts/invoices/presentation/controllers/index.ts new file mode 100644 index 00000000..24358c2b --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/index.ts @@ -0,0 +1,5 @@ +//export * from "./create-invoice"; +//export * from "./delete-invoice"; +export * from "./get-invoice"; +export * from "./list-invoices"; +//export * from "./update-invoice"; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/index.ts b/apps/server/src/contexts/invoices/presentation/controllers/list-invoices/index.ts similarity index 68% rename from apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/index.ts rename to apps/server/src/contexts/invoices/presentation/controllers/list-invoices/index.ts index 804a27b5..c140fc11 100644 --- a/apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/index.ts +++ b/apps/server/src/contexts/invoices/presentation/controllers/list-invoices/index.ts @@ -1,8 +1,11 @@ import { SequelizeTransactionManager } from "@common/infrastructure"; +import { invoiceRepository } from "@contexts/invoices/intrastructure"; +import { ListInvoicesUseCase } from "../../../application"; +import { InvoiceService } from "../../../domain"; import { ListInvoicesController } from "./list-invoices.controller"; import { listInvoicesPresenter } from "./presenter"; -export const listInvoicesController = () => { +export const buildListInvoicesController = () => { const transactionManager = new SequelizeTransactionManager(); const invoiceService = new InvoiceService(invoiceRepository); diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/list-invoices.controller.ts b/apps/server/src/contexts/invoices/presentation/controllers/list-invoices/list-invoices.controller.ts similarity index 89% rename from apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/list-invoices.controller.ts rename to apps/server/src/contexts/invoices/presentation/controllers/list-invoices/list-invoices.controller.ts index 50a17d7c..6dc668ec 100644 --- a/apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/list-invoices.controller.ts +++ b/apps/server/src/contexts/invoices/presentation/controllers/list-invoices/list-invoices.controller.ts @@ -1,6 +1,6 @@ -import { ListInvoicesUseCase } from "@/contexts/invoicing/application"; import { ExpressController } from "@common/presentation"; -import { IListInvoicesPresenter } from "./presenter"; +import { ListInvoicesUseCase } from "@contexts/invoices/application"; +import { IListInvoicesPresenter } from "./list-invoices.presenter"; export class ListInvoicesController extends ExpressController { public constructor( diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/presenter/InvoiceParticipant.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/list-invoices/presenter/InvoiceParticipant.presenter.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/presenter/InvoiceParticipant.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/list-invoices/presenter/InvoiceParticipant.presenter.ts diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/presenter/InvoiceParticipantAddress.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/list-invoices/presenter/InvoiceParticipantAddress.presenter.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/presenter/InvoiceParticipantAddress.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/list-invoices/presenter/InvoiceParticipantAddress.presenter.ts diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/presenter/index.ts b/apps/server/src/contexts/invoices/presentation/controllers/list-invoices/presenter/index.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/presenter/index.ts rename to apps/server/src/contexts/invoices/presentation/controllers/list-invoices/presenter/index.ts diff --git a/apps/server/src/contexts/invoices/presentation/controllers/list-invoices/presenter/list-invoices.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/list-invoices/presenter/list-invoices.presenter.ts new file mode 100644 index 00000000..9284330b --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/list-invoices/presenter/list-invoices.presenter.ts @@ -0,0 +1,31 @@ +import { Collection } from "@common/helpers"; +import { Invoice } from "@contexts/invoices/domain"; +import { IListInvoicesResponseDTO } from "@contexts/invoices/presentation/dto"; + +export interface IListInvoicesPresenter { + toDTO: (invoices: Collection) => IListInvoicesResponseDTO[]; +} + +export const listInvoicesPresenter: IListInvoicesPresenter = { + toDTO: (invoices: Collection): IListInvoicesResponseDTO[] => { + return invoices.map((invoice) => { + const result = { + 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(), + + //recipient: InvoiceParticipantPresenter(invoice.recipient), + }; + + return result; + }); + }, +}; diff --git a/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/index.ts b/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/index.ts new file mode 100644 index 00000000..dfe6e0b3 --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/index.ts @@ -0,0 +1,71 @@ +import { UpdateInvoiceUseCase } from "@/contexts/invoicing/application"; +import { + ContactRepository, + IInvoicingContext, + InvoiceParticipantAddressRepository, + InvoiceParticipantRepository, +} from "../../.."; +import { InvoiceRepository } from "../../../Invoice.repository"; +import { + createContactMapper, + createInvoiceMapper, + createInvoiceParticipantAddressMapper, + createInvoiceParticipantMapper, +} from "../../../mappers"; +import { updateInvoicePresenter } from "./presenter"; +import { UpdateInvoiceController } from "./update-invoice.controller"; + +export const updateInvoiceController = (context: IInvoicingContext) => { + const adapter = context.adapter; + const repoManager = context.repositoryManager; + + repoManager.registerRepository("Invoice", (params = { transaction: null }) => { + const { transaction } = params; + + return new InvoiceRepository({ + transaction, + adapter, + mapper: createInvoiceMapper(context), + }); + }); + + repoManager.registerRepository("Participant", (params = { transaction: null }) => { + const { transaction } = params; + + return new InvoiceParticipantRepository({ + transaction, + adapter, + mapper: createInvoiceParticipantMapper(context), + }); + }); + + repoManager.registerRepository("ParticipantAddress", (params = { transaction: null }) => { + const { transaction } = params; + + return new InvoiceParticipantAddressRepository({ + transaction, + adapter, + mapper: createInvoiceParticipantAddressMapper(context), + }); + }); + + repoManager.registerRepository("Contact", (params = { transaction: null }) => { + const { transaction } = params; + + return new ContactRepository({ + transaction, + adapter, + mapper: createContactMapper(context), + }); + }); + + const updateInvoiceUseCase = new UpdateInvoiceUseCase(context); + + return new UpdateInvoiceController( + { + useCase: updateInvoiceUseCase, + presenter: updateInvoicePresenter, + }, + context + ); +}; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/InvoiceItem.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/InvoiceItem.presenter.ts similarity index 62% rename from apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/InvoiceItem.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/InvoiceItem.presenter.ts index 6024c716..3177298b 100644 --- a/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/InvoiceItem.presenter.ts +++ b/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/InvoiceItem.presenter.ts @@ -4,16 +4,16 @@ import { ICollection, IMoney_Response_DTO } from "@shared/contexts"; export const invoiceItemPresenter = ( items: ICollection, - 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, })) : []; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/InvoiceParticipant.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/InvoiceParticipant.presenter.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/InvoiceParticipant.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/InvoiceParticipant.presenter.ts diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/InvoiceParticipantAddress.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/InvoiceParticipantAddress.presenter.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/InvoiceParticipantAddress.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/InvoiceParticipantAddress.presenter.ts diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/UpdateInvoice.presenter.ts b/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/UpdateInvoice.presenter.ts similarity index 78% rename from apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/UpdateInvoice.presenter.ts rename to apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/UpdateInvoice.presenter.ts index 90ed2125..bd0a9815 100644 --- a/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/UpdateInvoice.presenter.ts +++ b/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/UpdateInvoice.presenter.ts @@ -5,17 +5,11 @@ import { invoiceItemPresenter } from "./InvoiceItem.presenter"; import { InvoiceParticipantPresenter } from "./InvoiceParticipant.presenter"; export interface IUpdateInvoicePresenter { - map: ( - invoice: Invoice, - context: IInvoicingContext, - ) => IUpdateInvoice_Response_DTO; + map: (invoice: Invoice, context: IInvoicingContext) => IUpdateInvoice_Response_DTO; } export const updateInvoicePresenter: IUpdateInvoicePresenter = { - map: ( - invoice: Invoice, - context: IInvoicingContext, - ): IUpdateInvoice_Response_DTO => { + map: (invoice: Invoice, context: IInvoicingContext): IUpdateInvoice_Response_DTO => { return { id: invoice.id.toString(), @@ -26,8 +20,8 @@ export const updateInvoicePresenter: IUpdateInvoicePresenter = { operation_date: invoice.operationDate.toISO8601(), language_code: invoice.language.toString(), currency: invoice.currency.toString(), - subtotal: invoice.calculateSubtotal().toObject(), - total: invoice.calculateTotal().toObject(), + subtotal: invoice.calculateSubtotal().toPrimitive(), + total: invoice.calculateTotal().toPrimitive(), //sender: {}, //await InvoiceParticipantPresenter(invoice.senderId, context), diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/index.ts b/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/index.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/presenter/index.ts rename to apps/server/src/contexts/invoices/presentation/controllers/update-invoice/presenter/index.ts diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/UpdateInvoiceController.ts b/apps/server/src/contexts/invoices/presentation/controllers/update-invoice/update-invoice.controller.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/UpdateInvoiceController.ts rename to apps/server/src/contexts/invoices/presentation/controllers/update-invoice/update-invoice.controller.ts diff --git a/apps/server/src/contexts/invoicing/presentation/dto/index.ts b/apps/server/src/contexts/invoices/presentation/dto/index.ts similarity index 100% rename from apps/server/src/contexts/invoicing/presentation/dto/index.ts rename to apps/server/src/contexts/invoices/presentation/dto/index.ts diff --git a/apps/server/src/contexts/invoicing/presentation/dto/invoices.request.dto.ts b/apps/server/src/contexts/invoices/presentation/dto/invoices.request.dto.ts similarity index 58% rename from apps/server/src/contexts/invoicing/presentation/dto/invoices.request.dto.ts rename to apps/server/src/contexts/invoices/presentation/dto/invoices.request.dto.ts index 6df3cb63..6964685d 100644 --- a/apps/server/src/contexts/invoicing/presentation/dto/invoices.request.dto.ts +++ b/apps/server/src/contexts/invoices/presentation/dto/invoices.request.dto.ts @@ -2,28 +2,13 @@ export interface IListInvoicesRequestDTO {} export interface ICreateInvoiceRequestDTO { id: string; - is_freelancer: boolean; - name: string; - trade_name: string; - tin: string; - street: string; - city: string; - state: string; - postal_code: string; - country: string; - - email: string; - phone: string; - fax: string; - website: string; - - legal_record: string; - - default_tax: number; - lang_code: string; - currency_code: string; - logo: string; + invoice_number: string; + invoice_series: string; + issue_date: string; + operation_date: string; + language_code: string; + currency: string; } export interface IUpdateInvoiceRequestDTO { diff --git a/apps/server/src/contexts/invoices/presentation/dto/invoices.response.dto.ts b/apps/server/src/contexts/invoices/presentation/dto/invoices.response.dto.ts new file mode 100644 index 00000000..f335c770 --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/dto/invoices.response.dto.ts @@ -0,0 +1,66 @@ +import { IMoneyDTO } from "@common/presentation"; + +export interface IListInvoicesResponseDTO { + id: string; + + invoice_status: string; + invoice_number: string; + invoice_series: string; + issue_date: string; + operation_date: string; + language_code: string; + currency: string; + + subtotal: IMoneyDTO; + total: IMoneyDTO; +} + +export interface IGetInvoiceResponseDTO { + id: string; + + invoice_status: string; + invoice_number: string; + invoice_series: string; + issue_date: string; + operation_date: string; + language_code: string; + currency: string; + + subtotal: IMoneyDTO; + total: IMoneyDTO; + + //customer: +} + +export interface ICreateInvoiceResponseDTO { + id: string; + + invoice_status: string; + invoice_number: string; + invoice_series: string; + issue_date: string; + operation_date: string; + language_code: string; + currency: string; + + subtotal: IMoneyDTO; + total: IMoneyDTO; +} + +// Inferir el tipo en TypeScript desde el esquema Zod +//export type IUpdateAcccountResponseDTO = z.infer; + +export interface IUpdateInvoiceResponseDTO { + id: string; + + invoice_status: string; + invoice_number: string; + invoice_series: string; + issue_date: string; + operation_date: string; + language_code: string; + currency: string; + + subtotal: IMoneyDTO; + total: IMoneyDTO; +} diff --git a/apps/server/src/contexts/invoicing/presentation/dto/invoices.schemas.ts b/apps/server/src/contexts/invoices/presentation/dto/invoices.schemas.ts similarity index 81% rename from apps/server/src/contexts/invoicing/presentation/dto/invoices.schemas.ts rename to apps/server/src/contexts/invoices/presentation/dto/invoices.schemas.ts index 1f38009e..ab5fa566 100644 --- a/apps/server/src/contexts/invoicing/presentation/dto/invoices.schemas.ts +++ b/apps/server/src/contexts/invoices/presentation/dto/invoices.schemas.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -export const ListInvoicesRequestSchema = z.object({}); +export const IListInvoicesRequestSchema = z.object({}); export const IGetInvoiceRequestSchema = z.object({}); diff --git a/apps/server/src/contexts/invoices/presentation/index.ts b/apps/server/src/contexts/invoices/presentation/index.ts new file mode 100644 index 00000000..a123289d --- /dev/null +++ b/apps/server/src/contexts/invoices/presentation/index.ts @@ -0,0 +1,2 @@ +export * from "./controllers"; +export * from "./dto"; diff --git a/apps/server/src/contexts/invoicing/domain/Contact/Contact.ts b/apps/server/src/contexts/invoicing/domain/Contact/Contact.ts deleted file mode 100644 index 8962c536..00000000 --- a/apps/server/src/contexts/invoicing/domain/Contact/Contact.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { IDomainError } from "@/contexts/common/domain"; -import { - Entity, - GenericAddress, - IGenericAddressProps, - Name, - Result, - TINNumber, - UniqueID, -} from "@shared/contexts"; - -export interface IContactProps { - tin: TINNumber; - companyName: Name; - firstName: Name; - lastName: Name; - - billingAddress: GenericAddress; - shippingAddress: GenericAddress; -} - -export interface IContact { - id: UniqueID; - tin: TINNumber; - companyName: Name; - firstName: Name; - lastName: Name; - - billingAddress: GenericAddress; - shippingAddress: GenericAddress; -} - -export class Contact extends Entity implements IContact { - public static create( - props: IContactProps, - id?: UniqueID, - ): Result { - const participant = new Contact(props, id); - return Result.ok(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; - } -} diff --git a/apps/server/src/contexts/invoicing/domain/Contact/IContactRepository.interface.ts b/apps/server/src/contexts/invoicing/domain/Contact/IContactRepository.interface.ts deleted file mode 100644 index 42dc1aac..00000000 --- a/apps/server/src/contexts/invoicing/domain/Contact/IContactRepository.interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable no-unused-vars */ -import { IRepository } from "@/contexts/common/domain/repositories"; -import { UniqueID } from "@shared/contexts"; -import { Contact } from "."; - -export interface IContactRepository extends IRepository { - getById(id: UniqueID): Promise; - - getById2( - id: UniqueID, - billingAddressId: UniqueID, - shippingAddressId: UniqueID, - ): Promise; - - exists(id: UniqueID): Promise; -} diff --git a/apps/server/src/contexts/invoicing/domain/Contact/index.ts b/apps/server/src/contexts/invoicing/domain/Contact/index.ts deleted file mode 100644 index 5eba5e9c..00000000 --- a/apps/server/src/contexts/invoicing/domain/Contact/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./Contact"; -export * from "./IContactRepository.interface"; diff --git a/apps/server/src/contexts/invoicing/domain/ContactAddress.ts/ContactAddress.ts b/apps/server/src/contexts/invoicing/domain/ContactAddress.ts/ContactAddress.ts deleted file mode 100644 index 48f4fe4a..00000000 --- a/apps/server/src/contexts/invoicing/domain/ContactAddress.ts/ContactAddress.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - GenericAddress, - IGenericAddress, - IGenericAddressProps, - Result, - UniqueID, -} from "@shared/contexts"; - -export type ContactAddressType = "billing" | "shipping"; - -export interface IContactAddressProps extends IGenericAddressProps {} - -export interface IContactAddress extends IGenericAddress {} - -export class ContactAddress - extends GenericAddress - implements IContactAddress -{ - public static create(props: IContactAddressProps, id?: UniqueID) { - return Result.ok(new this(props, id)); - } -} diff --git a/apps/server/src/contexts/invoicing/domain/ContactAddress.ts/index.ts b/apps/server/src/contexts/invoicing/domain/ContactAddress.ts/index.ts deleted file mode 100644 index d07727c4..00000000 --- a/apps/server/src/contexts/invoicing/domain/ContactAddress.ts/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ContactAddress"; diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceBaseItem.ts b/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceBaseItem.ts deleted file mode 100644 index d72c9487..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceBaseItem.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { - Description, - Entity, - IEntityProps, - MoneyValue, - Quantity, -} from "@shared/contexts"; - -export interface IInvoiceBaseItemProps extends IEntityProps { - description: Description; // Descripción del artículo o servicio - quantity: Quantity; // Cantidad de unidades - unitPrice: MoneyValue; // Precio unitario en la moneda de la factura - //tax: Tax; // Tasa de impuesto en decimal (por ejemplo, 0.15 para 15%) -} - -export interface IInvoiceBaseItem { - description: Description; - quantity: Quantity; - unitPrice: MoneyValue; - //unitMeasure: string; - //tax: Tax; - //dto: Percentage | Number(10, 4) ???; - /*calculateSubtotal: () => number; - calculateTaxAmount: () => number; - calculateDtoAmount: () => number; - calculateTotal: () => number;*/ -} - -export abstract class InvoiceBaseItem

- extends Entity

- implements IInvoiceBaseItem -{ - // Método para calcular el total antes de impuestos - calculateSubtotal(): MoneyValue { - return this.unitPrice.multiply(this.quantity.toNumber()); - } - - // Método para calcular el monto del impuesto - calculateTaxAmount(): MoneyValue { - return MoneyValue.create({ amount: 0, precision: 4 }).object; - } - - // Método para calcular el total incluyendo impuestos - calculateTotal(): MoneyValue { - return this.calculateSubtotal().add(this.calculateTaxAmount()); - } - - // Getters para acceder a los atributos privados - get description(): Description { - return this.props.description; - } - - get quantity(): Quantity { - return this.props.quantity; - } - - get unitPrice(): MoneyValue { - return this.props.unitPrice; - } - - get taxRate(): number { - return this.props.taxRate; - } -} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceBudgetItem.ts b/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceBudgetItem.ts deleted file mode 100644 index 6847b0c5..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceBudgetItem.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - IInvoiceBaseItem, - IInvoiceBaseItemProps, - InvoiceBaseItem, -} from "./InvoiceBaseItem"; - -export interface IInvoiceBudgetItemProps extends IInvoiceBaseItemProps {} - -export interface IInvoiceBudgetItem extends IInvoiceBaseItem {} - -export class InvoiceBudgetItem - extends InvoiceBaseItem - implements IInvoiceBudgetItem { - //private contents: (InvoiceLineItem | InvoiceChapter)[] = []; -} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceItems.ts b/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceItems.ts deleted file mode 100644 index 0c915547..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceItems.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface WithInvoiceItems { - items: InvoiceItems; -} - -export class InvoiceIems { - private items: T[] = []; - - public length(): number { - return this.items.length; - } - - public lastPosition(): number {} - - public positionIsValid(position: number): boolean {} - - public delete(position: number): void { - if (position >= 0 && position < this.items.length) { - this.items.splice(position, 1); - } - } -} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceSimpleItem.ts b/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceSimpleItem.ts deleted file mode 100644 index a57e0a9b..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceItems/InvoiceSimpleItem.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { IDomainError } from "@/contexts/common/domain"; -import { Result, UniqueID } from "@shared/contexts"; -import { - IInvoiceBaseItem, - IInvoiceBaseItemProps, - InvoiceBaseItem, -} from "./InvoiceBaseItem"; - -export interface IInvoiceSimpleItemProps extends IInvoiceBaseItemProps {} - -export interface IInvoiceSimpleItem extends IInvoiceBaseItem {} - -export class InvoiceSimpleItem - extends InvoiceBaseItem - implements IInvoiceSimpleItem -{ - public static create( - props: IInvoiceSimpleItemProps, - id?: UniqueID, - ): Result { - return Result.ok(new InvoiceSimpleItem(props, id)); - } -} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceItems/index.ts b/apps/server/src/contexts/invoicing/domain/InvoiceItems/index.ts deleted file mode 100644 index b3f5ff76..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceItems/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { InvoiceBudgetItem } from "./InvoiceBudgetItem"; -import { InvoiceSimpleItem } from "./InvoiceSimpleItem"; - -export * from "./InvoiceBudgetItem"; -export * from "./InvoiceSimpleItem"; - -export type InvoiceItem = InvoiceSimpleItem | InvoiceBudgetItem; diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/IInvoiceParticipantRepository.interface.ts b/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/IInvoiceParticipantRepository.interface.ts deleted file mode 100644 index 35ae51bd..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/IInvoiceParticipantRepository.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable no-unused-vars */ -import { IRepository } from "@/contexts/common/domain/repositories"; -import { UniqueID } from "@shared/contexts"; -import { InvoiceParticipant } from "."; - -export interface IInvoiceParticipantRepository - extends IRepository { - getById(id: UniqueID): Promise; - exists(id: UniqueID): Promise; -} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/InvoiceParticipant.ts b/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/InvoiceParticipant.ts deleted file mode 100644 index 8d7b3cad..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/InvoiceParticipant.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { IDomainError } from "@/contexts/common/domain"; -import { Entity, Name, Result, TINNumber, UniqueID } from "@shared/contexts"; -import { - InvoiceParticipantBillingAddress, - InvoiceParticipantShippingAddress, -} from "../InvoiceParticipantAddress"; - -export interface IInvoiceParticipantProps { - tin: TINNumber; - companyName: Name; - firstName: Name; - lastName: Name; - - billingAddress?: InvoiceParticipantBillingAddress; - shippingAddress?: InvoiceParticipantShippingAddress; -} - -export interface IInvoiceParticipant { - id: UniqueID; - tin: TINNumber; - companyName: Name; - firstName: Name; - lastName: Name; - - billingAddress?: InvoiceParticipantBillingAddress; - shippingAddress?: InvoiceParticipantShippingAddress; -} - -export class InvoiceParticipant - extends Entity - implements IInvoiceParticipant -{ - public static create( - props: IInvoiceParticipantProps, - id?: UniqueID, - ): Result { - const participant = new InvoiceParticipant(props, id); - return Result.ok(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; - } -} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/Recipient.ts b/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/Recipient.ts deleted file mode 100644 index 8e1f49a1..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/Recipient.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { InvoiceParticipant } from "./InvoiceParticipant"; - -export class Recipient extends InvoiceParticipant {} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/Supplier.ts b/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/Supplier.ts deleted file mode 100644 index 62cf7932..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/Supplier.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { InvoiceParticipant } from "./InvoiceParticipant"; - -export class Supplier extends InvoiceParticipant {} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/index.ts b/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/index.ts deleted file mode 100644 index 51c6717b..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceParticipant/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./IInvoiceParticipantRepository.interface"; -export * from "./InvoiceParticipant"; -export * from "./Recipient"; -export * from "./Supplier"; diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantAddress.repository.interface.ts b/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantAddress.repository.interface.ts deleted file mode 100644 index d36b052c..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantAddress.repository.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable no-unused-vars */ -import { IRepository } from "@/contexts/common/domain/repositories"; -import { UniqueID } from "@shared/contexts"; -import { InvoiceParticipantAddress } from "./InvoiceParticipantAddress"; - -export interface IInvoiceParticipantAddressRepository - extends IRepository { - getById(id: UniqueID): Promise; - exists(id: UniqueID): Promise; -} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantAddress.ts b/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantAddress.ts deleted file mode 100644 index 8005bae9..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantAddress.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - GenericAddress, - IGenericAddress, - IGenericAddressProps, - Result, - UniqueID, -} from "@shared/contexts"; - -export type InvoiceParticipantAddressType = "billing" | "shipping"; - -export interface IInvoiceParticipantAddressProps extends IGenericAddressProps {} - -export interface IInvoiceParticipantAddress extends IGenericAddress {} - -export class InvoiceParticipantAddress - extends GenericAddress - implements IInvoiceParticipantAddress -{ - public static create(props: IInvoiceParticipantAddressProps, id?: UniqueID) { - return Result.ok(new this(props, id)); - } -} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantBillingAddress.ts b/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantBillingAddress.ts deleted file mode 100644 index d3743146..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantBillingAddress.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Result, UniqueID } from "@shared/contexts"; -import { - IInvoiceParticipantAddress, - IInvoiceParticipantAddressProps, - InvoiceParticipantAddress, -} from "./InvoiceParticipantAddress"; - -export interface IInvoiceParticipantBillingAddressProps - extends Omit {} - -export interface IInvoiceParticipantBillingAddress - extends IInvoiceParticipantAddress {} - -export class InvoiceParticipantBillingAddress - extends InvoiceParticipantAddress - implements IInvoiceParticipantBillingAddress -{ - public static create( - props: IInvoiceParticipantBillingAddressProps, - id?: UniqueID, - ) { - const address = new InvoiceParticipantAddress( - { - ...props, - type: "billing", - }, - id, - ); - - return Result.ok(address); - } -} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantShippingAddress.ts b/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantShippingAddress.ts deleted file mode 100644 index 96d489b4..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/InvoiceParticipantShippingAddress.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Result, UniqueID } from "@shared/contexts"; -import { - IInvoiceParticipantAddress, - IInvoiceParticipantAddressProps, - InvoiceParticipantAddress, -} from "./InvoiceParticipantAddress"; - -export interface IInvoiceParticipantShippingAddressProps - extends Omit {} - -export interface IInvoiceParticipantShippingAddress - extends IInvoiceParticipantAddress {} - -export class InvoiceParticipantShippingAddress - extends InvoiceParticipantAddress - implements IInvoiceParticipantShippingAddress -{ - public static create( - props: IInvoiceParticipantShippingAddressProps, - id?: UniqueID, - ) { - const address = new InvoiceParticipantAddress( - { - ...props, - type: "shipping", - }, - id, - ); - - return Result.ok(address); - } -} diff --git a/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/index.ts b/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/index.ts deleted file mode 100644 index 4591ab09..00000000 --- a/apps/server/src/contexts/invoicing/domain/InvoiceParticipantAddress/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./InvoiceParticipantAddress"; -export * from "./InvoiceParticipantAddress.repository.interface"; -export * from "./InvoiceParticipantBillingAddress"; -export * from "./InvoiceParticipantShippingAddress"; diff --git a/apps/server/src/contexts/invoicing/domain/index.ts b/apps/server/src/contexts/invoicing/domain/index.ts deleted file mode 100644 index 624f976d..00000000 --- a/apps/server/src/contexts/invoicing/domain/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./aggregates"; -export * from "./Contact"; -export * from "./ContactAddress.ts"; -export * from "./InvoiceItems"; -export * from "./InvoiceParticipant"; -export * from "./InvoiceParticipantAddress"; diff --git a/apps/server/src/contexts/invoicing/domain/value-objects/index.ts b/apps/server/src/contexts/invoicing/domain/value-objects/index.ts deleted file mode 100644 index 992c7ed2..00000000 --- a/apps/server/src/contexts/invoicing/domain/value-objects/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./invoice-status"; diff --git a/apps/server/src/contexts/invoicing/domain/value-objects/invoice-number.ts b/apps/server/src/contexts/invoicing/domain/value-objects/invoice-number.ts deleted file mode 100644 index a3b6a72a..00000000 --- a/apps/server/src/contexts/invoicing/domain/value-objects/invoice-number.ts +++ /dev/null @@ -1,41 +0,0 @@ -import Joi from "joi"; -import { UndefinedOr } from "../../../../utilities"; -import { - IStringValueObjectOptions, - Result, - RuleValidator, - StringValueObject, -} from "../../../common"; - -export class InvoiceNumber extends StringValueObject { - protected static validate( - value: UndefinedOr, - options: IStringValueObjectOptions, - ) { - const rule = Joi.string() - .allow(null, "") - .default("") - .trim() - .label(options.label ? options.label : "value"); - - return RuleValidator.validate(rule, value); - } - - public static create( - value: UndefinedOr, - options: IStringValueObjectOptions = {}, - ) { - const _options = { - label: "invoice_number", - ...options, - }; - - const validationResult = InvoiceNumber.validate(value, _options); - - if (validationResult.isFailure) { - return Result.fail(validationResult.error); - } - - return Result.ok(new InvoiceNumber(validationResult.object)); - } -} diff --git a/apps/server/src/contexts/invoicing/domain/value-objects/invoice-serie.ts b/apps/server/src/contexts/invoicing/domain/value-objects/invoice-serie.ts deleted file mode 100644 index f168a795..00000000 --- a/apps/server/src/contexts/invoicing/domain/value-objects/invoice-serie.ts +++ /dev/null @@ -1,41 +0,0 @@ -import Joi from "joi"; -import { UndefinedOr } from "../../../../utilities"; -import { - IStringValueObjectOptions, - Result, - RuleValidator, - StringValueObject, -} from "../../../common"; - -export class InvoiceSeries extends StringValueObject { - protected static validate( - value: UndefinedOr, - options: IStringValueObjectOptions, - ) { - const rule = Joi.string() - .allow(null, "") - .default("") - .trim() - .label(options.label ? options.label : "value"); - - return RuleValidator.validate(rule, value); - } - - public static create( - value: UndefinedOr, - options: IStringValueObjectOptions = {}, - ) { - const _options = { - label: "invoice_series", - ...options, - }; - - const validationResult = InvoiceSeries.validate(value, _options); - InvoiceSeries; - if (validationResult.isFailure) { - return Result.fail(validationResult.error); - } - - return Result.ok(new InvoiceSeries(validationResult.object)); - } -} diff --git a/apps/server/src/contexts/invoicing/intrastructure/mappers/index.ts b/apps/server/src/contexts/invoicing/intrastructure/mappers/index.ts deleted file mode 100644 index 2e676120..00000000 --- a/apps/server/src/contexts/invoicing/intrastructure/mappers/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./contact.mapper"; -export * from "./contactAddress.mapper"; -export * from "./invoice.mapper"; -export * from "./invoiceItem.mapper"; -export * from "./invoiceParticipant.mapper"; -export * from "./invoiceParticipantAddress.mapper"; diff --git a/apps/server/src/contexts/invoicing/intrastructure/mappers/invoice.mapper.ts b/apps/server/src/contexts/invoicing/intrastructure/mappers/invoice.mapper.ts deleted file mode 100644 index 56a6cec4..00000000 --- a/apps/server/src/contexts/invoicing/intrastructure/mappers/invoice.mapper.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { - Currency, - InvoiceDate, - InvoiceNumber, - InvoiceSeries, - Language, - UniqueID, -} from "@shared/contexts"; - -import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure"; -import { DraftInvoice, Invoice } from "../../domain"; -import { IBaseInvoiceProps } from "../../domain/aggregates/invoice"; -import { IInvoicingContext } from "../InvoicingContext"; -import { Invoice_Model, TCreationInvoice_Model } from "../sequelize"; -import { IInvoiceItemMapper, createInvoiceItemMapper } from "./invoiceItem.mapper"; -import { - IInvoiceParticipantMapper, - createInvoiceParticipantMapper, -} from "./invoiceParticipant.mapper"; - -export interface IInvoiceMapper - extends ISequelizeMapper {} - -export const createInvoiceMapper = (context: IInvoicingContext): IInvoiceMapper => - new InvoiceMapper({ - context, - invoiceItemMapper: createInvoiceItemMapper(context), - participantMapper: createInvoiceParticipantMapper(context), - }); - -class InvoiceMapper - extends SequelizeMapper - implements IInvoiceMapper -{ - public constructor(props: { - invoiceItemMapper: IInvoiceItemMapper; - participantMapper: IInvoiceParticipantMapper; - context: IInvoicingContext; - }) { - super(props); - } - - protected toDomainMappingImpl(source: Invoice_Model): Invoice { - const id = this.mapsValue(source, "id", UniqueID.create); - - /*if (!source.items) { - this.handleRequiredFieldError( - "items", - new Error("Missing invoice items"), - ); - }*/ - - const items = (this.props.invoiceItemMapper as IInvoiceItemMapper).mapArrayToDomain( - source.items, - { - sourceParent: source, - } - ); - - const participants = ( - this.props.participantMapper as IInvoiceParticipantMapper - ).mapArrayToDomain(source.participants); - - const props: IBaseInvoiceProps = { - invoiceNumber: this.mapsValue(source, "invoice_number", InvoiceNumber.create), - invoiceSeries: this.mapsValue(source, "invoice_series", InvoiceSeries.create), - issueDate: this.mapsValue(source, "issue_date", InvoiceDate.create), - operationDate: this.mapsValue(source, "operation_date", InvoiceDate.create), - invoiceCurrency: this.mapsValue(source, "invoice_currency", Currency.createFromCode), - language: this.mapsValue(source, "invoice_language", Language.createFromCode), - - //recipientId: id, - //senderId: id, - items, - recipient: participants.items[0], - }; - - const invoiceOrError = DraftInvoice.create(props, id); - - if (invoiceOrError.isFailure) { - throw invoiceOrError.error; - } - - return invoiceOrError.object; - } - - protected toPersistenceMappingImpl(source: Invoice) { - const items = (this.props.invoiceItemMapper as IInvoiceItemMapper).mapCollectionToPersistence( - source.items, - { sourceParent: source } - ); - - const recipientData = ( - this.props.participantMapper as IInvoiceParticipantMapper - ).mapToPersistence(source.recipient, { sourceParent: source }); - - const invoice: TCreationInvoice_Model = { - id: source.id.toPrimitive(), - invoice_status: source.status.toPrimitive(), - invoice_number: source.invoiceNumber.toPrimitive(), - invoice_series: source.invoiceSeries.toPrimitive(), - invoice_currency: source.currency.toPrimitive(), - invoice_language: source.language.toPrimitive(), - issue_date: source.issueDate.toPrimitive(), - operation_date: source.operationDate.toPrimitive(), - subtotal: source.calculateSubtotal().toPrimitive(), - total: source.calculateTotal().toPrimitive(), - - items, - participants: [recipientData], - }; - - return invoice; - } -} diff --git a/apps/server/src/contexts/invoicing/intrastructure/sequelize/invoice.model.ts b/apps/server/src/contexts/invoicing/intrastructure/sequelize/invoice.model.ts deleted file mode 100644 index 38fa5975..00000000 --- a/apps/server/src/contexts/invoicing/intrastructure/sequelize/invoice.model.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { - CreationOptional, - DataTypes, - InferAttributes, - InferCreationAttributes, - Model, - NonAttribute, - Sequelize, -} from "sequelize"; -import { SequelizeRevision } from "sequelize-revision"; -import { - InvoiceItem_Model, - TCreationInvoiceItem_Model, -} from "./invoiceItem.model"; -import { - InvoiceParticipant_Model, - TCreationInvoiceParticipant_Model, -} from "./invoiceParticipant.model"; - -export type TCreationInvoice_Model = InferCreationAttributes< - Invoice_Model, - { omit: "items" | "participants" } -> & { - items: TCreationInvoiceItem_Model[]; - participants: TCreationInvoiceParticipant_Model[]; -}; - -export class Invoice_Model extends Model< - InferAttributes, - InferCreationAttributes -> { - static async trackRevision( - connection: Sequelize, - sequelizeRevision: SequelizeRevision - ) { - const { - Invoice_Model, - InvoiceItem_Model, - InvoiceParticipant_Model, - InvoiceParticipantAddress_Model, - } = connection.models; - sequelizeRevision.trackRevision(Invoice_Model); - sequelizeRevision.trackRevision(InvoiceItem_Model); - sequelizeRevision.trackRevision(InvoiceParticipant_Model); - sequelizeRevision.trackRevision(InvoiceParticipantAddress_Model); - } - - 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: "participants", - foreignKey: "invoice_id", - onDelete: "CASCADE", - }); - } - - declare id: string; - declare invoice_status: string; - declare invoice_series: CreationOptional; - declare invoice_number: CreationOptional; - declare issue_date: CreationOptional; - declare operation_date: CreationOptional; - declare invoice_language: string; - declare invoice_currency: string; - declare subtotal: number; - declare total: number; - - declare items: NonAttribute; - declare participants: NonAttribute; -} - -export default (sequelize: Sequelize) => { - Invoice_Model.init( - { - id: { - type: new DataTypes.UUID(), - primaryKey: true, - }, - - invoice_status: { - type: new DataTypes.STRING(), - allowNull: false, // Puede ser nulo - }, - - invoice_series: { - type: new DataTypes.STRING(), - allowNull: true, // Puede ser nulo - }, - - invoice_number: { - type: new DataTypes.STRING(), - allowNull: true, // Puede ser nulo - }, - - issue_date: { - type: new DataTypes.DATE(), - allowNull: true, // Puede ser nulo - }, - - operation_date: { - type: new DataTypes.DATE(), - allowNull: true, // Puede ser nulo - }, - - invoice_language: { - type: new DataTypes.STRING(), - allowNull: false, - }, - - invoice_currency: { - type: new DataTypes.STRING(), - allowNull: false, - }, - - subtotal: { - type: new DataTypes.BIGINT(), - allowNull: true, - }, - total: { - type: new DataTypes.BIGINT(), - allowNull: true, - }, - }, - { - sequelize, - tableName: "invoices", - - paranoid: true, // softs deletes - timestamps: true, - //version: true, - - createdAt: "created_at", - updatedAt: "updated_at", - deletedAt: "deleted_at", - } - ); - - return Invoice_Model; -}; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/CreateInvoiceController.ts b/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/CreateInvoiceController.ts deleted file mode 100644 index c04f9f19..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/CreateInvoiceController.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { UseCaseError } from "@/contexts/common/application/useCases"; -import { ExpressController } from "@/contexts/common/infrastructure/express"; -import { - CreateInvoiceResponseOrError, - CreateInvoiceUseCase, -} from "@/contexts/invoicing/application"; -import { - ICreateInvoice_DTO, - ICreateInvoice_Response_DTO, - ensureCreateInvoice_DTOIsValid, -} from "@shared/contexts"; - -import { IServerError } from "@/contexts/common/domain/errors"; -import { Invoice } from "@/contexts/invoicing/domain"; -import { IInvoicingContext } from "../../../InvoicingContext"; -import { ICreateInvoicePresenter } from "./presenter"; - -export class CreateInvoiceController extends ExpressController { - private useCase: CreateInvoiceUseCase; - private presenter: ICreateInvoicePresenter; - private context: IInvoicingContext; - - constructor( - props: { - useCase: CreateInvoiceUseCase; - presenter: ICreateInvoicePresenter; - }, - context: IInvoicingContext - ) { - super(); - - const { useCase, presenter } = props; - this.useCase = useCase; - this.presenter = presenter; - this.context = context; - } - - async executeImpl(): Promise { - try { - const invoiceDTO: ICreateInvoice_DTO = this.req.body; - - // Validaciones de DTO - const invoiceDTOOrError = ensureCreateInvoice_DTOIsValid(invoiceDTO); - if (invoiceDTOOrError.isFailure) { - return this.invalidInputError(invoiceDTOOrError.error.message); - } - - // Llamar al caso de uso - const result: CreateInvoiceResponseOrError = await this.useCase.execute( - invoiceDTO - ); - - if (result.isFailure) { - const { error } = result; - - switch (error.code) { - case UseCaseError.INVALID_REQUEST_PARAM: - return this.invalidInputError(error.message, error); - - case UseCaseError.INVALID_INPUT_DATA: - return this.invalidInputError(error.message, error); - - case UseCaseError.UNEXCEPTED_ERROR: - return this.internalServerError(error.message, error); - - case UseCaseError.REPOSITORY_ERROR: - return this.conflictError(error, error.details); - - case UseCaseError.NOT_FOUND_ERROR: - return this.notFoundError(error.message, error); - - case UseCaseError.RESOURCE_ALREADY_EXITS: - return this.conflictError(error); - - default: - return this.clientError(error.message); - } - } - - const invoice = result.object; - - return this.created( - this.presenter.map(invoice, this.context) - ); - } catch (error: unknown) { - return this.fail(error as IServerError); - } - } -} diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/index.ts b/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/index.ts deleted file mode 100644 index 6f1b8cb4..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/index.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { CreateInvoiceUseCase } from "@/contexts/invoicing/application"; -import { - ContactRepository, - IInvoicingContext, - InvoiceParticipantAddressRepository, - InvoiceParticipantRepository, - InvoiceRepository, -} from "../../.."; - -import { - createInvoiceMapper, - createInvoiceParticipantAddressMapper, - createInvoiceParticipantMapper, -} from "../../../mappers"; -import { createContactMapper } from "../../../mappers/contact.mapper"; -import { CreateInvoiceController } from "./CreateInvoiceController"; -import { createInvoicePresenter } from "./presenter"; - -export const createInvoiceController = (context: IInvoicingContext) => { - const adapter = context.adapter; - const repoManager = context.repositoryManager; - - repoManager.registerRepository( - "Invoice", - (params = { transaction: null }) => { - const { transaction } = params; - - return new InvoiceRepository({ - transaction, - adapter, - mapper: createInvoiceMapper(context), - }); - }, - ); - - repoManager.registerRepository( - "Participant", - (params = { transaction: null }) => { - const { transaction } = params; - - return new InvoiceParticipantRepository({ - transaction, - adapter, - mapper: createInvoiceParticipantMapper(context), - }); - }, - ); - - repoManager.registerRepository( - "ParticipantAddress", - (params = { transaction: null }) => { - const { transaction } = params; - - return new InvoiceParticipantAddressRepository({ - transaction, - adapter, - mapper: createInvoiceParticipantAddressMapper(context), - }); - }, - ); - - repoManager.registerRepository( - "Contact", - (params = { transaction: null }) => { - const { transaction } = params; - - return new ContactRepository({ - transaction, - adapter, - mapper: createContactMapper(context), - }); - }, - ); - - const createInvoiceUseCase = new CreateInvoiceUseCase(context); - - return new CreateInvoiceController( - { - useCase: createInvoiceUseCase, - presenter: createInvoicePresenter, - }, - context, - ); -}; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/CreateInvoice.presenter.ts b/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/CreateInvoice.presenter.ts deleted file mode 100644 index 0feae5c7..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/CreateInvoice.presenter.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Invoice } from "@/contexts/invoicing/domain"; -import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext"; -import { ICreateInvoice_Response_DTO } from "@shared/contexts"; -import { invoiceItemPresenter } from "./InvoiceItem.presenter"; -import { InvoiceParticipantPresenter } from "./InvoiceParticipant.presenter"; - -export interface ICreateInvoicePresenter { - map: ( - invoice: Invoice, - context: IInvoicingContext, - ) => ICreateInvoice_Response_DTO; -} - -export const createInvoicePresenter: ICreateInvoicePresenter = { - map: ( - invoice: Invoice, - context: IInvoicingContext, - ): ICreateInvoice_Response_DTO => { - return { - id: invoice.id.toString(), - - invoice_status: invoice.status.toString(), - invoice_number: invoice.invoiceNumber.toString(), - invoice_series: invoice.invoiceSeries.toString(), - issue_date: invoice.issueDate.toISO8601(), - operation_date: invoice.operationDate.toISO8601(), - language_code: invoice.language.toString(), - currency: invoice.currency.toString(), - subtotal: invoice.calculateSubtotal().toObject(), - total: invoice.calculateTotal().toObject(), - - //sender: {}, //await InvoiceParticipantPresenter(invoice.senderId, context), - - recipient: InvoiceParticipantPresenter(invoice.recipient, context), - - items: invoiceItemPresenter(invoice.items, context), - }; - }, -}; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/index.ts b/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/index.ts deleted file mode 100644 index a9312352..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/create-invoice/presenter/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./CreateInvoice.presenter"; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/delete-invoice/DeleteInvoiceController.ts b/apps/server/src/contexts/invoicing/presentation/controllers/delete-invoice/DeleteInvoiceController.ts deleted file mode 100644 index c7075473..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/delete-invoice/DeleteInvoiceController.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { UseCaseError } from "@/contexts/common/application/useCases"; -import { IServerError } from "@/contexts/common/domain"; -import { ExpressController } from "@/contexts/common/infrastructure/express"; -import { DeleteInvoiceUseCase } from "@/contexts/invoicing/application"; -import { - IDeleteInvoiceRequest_DTO, - RuleValidator, - UniqueID, -} from "@shared/contexts"; - -export class DeleteInvoiceController extends ExpressController { - private useCase: DeleteInvoiceUseCase; - - constructor(props: { useCase: DeleteInvoiceUseCase }) { - super(); - - const { useCase } = props; - this.useCase = useCase; - } - - async executeImpl(): Promise { - const { invoiceId } = this.req.params; - - if ( - RuleValidator.validate( - RuleValidator.RULE_NOT_NULL_OR_UNDEFINED, - invoiceId, - ).isFailure - ) { - return this.invalidInputError("Invoice Id param is required!"); - } - - const idOrError = UniqueID.create(invoiceId); - if (idOrError.isFailure) { - return this.invalidInputError("Invalid invoice Id param!"); - } - - try { - const deleteInvoiceRequest: IDeleteInvoiceRequest_DTO = { - id: idOrError.object, - }; - - const result = await this.useCase.execute(deleteInvoiceRequest); - - if (result.isFailure) { - const { error } = result; - - switch (error.code) { - case UseCaseError.NOT_FOUND_ERROR: - return this.notFoundError("Invoice not found", error); - - case UseCaseError.UNEXCEPTED_ERROR: - return this.internalServerError(result.error.message, result.error); - - default: - return this.clientError(result.error.message); - } - } - - return this.noContent(); - } catch (e: unknown) { - return this.fail(e as IServerError); - } - } -} diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/delete-invoice/index.ts b/apps/server/src/contexts/invoicing/presentation/controllers/delete-invoice/index.ts deleted file mode 100644 index fb9c949e..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/delete-invoice/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { RepositoryManager } from "@/contexts/common/domain"; -import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize"; - -import { DeleteInvoiceUseCase } from "@/contexts/invoicing/application"; -import { IInvoicingContext } from "../../.."; -import { InvoiceRepository } from "../../../Invoice.repository"; -import { createInvoiceMapper } from "../../../mappers"; -import { DeleteInvoiceController } from "./DeleteInvoiceController"; - -export const createDeleteInvoiceController = (context: IInvoicingContext) => { - const adapter = createSequelizeAdapter(); - const repoManager = RepositoryManager.getInstance(); - - repoManager.registerRepository( - "Invoice", - (params = { transaction: null }) => { - const { transaction } = params; - - return new InvoiceRepository({ - transaction, - adapter, - mapper: createInvoiceMapper(context), - }); - }, - ); - - const deleteInvoiceUseCase = new DeleteInvoiceUseCase({ - adapter, - repositoryManager: repoManager, - }); - - return new DeleteInvoiceController({ - useCase: deleteInvoiceUseCase, - }); -}; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/GetInvoiceController.ts b/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/GetInvoiceController.ts deleted file mode 100644 index af74cb81..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/GetInvoiceController.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { UseCaseError } from "@/contexts/common/application/useCases"; -import { IServerError } from "@/contexts/common/domain/errors"; -import { ExpressController } from "@/contexts/common/infrastructure/express"; -import { - IGetInvoice_Request_DTO, - IGetInvoice_Response_DTO, - RuleValidator, - UniqueID, -} from "@shared/contexts"; - -import { GetInvoiceUseCase } from "@/contexts/invoicing/application"; -import { Invoice } from "@/contexts/invoicing/domain"; -import { IInvoicingContext } from "../../../InvoicingContext"; -import { IGetInvoicePresenter } from "./presenter"; - -export class GetInvoiceController extends ExpressController { - private useCase: GetInvoiceUseCase; - private presenter: IGetInvoicePresenter; - private context: IInvoicingContext; - - constructor( - props: { - useCase: GetInvoiceUseCase; - presenter: IGetInvoicePresenter; - }, - context: IInvoicingContext - ) { - super(); - - const { useCase, presenter } = props; - this.useCase = useCase; - this.presenter = presenter; - this.context = context; - } - - async executeImpl(): Promise { - const { invoiceId } = this.req.params; - if ( - RuleValidator.validate( - RuleValidator.RULE_NOT_NULL_OR_UNDEFINED, - invoiceId - ).isFailure - ) { - return this.invalidInputError("Invoice Id param is required!"); - } - - const idOrError = UniqueID.create(invoiceId); - if (idOrError.isFailure) { - return this.invalidInputError("Invalid invoice Id param!"); - } - - try { - const getInvoiceRequest: IGetInvoice_Request_DTO = { - id: idOrError.object, - }; - - const result = await this.useCase.execute(getInvoiceRequest); - - if (result.isFailure) { - const { error } = result; - - switch (error.code) { - case UseCaseError.NOT_FOUND_ERROR: - return this.notFoundError( - `Invoice with id ${idOrError.object} not found`, - error - ); - - case UseCaseError.UNEXCEPTED_ERROR: - return this.internalServerError(result.error.message, result.error); - - default: - return this.clientError(result.error.message); - } - } - - const invoice = result.object; - - return this.ok( - await this.presenter.map(invoice, this.context) - ); - } catch (e: unknown) { - return this.fail(e as IServerError); - } - } -} diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/index.ts b/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/index.ts deleted file mode 100644 index ed0a5c55..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { GetInvoiceUseCase } from "@/contexts/invoicing/application"; -import { InvoiceRepository } from "../../../Invoice.repository"; -import { IInvoicingContext } from "../../../InvoicingContext"; -import { createInvoiceMapper } from "../../../mappers"; -import { GetInvoiceController } from "./GetInvoiceController"; -import { getInvoicePresenter } from "./presenter"; - -export const createGetInvoiceController = (context: IInvoicingContext) => { - const adapter = context.adapter; - const repoManager = context.repositoryManager; - - repoManager.registerRepository( - "Invoice", - (params = { transaction: null }) => { - const { transaction } = params; - - return new InvoiceRepository({ - transaction, - adapter, - mapper: createInvoiceMapper(context), - }); - }, - ); - - const getInvoiceUseCase = new GetInvoiceUseCase(context); - - return new GetInvoiceController( - { - useCase: getInvoiceUseCase, - presenter: getInvoicePresenter, - }, - context, - ); -}; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/GetInvoice.presenter.ts b/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/GetInvoice.presenter.ts deleted file mode 100644 index eb766427..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/GetInvoice.presenter.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Invoice } from "@/contexts/invoicing/domain"; -import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext"; -import { IGetInvoice_Response_DTO } from "@shared/contexts"; -import { invoiceItemPresenter } from "./InvoiceItem.presenter"; -import { InvoiceParticipantPresenter } from "./InvoiceParticipant.presenter"; - -export interface IGetInvoicePresenter { - map: ( - invoice: Invoice, - context: IInvoicingContext, - ) => Promise; -} - -export const getInvoicePresenter: IGetInvoicePresenter = { - map: async ( - invoice: Invoice, - context: IInvoicingContext, - ): Promise => { - return { - id: invoice.id.toString(), - - invoice_status: invoice.status.toString(), - invoice_number: invoice.invoiceNumber.toString(), - invoice_series: invoice.invoiceSeries.toString(), - issue_date: invoice.issueDate.toISO8601(), - operation_date: invoice.operationDate.toISO8601(), - language_code: invoice.language.toString(), - currency: invoice.currency.toString(), - subtotal: invoice.calculateSubtotal().toObject(), - total: invoice.calculateTotal().toObject(), - - //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: "", - }, - }; - }, -}; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/index.ts b/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/index.ts deleted file mode 100644 index f91273b1..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/get-invoice/presenter/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./GetInvoice.presenter"; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/index.ts b/apps/server/src/contexts/invoicing/presentation/controllers/index.ts deleted file mode 100644 index beae3e39..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./create-invoice"; -export * from "./delete-invoice"; -export * from "./get-invoice"; -export * from "./list-invoices"; -export * from "./update-invoice"; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/presenter/list-invoices.presenter.ts b/apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/presenter/list-invoices.presenter.ts deleted file mode 100644 index 1802522b..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/list-invoices/presenter/list-invoices.presenter.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Collection } from "@common/helpers"; -import { Invoice } from "@contexts/invoicing/domain"; -import { IListInvoicesResponseDTO } from "@contexts/invoicing/presentation/dto"; - -export interface IListInvoicesPresenter { - toDTO: (invoices: Collection) => IListInvoicesResponseDTO[]; -} - -export const listInvoicesPresenter: IListInvoicesPresenter = { - toDTO: (invoices: Collection): IListInvoicesResponseDTO[] => { - return invoices.map((invoice) => { - const result = { - id: invoice.id.toPrimitive(), - - invoice_status: invoice.status.toPrimitive(), - invoice_number: invoice.invoiceNumber.toPrimitive(), - invoice_series: invoice.invoiceSeries.toPrimitive(), - issue_date: invoice.issueDate.toPrimitive()!, - operation_date: invoice.operationDate.toPrimitive()!, - language_code: invoice.language.toPrimitive(), - currency: invoice.currency.toPrimitive(), - subtotal: invoice.calculateSubtotal().toObject(), - total: invoice.calculateTotal().toObject(), - - //recipient: InvoiceParticipantPresenter(invoice.recipient), - }; - - console.timeEnd("listInvoicesPresenter.map"); - - return result; - }); - }, -}; diff --git a/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/index.ts b/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/index.ts deleted file mode 100644 index 1fd59f14..00000000 --- a/apps/server/src/contexts/invoicing/presentation/controllers/update-invoice/index.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { UpdateInvoiceUseCase } from "@/contexts/invoicing/application"; -import { - ContactRepository, - IInvoicingContext, - InvoiceParticipantAddressRepository, - InvoiceParticipantRepository, -} from "../../.."; -import { InvoiceRepository } from "../../../Invoice.repository"; -import { - createContactMapper, - createInvoiceMapper, - createInvoiceParticipantAddressMapper, - createInvoiceParticipantMapper, -} from "../../../mappers"; -import { UpdateInvoiceController } from "./UpdateInvoiceController"; -import { updateInvoicePresenter } from "./presenter"; - -export const updateInvoiceController = (context: IInvoicingContext) => { - const adapter = context.adapter; - const repoManager = context.repositoryManager; - - repoManager.registerRepository( - "Invoice", - (params = { transaction: null }) => { - const { transaction } = params; - - return new InvoiceRepository({ - transaction, - adapter, - mapper: createInvoiceMapper(context), - }); - }, - ); - - repoManager.registerRepository( - "Participant", - (params = { transaction: null }) => { - const { transaction } = params; - - return new InvoiceParticipantRepository({ - transaction, - adapter, - mapper: createInvoiceParticipantMapper(context), - }); - }, - ); - - repoManager.registerRepository( - "ParticipantAddress", - (params = { transaction: null }) => { - const { transaction } = params; - - return new InvoiceParticipantAddressRepository({ - transaction, - adapter, - mapper: createInvoiceParticipantAddressMapper(context), - }); - }, - ); - - repoManager.registerRepository( - "Contact", - (params = { transaction: null }) => { - const { transaction } = params; - - return new ContactRepository({ - transaction, - adapter, - mapper: createContactMapper(context), - }); - }, - ); - - const updateInvoiceUseCase = new UpdateInvoiceUseCase(context); - - return new UpdateInvoiceController( - { - useCase: updateInvoiceUseCase, - presenter: updateInvoicePresenter, - }, - context, - ); -}; diff --git a/apps/server/src/contexts/invoicing/presentation/dto/invoices.response.dto.ts b/apps/server/src/contexts/invoicing/presentation/dto/invoices.response.dto.ts deleted file mode 100644 index b0b6dc2c..00000000 --- a/apps/server/src/contexts/invoicing/presentation/dto/invoices.response.dto.ts +++ /dev/null @@ -1,114 +0,0 @@ -export interface IListInvoicesResponseDTO { - id: string; - - is_freelancer: boolean; - name: string; - trade_name: string; - tin: string; - - street: string; - city: string; - state: string; - postal_code: string; - country: string; - - email: string; - phone: string; - fax: string; - website: string; - - legal_record: string; - - default_tax: number; - status: string; - lang_code: string; - currency_code: string; - logo: string; -} - -export interface IGetInvoiceResponseDTO { - id: string; - - is_freelancer: boolean; - name: string; - trade_name: string; - tin: string; - - street: string; - city: string; - state: string; - postal_code: string; - country: string; - - email: string; - phone: string; - fax: string; - website: string; - - legal_record: string; - - default_tax: number; - status: string; - lang_code: string; - currency_code: string; - logo: string; -} - -export interface ICreateInvoiceResponseDTO { - id: string; - - is_freelancer: boolean; - name: string; - trade_name: string; - tin: string; - - street: string; - city: string; - state: string; - postal_code: string; - country: string; - - email: string; - phone: string; - fax: string; - website: string; - - legal_record: string; - - default_tax: number; - status: string; - lang_code: string; - currency_code: string; - logo: string; -} - -// Inferir el tipo en TypeScript desde el esquema Zod -//export type IUpdateAcccountResponseDTO = z.infer; - -export interface IUpdateInvoiceResponseDTO { - id: string; - - is_freelancer: boolean; - name: string; - trade_name: string; - tin: string; - - street: string; - city: string; - state: string; - postal_code: string; - country: string; - - email: string; - phone: string; - fax: string; - website: string; - - legal_record: string; - - default_tax: number; - status: string; - lang_code: string; - currency_code: string; - logo: string; -} diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index f086e5c1..0909f316 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -93,7 +93,7 @@ const server = http process.on("unhandledRejection", (reason: any, promise: Promise) => { logger.error("❌ Unhandled rejection at:", promise, "reason:", reason); // Dependiendo de la aplicación, podrías desear una salida total o un cierre controlado - // process.exit(1); + process.exit(1); }); // Manejo de excepciones no controladas diff --git a/apps/server/src/routes/accounts.routes.ts b/apps/server/src/routes/accounts.routes.ts index da094817..541e3215 100644 --- a/apps/server/src/routes/accounts.routes.ts +++ b/apps/server/src/routes/accounts.routes.ts @@ -6,10 +6,10 @@ import { ListAccountsRequestSchema, } from "@contexts/accounts/presentation"; import { - createAccountController, - getAccountController, - listAccountsController, - updateAccountController, + buildCreateAccountController, + buildGetAccountController, + buildListAccountsController, + buildUpdateAccountController, } from "@contexts/accounts/presentation/controllers"; import { checkTabContext } from "@contexts/auth/infraestructure"; import { NextFunction, Request, Response, Router } from "express"; @@ -23,7 +23,7 @@ export const accountsRouter = (appRouter: Router) => { checkTabContext, //checkUser, (req: Request, res: Response, next: NextFunction) => { - listAccountsController().execute(req, res, next); + buildListAccountsController().execute(req, res, next); } ); @@ -33,7 +33,7 @@ export const accountsRouter = (appRouter: Router) => { checkTabContext, //checkUser, (req: Request, res: Response, next: NextFunction) => { - getAccountController().execute(req, res, next); + buildGetAccountController().execute(req, res, next); } ); @@ -43,7 +43,7 @@ export const accountsRouter = (appRouter: Router) => { checkTabContext, //checkUser, (req: Request, res: Response, next: NextFunction) => { - createAccountController().execute(req, res, next); + buildCreateAccountController().execute(req, res, next); } ); @@ -53,7 +53,7 @@ export const accountsRouter = (appRouter: Router) => { checkTabContext, //checkUser, (req: Request, res: Response, next: NextFunction) => { - updateAccountController().execute(req, res, next); + buildUpdateAccountController().execute(req, res, next); } ); diff --git a/apps/server/src/routes/auth.routes.ts b/apps/server/src/routes/auth.routes.ts index fbb50677..edaf5b13 100644 --- a/apps/server/src/routes/auth.routes.ts +++ b/apps/server/src/routes/auth.routes.ts @@ -1,10 +1,10 @@ import { validateRequestDTO } from "@common/presentation"; import { checkTabContext, checkUser } from "@contexts/auth/infraestructure"; import { - loginController, - logoutController, - refreshTokenController, - registerController, + buildLoginController, + buildLogoutController, + buildRefreshTokenController, + buildRegisterController, } from "@contexts/auth/presentation/controllers"; import { LoginUserSchema, @@ -30,7 +30,7 @@ export const authRouter = (appRouter: Router) => { * @apiError (400) {String} message Error message. */ routes.post("/register", validateRequestDTO(RegisterUserSchema), (req, res, next) => { - registerController().execute(req, res, next); + buildRegisterController().execute(req, res, next); }); /** @@ -53,7 +53,7 @@ export const authRouter = (appRouter: Router) => { validateRequestDTO(LoginUserSchema), checkTabContext, (req: Request, res: Response, next: NextFunction) => { - loginController().execute(req, res, next); + buildLoginController().execute(req, res, next); } ); @@ -73,7 +73,7 @@ export const authRouter = (appRouter: Router) => { checkTabContext, checkUser, (req: Request, res: Response, next: NextFunction) => { - logoutController().execute(req, res, next); + buildLogoutController().execute(req, res, next); } ); @@ -82,7 +82,7 @@ export const authRouter = (appRouter: Router) => { validateRequestDTO(RefreshTokenSchema), checkTabContext, (req: Request, res: Response, next: NextFunction) => { - refreshTokenController().execute(req, res, next); + buildRefreshTokenController().execute(req, res, next); } ); diff --git a/apps/server/src/routes/customer-invoices.routes.ts b/apps/server/src/routes/customer-invoices.routes.ts deleted file mode 100644 index 1f24f6f9..00000000 --- a/apps/server/src/routes/customer-invoices.routes.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { validateRequestDTO } from "@common/presentation"; -import { checkTabContext, checkUser } from "@contexts/auth/infraestructure"; -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) => { - const routes: Router = Router({ mergeParams: true }); - - routes.get( - "/", - validateRequestDTO(ListCustomerInvoicesSchema), - checkTabContext, - checkUser, - (req: Request, res: Response, next: NextFunction) => { - listCustomerInvoicesController().execute(req, res, next); - } - ); - - routes.get( - "/:invoiceId", - validateRequestDTO(GetCustomerInvoiceSchema), - checkTabContext, - checkUser, - (req: Request, res: Response, next: NextFunction) => { - getCustomerInvoiceController().execute(req, res, next); - } - ); - - appRouter.use("/customer-invoices", routes); -}; diff --git a/apps/server/src/routes/invoicingRoutes.ts b/apps/server/src/routes/invoices.routes.ts similarity index 61% rename from apps/server/src/routes/invoicingRoutes.ts rename to apps/server/src/routes/invoices.routes.ts index db549683..b2cc8fe6 100644 --- a/apps/server/src/routes/invoicingRoutes.ts +++ b/apps/server/src/routes/invoices.routes.ts @@ -1,44 +1,42 @@ import { validateRequestDTO } from "@common/presentation"; -import { checkTabContext } from "@contexts/auth/infraestructure"; import { - ICreateInvoiceRequestSchema, - IDeleteInvoiceRequestSchema, + buildGetInvoiceController, + buildListInvoicesController, IGetInvoiceRequestSchema, - IUpdateInvoiceRequestSchema, - ListInvoicesRequestSchema, -} from "@contexts/invoicing/presentation/dto"; + IListInvoicesRequestSchema, +} from "@contexts/invoices/presentation"; import { NextFunction, Request, Response, Router } from "express"; -export const invoicingRouter = (appRouter: Router) => { +export const invoicesRouter = (appRouter: Router) => { const routes: Router = Router({ mergeParams: true }); routes.get( "/", - validateRequestDTO(ListInvoicesRequestSchema), - checkTabContext, + validateRequestDTO(IListInvoicesRequestSchema), + //checkTabContext, //checkUser, (req: Request, res: Response, next: NextFunction) => { - listInvoicesController().execute(req, res, next); + buildListInvoicesController().execute(req, res, next); } ); routes.get( "/:invoiceId", validateRequestDTO(IGetInvoiceRequestSchema), - checkTabContext, + //checkTabContext, //checkUser, (req: Request, res: Response, next: NextFunction) => { - getInvoiceController().execute(req, res, next); + buildGetInvoiceController().execute(req, res, next); } ); - routes.post( + /*routes.post( "/", validateRequestDTO(ICreateInvoiceRequestSchema), checkTabContext, //checkUser, (req: Request, res: Response, next: NextFunction) => { - createInvoiceController().execute(req, res, next); + buildCreateInvoiceController().execute(req, res, next); } ); @@ -48,7 +46,7 @@ export const invoicingRouter = (appRouter: Router) => { checkTabContext, //checkUser, (req: Request, res: Response, next: NextFunction) => { - updateInvoiceController().execute(req, res, next); + buildUpdateInvoiceController().execute(req, res, next); } ); @@ -58,9 +56,9 @@ export const invoicingRouter = (appRouter: Router) => { checkTabContext, //checkUser, (req: Request, res: Response, next: NextFunction) => { - updateInvoiceController().execute(req, res, next); + buildDeleteInvoiceController().execute(req, res, next); } - ); + );*/ appRouter.use("/invoices", routes); }; diff --git a/apps/server/src/routes/users.routes.ts b/apps/server/src/routes/users.routes.ts index ecdce4bf..be99b071 100644 --- a/apps/server/src/routes/users.routes.ts +++ b/apps/server/src/routes/users.routes.ts @@ -1,6 +1,6 @@ import { validateRequestDTO } from "@common/presentation"; import { checkTabContext, checkUserIsAdmin } from "@contexts/auth/infraestructure"; -import { listUsersController, ListUsersSchema } from "@contexts/auth/presentation"; +import { buildListUsersController, ListUsersSchema } from "@contexts/auth/presentation"; import { NextFunction, Request, Response, Router } from "express"; export const usersRouter = (appRouter: Router) => { @@ -12,7 +12,7 @@ export const usersRouter = (appRouter: Router) => { checkTabContext, checkUserIsAdmin, (req: Request, res: Response, next: NextFunction) => { - listUsersController().execute(req, res, next); + buildListUsersController().execute(req, res, next); } ); diff --git a/apps/server/src/routes/v1.routes.ts b/apps/server/src/routes/v1.routes.ts index 669c040e..e0c4aeef 100644 --- a/apps/server/src/routes/v1.routes.ts +++ b/apps/server/src/routes/v1.routes.ts @@ -1,7 +1,7 @@ import { Router } from "express"; import { accountsRouter } from "./accounts.routes"; import { authRouter } from "./auth.routes"; -import { customerInvoicesRouter } from "./customer-invoices.routes"; +import { invoicesRouter } from "./invoices.routes"; import { usersRouter } from "./users.routes"; export const v1Routes = () => { @@ -16,7 +16,7 @@ export const v1Routes = () => { accountsRouter(routes); // Sales - customerInvoicesRouter(routes); + invoicesRouter(routes); return routes; }; diff --git a/src-1/contexts/billing/application/create-factura/create-factura.use-case.ts b/src-1/contexts/billing/application/create-factura/create-factura.use-case.ts new file mode 100644 index 00000000..8ea1d54f --- /dev/null +++ b/src-1/contexts/billing/application/create-factura/create-factura.use-case.ts @@ -0,0 +1,63 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + + // 🔹 Disparar evento de dominio "FacturaDraft" + factura.addDomainEvent(new FacturaDraft(factura.id, props.referencia)); + + return Result.ok(factura); + } + + modify(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + // Lógica para modificar la factura + return Result.ok(); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + // Lógica para eliminar la factura + return Result.ok(); + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id, this.props.referencia)); + return Result.ok(); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id, this.props.referencia)); + return Result.ok(); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id, this.props.referencia)); + return Result.ok(); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/application/delete-factura/delete-factura.use-case.ts b/src-1/contexts/billing/application/delete-factura/delete-factura.use-case.ts new file mode 100644 index 00000000..b2a91a28 --- /dev/null +++ b/src-1/contexts/billing/application/delete-factura/delete-factura.use-case.ts @@ -0,0 +1,63 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(props.id)); + return Result.ok(factura); + } + + modify(props: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + Object.assign(this.props, props); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id)); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id)); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id)); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/application/emit-factura/emit-factura.use-case.ts b/src-1/contexts/billing/application/emit-factura/emit-factura.use-case.ts new file mode 100644 index 00000000..f546c1ad --- /dev/null +++ b/src-1/contexts/billing/application/emit-factura/emit-factura.use-case.ts @@ -0,0 +1,63 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + + // Emitir evento de dominio "FacturaDraft" + factura.addDomainEvent(new FacturaDraft(factura.id, props.referencia)); + + return Result.ok(factura); + } + + modify(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + // Lógica para modificar la factura + return Result.ok(); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + // Lógica para eliminar la factura + return Result.ok(); + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id)); + return Result.ok(); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id)); + return Result.ok(); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id)); + return Result.ok(); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/application/reject-factura/reject-factura.use-case.ts b/src-1/contexts/billing/application/reject-factura/reject-factura.use-case.ts new file mode 100644 index 00000000..5f4c1aea --- /dev/null +++ b/src-1/contexts/billing/application/reject-factura/reject-factura.use-case.ts @@ -0,0 +1,73 @@ +// filepath: /home/rodax/Documentos/uecko-erp/apps/server/src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + factura.addDomainEvent(new FacturaDraft(factura.id)); + return Result.ok(factura); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo puede ser emitida en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id)); + return Result.ok(); + } + + send(): Result { + if (this.props.estado !== FacturaStatus.createEmitted()) { + return Result.fail(new Error("La factura solo puede ser enviada en estado 'emitted'.")); + } + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id)); + return Result.ok(); + } + + reject(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo puede ser rechazada en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id)); + return Result.ok(); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } + + get lineasDetalle(): any[] { + return this.props.lineasDetalle; + } + + get clienteOProveedor(): string { + return this.props.clienteOProveedor; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/application/search-factura-by-id/search-factura-by-id.use-case.ts b/src-1/contexts/billing/application/search-factura-by-id/search-factura-by-id.use-case.ts new file mode 100644 index 00000000..91170a8f --- /dev/null +++ b/src-1/contexts/billing/application/search-factura-by-id/search-factura-by-id.use-case.ts @@ -0,0 +1,68 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + + // Emitir evento de dominio "FacturaDraft" + factura.addDomainEvent(new FacturaDraft(props.id)); + + return Result.ok(factura); + } + + modify(newProps: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + + Object.assign(this.props, newProps); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id)); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id)); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id)); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/application/search-factura-by-reference/search-factura-by-reference.use-case.ts b/src-1/contexts/billing/application/search-factura-by-reference/search-factura-by-reference.use-case.ts new file mode 100644 index 00000000..a0f77422 --- /dev/null +++ b/src-1/contexts/billing/application/search-factura-by-reference/search-factura-by-reference.use-case.ts @@ -0,0 +1,60 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + + // Emitir evento de dominio "FacturaDraft" + factura.addDomainEvent(new FacturaDraft(factura.id, props.referencia)); + + return Result.ok(factura); + } + + modify(newProps: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + + Object.assign(this.props, newProps); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + + // Lógica para eliminar la factura + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id, this.props.referencia)); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id, this.props.referencia)); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id, this.props.referencia)); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/domain/events/factura-rejected.ts b/src-1/contexts/billing/domain/events/factura-rejected.ts new file mode 100644 index 00000000..c35df344 --- /dev/null +++ b/src-1/contexts/billing/domain/events/factura-rejected.ts @@ -0,0 +1,60 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + + // Emitir evento de dominio "FacturaDraft" + factura.addDomainEvent(new FacturaDraft(factura.id)); + + return Result.ok(factura); + } + + modify(newProps: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + + Object.assign(this.props, newProps); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + + // Lógica para eliminar la factura + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id)); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id)); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id)); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/domain/events/factura-sent.ts b/src-1/contexts/billing/domain/events/factura-sent.ts new file mode 100644 index 00000000..76313e42 --- /dev/null +++ b/src-1/contexts/billing/domain/events/factura-sent.ts @@ -0,0 +1,69 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(props.id)); + return Result.ok(factura); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id)); + return Result.ok(); + } + + send(): Result { + if (this.props.estado !== FacturaStatus.createEmitted()) { + return Result.fail(new Error("La factura solo se puede enviar en estado 'emitted'.")); + } + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id)); + return Result.ok(); + } + + reject(): Result { + if (this.props.estado !== FacturaStatus.createEmitted()) { + return Result.fail(new Error("La factura solo se puede rechazar en estado 'emitted'.")); + } + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id)); + return Result.ok(); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } + + get lineasDetalle(): any[] { + return this.props.lineasDetalle; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/domain/events/index.ts b/src-1/contexts/billing/domain/events/index.ts new file mode 100644 index 00000000..c35df344 --- /dev/null +++ b/src-1/contexts/billing/domain/events/index.ts @@ -0,0 +1,60 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + + // Emitir evento de dominio "FacturaDraft" + factura.addDomainEvent(new FacturaDraft(factura.id)); + + return Result.ok(factura); + } + + modify(newProps: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + + Object.assign(this.props, newProps); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + + // Lógica para eliminar la factura + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id)); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id)); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id)); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/domain/repositories/factura-repository.interface.ts b/src-1/contexts/billing/domain/repositories/factura-repository.interface.ts new file mode 100644 index 00000000..a0f77422 --- /dev/null +++ b/src-1/contexts/billing/domain/repositories/factura-repository.interface.ts @@ -0,0 +1,60 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + + // Emitir evento de dominio "FacturaDraft" + factura.addDomainEvent(new FacturaDraft(factura.id, props.referencia)); + + return Result.ok(factura); + } + + modify(newProps: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + + Object.assign(this.props, newProps); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + + // Lógica para eliminar la factura + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id, this.props.referencia)); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id, this.props.referencia)); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id, this.props.referencia)); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/domain/repositories/index.ts b/src-1/contexts/billing/domain/repositories/index.ts new file mode 100644 index 00000000..577b5d4c --- /dev/null +++ b/src-1/contexts/billing/domain/repositories/index.ts @@ -0,0 +1,67 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(props.id, props.referencia)); + return Result.ok(factura); + } + + modify(newProps: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + Object.assign(this.props, newProps); + return Result.ok(undefined); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id, this.props.referencia)); + return Result.ok(undefined); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id, this.props.referencia)); + return Result.ok(undefined); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id, this.props.referencia)); + return Result.ok(undefined); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/domain/services/index.ts b/src-1/contexts/billing/domain/services/index.ts new file mode 100644 index 00000000..8b316ef6 --- /dev/null +++ b/src-1/contexts/billing/domain/services/index.ts @@ -0,0 +1,63 @@ +// filepath: /home/rodax/Documentos/uecko-erp/apps/server/src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + + // Emitir evento de dominio "FacturaDraft" + factura.addDomainEvent(new FacturaDraft(factura.id, props.referencia)); + + return Result.ok(factura); + } + + modify(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + // Lógica para modificar la factura + return Result.ok(); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + // Lógica para eliminar la factura + return Result.ok(); + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id)); + return Result.ok(); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id)); + return Result.ok(); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id)); + return Result.ok(); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/domain/value-objects/factura-reference.ts b/src-1/contexts/billing/domain/value-objects/factura-reference.ts new file mode 100644 index 00000000..d6702796 --- /dev/null +++ b/src-1/contexts/billing/domain/value-objects/factura-reference.ts @@ -0,0 +1,68 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + + // Emitir evento de dominio "FacturaDraft" + factura.addDomainEvent(new FacturaDraft(factura.id, props.referencia)); + + return Result.ok(factura); + } + + modify(newProps: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + + Object.assign(this.props, newProps); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id, this.props.referencia)); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id, this.props.referencia)); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id, this.props.referencia)); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/domain/value-objects/factura-status.ts b/src-1/contexts/billing/domain/value-objects/factura-status.ts new file mode 100644 index 00000000..cf35769f --- /dev/null +++ b/src-1/contexts/billing/domain/value-objects/factura-status.ts @@ -0,0 +1,65 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Se puede definir una entidad específica para las líneas de detalle + clienteOProveedor: any; // Se puede definir una entidad específica para el cliente o proveedor + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + factura.addDomainEvent(new FacturaDraft(factura.id, props.referencia)); + return Result.ok(factura); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.draft) { + return Result.fail(new Error("La factura solo puede ser emitida en estado 'draft'.")); + } + this.props.estado = FacturaStatus.emitted(); + this.addDomainEvent(new FacturaEmitted(this.id)); + return Result.ok(); + } + + send(): Result { + if (this.props.estado !== FacturaStatus.emitted) { + return Result.fail(new Error("La factura solo puede ser enviada en estado 'emitted'.")); + } + this.props.estado = FacturaStatus.sent(); + this.addDomainEvent(new FacturaSent(this.id)); + return Result.ok(); + } + + reject(): Result { + if (this.props.estado !== FacturaStatus.draft) { + return Result.fail(new Error("La factura solo puede ser rechazada en estado 'draft'.")); + } + this.props.estado = FacturaStatus.rejected(); + this.addDomainEvent(new FacturaRejected(this.id)); + return Result.ok(); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/domain/value-objects/index.ts b/src-1/contexts/billing/domain/value-objects/index.ts new file mode 100644 index 00000000..65904df5 --- /dev/null +++ b/src-1/contexts/billing/domain/value-objects/index.ts @@ -0,0 +1,63 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(props.id)); + return Result.ok(factura); + } + + update(props: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + Object.assign(this.props, props); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id)); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id)); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id)); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/infrastructure/express/index.ts b/src-1/contexts/billing/infrastructure/express/index.ts new file mode 100644 index 00000000..036fa33f --- /dev/null +++ b/src-1/contexts/billing/infrastructure/express/index.ts @@ -0,0 +1,66 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(props.id)); + return Result.ok(factura); + } + + update(props: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + Object.assign(this.props, props); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id)); + return Result.ok(); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id)); + return Result.ok(); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id)); + return Result.ok(); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/infrastructure/mappers/index.ts b/src-1/contexts/billing/infrastructure/mappers/index.ts new file mode 100644 index 00000000..f6f9541d --- /dev/null +++ b/src-1/contexts/billing/infrastructure/mappers/index.ts @@ -0,0 +1,73 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(props.id)); + return Result.ok(factura); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.draft) { + return Result.fail(new Error("La factura solo puede ser emitida en estado 'draft'.")); + } + this.props.estado = FacturaStatus.emitted; + this.addDomainEvent(new FacturaEmitted(this.props.id)); + return Result.ok(); + } + + send(): Result { + if (this.props.estado !== FacturaStatus.emitted) { + return Result.fail(new Error("La factura solo puede ser enviada en estado 'emitted'.")); + } + this.props.estado = FacturaStatus.sent; + this.addDomainEvent(new FacturaSent(this.props.id)); + return Result.ok(); + } + + reject(): Result { + if (this.props.estado !== FacturaStatus.draft) { + return Result.fail(new Error("La factura solo puede ser rechazada en estado 'draft'.")); + } + this.props.estado = FacturaStatus.rejected; + this.addDomainEvent(new FacturaRejected(this.props.id)); + return Result.ok(); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } + + get lineasDetalle(): any[] { + return this.props.lineasDetalle; + } + + get clienteOProveedor(): string { + return this.props.clienteOProveedor; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/infrastructure/sequelize/index.ts b/src-1/contexts/billing/infrastructure/sequelize/index.ts new file mode 100644 index 00000000..1925dbe6 --- /dev/null +++ b/src-1/contexts/billing/infrastructure/sequelize/index.ts @@ -0,0 +1,63 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(props.id)); + return Result.ok(factura); + } + + modify(newProps: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + Object.assign(this.props, newProps); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id)); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id)); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id)); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/presentation/controllers/create-factura/create-factura.controller.ts b/src-1/contexts/billing/presentation/controllers/create-factura/create-factura.controller.ts new file mode 100644 index 00000000..15f78f5b --- /dev/null +++ b/src-1/contexts/billing/presentation/controllers/create-factura/create-factura.controller.ts @@ -0,0 +1,60 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(props.id)); + return Result.ok(factura); + } + + modify(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + // Lógica para modificar la factura + return Result.ok(); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + // Lógica para eliminar la factura + return Result.ok(); + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id)); + return Result.ok(); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id)); + return Result.ok(); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id)); + return Result.ok(); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/presentation/controllers/delete-factura/delete-factura.controller.ts b/src-1/contexts/billing/presentation/controllers/delete-factura/delete-factura.controller.ts new file mode 100644 index 00000000..b416e44a --- /dev/null +++ b/src-1/contexts/billing/presentation/controllers/delete-factura/delete-factura.controller.ts @@ -0,0 +1,60 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: any; // Aquí puedes definir un tipo más específico si es necesario + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + factura.addDomainEvent(new FacturaDraft(factura.id, props.referencia)); + return Result.ok(factura); + } + + modify(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + // Lógica para modificar la factura + return Result.ok(); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + // Lógica para eliminar la factura + return Result.ok(); + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id)); + return Result.ok(); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id)); + return Result.ok(); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id)); + return Result.ok(); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/presentation/controllers/emit-factura/emit-factura.controller.ts b/src-1/contexts/billing/presentation/controllers/emit-factura/emit-factura.controller.ts new file mode 100644 index 00000000..5993a03b --- /dev/null +++ b/src-1/contexts/billing/presentation/controllers/emit-factura/emit-factura.controller.ts @@ -0,0 +1,60 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(factura.id)); + return Result.ok(factura); + } + + update(props: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + Object.assign(this.props, props); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id)); + return Result.ok(); + } + + send(): Result { + if (this.props.estado !== FacturaStatus.createEmitted()) { + return Result.fail(new Error("La factura solo se puede enviar si ha sido emitida.")); + } + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id)); + return Result.ok(); + } + + reject(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede rechazar en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id)); + return Result.ok(); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/presentation/controllers/reject-factura/reject-factura.controller.ts b/src-1/contexts/billing/presentation/controllers/reject-factura/reject-factura.controller.ts new file mode 100644 index 00000000..036fa33f --- /dev/null +++ b/src-1/contexts/billing/presentation/controllers/reject-factura/reject-factura.controller.ts @@ -0,0 +1,66 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(props.id)); + return Result.ok(factura); + } + + update(props: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + Object.assign(this.props, props); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id)); + return Result.ok(); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id)); + return Result.ok(); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id)); + return Result.ok(); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/presentation/controllers/search-factura-by-id/search-factura-by-id.controller.ts b/src-1/contexts/billing/presentation/controllers/search-factura-by-id/search-factura-by-id.controller.ts new file mode 100644 index 00000000..28654ca7 --- /dev/null +++ b/src-1/contexts/billing/presentation/controllers/search-factura-by-id/search-factura-by-id.controller.ts @@ -0,0 +1,73 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(factura.id)); + return Result.ok(factura); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.draft) { + return Result.fail(new Error("La factura solo puede ser emitida en estado 'draft'.")); + } + this.props.estado = FacturaStatus.emitted; + this.addDomainEvent(new FacturaEmitted(this.id)); + return Result.ok(); + } + + send(): Result { + if (this.props.estado !== FacturaStatus.emitted) { + return Result.fail(new Error("La factura solo puede ser enviada en estado 'emitted'.")); + } + this.props.estado = FacturaStatus.sent; + this.addDomainEvent(new FacturaSent(this.id)); + return Result.ok(); + } + + reject(): Result { + if (this.props.estado !== FacturaStatus.draft) { + return Result.fail(new Error("La factura solo puede ser rechazada en estado 'draft'.")); + } + this.props.estado = FacturaStatus.rejected; + this.addDomainEvent(new FacturaRejected(this.id)); + return Result.ok(); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } + + get lineasDetalle(): any[] { + return this.props.lineasDetalle; + } + + get clienteOProveedor(): string { + return this.props.clienteOProveedor; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/presentation/controllers/search-factura-by-reference/search-factura-by-reference.controller.ts b/src-1/contexts/billing/presentation/controllers/search-factura-by-reference/search-factura-by-reference.controller.ts new file mode 100644 index 00000000..f86409fb --- /dev/null +++ b/src-1/contexts/billing/presentation/controllers/search-factura-by-reference/search-factura-by-reference.controller.ts @@ -0,0 +1,66 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(props.id)); + return Result.ok(factura); + } + + modify(newProps: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + Object.assign(this.props, newProps); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id)); + return Result.ok(); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id)); + return Result.ok(); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id)); + return Result.ok(); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/presentation/dto/factura-request.ts b/src-1/contexts/billing/presentation/dto/factura-request.ts new file mode 100644 index 00000000..099513df --- /dev/null +++ b/src-1/contexts/billing/presentation/dto/factura-request.ts @@ -0,0 +1,65 @@ +// filepath: /home/rodax/Documentos/uecko-erp/apps/server/src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + + // Emitir evento de dominio "FacturaDraft" + factura.addDomainEvent(new FacturaDraft(factura.id)); + + return Result.ok(factura); + } + + modify(newProps: Partial): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + + Object.assign(this.props, newProps); + return Result.ok(); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + + // Lógica para eliminar la factura + return Result.ok(); + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id)); + return Result.ok(); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id)); + return Result.ok(); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id)); + return Result.ok(); + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/presentation/dto/factura-response.ts b/src-1/contexts/billing/presentation/dto/factura-response.ts new file mode 100644 index 00000000..2de0b040 --- /dev/null +++ b/src-1/contexts/billing/presentation/dto/factura-response.ts @@ -0,0 +1,73 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico para las líneas de detalle + clienteOProveedor: string; // Puede ser un ID o un objeto más complejo + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props); + factura.addDomainEvent(new FacturaDraft(props.id)); + return Result.ok(factura); + } + + emit(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede emitir en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.props.id)); + return Result.ok(); + } + + send(): Result { + if (this.props.estado !== FacturaStatus.createEmitted()) { + return Result.fail(new Error("La factura solo se puede enviar en estado 'emitted'.")); + } + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.props.id)); + return Result.ok(); + } + + reject(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede rechazar en estado 'draft'.")); + } + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.props.id)); + return Result.ok(); + } + + get referencia(): FacturaReference { + return this.props.referencia; + } + + get estado(): FacturaStatus { + return this.props.estado; + } + + get fecha(): UTCDate { + return this.props.fecha; + } + + get lineasDetalle(): any[] { + return this.props.lineasDetalle; + } + + get clienteOProveedor(): string { + return this.props.clienteOProveedor; + } +} \ No newline at end of file diff --git a/src-1/contexts/billing/presentation/dto/factura-schemas.ts b/src-1/contexts/billing/presentation/dto/factura-schemas.ts new file mode 100644 index 00000000..840b79c4 --- /dev/null +++ b/src-1/contexts/billing/presentation/dto/factura-schemas.ts @@ -0,0 +1,60 @@ +// filepath: src/contexts/billing/domain/aggregates/factura.ts +import { AggregateRoot, UniqueID, UTCDate } from "@common/domain"; +import { Result } from "@common/helpers"; +import { FacturaDraft } from "../events/factura-draft"; +import { FacturaEmitted } from "../events/factura-emitted"; +import { FacturaSent } from "../events/factura-sent"; +import { FacturaRejected } from "../events/factura-rejected"; +import { FacturaReference } from "../value-objects/factura-reference"; +import { FacturaStatus } from "../value-objects/factura-status"; + +export interface IFacturaProps { + id: UniqueID; + referencia: FacturaReference; + lineasDetalle: any[]; // Aquí puedes definir un tipo más específico si es necesario + clienteOProveedor: any; // Aquí puedes definir un tipo más específico si es necesario + estado: FacturaStatus; + fecha: UTCDate; +} + +export class Factura extends AggregateRoot { + static create(props: IFacturaProps): Result { + const factura = new Factura(props, props.id); + factura.addDomainEvent(new FacturaDraft(factura.id, props.referencia)); + return Result.ok(factura); + } + + modify(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede modificar en estado 'draft'.")); + } + // Lógica para modificar la factura + return Result.ok(); + } + + delete(): Result { + if (this.props.estado !== FacturaStatus.createDraft()) { + return Result.fail(new Error("La factura solo se puede eliminar en estado 'draft'.")); + } + // Lógica para eliminar la factura + return Result.ok(); + } + + emit(): Result { + this.props.estado = FacturaStatus.createEmitted(); + this.addDomainEvent(new FacturaEmitted(this.id)); + return Result.ok(); + } + + send(): Result { + this.props.estado = FacturaStatus.createSent(); + this.addDomainEvent(new FacturaSent(this.id)); + return Result.ok(); + } + + reject(): Result { + this.props.estado = FacturaStatus.createRejected(); + this.addDomainEvent(new FacturaRejected(this.id)); + return Result.ok(); + } +} \ No newline at end of file