From 2df2ce708358f6becc0ca918932827a9758326fb Mon Sep 17 00:00:00 2001 From: david Date: Tue, 16 Sep 2025 11:17:29 +0200 Subject: [PATCH] Facturas de cliente --- .../express/express-controller.ts | 4 +- .../mappers/sequelize-mapper copy.ts | 111 ------------------ .../mappers/sequelize-read-model-mapper.ts | 10 +- .../domain/customer.full.presenter.ts | 9 +- .../queries/list-customers.presenter.ts | 9 +- .../src/api/domain/aggregates/customer.ts | 32 ++++- .../mappers/domain/customer.mapper.ts | 59 ++++++++-- .../mappers/queries/customer.list.mapper.ts | 53 +++++++-- .../sequelize/models/customer.model.ts | 44 ++++++- .../get-customer-by-id.response.dto.ts | 9 +- 10 files changed, 185 insertions(+), 155 deletions(-) delete mode 100644 modules/core/src/api/infrastructure/sequelize/mappers/sequelize-mapper copy.ts diff --git a/modules/core/src/api/infrastructure/express/express-controller.ts b/modules/core/src/api/infrastructure/express/express-controller.ts index dedd4b58..81141bb1 100644 --- a/modules/core/src/api/infrastructure/express/express-controller.ts +++ b/modules/core/src/api/infrastructure/express/express-controller.ts @@ -61,9 +61,7 @@ export abstract class ExpressController { await this.executeImpl(); } catch (error: unknown) { - const err = error as Error; - console.debug("❌ Unhandled error executing controller:", err.message); - this.handleError(new InternalApiError(err.message)); + this.handleError(error as Error); } } diff --git a/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-mapper copy.ts b/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-mapper copy.ts deleted file mode 100644 index 90540f7b..00000000 --- a/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-mapper copy.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Collection, Result } from "@repo/rdx-utils"; -import { Model } from "sequelize"; - -export type MapperParamsType = Record; - -interface IDomainMapper { - mapToDomain(source: TModel, params?: MapperParamsType): Result; - mapArrayToDomain(source: TModel[], params?: MapperParamsType): Result, Error>; - mapArrayAndCountToDomain( - source: TModel[], - totalCount: number, - params?: MapperParamsType - ): Result, Error>; -} - -interface IPersistenceMapper { - mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes; - mapCollectionToPersistence( - source: Collection, - params?: MapperParamsType - ): TModelAttributes[]; -} - -export interface ISequelizeMapper - extends IDomainMapper, - IPersistenceMapper {} - -export abstract class SequelizeMapper - implements ISequelizeMapper -{ - public abstract mapToDomain(source: TModel, params?: MapperParamsType): Result; - - public mapArrayToDomain( - source: TModel[], - params?: MapperParamsType - ): Result, Error> { - const items = source ?? []; - return this.mapArrayAndCountToDomain(items, items.length, params); - } - - public mapArrayAndCountToDomain( - source: TModel[], - totalCount: number, - params?: MapperParamsType - ): Result, Error> { - const _source = source ?? []; - - try { - if (_source.length === 0) { - return Result.ok(new Collection([], totalCount)); - } - - const items = _source.map( - (value, index) => this.mapToDomain(value, { index, ...params }).data - ); - return Result.ok(new Collection(items, totalCount)); - } catch (error) { - return Result.fail(error as Error); - } - } - - public abstract mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes; - - public mapCollectionToPersistence( - source: Collection, - params?: MapperParamsType - ): TModelAttributes[] { - return source.map((value, index) => this.mapToPersistence(value, { index, ...params })); - } - - protected safeMap(operation: () => T, key: string): Result { - try { - return Result.ok(operation()); - } catch (error: unknown) { - return Result.fail(error as Error); - } - } - - protected mapsValue( - row: TModel, - key: string, - customMapFn: (value: any, params: MapperParamsType) => Result, - params: MapperParamsType = { defaultValue: null } - ): Result { - return customMapFn(row?.dataValues[key] ?? params.defaultValue, params); - } - - protected mapsAssociation( - row: TModel, - associationName: string, - customMapper: IDomainMapper, - params: MapperParamsType = {} - ): Result { - if (!customMapper) { - Result.fail(Error(`Custom mapper undefined for ${associationName}`)); - } - - const { filter, ...otherParams } = params; - let associationRows = row?.dataValues[associationName] ?? []; - - if (filter) { - associationRows = Array.isArray(associationRows) - ? associationRows.filter(filter) - : filter(associationRows); - } - - return Array.isArray(associationRows) - ? customMapper.mapArrayToDomain(associationRows, otherParams) - : customMapper.mapToDomain(associationRows, otherParams); - } -} diff --git a/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-read-model-mapper.ts b/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-read-model-mapper.ts index 455bb48d..245ff928 100644 --- a/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-read-model-mapper.ts +++ b/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-read-model-mapper.ts @@ -20,9 +20,13 @@ export abstract class SequelizeQueryMapper return Result.ok(new Collection([], totalCount)); } - const items = _source.map( - (value, index) => this.mapToDTO(value as TModel, { index, ...params }).data - ); + const items = _source.map((value, index) => { + const result = this.mapToDTO(value as TModel, { index, ...params }); + if (result.isFailure) { + throw result.error; + } + return result.data; + }); return Result.ok(new Collection(items, totalCount)); } catch (error) { return Result.fail(error as Error); diff --git a/modules/customers/src/api/application/presenters/domain/customer.full.presenter.ts b/modules/customers/src/api/application/presenters/domain/customer.full.presenter.ts index f9b929a1..cc3dfd8e 100644 --- a/modules/customers/src/api/application/presenters/domain/customer.full.presenter.ts +++ b/modules/customers/src/api/application/presenters/domain/customer.full.presenter.ts @@ -27,8 +27,13 @@ export class CustomerFullPresenter extends Presenter value.toPrimitive()), country: toEmptyString(address.country, (value) => value.toPrimitive()), - email: toEmptyString(customer.email, (value) => value.toPrimitive()), - phone: toEmptyString(customer.phone, (value) => value.toPrimitive()), + email_primary: toEmptyString(customer.emailPrimary, (value) => value.toPrimitive()), + email_secondary: toEmptyString(customer.emailSecondary, (value) => value.toPrimitive()), + phone_primary: toEmptyString(customer.phonePrimary, (value) => value.toPrimitive()), + phone_secondary: toEmptyString(customer.phoneSecondary, (value) => value.toPrimitive()), + mobile_primary: toEmptyString(customer.mobilePrimary, (value) => value.toPrimitive()), + mobile_secondary: toEmptyString(customer.mobileSecondary, (value) => value.toPrimitive()), + fax: toEmptyString(customer.fax, (value) => value.toPrimitive()), website: toEmptyString(customer.website, (value) => value.toPrimitive()), diff --git a/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts b/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts index 327b3e05..b151811b 100644 --- a/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts +++ b/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts @@ -29,8 +29,13 @@ export class ListCustomersPresenter extends Presenter { postal_code: toEmptyString(address.postalCode, (value) => value.toPrimitive()), country: toEmptyString(address.country, (value) => value.toPrimitive()), - email: toEmptyString(customer.email, (value) => value.toPrimitive()), - phone: toEmptyString(customer.phone, (value) => value.toPrimitive()), + email_primary: toEmptyString(customer.emailPrimary, (value) => value.toPrimitive()), + email_secondary: toEmptyString(customer.emailSecondary, (value) => value.toPrimitive()), + phone_primary: toEmptyString(customer.phonePrimary, (value) => value.toPrimitive()), + phone_secondary: toEmptyString(customer.phoneSecondary, (value) => value.toPrimitive()), + mobile_primary: toEmptyString(customer.mobilePrimary, (value) => value.toPrimitive()), + mobile_secondary: toEmptyString(customer.mobileSecondary, (value) => value.toPrimitive()), + fax: toEmptyString(customer.fax, (value) => value.toPrimitive()), website: toEmptyString(customer.website, (value) => value.toPrimitive()), diff --git a/modules/customers/src/api/domain/aggregates/customer.ts b/modules/customers/src/api/domain/aggregates/customer.ts index b46c76e9..58c1d74a 100644 --- a/modules/customers/src/api/domain/aggregates/customer.ts +++ b/modules/customers/src/api/domain/aggregates/customer.ts @@ -28,8 +28,12 @@ export interface CustomerProps { address: PostalAddress; - email: Maybe; - phone: Maybe; + emailPrimary: Maybe; + emailSecondary: Maybe; + phonePrimary: Maybe; + phoneSecondary: Maybe; + mobilePrimary: Maybe; + mobileSecondary: Maybe; fax: Maybe; website: Maybe; @@ -114,12 +118,28 @@ export class Customer extends AggregateRoot { return this.props.address; } - public get email(): Maybe { - return this.props.email; + public get emailPrimary(): Maybe { + return this.props.emailPrimary; } - public get phone(): Maybe { - return this.props.phone; + public get emailSecondary(): Maybe { + return this.props.emailSecondary; + } + + public get phonePrimary(): Maybe { + return this.props.phonePrimary; + } + + public get phoneSecondary(): Maybe { + return this.props.phoneSecondary; + } + + public get mobilePrimary(): Maybe { + return this.props.mobilePrimary; + } + + public get mobileSecondary(): Maybe { + return this.props.mobileSecondary; } public get fax(): Maybe { diff --git a/modules/customers/src/api/infrastructure/mappers/domain/customer.mapper.ts b/modules/customers/src/api/infrastructure/mappers/domain/customer.mapper.ts index 720bc690..785416ef 100644 --- a/modules/customers/src/api/infrastructure/mappers/domain/customer.mapper.ts +++ b/modules/customers/src/api/infrastructure/mappers/domain/customer.mapper.ts @@ -106,15 +106,39 @@ export class CustomerDomainMapper errors ); - const emailAddress = extractOrPushError( - maybeFromNullableVO(source.email, (value) => EmailAddress.create(value)), - "email", + const emailPrimaryAddress = extractOrPushError( + maybeFromNullableVO(source.email_primary, (value) => EmailAddress.create(value)), + "email_primary", errors ); - const phoneNumber = extractOrPushError( - maybeFromNullableVO(source.phone, (value) => PhoneNumber.create(value)), - "phone", + const emailSecondaryAddress = extractOrPushError( + maybeFromNullableVO(source.email_secondary, (value) => EmailAddress.create(value)), + "email_secondary", + errors + ); + + const phonePrimaryNumber = extractOrPushError( + maybeFromNullableVO(source.phone_primary, (value) => PhoneNumber.create(value)), + "phone_primary", + errors + ); + + const phoneSecondaryNumber = extractOrPushError( + maybeFromNullableVO(source.phone_secondary, (value) => PhoneNumber.create(value)), + "phone_secondary", + errors + ); + + const mobilePrimaryNumber = extractOrPushError( + maybeFromNullableVO(source.mobile_primary, (value) => PhoneNumber.create(value)), + "mobile_primary", + errors + ); + + const mobileSecondaryNumber = extractOrPushError( + maybeFromNullableVO(source.mobile_secondary, (value) => PhoneNumber.create(value)), + "mobile_secondary", errors ); @@ -192,8 +216,12 @@ export class CustomerDomainMapper address: postalAddress!, - email: emailAddress!, - phone: phoneNumber!, + emailPrimary: emailPrimaryAddress!, + emailSecondary: emailSecondaryAddress!, + phonePrimary: phonePrimaryNumber!, + phoneSecondary: phoneSecondaryNumber!, + mobilePrimary: mobilePrimaryNumber!, + mobileSecondary: mobileSecondaryNumber!, fax: faxNumber!, website: website!, @@ -209,7 +237,10 @@ export class CustomerDomainMapper } } - public mapToPersistence(source: Customer, params?: MapperParamsType): CustomerCreationAttributes { + public mapToPersistence( + source: Customer, + params?: MapperParamsType + ): Result { const customerValues: Partial = { id: source.id.toPrimitive(), company_id: source.companyId.toPrimitive(), @@ -220,8 +251,12 @@ export class CustomerDomainMapper trade_name: toNullable(source.tradeName, (tradeName) => tradeName.toPrimitive()), tin: toNullable(source.tin, (tin) => tin.toPrimitive()), - email: toNullable(source.email, (email) => email.toPrimitive()), - phone: toNullable(source.phone, (phone) => phone.toPrimitive()), + email_primary: toNullable(source.emailPrimary, (email) => email.toPrimitive()), + email_secondary: toNullable(source.emailSecondary, (email) => email.toPrimitive()), + phone_primary: toNullable(source.phonePrimary, (phone) => phone.toPrimitive()), + phone_secondary: toNullable(source.phoneSecondary, (phone) => phone.toPrimitive()), + mobile_primary: toNullable(source.mobilePrimary, (mobile) => mobile.toPrimitive()), + mobile_secondary: toNullable(source.mobileSecondary, (mobile) => mobile.toPrimitive()), fax: toNullable(source.fax, (fax) => fax.toPrimitive()), website: toNullable(source.website, (website) => website.toPrimitive()), @@ -246,6 +281,6 @@ export class CustomerDomainMapper }); } - return customerValues as CustomerCreationAttributes; + return Result.ok(customerValues as CustomerCreationAttributes); } } diff --git a/modules/customers/src/api/infrastructure/mappers/queries/customer.list.mapper.ts b/modules/customers/src/api/infrastructure/mappers/queries/customer.list.mapper.ts index fe643333..46b70466 100644 --- a/modules/customers/src/api/infrastructure/mappers/queries/customer.list.mapper.ts +++ b/modules/customers/src/api/infrastructure/mappers/queries/customer.list.mapper.ts @@ -43,8 +43,13 @@ export type CustomerListDTO = { address: PostalAddress; - email: Maybe; - phone: Maybe; + email_primary: Maybe; + email_secondary: Maybe; + phone_primary: Maybe; + phone_secondary: Maybe; + mobile_primary: Maybe; + mobile_secondary: Maybe; + fax: Maybe; website: Maybe; @@ -124,15 +129,39 @@ export class CustomerListMapper errors ); - const emailAddress = extractOrPushError( - maybeFromNullableVO(raw.email, (value) => EmailAddress.create(value)), - "email", + const emailPrimaryAddress = extractOrPushError( + maybeFromNullableVO(raw.email_primary, (value) => EmailAddress.create(value)), + "email_primary", errors ); - const phoneNumber = extractOrPushError( - maybeFromNullableVO(raw.phone, (value) => PhoneNumber.create(value)), - "phone", + const emailSecondaryAddress = extractOrPushError( + maybeFromNullableVO(raw.email_secondary, (value) => EmailAddress.create(value)), + "email_secondary", + errors + ); + + const phonePrimaryNumber = extractOrPushError( + maybeFromNullableVO(raw.phone_primary, (value) => PhoneNumber.create(value)), + "phone_primary", + errors + ); + + const phoneSecondaryNumber = extractOrPushError( + maybeFromNullableVO(raw.phone_secondary, (value) => PhoneNumber.create(value)), + "phone_secondary", + errors + ); + + const mobilePrimaryNumber = extractOrPushError( + maybeFromNullableVO(raw.mobile_primary, (value) => PhoneNumber.create(value)), + "mobile_primary", + errors + ); + + const mobileSecondaryNumber = extractOrPushError( + maybeFromNullableVO(raw.mobile_secondary, (value) => PhoneNumber.create(value)), + "mobile_secondary", errors ); @@ -200,8 +229,12 @@ export class CustomerListMapper address: postalAddress!, - email: emailAddress!, - phone: phoneNumber!, + email_primary: emailPrimaryAddress!, + email_secondary: emailSecondaryAddress!, + phone_primary: phonePrimaryNumber!, + phone_secondary: phoneSecondaryNumber!, + mobile_primary: mobilePrimaryNumber!, + mobile_secondary: mobileSecondaryNumber!, fax: faxNumber!, website: website!, diff --git a/modules/customers/src/api/infrastructure/sequelize/models/customer.model.ts b/modules/customers/src/api/infrastructure/sequelize/models/customer.model.ts index 4497e8ac..0dfd77d6 100644 --- a/modules/customers/src/api/infrastructure/sequelize/models/customer.model.ts +++ b/modules/customers/src/api/infrastructure/sequelize/models/customer.model.ts @@ -27,8 +27,18 @@ export class CustomerModel extends Model< declare postal_code: string; declare country: string; - declare email: string; - declare phone: string; + // Correos electrónicos + declare email_primary: string; + declare email_secondary: string; + + // Teléfonos fijos + declare phone_primary: string; + declare phone_secondary: string; + + // Móviles + declare mobile_primary: string; + declare mobile_secondary: string; + declare fax: string; declare website: string; @@ -113,7 +123,7 @@ export default (database: Sequelize) => { defaultValue: null, }, - email: { + email_primary: { type: DataTypes.STRING, allowNull: true, defaultValue: null, @@ -121,11 +131,37 @@ export default (database: Sequelize) => { isEmail: true, }, }, - phone: { + email_secondary: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + validate: { + isEmail: true, + }, + }, + + phone_primary: { type: DataTypes.STRING, allowNull: true, defaultValue: null, }, + phone_secondary: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, + + mobile_primary: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, + mobile_secondary: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + }, + fax: { type: DataTypes.STRING, allowNull: true, diff --git a/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts b/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts index 24c33d13..06d1af60 100644 --- a/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts +++ b/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts @@ -18,8 +18,13 @@ export const GetCustomerByIdResponseSchema = z.object({ postal_code: z.string(), country: z.string(), - email: z.string(), - phone: z.string(), + email_primary: z.string(), + email_secondary: z.string(), + phone_primary: z.string(), + phone_secondary: z.string(), + mobile_primary: z.string(), + mobile_secondary: z.string(), + fax: z.string(), website: z.string(),