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 f5e37634..10648159 100644 --- a/apps/server/src/common/domain/value-objects/email-address.ts +++ b/apps/server/src/common/domain/value-objects/email-address.ts @@ -18,10 +18,10 @@ export class EmailAddress extends ValueObject { static createNullable(value?: string): Result, Error> { if (!value || value.trim() === "") { - return Result.ok(Maybe.None()); + return Result.ok(Maybe.none()); } - return EmailAddress.create(value!).map((value) => Maybe.Some(value)); + return EmailAddress.create(value!).map((value) => Maybe.some(value)); } private static validate(value: string) { diff --git a/apps/server/src/common/domain/value-objects/name.ts b/apps/server/src/common/domain/value-objects/name.ts index 8fdf0171..0924d207 100644 --- a/apps/server/src/common/domain/value-objects/name.ts +++ b/apps/server/src/common/domain/value-objects/name.ts @@ -28,10 +28,10 @@ export class Name extends ValueObject { static createNullable(value?: string): Result, Error> { if (!value || value.trim() === "") { - return Result.ok(Maybe.None()); + return Result.ok(Maybe.none()); } - return Name.create(value!).map((value) => Maybe.Some(value)); + return Name.create(value!).map((value) => Maybe.some(value)); } static generateAcronym(name: string): string { diff --git a/apps/server/src/common/domain/value-objects/phone-number.spec.ts b/apps/server/src/common/domain/value-objects/phone-number.spec.ts index 44f5007b..e888dee6 100644 --- a/apps/server/src/common/domain/value-objects/phone-number.spec.ts +++ b/apps/server/src/common/domain/value-objects/phone-number.spec.ts @@ -24,7 +24,7 @@ describe("PhoneNumber", () => { test("debe devolver None para valores nulos o vacíos", () => { const result = PhoneNumber.createNullable(nullablePhone); expect(result.isSuccess).toBe(true); - expect(result.data).toEqual(Maybe.None()); + expect(result.data).toEqual(Maybe.none()); }); test("debe devolver Some con un número de teléfono válido", () => { 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 ec6fa8af..33b5df9a 100644 --- a/apps/server/src/common/domain/value-objects/phone-number.ts +++ b/apps/server/src/common/domain/value-objects/phone-number.ts @@ -20,10 +20,10 @@ export class PhoneNumber extends ValueObject { static createNullable(value?: string): Result, Error> { if (!value || value.trim() === "") { - return Result.ok(Maybe.None()); + return Result.ok(Maybe.none()); } - return PhoneNumber.create(value!).map((value) => Maybe.Some(value)); + return PhoneNumber.create(value!).map((value) => Maybe.some(value)); } static validate(value: string) { diff --git a/apps/server/src/common/domain/value-objects/postal-address.spec.ts b/apps/server/src/common/domain/value-objects/postal-address.spec.ts index 1319b8ad..e1ca44e1 100644 --- a/apps/server/src/common/domain/value-objects/postal-address.spec.ts +++ b/apps/server/src/common/domain/value-objects/postal-address.spec.ts @@ -24,7 +24,7 @@ describe("PostalAddress Value Object", () => { expect(result.error?.message).toBe("Invalid postal code format"); }); - test("✅ `createNullable` debería devolver Maybe.None si los valores son nulos o vacíos", () => { + test("✅ `createNullable` debería devolver Maybe.none si los valores son nulos o vacíos", () => { expect(PostalAddress.createNullable().data.isSome()).toBe(false); expect( PostalAddress.createNullable({ @@ -37,7 +37,7 @@ describe("PostalAddress Value Object", () => { ).toBe(false); }); - test("✅ `createNullable` debería devolver Maybe.Some si los valores son válidos", () => { + test("✅ `createNullable` debería devolver Maybe.some si los valores son válidos", () => { const result = PostalAddress.createNullable(validAddress); expect(result.isSuccess).toBe(true); 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 3080ee8e..f47f232e 100644 --- a/apps/server/src/common/domain/value-objects/postal-address.ts +++ b/apps/server/src/common/domain/value-objects/postal-address.ts @@ -18,28 +18,23 @@ const provinceSchema = z.string().min(2).max(50); const citySchema = z.string().min(2).max(50); const streetSchema = z.string().min(2).max(255); +const street2Schema = z.string().optional(); interface IPostalAddressProps { street: string; + street2?: string; city: string; postalCode: string; state: string; country: string; } -export interface IPostalAddressPrimitives extends IPostalAddressProps { - street: string; - city: string; - postal_code: string; - state: string; - country: string; -} - export class PostalAddress extends ValueObject { protected static validate(values: IPostalAddressProps) { return z .object({ street: streetSchema, + street2: street2Schema, city: citySchema, postalCode: postalCodeSchema, state: provinceSchema, @@ -54,21 +49,25 @@ export class PostalAddress extends ValueObject { if (!valueIsValid.success) { return Result.fail(new Error(valueIsValid.error.errors[0].message)); } - return Result.ok(new PostalAddress(valueIsValid.data!)); + return Result.ok(new PostalAddress(values)); } static createNullable(values?: IPostalAddressProps): Result, Error> { if (!values || Object.values(values).every((value) => value.trim() === "")) { - return Result.ok(Maybe.None()); + return Result.ok(Maybe.none()); } - return PostalAddress.create(values!).map((value) => Maybe.Some(value)); + return PostalAddress.create(values!).map((value) => Maybe.some(value)); } get street(): string { return this.props.street; } + get street2(): Maybe { + return Maybe.fromNullable(this.props.street2); + } + get city(): string { return this.props.city; } @@ -90,6 +89,6 @@ export class PostalAddress extends ValueObject { } toString(): string { - return `${this.props.street}, ${this.props.city}, ${this.props.postalCode}, ${this.props.state}, ${this.props.country}`; + 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/slug.ts b/apps/server/src/common/domain/value-objects/slug.ts index 42d74dbb..c832452b 100644 --- a/apps/server/src/common/domain/value-objects/slug.ts +++ b/apps/server/src/common/domain/value-objects/slug.ts @@ -33,10 +33,10 @@ export class Slug extends ValueObject { static createNullable(value?: string): Result, Error> { if (!value || value.trim() === "") { - return Result.ok(Maybe.None()); + return Result.ok(Maybe.none()); } - return Slug.create(value!).map((value: Slug) => Maybe.Some(value)); + return Slug.create(value!).map((value: Slug) => Maybe.some(value)); } getValue(): string { 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 ffed5d45..286c1538 100644 --- a/apps/server/src/common/domain/value-objects/tin-number.ts +++ b/apps/server/src/common/domain/value-objects/tin-number.ts @@ -35,10 +35,10 @@ export class TINNumber extends ValueObject { static createNullable(value?: string): Result, Error> { if (!value || value.trim() === "") { - return Result.ok(Maybe.None()); + return Result.ok(Maybe.none()); } - return TINNumber.create(value!).map((value) => Maybe.Some(value)); + return TINNumber.create(value!).map((value) => Maybe.some(value)); } getValue(): string { diff --git a/apps/server/src/common/helpers/maybe.spec.ts b/apps/server/src/common/helpers/maybe.spec.ts index b6a145a6..8865a40f 100644 --- a/apps/server/src/common/helpers/maybe.spec.ts +++ b/apps/server/src/common/helpers/maybe.spec.ts @@ -2,19 +2,19 @@ import { Maybe } from "./maybe"; describe("Maybe", () => { test("debe contener un valor cuando se usa Some", () => { - const maybeNumber = Maybe.Some(42); + const maybeNumber = Maybe.some(42); expect(maybeNumber.isSome()).toBe(true); expect(maybeNumber.getOrUndefined()).toBe(42); }); test("debe estar vacío cuando se usa None", () => { - const maybeEmpty = Maybe.None(); + const maybeEmpty = Maybe.none(); expect(maybeEmpty.isSome()).toBe(false); expect(maybeEmpty.getOrUndefined()).toBeUndefined(); }); test("map debe transformar el valor si existe", () => { - const maybeNumber = Maybe.Some(10); + const maybeNumber = Maybe.some(10); const maybeDoubled = maybeNumber.map((n) => n * 2); expect(maybeDoubled.isSome()).toBe(true); @@ -22,7 +22,7 @@ describe("Maybe", () => { }); test("map debe retornar None si el valor no existe", () => { - const maybeEmpty = Maybe.None(); + const maybeEmpty = Maybe.none(); const maybeTransformed = maybeEmpty.map((n) => n * 2); expect(maybeTransformed.isSome()).toBe(false); diff --git a/apps/server/src/common/helpers/maybe.ts b/apps/server/src/common/helpers/maybe.ts index 7572293b..390c090b 100644 --- a/apps/server/src/common/helpers/maybe.ts +++ b/apps/server/src/common/helpers/maybe.ts @@ -1,22 +1,26 @@ /** * Uso: * - * const maybeNumber = Maybe.Some(10); + * const maybeNumber = Maybe.some(10); * const doubled = maybeNumber.map(n => n * 2); * console.log(doubled.getValue()); // 20 - * const noValue = Maybe.None(); + * const noValue = Maybe.none(); * console.log(noValue.isSome()); // false **/ export class Maybe { private constructor(private readonly value?: T) {} - static Some(value: T): Maybe { + static fromNullable(value?: T): Maybe { + return value === undefined || value === null ? Maybe.none() : Maybe.some(value); + } + + static some(value: T): Maybe { return new Maybe(value); } - static None(): Maybe { + static none(): Maybe { return new Maybe(); } @@ -37,6 +41,6 @@ export class Maybe { } map(fn: (value: T) => U): Maybe { - return this.isSome() ? Maybe.Some(fn(this.value as T)) : Maybe.None(); + return this.isSome() ? Maybe.some(fn(this.value as T)) : Maybe.none(); } } 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 cb330243..03b64de1 100644 --- a/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts +++ b/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts @@ -46,19 +46,19 @@ export class AccountMapper { isFreelancer: source.is_freelancer, name: source.name, - tradeName: source.trade_name ? Maybe.Some(source.trade_name) : Maybe.None(), + tradeName: source.trade_name ? Maybe.some(source.trade_name) : Maybe.none(), tin: tinOrError.data, address: postalAddressOrError.data, email: emailOrError.data, phone: phoneOrError.data, fax: faxOrError.data, - website: source.website ? Maybe.Some(source.website) : Maybe.None(), + website: source.website ? Maybe.some(source.website) : Maybe.none(), legalRecord: source.legal_record, defaultTax: source.default_tax, status: source.status, langCode: source.lang_code, currencyCode: source.currency_code, - logo: source.logo ? Maybe.Some(source.logo) : Maybe.None(), + logo: source.logo ? Maybe.some(source.logo) : Maybe.none(), }, idOrError.data ); diff --git a/apps/server/src/contexts/contacts/infraestructure/mappers/contact.mapper.ts b/apps/server/src/contexts/contacts/infraestructure/mappers/contact.mapper.ts index 2d9c7b1b..89879aca 100644 --- a/apps/server/src/contexts/contacts/infraestructure/mappers/contact.mapper.ts +++ b/apps/server/src/contexts/contacts/infraestructure/mappers/contact.mapper.ts @@ -47,13 +47,13 @@ export class ContactMapper isFreelancer: source.is_freelancer, reference: source.reference, name: source.name, - tradeName: source.trade_name ? Maybe.Some(source.trade_name) : Maybe.None(), + tradeName: source.trade_name ? Maybe.some(source.trade_name) : Maybe.none(), tin: tinOrError.data, address: postalAddressOrError.data, email: emailOrError.data, phone: phoneOrError.data, fax: faxOrError.data, - website: source.website ? Maybe.Some(source.website) : Maybe.None(), + website: source.website ? Maybe.some(source.website) : Maybe.none(), legalRecord: source.legal_record, defaultTax: source.default_tax, status: source.status, diff --git a/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice/customer-invoice.ts b/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice.ts similarity index 84% rename from apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice/customer-invoice.ts rename to apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice.ts index fc0a8142..9b45f932 100644 --- a/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice/customer-invoice.ts +++ b/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice.ts @@ -1,14 +1,14 @@ import { AggregateRoot, UniqueID, UtcDate } from "@common/domain"; import { Result } from "@common/helpers"; -import { CustomerInvoiceItem } from "../../entities"; -import { InvoiceStatus } from "../../value-objetcs"; -import { Customer } from "../customer/customer"; +import { Customer, CustomerInvoiceItem } from "../entities"; +import { InvoiceStatus } from "../value-objetcs"; export interface ICustomerInvoiceProps { status: InvoiceStatus; issueDate: UtcDate; invoiceNumber: string; invoiceType: string; + invoiceCustomerReference: string; customer: Customer; items: CustomerInvoiceItem[]; @@ -20,6 +20,7 @@ export interface ICustomerInvoice { issueDate: UtcDate; invoiceNumber: string; invoiceType: string; + invoiceCustomerReference: string; customer: Customer; items: CustomerInvoiceItem[]; @@ -62,6 +63,10 @@ export class CustomerInvoice return this.props.invoiceType; } + get invoiceCustomerReference(): string { + return this.props.invoiceCustomerReference; + } + get customer(): Customer { return this.props.customer; } diff --git a/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice/index.ts b/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice/index.ts deleted file mode 100644 index 8fdd6983..00000000 --- a/apps/server/src/contexts/customer-billing/domain/aggregates/customer-invoice/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./customer-invoice"; diff --git a/apps/server/src/contexts/customer-billing/domain/aggregates/customer/customer.ts b/apps/server/src/contexts/customer-billing/domain/aggregates/customer/customer.ts deleted file mode 100644 index 805c2350..00000000 --- a/apps/server/src/contexts/customer-billing/domain/aggregates/customer/customer.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { - AggregateRoot, - EmailAddress, - PhoneNumber, - PostalAddress, - TINNumber, - UniqueID, -} from "@common/domain"; -import { Maybe, Result } from "@common/helpers"; - -export interface ICustomerProps { - reference: string; - isFreelancer: boolean; - name: string; - tin: TINNumber; - address: PostalAddress; - email: EmailAddress; - phone: PhoneNumber; - legalRecord: string; - defaultTax: number; - status: string; - langCode: string; - currencyCode: string; - - tradeName: Maybe; - website: Maybe; - fax: Maybe; -} - -export interface ICustomer { - id: UniqueID; - reference: string; - name: string; - tin: TINNumber; - address: PostalAddress; - email: EmailAddress; - phone: PhoneNumber; - legalRecord: string; - defaultTax: number; - langCode: string; - currencyCode: string; - - tradeName: Maybe; - fax: Maybe; - website: Maybe; - - isCustomer: boolean; - isFreelancer: boolean; - isActive: boolean; -} - -export class Customer extends AggregateRoot implements ICustomer { - static create(props: ICustomerProps, id?: UniqueID): Result { - const customer = new Customer(props, id); - - // Reglas de negocio / validaciones - // ... - // ... - - // 🔹 Disparar evento de dominio "CustomerAuthenticatedEvent" - //const { customer } = props; - //user.addDomainEvent(new CustomerAuthenticatedEvent(id, customer.toString())); - - return Result.ok(customer); - } - - get reference() { - return this.props.reference; - } - - get name() { - return this.props.name; - } - - get tradeName() { - return this.props.tradeName; - } - - get tin(): TINNumber { - return this.props.tin; - } - - get address(): PostalAddress { - return this.props.address; - } - - get email(): EmailAddress { - return this.props.email; - } - - get phone(): PhoneNumber { - return this.props.phone; - } - - get fax(): Maybe { - return this.props.fax; - } - - get website() { - return this.props.website; - } - - get legalRecord() { - return this.props.legalRecord; - } - - get defaultTax() { - return this.props.defaultTax; - } - - get langCode() { - return this.props.langCode; - } - - get currencyCode() { - return this.props.currencyCode; - } - - get isCustomer(): boolean { - return !this.props.isFreelancer; - } - - get isFreelancer(): boolean { - return this.props.isFreelancer; - } - - get isActive(): boolean { - return this.props.status === "active"; - } -} diff --git a/apps/server/src/contexts/customer-billing/domain/aggregates/customer/index.ts b/apps/server/src/contexts/customer-billing/domain/aggregates/customer/index.ts deleted file mode 100644 index 2b295031..00000000 --- a/apps/server/src/contexts/customer-billing/domain/aggregates/customer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./customer"; diff --git a/apps/server/src/contexts/customer-billing/domain/aggregates/index.ts b/apps/server/src/contexts/customer-billing/domain/aggregates/index.ts index b3a2d541..8fdd6983 100644 --- a/apps/server/src/contexts/customer-billing/domain/aggregates/index.ts +++ b/apps/server/src/contexts/customer-billing/domain/aggregates/index.ts @@ -1,2 +1 @@ -export * from "./customer"; export * from "./customer-invoice"; diff --git a/apps/server/src/contexts/customer-billing/domain/entities/customer-invoice-item.ts b/apps/server/src/contexts/customer-billing/domain/entities/customer-invoice-item.ts index de02f17f..35cc8ddb 100644 --- a/apps/server/src/contexts/customer-billing/domain/entities/customer-invoice-item.ts +++ b/apps/server/src/contexts/customer-billing/domain/entities/customer-invoice-item.ts @@ -57,28 +57,28 @@ export class CustomerInvoiceItem calculateSubtotal(): Maybe { if (this.quantity.isNone() || this.unitPrice.isNone()) { - return Maybe.None(); + return Maybe.none(); } const _quantity = this.quantity.getOrUndefined()!; const _unitPrice = this.unitPrice.getOrUndefined()!; const _subtotal = _unitPrice.multiply(_quantity); - return Maybe.Some(_subtotal); + return Maybe.some(_subtotal); } calculateTotal(): Maybe { const subtotal = this.calculateSubtotal(); if (subtotal.isNone()) { - return Maybe.None(); + return Maybe.none(); } const _subtotal = subtotal.getOrUndefined()!; const _discount = this.discount.getOrUndefined()!; const _total = _subtotal.subtract(_subtotal.percentage(_discount)); - return Maybe.Some(_total); + return Maybe.some(_total); } get description(): Maybe { diff --git a/apps/server/src/contexts/customer-billing/domain/entities/customer.ts b/apps/server/src/contexts/customer-billing/domain/entities/customer.ts new file mode 100644 index 00000000..23cf71c6 --- /dev/null +++ b/apps/server/src/contexts/customer-billing/domain/entities/customer.ts @@ -0,0 +1,44 @@ +import { AggregateRoot, PostalAddress, TINNumber, UniqueID } from "@common/domain"; +import { Result } from "@common/helpers"; + +export interface ICustomerProps { + name: string; + tin: TINNumber; + address: PostalAddress; +} + +export interface ICustomer { + id: UniqueID; + + name: string; + tin: TINNumber; + address: PostalAddress; +} + +export class Customer extends AggregateRoot implements ICustomer { + static create(props: ICustomerProps, id?: UniqueID): Result { + const customer = new Customer(props, id); + + // Reglas de negocio / validaciones + // ... + // ... + + // 🔹 Disparar evento de dominio "CustomerAuthenticatedEvent" + //const { customer } = props; + //user.addDomainEvent(new CustomerAuthenticatedEvent(id, customer.toString())); + + return Result.ok(customer); + } + + get name() { + return this.props.name; + } + + get tin(): TINNumber { + return this.props.tin; + } + + get address(): PostalAddress { + return this.props.address; + } +} diff --git a/apps/server/src/contexts/customer-billing/domain/entities/index.ts b/apps/server/src/contexts/customer-billing/domain/entities/index.ts index 11bac671..daed5a19 100644 --- a/apps/server/src/contexts/customer-billing/domain/entities/index.ts +++ b/apps/server/src/contexts/customer-billing/domain/entities/index.ts @@ -1,3 +1,4 @@ +export * from "./customer"; export * from "./customer-invoice-item"; export * from "./tax"; export * from "./tax-collection"; diff --git a/apps/server/src/contexts/customer-billing/domain/index.ts b/apps/server/src/contexts/customer-billing/domain/index.ts index ef023faa..eddad70d 100644 --- a/apps/server/src/contexts/customer-billing/domain/index.ts +++ b/apps/server/src/contexts/customer-billing/domain/index.ts @@ -1,3 +1,4 @@ export * from "./aggregates"; +export * from "./entities"; export * from "./repositories"; export * from "./services"; diff --git a/apps/server/src/contexts/customer-billing/domain/repositories/customer-repository.interface.ts b/apps/server/src/contexts/customer-billing/domain/repositories/customer-repository.interface.ts deleted file mode 100644 index c2189ec0..00000000 --- a/apps/server/src/contexts/customer-billing/domain/repositories/customer-repository.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmailAddress, UniqueID } from "@common/domain"; -import { Collection, Result } from "@common/helpers"; -import { Customer } from "../aggregates"; - -export interface ICustomerRepository { - findAll(transaction?: any): Promise, Error>>; - findById(id: UniqueID, transaction?: any): Promise>; - findByEmail(email: EmailAddress, transaction?: any): Promise>; -} diff --git a/apps/server/src/contexts/customer-billing/domain/services/customer-service.interface.ts b/apps/server/src/contexts/customer-billing/domain/services/customer-service.interface.ts deleted file mode 100644 index 73d19547..00000000 --- a/apps/server/src/contexts/customer-billing/domain/services/customer-service.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { UniqueID } from "@common/domain"; -import { Collection, Result } from "@common/helpers"; -import { Customer } from "../aggregates"; - -export interface ICustomerService { - findCustomer(transaction?: any): Promise, Error>>; - findCustomerById(customerId: UniqueID, transaction?: any): Promise>; -} diff --git a/apps/server/src/contexts/customer-billing/domain/services/customer.service.ts b/apps/server/src/contexts/customer-billing/domain/services/customer.service.ts deleted file mode 100644 index 926f67a7..00000000 --- a/apps/server/src/contexts/customer-billing/domain/services/customer.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { UniqueID } from "@common/domain"; -import { Collection, Result } from "@common/helpers"; -import { Customer } from "../aggregates"; -import { ICustomerRepository } from "../repositories"; -import { ICustomerService } from "./customer-service.interface"; - -export class CustomerService implements ICustomerService { - constructor(private readonly customerRepository: ICustomerRepository) {} - - async findCustomer(transaction?: any): Promise, Error>> { - const customersOrError = await this.customerRepository.findAll(transaction); - if (customersOrError.isFailure) { - return Result.fail(customersOrError.error); - } - - // Solo devolver usuarios activos - const activeCustomers = customersOrError.data.filter((customer) => customer.isActive); - return Result.ok(new Collection(activeCustomers)); - } - - async findCustomerById(customerId: UniqueID, transaction?: any): Promise> { - return await this.customerRepository.findById(customerId, transaction); - } -} diff --git a/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer-invoice.mapper.ts b/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer-invoice.mapper.ts index ff38a0ec..43e1921c 100644 --- a/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer-invoice.mapper.ts +++ b/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer-invoice.mapper.ts @@ -1,11 +1,11 @@ -import { UniqueID, UtcDate } from "@common/domain"; +import { PostalAddress, TINNumber, UniqueID, UtcDate } from "@common/domain"; import { Result } from "@common/helpers"; import { ISequelizeMapper, MapperParamsType, SequelizeMapper, } from "@common/infrastructure/sequelize/sequelize-mapper"; -import { CustomerInvoice } from "@contexts/customer-billing/domain"; +import { Customer, CustomerInvoice } from "@contexts/customer-billing/domain"; import { InvoiceStatus } from "@contexts/customer-billing/domain/value-objetcs"; import { CustomerInvoiceCreationAttributes, @@ -31,67 +31,51 @@ export class CustomerInvoiceMapper const statusOrError = InvoiceStatus.create(source.status); const issueDateOrError = UtcDate.create(source.issue_date); - const result = Result.combine([idOrError, statusOrError]); + const result = Result.combine([idOrError, statusOrError, issueDateOrError]); if (result.isFailure) { return Result.fail(result.error); } + // Customer + const customerIdOrError = UniqueID.create(source.customer_id); + const tinOrError = TINNumber.create(source.customer_tin); + const postalAddressOrError = PostalAddress.create({ + street: source.customer_street, + street2: source.customer_street2, + city: source.customer_city, + state: source.customer_state, + postalCode: source.customer_postal_code, + country: source.customer_country, + }); + + const check2 = Result.combine([idOrError, tinOrError, postalAddressOrError]); + + if (check2.isFailure) { + return Result.fail(check2.error); + } + + const customerOrError = Customer.create( + { + name: source.customer_name, + tin: tinOrError.data, + address: postalAddressOrError.data, + }, + customerIdOrError.data + ); + return CustomerInvoice.create( { status: statusOrError.data, issueDate: issueDateOrError.data, invoiceNumber: source.invoice_number, invoiceType: source.invoice_type, + invoiceCustomerReference: source.invoice_customer_reference, + customer: customerOrError.data, + items: [], }, idOrError.data ); - - /*const tinOrError = TINNumber.create(source.tin); - const emailOrError = EmailAddress.create(source.email); - const phoneOrError = PhoneNumber.create(source.phone); - const faxOrError = PhoneNumber.createNullable(source.fax); - const postalAddressOrError = PostalAddress.create({ - street: source.street, - city: source.city, - state: source.state, - postalCode: source.postal_code, - country: source.country, - }); - - const result = Result.combine([ - idOrError, - tinOrError, - emailOrError, - phoneOrError, - faxOrError, - postalAddressOrError, - ]); - - if (result.isFailure) { - return Result.fail(result.error); - } - - return Customer.create( - { - isFreelancer: source.is_freelancer, - reference: source.reference, - name: source.name, - tradeName: source.trade_name ? Maybe.Some(source.trade_name) : Maybe.None(), - tin: tinOrError.data, - address: postalAddressOrError.data, - email: emailOrError.data, - phone: phoneOrError.data, - fax: faxOrError.data, - website: source.website ? Maybe.Some(source.website) : Maybe.None(), - legalRecord: source.legal_record, - defaultTax: source.default_tax, - status: source.status, - langCode: source.lang_code, - currencyCode: source.currency_code, - }, - idOrError.data - );*/ } public mapToPersistence( @@ -101,32 +85,25 @@ export class CustomerInvoiceMapper return Result.ok({ id: source.id.toString(), status: source.status.toString(), + + issue_date: source.issueDate.toDateString(), + invoice_number: source.invoiceNumber, + invoice_type: source.invoiceType, + invoice_customer_reference: source.invoiceCustomerReference, + + lang_code: "es", + currency_code: "EUR", + + customer_id: source.customer.id.toString(), + customer_name: source.customer.name, + customer_tin: source.customer.tin.toString(), + customer_street: source.customer.address.street, + customer_street2: source.customer.address.street2.getOrUndefined(), + customer_city: source.customer.address.city, + customer_postal_code: source.customer.address.postalCode, + customer_state: source.customer.address.state, + customer_country: source.customer.address.country, }); - /*return Result.ok({ - id: source.id.toString(), - reference: source.reference, - is_freelancer: source.isFreelancer, - name: source.name, - trade_name: source.tradeName.isSome() ? source.tradeName.getValue() : undefined, - tin: source.tin.toString(), - - street: source.address.street, - city: source.address.city, - state: source.address.state, - postal_code: source.address.postalCode, - country: source.address.country, - - email: source.email.toString(), - phone: source.phone.toString(), - fax: source.fax.isSome() ? source.fax.getValue()?.toString() : undefined, - website: source.website.isSome() ? source.website.getValue() : undefined, - - legal_record: source.legalRecord, - default_tax: source.defaultTax, - status: source.isActive ? "active" : "inactive", - lang_code: source.langCode, - currency_code: source.currencyCode, - });*/ } } diff --git a/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer.mapper.ts b/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer.mapper.ts deleted file mode 100644 index 2778b7c4..00000000 --- a/apps/server/src/contexts/customer-billing/infraestructure/mappers/customer.mapper.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { EmailAddress, PhoneNumber, PostalAddress, TINNumber, UniqueID } from "@common/domain"; -import { Maybe, Result } from "@common/helpers"; -import { - ISequelizeMapper, - MapperParamsType, - SequelizeMapper, -} from "@common/infrastructure/sequelize/sequelize-mapper"; -import { Customer } from "@contexts/customer-billing/domain"; -import { CustomerCreationAttributes, CustomerModel } from "../sequelize/customer.model"; - -export interface ICustomerMapper - extends ISequelizeMapper {} - -export class CustomerMapper - extends SequelizeMapper - implements ICustomerMapper -{ - public mapToDomain(source: CustomerModel, params?: MapperParamsType): Result { - const idOrError = UniqueID.create(source.id); - const tinOrError = TINNumber.create(source.tin); - const emailOrError = EmailAddress.create(source.email); - const phoneOrError = PhoneNumber.create(source.phone); - const faxOrError = PhoneNumber.createNullable(source.fax); - const postalAddressOrError = PostalAddress.create({ - street: source.street, - city: source.city, - state: source.state, - postalCode: source.postal_code, - country: source.country, - }); - - const result = Result.combine([ - idOrError, - tinOrError, - emailOrError, - phoneOrError, - faxOrError, - postalAddressOrError, - ]); - - if (result.isFailure) { - return Result.fail(result.error); - } - - return Customer.create( - { - isFreelancer: source.is_freelancer, - reference: source.reference, - name: source.name, - tradeName: source.trade_name ? Maybe.Some(source.trade_name) : Maybe.None(), - tin: tinOrError.data, - address: postalAddressOrError.data, - email: emailOrError.data, - phone: phoneOrError.data, - fax: faxOrError.data, - website: source.website ? Maybe.Some(source.website) : Maybe.None(), - legalRecord: source.legal_record, - defaultTax: source.default_tax, - status: source.status, - langCode: source.lang_code, - currencyCode: source.currency_code, - }, - idOrError.data - ); - } - - public mapToPersistence( - source: Customer, - params?: MapperParamsType - ): Result { - return Result.ok({ - id: source.id.toString(), - reference: source.reference, - is_freelancer: source.isFreelancer, - name: source.name, - trade_name: source.tradeName.getOrUndefined(), - tin: source.tin.toString(), - - street: source.address.street, - city: source.address.city, - state: source.address.state, - postal_code: source.address.postalCode, - country: source.address.country, - - email: source.email.toString(), - phone: source.phone.toString(), - fax: source.fax.isSome() ? source.fax.getOrUndefined()?.toString() : undefined, - website: source.website.getOrUndefined(), - - legal_record: source.legalRecord, - default_tax: source.defaultTax, - status: source.isActive ? "active" : "inactive", - lang_code: source.langCode, - currency_code: source.currencyCode, - }); - } -} - -const customerMapper: CustomerMapper = new CustomerMapper(); -export { customerMapper }; diff --git a/apps/server/src/contexts/customer-billing/infraestructure/mappers/index.ts b/apps/server/src/contexts/customer-billing/infraestructure/mappers/index.ts index 7f5fae75..5d78e8fc 100644 --- a/apps/server/src/contexts/customer-billing/infraestructure/mappers/index.ts +++ b/apps/server/src/contexts/customer-billing/infraestructure/mappers/index.ts @@ -1 +1 @@ -export * from "./customer.mapper"; +export * from "./customer-invoice.mapper"; diff --git a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice-item.model.ts b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice-item.model.ts index 91919de0..f05ae494 100644 --- a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice-item.model.ts +++ b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice-item.model.ts @@ -23,21 +23,21 @@ export class CustomerInvoiceItemModel extends Model< CustomerInvoiceItemModel.belongsTo(CustomerInvoiceModel, { as: "invoice", - foreignKey: "invoice_id", + foreignKey: "id", onDelete: "CASCADE", }); } - declare invoice_id: string; + declare customer_invoice_id: string; declare item_id: string; - declare id_article: CreationOptional; + declare id_article: CreationOptional; declare position: number; - declare description: CreationOptional; - declare quantity: CreationOptional; - declare unit_price: CreationOptional; - declare subtotal_price: CreationOptional; - declare discount: CreationOptional; - declare total_price: CreationOptional; + declare description: CreationOptional; + declare quantity: CreationOptional; + declare unit_price: CreationOptional; + declare subtotal_price: CreationOptional; + declare discount: CreationOptional; + declare total_price: CreationOptional; declare invoice: NonAttribute; } @@ -49,7 +49,7 @@ export default (sequelize: Sequelize) => { type: new DataTypes.UUID(), primaryKey: true, }, - invoice_id: { + customer_invoice_id: { type: new DataTypes.UUID(), primaryKey: true, }, @@ -89,7 +89,7 @@ export default (sequelize: Sequelize) => { }, { sequelize, - tableName: "invoice_items", + tableName: "customer_invoice_items", timestamps: false, indexes: [], diff --git a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice.model.ts b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice.model.ts index 913313b9..62a98346 100644 --- a/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice.model.ts +++ b/apps/server/src/contexts/customer-billing/infraestructure/sequelize/customer-invoice.model.ts @@ -8,19 +8,19 @@ import { Sequelize, } from "sequelize"; import { CustomerInvoiceItemModel } from "./customer-invoice-item.model"; -import { CustomerModel } from "./customer.model"; export type CustomerInvoiceCreationAttributes = InferCreationAttributes< CustomerInvoiceModel, - { omit: "items" | "customer" } + { omit: "items" } > & { + // creo que no es necesario //items: CustomerInvoiceItemCreationAttributes[]; - customer_id: string; + //customer_id: string; }; export class CustomerInvoiceModel extends Model< - InferAttributes, - InferCreationAttributes + InferAttributes, + InferCreationAttributes > { // To avoid table creation /*static async sync(): Promise { @@ -32,15 +32,9 @@ export class CustomerInvoiceModel extends Model< CustomerInvoiceModel.hasMany(CustomerInvoiceItemModel, { as: "items", - foreignKey: "quote_id", + foreignKey: "customer_invoice_id", onDelete: "CASCADE", }); - - CustomerInvoiceModel.belongsTo(CustomerModel, { - foreignKey: "dealer_id", - as: "dealer", - onDelete: "RESTRICT", - }); } declare id: string; @@ -49,6 +43,7 @@ export class CustomerInvoiceModel extends Model< declare issue_date: string; declare invoice_number: string; declare invoice_type: string; + declare invoice_customer_reference?: CreationOptional; declare lang_code: string; declare currency_code: string; @@ -57,34 +52,32 @@ export class CustomerInvoiceModel extends Model< declare customer_tin: string; declare customer_name: string; - declare customer_reference: CreationOptional; - declare customer_street: string; + declare customer_street2?: CreationOptional; declare customer_city: string; declare customer_state: string; declare customer_postal_code: string; declare customer_country: string; - declare subtotal_price: CreationOptional; + declare subtotal_price?: CreationOptional; - declare discount: CreationOptional; - declare discount_price: CreationOptional; + declare discount?: CreationOptional; + declare discount_price?: CreationOptional; - declare before_tax_price: CreationOptional; + declare before_tax_price?: CreationOptional; - declare tax: CreationOptional; - declare tax_price: CreationOptional; + declare tax?: CreationOptional; + declare tax_price?: CreationOptional; - declare total_price: CreationOptional; + declare total_price?: CreationOptional; - declare notes: CreationOptional; + declare notes?: CreationOptional; declare items: NonAttribute; - declare customer: NonAttribute; - declare integrity_hash: CreationOptional; - declare previous_invoice_id: CreationOptional; - declare signed_at: CreationOptional; + declare integrity_hash?: CreationOptional; + declare previous_invoice_id?: CreationOptional; + declare signed_at?: CreationOptional; } export default (sequelize: Sequelize) => { @@ -110,6 +103,10 @@ export default (sequelize: Sequelize) => { allowNull: false, }, + invoice_customer_reference: { + type: new DataTypes.STRING(), + }, + invoice_type: { type: new DataTypes.STRING(), allowNull: false, @@ -129,7 +126,6 @@ export default (sequelize: Sequelize) => { customer_id: { type: new DataTypes.UUID(), - primaryKey: true, }, customer_name: { @@ -142,14 +138,16 @@ export default (sequelize: Sequelize) => { allowNull: false, }, - customer_reference: { - type: new DataTypes.STRING(), - }, - customer_street: { type: DataTypes.STRING, allowNull: false, }, + + customer_street2: { + type: DataTypes.STRING, + allowNull: true, + }, + customer_city: { type: DataTypes.STRING, allowNull: false, @@ -204,11 +202,12 @@ export default (sequelize: Sequelize) => { notes: { type: DataTypes.TEXT, + allowNull: true, }, integrity_hash: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, comment: "Hash criptográfico para asegurar integridad", }, previous_invoice_id: { @@ -218,7 +217,7 @@ export default (sequelize: Sequelize) => { }, signed_at: { type: DataTypes.DATE, - allowNull: false, + allowNull: true, comment: "Fecha en que la factura fue firmada digitalmente", }, }, @@ -235,7 +234,7 @@ export default (sequelize: Sequelize) => { indexes: [ { name: "status_idx", fields: ["status"] }, - { name: "reference_idx", fields: ["reference"] }, + { name: "invoice_number_idx", fields: ["invoice_number"] }, { name: "deleted_at_idx", fields: ["deleted_at"] }, { name: "signed_at_idx", fields: ["signed_at"] }, ], diff --git a/apps/server/src/routes/customers.routes.ts b/apps/server/src/routes/customers.routes.ts deleted file mode 100644 index 6407c543..00000000 --- a/apps/server/src/routes/customers.routes.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { validateRequestDTO } from "@common/presentation"; -import { checkTabContext } from "@contexts/auth/infraestructure"; -import { - listCustomersController, - ListCustomersSchema, -} from "@contexts/customer-billing/presentation"; -import { NextFunction, Request, Response, Router } from "express"; - -export const customersRouter = (appRouter: Router) => { - const routes: Router = Router({ mergeParams: true }); - - routes.get( - "/", - validateRequestDTO(ListCustomersSchema), - checkTabContext, - //checkUserIsAdmin, - (req: Request, res: Response, next: NextFunction) => { - listCustomersController().execute(req, res, next); - } - ); - - appRouter.use("/customers", routes); -}; diff --git a/apps/server/src/routes/v1.routes.ts b/apps/server/src/routes/v1.routes.ts index c127ef13..669c040e 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 { customersRouter } from "./customers.routes"; +import { customerInvoicesRouter } from "./customer-invoices.routes"; import { usersRouter } from "./users.routes"; export const v1Routes = () => { @@ -16,7 +16,7 @@ export const v1Routes = () => { accountsRouter(routes); // Sales - customersRouter(routes); + customerInvoicesRouter(routes); return routes; };