This commit is contained in:
David Arranz 2025-02-25 20:17:30 +01:00
parent 5a9e7261f9
commit 42987d688f
31 changed files with 198 additions and 465 deletions

View File

@ -18,10 +18,10 @@ export class EmailAddress extends ValueObject<EmailAddressProps> {
static createNullable(value?: string): Result<Maybe<EmailAddress>, Error> {
if (!value || value.trim() === "") {
return Result.ok(Maybe.None<EmailAddress>());
return Result.ok(Maybe.none<EmailAddress>());
}
return EmailAddress.create(value!).map((value) => Maybe.Some(value));
return EmailAddress.create(value!).map((value) => Maybe.some(value));
}
private static validate(value: string) {

View File

@ -28,10 +28,10 @@ export class Name extends ValueObject<INameProps> {
static createNullable(value?: string): Result<Maybe<Name>, Error> {
if (!value || value.trim() === "") {
return Result.ok(Maybe.None<Name>());
return Result.ok(Maybe.none<Name>());
}
return Name.create(value!).map((value) => Maybe.Some(value));
return Name.create(value!).map((value) => Maybe.some(value));
}
static generateAcronym(name: string): string {

View File

@ -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", () => {

View File

@ -20,10 +20,10 @@ export class PhoneNumber extends ValueObject<PhoneNumberProps> {
static createNullable(value?: string): Result<Maybe<PhoneNumber>, Error> {
if (!value || value.trim() === "") {
return Result.ok(Maybe.None<PhoneNumber>());
return Result.ok(Maybe.none<PhoneNumber>());
}
return PhoneNumber.create(value!).map((value) => Maybe.Some(value));
return PhoneNumber.create(value!).map((value) => Maybe.some(value));
}
static validate(value: string) {

View File

@ -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);

View File

@ -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<IPostalAddressProps> {
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<IPostalAddressProps> {
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<Maybe<PostalAddress>, Error> {
if (!values || Object.values(values).every((value) => value.trim() === "")) {
return Result.ok(Maybe.None<PostalAddress>());
return Result.ok(Maybe.none<PostalAddress>());
}
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<string> {
return Maybe.fromNullable(this.props.street2);
}
get city(): string {
return this.props.city;
}
@ -90,6 +89,6 @@ export class PostalAddress extends ValueObject<IPostalAddressProps> {
}
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}`;
}
}

View File

@ -33,10 +33,10 @@ export class Slug extends ValueObject<SlugProps> {
static createNullable(value?: string): Result<Maybe<Slug>, Error> {
if (!value || value.trim() === "") {
return Result.ok(Maybe.None<Slug>());
return Result.ok(Maybe.none<Slug>());
}
return Slug.create(value!).map((value: Slug) => Maybe.Some(value));
return Slug.create(value!).map((value: Slug) => Maybe.some(value));
}
getValue(): string {

View File

@ -35,10 +35,10 @@ export class TINNumber extends ValueObject<TINNumberProps> {
static createNullable(value?: string): Result<Maybe<TINNumber>, Error> {
if (!value || value.trim() === "") {
return Result.ok(Maybe.None<TINNumber>());
return Result.ok(Maybe.none<TINNumber>());
}
return TINNumber.create(value!).map((value) => Maybe.Some(value));
return TINNumber.create(value!).map((value) => Maybe.some(value));
}
getValue(): string {

View File

@ -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<number>();
const maybeEmpty = Maybe.none<number>();
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<number>();
const maybeEmpty = Maybe.none<number>();
const maybeTransformed = maybeEmpty.map((n) => n * 2);
expect(maybeTransformed.isSome()).toBe(false);

View File

@ -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<number>();
* const noValue = Maybe.none<number>();
* console.log(noValue.isSome()); // false
**/
export class Maybe<T> {
private constructor(private readonly value?: T) {}
static Some<T>(value: T): Maybe<T> {
static fromNullable<T>(value?: T): Maybe<T> {
return value === undefined || value === null ? Maybe.none() : Maybe.some(value);
}
static some<T>(value: T): Maybe<T> {
return new Maybe<T>(value);
}
static None<T>(): Maybe<T> {
static none<T>(): Maybe<T> {
return new Maybe<T>();
}
@ -37,6 +41,6 @@ export class Maybe<T> {
}
map<U>(fn: (value: T) => U): Maybe<U> {
return this.isSome() ? Maybe.Some(fn(this.value as T)) : Maybe.None();
return this.isSome() ? Maybe.some(fn(this.value as T)) : Maybe.none();
}
}

View File

@ -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
);

View File

@ -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,

View File

@ -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;
}

View File

@ -1 +0,0 @@
export * from "./customer-invoice";

View File

@ -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<string>;
website: Maybe<string>;
fax: Maybe<PhoneNumber>;
}
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<string>;
fax: Maybe<PhoneNumber>;
website: Maybe<string>;
isCustomer: boolean;
isFreelancer: boolean;
isActive: boolean;
}
export class Customer extends AggregateRoot<ICustomerProps> implements ICustomer {
static create(props: ICustomerProps, id?: UniqueID): Result<Customer, Error> {
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<PhoneNumber> {
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";
}
}

View File

@ -1 +0,0 @@
export * from "./customer";

View File

@ -1,2 +1 @@
export * from "./customer";
export * from "./customer-invoice";

View File

@ -57,28 +57,28 @@ export class CustomerInvoiceItem
calculateSubtotal(): Maybe<MoneyValue> {
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<MoneyValue> {
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<string> {

View File

@ -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<ICustomerProps> implements ICustomer {
static create(props: ICustomerProps, id?: UniqueID): Result<Customer, Error> {
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;
}
}

View File

@ -1,3 +1,4 @@
export * from "./customer";
export * from "./customer-invoice-item";
export * from "./tax";
export * from "./tax-collection";

View File

@ -1,3 +1,4 @@
export * from "./aggregates";
export * from "./entities";
export * from "./repositories";
export * from "./services";

View File

@ -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<Result<Collection<Customer>, Error>>;
findById(id: UniqueID, transaction?: any): Promise<Result<Customer, Error>>;
findByEmail(email: EmailAddress, transaction?: any): Promise<Result<Customer, Error>>;
}

View File

@ -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<Result<Collection<Customer>, Error>>;
findCustomerById(customerId: UniqueID, transaction?: any): Promise<Result<Customer>>;
}

View File

@ -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<Result<Collection<Customer>, 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<Result<Customer>> {
return await this.customerRepository.findById(customerId, transaction);
}
}

View File

@ -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,
});*/
}
}

View File

@ -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<CustomerModel, CustomerCreationAttributes, Customer> {}
export class CustomerMapper
extends SequelizeMapper<CustomerModel, CustomerCreationAttributes, Customer>
implements ICustomerMapper
{
public mapToDomain(source: CustomerModel, params?: MapperParamsType): Result<Customer, Error> {
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<CustomerCreationAttributes, Error> {
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 };

View File

@ -1 +1 @@
export * from "./customer.mapper";
export * from "./customer-invoice.mapper";

View File

@ -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<number | null>;
declare id_article: CreationOptional<number>;
declare position: number;
declare description: CreationOptional<string | null>;
declare quantity: CreationOptional<number | null>;
declare unit_price: CreationOptional<number | null>;
declare subtotal_price: CreationOptional<number | null>;
declare discount: CreationOptional<number | null>;
declare total_price: CreationOptional<number | null>;
declare description: CreationOptional<string>;
declare quantity: CreationOptional<number>;
declare unit_price: CreationOptional<number>;
declare subtotal_price: CreationOptional<number>;
declare discount: CreationOptional<number>;
declare total_price: CreationOptional<number>;
declare invoice: NonAttribute<CustomerInvoiceModel>;
}
@ -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: [],

View File

@ -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<CustomerInvoiceModel, { omit: "items" | "customer" }>,
InferCreationAttributes<CustomerInvoiceModel, { omit: "items" | "customer" }>
InferAttributes<CustomerInvoiceModel, { omit: "items" }>,
InferCreationAttributes<CustomerInvoiceModel, { omit: "items" }>
> {
// To avoid table creation
/*static async sync(): Promise<any> {
@ -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<string>;
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<string>;
declare customer_street: string;
declare customer_street2?: CreationOptional<string>;
declare customer_city: string;
declare customer_state: string;
declare customer_postal_code: string;
declare customer_country: string;
declare subtotal_price: CreationOptional<number | null>;
declare subtotal_price?: CreationOptional<number>;
declare discount: CreationOptional<number | null>;
declare discount_price: CreationOptional<number | null>;
declare discount?: CreationOptional<number>;
declare discount_price?: CreationOptional<number>;
declare before_tax_price: CreationOptional<number | null>;
declare before_tax_price?: CreationOptional<number>;
declare tax: CreationOptional<number | null>;
declare tax_price: CreationOptional<number | null>;
declare tax?: CreationOptional<number>;
declare tax_price?: CreationOptional<number>;
declare total_price: CreationOptional<number | null>;
declare total_price?: CreationOptional<number>;
declare notes: CreationOptional<string>;
declare notes?: CreationOptional<string>;
declare items: NonAttribute<CustomerInvoiceItemModel[]>;
declare customer: NonAttribute<CustomerModel>;
declare integrity_hash: CreationOptional<string>;
declare previous_invoice_id: CreationOptional<string>;
declare signed_at: CreationOptional<string>;
declare integrity_hash?: CreationOptional<string>;
declare previous_invoice_id?: CreationOptional<string>;
declare signed_at?: CreationOptional<string>;
}
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"] },
],

View File

@ -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);
};

View File

@ -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;
};