Facturas de cliente
This commit is contained in:
parent
d5c6079d26
commit
2df2ce7083
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,111 +0,0 @@
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Model } from "sequelize";
|
||||
|
||||
export type MapperParamsType = Record<string, unknown>;
|
||||
|
||||
interface IDomainMapper<TModel extends Model, TEntity> {
|
||||
mapToDomain(source: TModel, params?: MapperParamsType): Result<TEntity, Error>;
|
||||
mapArrayToDomain(source: TModel[], params?: MapperParamsType): Result<Collection<TEntity>, Error>;
|
||||
mapArrayAndCountToDomain(
|
||||
source: TModel[],
|
||||
totalCount: number,
|
||||
params?: MapperParamsType
|
||||
): Result<Collection<TEntity>, Error>;
|
||||
}
|
||||
|
||||
interface IPersistenceMapper<TModelAttributes, TEntity> {
|
||||
mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes;
|
||||
mapCollectionToPersistence(
|
||||
source: Collection<TEntity>,
|
||||
params?: MapperParamsType
|
||||
): TModelAttributes[];
|
||||
}
|
||||
|
||||
export interface ISequelizeMapper<TModel extends Model, TModelAttributes, TEntity>
|
||||
extends IDomainMapper<TModel, TEntity>,
|
||||
IPersistenceMapper<TModelAttributes, TEntity> {}
|
||||
|
||||
export abstract class SequelizeMapper<TModel extends Model, TModelAttributes, TEntity>
|
||||
implements ISequelizeMapper<TModel, TModelAttributes, TEntity>
|
||||
{
|
||||
public abstract mapToDomain(source: TModel, params?: MapperParamsType): Result<TEntity, Error>;
|
||||
|
||||
public mapArrayToDomain(
|
||||
source: TModel[],
|
||||
params?: MapperParamsType
|
||||
): Result<Collection<TEntity>, Error> {
|
||||
const items = source ?? [];
|
||||
return this.mapArrayAndCountToDomain(items, items.length, params);
|
||||
}
|
||||
|
||||
public mapArrayAndCountToDomain(
|
||||
source: TModel[],
|
||||
totalCount: number,
|
||||
params?: MapperParamsType
|
||||
): Result<Collection<TEntity>, 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<TEntity>,
|
||||
params?: MapperParamsType
|
||||
): TModelAttributes[] {
|
||||
return source.map((value, index) => this.mapToPersistence(value, { index, ...params }));
|
||||
}
|
||||
|
||||
protected safeMap<T>(operation: () => T, key: string): Result<T, Error> {
|
||||
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<any, Error>,
|
||||
params: MapperParamsType = { defaultValue: null }
|
||||
): Result<any, Error> {
|
||||
return customMapFn(row?.dataValues[key] ?? params.defaultValue, params);
|
||||
}
|
||||
|
||||
protected mapsAssociation(
|
||||
row: TModel,
|
||||
associationName: string,
|
||||
customMapper: IDomainMapper<any, any>,
|
||||
params: MapperParamsType = {}
|
||||
): Result<any, Error> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -20,9 +20,13 @@ export abstract class SequelizeQueryMapper<TModel extends Model, TEntity>
|
||||
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);
|
||||
|
||||
@ -27,8 +27,13 @@ export class CustomerFullPresenter extends Presenter<Customer, GetCustomerByIdRe
|
||||
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()),
|
||||
|
||||
|
||||
@ -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()),
|
||||
|
||||
|
||||
@ -28,8 +28,12 @@ export interface CustomerProps {
|
||||
|
||||
address: PostalAddress;
|
||||
|
||||
email: Maybe<EmailAddress>;
|
||||
phone: Maybe<PhoneNumber>;
|
||||
emailPrimary: Maybe<EmailAddress>;
|
||||
emailSecondary: Maybe<EmailAddress>;
|
||||
phonePrimary: Maybe<PhoneNumber>;
|
||||
phoneSecondary: Maybe<PhoneNumber>;
|
||||
mobilePrimary: Maybe<PhoneNumber>;
|
||||
mobileSecondary: Maybe<PhoneNumber>;
|
||||
fax: Maybe<PhoneNumber>;
|
||||
website: Maybe<URLAddress>;
|
||||
|
||||
@ -114,12 +118,28 @@ export class Customer extends AggregateRoot<CustomerProps> {
|
||||
return this.props.address;
|
||||
}
|
||||
|
||||
public get email(): Maybe<EmailAddress> {
|
||||
return this.props.email;
|
||||
public get emailPrimary(): Maybe<EmailAddress> {
|
||||
return this.props.emailPrimary;
|
||||
}
|
||||
|
||||
public get phone(): Maybe<PhoneNumber> {
|
||||
return this.props.phone;
|
||||
public get emailSecondary(): Maybe<EmailAddress> {
|
||||
return this.props.emailSecondary;
|
||||
}
|
||||
|
||||
public get phonePrimary(): Maybe<PhoneNumber> {
|
||||
return this.props.phonePrimary;
|
||||
}
|
||||
|
||||
public get phoneSecondary(): Maybe<PhoneNumber> {
|
||||
return this.props.phoneSecondary;
|
||||
}
|
||||
|
||||
public get mobilePrimary(): Maybe<PhoneNumber> {
|
||||
return this.props.mobilePrimary;
|
||||
}
|
||||
|
||||
public get mobileSecondary(): Maybe<PhoneNumber> {
|
||||
return this.props.mobileSecondary;
|
||||
}
|
||||
|
||||
public get fax(): Maybe<PhoneNumber> {
|
||||
|
||||
@ -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<CustomerCreationAttributes, Error> {
|
||||
const customerValues: Partial<CustomerCreationAttributes> = {
|
||||
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<CustomerCreationAttributes>(customerValues as CustomerCreationAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,8 +43,13 @@ export type CustomerListDTO = {
|
||||
|
||||
address: PostalAddress;
|
||||
|
||||
email: Maybe<EmailAddress>;
|
||||
phone: Maybe<PhoneNumber>;
|
||||
email_primary: Maybe<EmailAddress>;
|
||||
email_secondary: Maybe<EmailAddress>;
|
||||
phone_primary: Maybe<PhoneNumber>;
|
||||
phone_secondary: Maybe<PhoneNumber>;
|
||||
mobile_primary: Maybe<PhoneNumber>;
|
||||
mobile_secondary: Maybe<PhoneNumber>;
|
||||
|
||||
fax: Maybe<PhoneNumber>;
|
||||
website: Maybe<URLAddress>;
|
||||
|
||||
@ -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!,
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(),
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user