.
This commit is contained in:
parent
5a9e7261f9
commit
42987d688f
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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", () => {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./customer-invoice";
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./customer";
|
||||
@ -1,2 +1 @@
|
||||
export * from "./customer";
|
||||
export * from "./customer-invoice";
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
export * from "./customer";
|
||||
export * from "./customer-invoice-item";
|
||||
export * from "./tax";
|
||||
export * from "./tax-collection";
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from "./aggregates";
|
||||
export * from "./entities";
|
||||
export * from "./repositories";
|
||||
export * from "./services";
|
||||
|
||||
@ -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>>;
|
||||
}
|
||||
@ -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>>;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
});*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 };
|
||||
@ -1 +1 @@
|
||||
export * from "./customer.mapper";
|
||||
export * from "./customer-invoice.mapper";
|
||||
|
||||
@ -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: [],
|
||||
|
||||
@ -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"] },
|
||||
],
|
||||
|
||||
@ -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);
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user