Compare commits
No commits in common. "366f90d403456d5e17d949bb359f5df0c03db96b" and "936c440cf34ee8690cd915b30bcb27178807cd5e" have entirely different histories.
366f90d403
...
936c440cf3
@ -38,7 +38,6 @@
|
|||||||
"@erp/customers": "workspace:*",
|
"@erp/customers": "workspace:*",
|
||||||
"@erp/customer-invoices": "workspace:*",
|
"@erp/customer-invoices": "workspace:*",
|
||||||
"@erp/factuges": "workspace:*",
|
"@erp/factuges": "workspace:*",
|
||||||
"@erp/suppliers": "workspace:*",
|
|
||||||
"@repo/rdx-logger": "workspace:*",
|
"@repo/rdx-logger": "workspace:*",
|
||||||
"@repo/rdx-utils": "workspace:*",
|
"@repo/rdx-utils": "workspace:*",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import customerInvoicesAPIModule from "@erp/customer-invoices/api";
|
import customerInvoicesAPIModule from "@erp/customer-invoices/api";
|
||||||
import customersAPIModule from "@erp/customers/api";
|
import customersAPIModule from "@erp/customers/api";
|
||||||
import factuGESAPIModule from "@erp/factuges/api";
|
import factuGESAPIModule from "@erp/factuges/api";
|
||||||
import suppliersAPIModule from "@erp/suppliers/api";
|
|
||||||
|
|
||||||
import { registerModule } from "./lib";
|
import { registerModule } from "./lib";
|
||||||
|
|
||||||
@ -10,5 +9,4 @@ export const registerModules = () => {
|
|||||||
registerModule(customersAPIModule);
|
registerModule(customersAPIModule);
|
||||||
registerModule(customerInvoicesAPIModule);
|
registerModule(customerInvoicesAPIModule);
|
||||||
registerModule(factuGESAPIModule);
|
registerModule(factuGESAPIModule);
|
||||||
registerModule(suppliersAPIModule);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -81,10 +81,9 @@ export interface ICustomer {
|
|||||||
readonly phoneSecondary: Maybe<PhoneNumber>;
|
readonly phoneSecondary: Maybe<PhoneNumber>;
|
||||||
readonly mobilePrimary: Maybe<PhoneNumber>;
|
readonly mobilePrimary: Maybe<PhoneNumber>;
|
||||||
readonly mobileSecondary: Maybe<PhoneNumber>;
|
readonly mobileSecondary: Maybe<PhoneNumber>;
|
||||||
|
|
||||||
readonly fax: Maybe<PhoneNumber>;
|
readonly fax: Maybe<PhoneNumber>;
|
||||||
readonly website: Maybe<URLAddress>;
|
|
||||||
|
|
||||||
|
readonly website: Maybe<URLAddress>;
|
||||||
readonly legalRecord: Maybe<TextValue>;
|
readonly legalRecord: Maybe<TextValue>;
|
||||||
|
|
||||||
readonly defaultTaxes: CustomerTaxes;
|
readonly defaultTaxes: CustomerTaxes;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { ICustomerPublicServices } from "./application";
|
|||||||
import { customersRouter, models } from "./infrastructure";
|
import { customersRouter, models } from "./infrastructure";
|
||||||
import { buildCustomerPublicServices, buildCustomersDependencies } from "./infrastructure/di";
|
import { buildCustomerPublicServices, buildCustomersDependencies } from "./infrastructure/di";
|
||||||
|
|
||||||
export * from "./infrastructure/persistence/sequelize";
|
export * from "./infrastructure/sequelize";
|
||||||
|
|
||||||
export const customersAPIModule: IModuleServer = {
|
export const customersAPIModule: IModuleServer = {
|
||||||
name: "customers",
|
name: "customers",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ICatalogs } from "@erp/core/api";
|
import type { ICatalogs } from "@erp/core/api";
|
||||||
|
|
||||||
import { SequelizeCustomerDomainMapper, SequelizeCustomerSummaryMapper } from "../persistence";
|
import { SequelizeCustomerDomainMapper, SequelizeCustomerSummaryMapper } from "../mappers";
|
||||||
|
|
||||||
export interface ICustomerPersistenceMappers {
|
export interface ICustomerPersistenceMappers {
|
||||||
domainMapper: SequelizeCustomerDomainMapper;
|
domainMapper: SequelizeCustomerDomainMapper;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Sequelize } from "sequelize";
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
import { CustomerRepository } from "../persistence/sequelize";
|
import { CustomerRepository } from "../sequelize";
|
||||||
|
|
||||||
import type { ICustomerPersistenceMappers } from "./customer-persistence-mappers.di";
|
import type { ICustomerPersistenceMappers } from "./customer-persistence-mappers.di";
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { ApiErrorMapper, type ErrorToApiRule, NotFoundApiError } from "@erp/core/api";
|
import { ApiErrorMapper, ErrorToApiRule, NotFoundApiError } from "@erp/core/api";
|
||||||
|
import { CustomerNotFoundError, isCustomerNotFoundError } from "../../domain";
|
||||||
import { type CustomerNotFoundError, isCustomerNotFoundError } from "../../domain";
|
|
||||||
|
|
||||||
// Crea una regla específica (prioridad alta para sobreescribir mensajes)
|
// Crea una regla específica (prioridad alta para sobreescribir mensajes)
|
||||||
const customerNotFoundRule: ErrorToApiRule = {
|
const customerNotFoundRule: ErrorToApiRule = {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from "./di";
|
export * from "./di";
|
||||||
export * from "./express";
|
export * from "./express";
|
||||||
export * from "./persistence";
|
export * from "./mappers";
|
||||||
|
export * from "./sequelize";
|
||||||
|
|||||||
@ -29,8 +29,8 @@ import {
|
|||||||
CustomerStatus,
|
CustomerStatus,
|
||||||
CustomerTaxes,
|
CustomerTaxes,
|
||||||
type ICustomerCreateProps,
|
type ICustomerCreateProps,
|
||||||
} from "../../../../../domain";
|
} from "../../../domain";
|
||||||
import type { CustomerCreationAttributes, CustomerModel } from "../../models";
|
import type { CustomerCreationAttributes, CustomerModel } from "../../sequelize";
|
||||||
|
|
||||||
export class SequelizeCustomerDomainMapper extends SequelizeDomainMapper<
|
export class SequelizeCustomerDomainMapper extends SequelizeDomainMapper<
|
||||||
CustomerModel,
|
CustomerModel,
|
||||||
@ -22,9 +22,9 @@ import {
|
|||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { CustomerSummary } from "../../../../../application";
|
import type { CustomerSummary } from "../../../application";
|
||||||
import { CustomerStatus } from "../../../../../domain";
|
import { CustomerStatus } from "../../../domain";
|
||||||
import type { CustomerModel } from "../../models";
|
import type { CustomerModel } from "../../sequelize";
|
||||||
|
|
||||||
export class SequelizeCustomerSummaryMapper extends SequelizeQueryMapper<
|
export class SequelizeCustomerSummaryMapper extends SequelizeQueryMapper<
|
||||||
CustomerModel,
|
CustomerModel,
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./sequelize";
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./sequelize-customer.model";
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import customerModelInit from "./models/sequelize-customer.model";
|
import customerModelInit from "./models/customer.model";
|
||||||
|
|
||||||
export * from "./mappers";
|
|
||||||
export * from "./models";
|
export * from "./models";
|
||||||
export * from "./repositories";
|
export * from "./repositories";
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./customer.model";
|
||||||
@ -9,10 +9,10 @@ import type { TINNumber, UniqueID } from "@repo/rdx-ddd";
|
|||||||
import { type Collection, Result } from "@repo/rdx-utils";
|
import { type Collection, Result } from "@repo/rdx-utils";
|
||||||
import type { FindOptions, InferAttributes, OrderItem, Sequelize, Transaction } from "sequelize";
|
import type { FindOptions, InferAttributes, OrderItem, Sequelize, Transaction } from "sequelize";
|
||||||
|
|
||||||
import type { CustomerSummary, ICustomerRepository } from "../../../../application";
|
import type { CustomerSummary, ICustomerRepository } from "../../../application";
|
||||||
import type { Customer } from "../../../../domain";
|
import type { Customer } from "../../../domain";
|
||||||
import type { SequelizeCustomerDomainMapper, SequelizeCustomerSummaryMapper } from "../../mappers";
|
import type { SequelizeCustomerDomainMapper, SequelizeCustomerSummaryMapper } from "../../mappers";
|
||||||
import { CustomerModel } from "../models/sequelize-customer.model";
|
import { CustomerModel } from "../models/customer.model";
|
||||||
|
|
||||||
export class CustomerRepository
|
export class CustomerRepository
|
||||||
extends SequelizeRepository<Customer>
|
extends SequelizeRepository<Customer>
|
||||||
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@erp/supplier-invoices",
|
|
||||||
"description": "Supplier invoices",
|
|
||||||
"version": "0.5.0",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"sideEffects": false,
|
|
||||||
"scripts": {
|
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
||||||
"clean": "rimraf .turbo node_modules dist"
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
".": "./src/common/index.ts",
|
|
||||||
"./common": "./src/common/index.ts",
|
|
||||||
"./api": "./src/api/index.ts"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/express": "^4.17.21",
|
|
||||||
"typescript": "^5.9.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@erp/auth": "workspace:*",
|
|
||||||
"@erp/core": "workspace:*",
|
|
||||||
"@repo/i18next": "workspace:*",
|
|
||||||
"@repo/rdx-criteria": "workspace:*",
|
|
||||||
"@repo/rdx-ddd": "workspace:*",
|
|
||||||
"@repo/rdx-logger": "workspace:*",
|
|
||||||
"@repo/rdx-utils": "workspace:*",
|
|
||||||
"express": "^4.18.2",
|
|
||||||
"sequelize": "^6.37.5",
|
|
||||||
"zod": "^4.1.11"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export * from "./di";
|
|
||||||
export * from "./models";
|
|
||||||
export * from "./repositories";
|
|
||||||
export * from "./services";
|
|
||||||
export * from "./snapshot-builders";
|
|
||||||
export * from "./use-cases";
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./supplier-invoice-summary";
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import type { CurrencyCode, LanguageCode, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
|
||||||
import type { Maybe } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { InvoiceAmount, SupplierInvoiceStatus } from "../../domain";
|
|
||||||
|
|
||||||
export type SupplierInvoiceSummary = {
|
|
||||||
id: UniqueID;
|
|
||||||
companyId: UniqueID;
|
|
||||||
|
|
||||||
//invoiceNumber: InvoiceNumber;
|
|
||||||
status: SupplierInvoiceStatus;
|
|
||||||
//series: Maybe<InvoiceSerie>;
|
|
||||||
|
|
||||||
invoiceDate: UtcDate;
|
|
||||||
dueDate: Maybe<UtcDate>;
|
|
||||||
|
|
||||||
reference: Maybe<string>;
|
|
||||||
description: Maybe<string>;
|
|
||||||
|
|
||||||
supplierId: UniqueID;
|
|
||||||
//supplier: InvoiceSupplier;
|
|
||||||
|
|
||||||
languageCode: LanguageCode;
|
|
||||||
currencyCode: CurrencyCode;
|
|
||||||
|
|
||||||
taxableAmount: InvoiceAmount;
|
|
||||||
taxesAmount: InvoiceAmount;
|
|
||||||
totalAmount: InvoiceAmount;
|
|
||||||
};
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./supplier-invoice-repository.interface";
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import type { Collection, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { SupplierInvoice } from "../../domain";
|
|
||||||
import type { SupplierInvoiceSummary } from "../models";
|
|
||||||
|
|
||||||
export interface ISupplierInvoiceRepository {
|
|
||||||
create(invoice: SupplierInvoice, transaction?: unknown): Promise<Result<void, Error>>;
|
|
||||||
|
|
||||||
update(invoice: SupplierInvoice, transaction?: unknown): Promise<Result<void, Error>>;
|
|
||||||
|
|
||||||
save(invoice: SupplierInvoice, transaction?: unknown): Promise<Result<void, Error>>;
|
|
||||||
|
|
||||||
findByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
invoiceId: UniqueID,
|
|
||||||
transaction?: unknown
|
|
||||||
): Promise<Result<SupplierInvoice, Error>>;
|
|
||||||
|
|
||||||
findBySupplierAndNumberInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
supplierId: UniqueID,
|
|
||||||
invoiceNumber: string,
|
|
||||||
transaction?: unknown
|
|
||||||
): Promise<Result<SupplierInvoice, Error>>;
|
|
||||||
|
|
||||||
existsBySupplierAndNumberInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
supplierId: UniqueID,
|
|
||||||
invoiceNumber: string,
|
|
||||||
transaction?: unknown
|
|
||||||
): Promise<Result<boolean, Error>>;
|
|
||||||
|
|
||||||
findByCriteriaInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
criteria: Criteria,
|
|
||||||
transaction?: unknown
|
|
||||||
): Promise<Result<Collection<SupplierInvoiceSummary>, Error>>;
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./supplier-invoice.aggregate";
|
|
||||||
@ -1,313 +0,0 @@
|
|||||||
import {
|
|
||||||
AggregateRoot,
|
|
||||||
type CurrencyCode,
|
|
||||||
type LanguageCode,
|
|
||||||
type UniqueID,
|
|
||||||
type UtcDate,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { InvoicePaymentMethod, SupplierInvoiceTaxes } from "../entities";
|
|
||||||
import type { SupplierInvoiceSourceType } from "../enums";
|
|
||||||
import { type InvoiceAmount, SupplierInvoiceStatus } from "../value-objects";
|
|
||||||
|
|
||||||
export interface ISupplierInvoiceCreateProps {
|
|
||||||
companyId: UniqueID;
|
|
||||||
status: SupplierInvoiceStatus;
|
|
||||||
|
|
||||||
supplierInvoiceCategoryId: UniqueID;
|
|
||||||
|
|
||||||
supplierId: UniqueID;
|
|
||||||
|
|
||||||
invoiceNumber: string;
|
|
||||||
invoiceDate: UtcDate;
|
|
||||||
dueDate: Maybe<UtcDate>;
|
|
||||||
|
|
||||||
description: Maybe<string>;
|
|
||||||
notes: Maybe<string>;
|
|
||||||
|
|
||||||
paymentMethod: Maybe<InvoicePaymentMethod>;
|
|
||||||
|
|
||||||
taxes: SupplierInvoiceTaxes;
|
|
||||||
|
|
||||||
currencyCode: CurrencyCode;
|
|
||||||
languageCode: LanguageCode;
|
|
||||||
|
|
||||||
sourceType: SupplierInvoiceSourceType;
|
|
||||||
documentId: Maybe<UniqueID>;
|
|
||||||
|
|
||||||
version?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISupplierInvoiceTotals {
|
|
||||||
taxableAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
ivaAmount: InvoiceAmount;
|
|
||||||
recAmount: InvoiceAmount;
|
|
||||||
retentionAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
taxesAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
transferredTaxesAmount: InvoiceAmount;
|
|
||||||
netTaxesAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
totalAmount: InvoiceAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ISupplierInvoice {
|
|
||||||
companyId: UniqueID;
|
|
||||||
status: SupplierInvoiceStatus;
|
|
||||||
|
|
||||||
supplierInvoiceCategoryId: UniqueID;
|
|
||||||
|
|
||||||
supplierId: UniqueID;
|
|
||||||
|
|
||||||
invoiceNumber: string;
|
|
||||||
invoiceDate: UtcDate;
|
|
||||||
dueDate: Maybe<UtcDate>;
|
|
||||||
|
|
||||||
description: Maybe<string>;
|
|
||||||
notes: Maybe<string>;
|
|
||||||
|
|
||||||
paymentMethod: Maybe<InvoicePaymentMethod>;
|
|
||||||
|
|
||||||
currencyCode: CurrencyCode;
|
|
||||||
languageCode: LanguageCode;
|
|
||||||
|
|
||||||
sourceType: SupplierInvoiceSourceType;
|
|
||||||
documentId: Maybe<UniqueID>;
|
|
||||||
|
|
||||||
version: number;
|
|
||||||
|
|
||||||
taxes: SupplierInvoiceTaxes;
|
|
||||||
totals(): ISupplierInvoiceTotals;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SupplierInvoicePatchProps = Partial<
|
|
||||||
Omit<ISupplierInvoiceCreateProps, "companyId" | "status" | "sourceType" | "version">
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type InternalSupplierInvoiceProps = Omit<ISupplierInvoiceCreateProps, "version"> & {
|
|
||||||
version: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aggregate raíz de factura de proveedor.
|
|
||||||
*
|
|
||||||
* Reglas MVP:
|
|
||||||
* - supplierId requerido
|
|
||||||
* - invoiceNumber requerido
|
|
||||||
* - invoiceDate requerido
|
|
||||||
* - totalAmount > 0
|
|
||||||
* - companyId requerido
|
|
||||||
* - editable en cualquier estado
|
|
||||||
*
|
|
||||||
* Notas:
|
|
||||||
* - create() aplica validaciones de negocio de alta.
|
|
||||||
* - rehydrate() reconstruye desde persistencia sin revalidar.
|
|
||||||
*/
|
|
||||||
export class SupplierInvoice
|
|
||||||
extends AggregateRoot<InternalSupplierInvoiceProps>
|
|
||||||
implements ISupplierInvoice
|
|
||||||
{
|
|
||||||
private constructor(props: InternalSupplierInvoiceProps, id?: UniqueID) {
|
|
||||||
super(props, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static create(
|
|
||||||
props: ISupplierInvoiceCreateProps,
|
|
||||||
id?: UniqueID
|
|
||||||
): Result<SupplierInvoice, Error> {
|
|
||||||
const validationResult = SupplierInvoice.validateCreateProps(props);
|
|
||||||
|
|
||||||
if (validationResult.isFailure) {
|
|
||||||
return Result.fail(validationResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const supplierInvoice = new SupplierInvoice(
|
|
||||||
{
|
|
||||||
...props,
|
|
||||||
status: props.status ?? SupplierInvoiceStatus.draft(),
|
|
||||||
version: props.version ?? 1,
|
|
||||||
},
|
|
||||||
id
|
|
||||||
);
|
|
||||||
|
|
||||||
return Result.ok(supplierInvoice);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reconstruye el agregado desde persistencia.
|
|
||||||
*
|
|
||||||
* Debe usarse exclusivamente desde infraestructura.
|
|
||||||
*/
|
|
||||||
public static rehydrate(props: InternalSupplierInvoiceProps, id: UniqueID): SupplierInvoice {
|
|
||||||
return new SupplierInvoice(props, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get companyId(): UniqueID {
|
|
||||||
return this.props.companyId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get supplierId(): UniqueID {
|
|
||||||
return this.props.supplierId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get invoiceNumber(): string {
|
|
||||||
return this.props.invoiceNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get invoiceDate(): UtcDate {
|
|
||||||
return this.props.invoiceDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get currencyCode(): CurrencyCode {
|
|
||||||
return this.props.currencyCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get languageCode(): LanguageCode {
|
|
||||||
return this.props.languageCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get taxes(): SupplierInvoiceTaxes {
|
|
||||||
return this.props.taxes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get status(): SupplierInvoiceStatus {
|
|
||||||
return this.props.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get sourceType(): SupplierInvoiceSourceType {
|
|
||||||
return this.props.sourceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get documentId(): Maybe<UniqueID> {
|
|
||||||
return this.props.documentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get version(): number {
|
|
||||||
return this.props.version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get description(): Maybe<string> {
|
|
||||||
return this.props.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get notes(): Maybe<string> {
|
|
||||||
return this.props.notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get paymentMethod(): Maybe<InvoicePaymentMethod> {
|
|
||||||
return this.props.paymentMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get dueDate(): Maybe<UtcDate> {
|
|
||||||
return this.props.dueDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get supplierInvoiceCategoryId(): UniqueID {
|
|
||||||
return this.props.supplierInvoiceCategoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public totals(): ISupplierInvoiceTotals {
|
|
||||||
return {
|
|
||||||
taxableAmount: this.taxes.getTaxableAmount(),
|
|
||||||
|
|
||||||
ivaAmount: this.taxes.getIvaAmount(),
|
|
||||||
recAmount: this.taxes.getRecAmount(),
|
|
||||||
retentionAmount: this.taxes.getRetentionAmount(),
|
|
||||||
|
|
||||||
taxesAmount: this.taxes.getTaxesAmount(),
|
|
||||||
|
|
||||||
transferredTaxesAmount: this.taxes.getTransferredTaxesAmount(),
|
|
||||||
netTaxesAmount: this.taxes.getNetTaxesAmount(),
|
|
||||||
|
|
||||||
totalAmount: this.taxes.getTotalAmount(),
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actualiza campos editables del agregado.
|
|
||||||
*
|
|
||||||
* Regla MVP:
|
|
||||||
* - la factura es editable en cualquier estado
|
|
||||||
*
|
|
||||||
* Notas:
|
|
||||||
* - no permite alterar companyId, status, sourceType ni version externamente
|
|
||||||
* - incrementa version tras mutación válida
|
|
||||||
*/
|
|
||||||
public update(patch: SupplierInvoicePatchProps): Result<void, Error> {
|
|
||||||
const candidateProps: InternalSupplierInvoiceProps = {
|
|
||||||
...this.props,
|
|
||||||
...patch,
|
|
||||||
version: this.props.version + 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validacciones
|
|
||||||
|
|
||||||
Object.assign(this.props, candidateProps);
|
|
||||||
|
|
||||||
return Result.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marca la factura como confirmada.
|
|
||||||
*/
|
|
||||||
public confirm(): Result<void, Error> {
|
|
||||||
if (this.props.status === SupplierInvoiceStatus.confirmed()) {
|
|
||||||
return Result.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.status = SupplierInvoiceStatus.confirmed();
|
|
||||||
this.incrementVersion();
|
|
||||||
|
|
||||||
return Result.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marca la factura como cancelada.
|
|
||||||
*/
|
|
||||||
public cancel(): Result<void, Error> {
|
|
||||||
if (this.props.status === SupplierInvoiceStatus.cancelled()) {
|
|
||||||
return Result.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.status = SupplierInvoiceStatus.cancelled();
|
|
||||||
this.incrementVersion();
|
|
||||||
|
|
||||||
return Result.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
private incrementVersion(): void {
|
|
||||||
this.props.version += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static validateCreateProps(props: ISupplierInvoiceCreateProps): Result<void, Error> {
|
|
||||||
if (props.dueDate.isSome() && props.dueDate.unwrap() < props.invoiceDate) {
|
|
||||||
return Result.fail(
|
|
||||||
new Error("La fecha de vencimiento no puede ser anterior a la fecha de factura")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/*if (!props.companyId?.trim()) {
|
|
||||||
return Result.fail(new InvalidSupplierInvoiceCompanyError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!props.supplierId?.trim()) {
|
|
||||||
return Result.fail(new InvalidSupplierInvoiceSupplierError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!props.invoiceNumber?.trim()) {
|
|
||||||
return Result.fail(new InvalidSupplierInvoiceNumberError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(props.invoiceDate instanceof Date) || Number.isNaN(props.invoiceDate.getTime())) {
|
|
||||||
return Result.fail(new InvalidSupplierInvoiceDateError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.totalAmount <= 0) {
|
|
||||||
return Result.fail(new InvalidSupplierInvoiceAmountError());
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return Result.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./invoice-payment-method";
|
|
||||||
export * from "./supplier-invoice-taxes";
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import { DomainEntity, type UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
export interface InvoicePaymentMethodProps {
|
|
||||||
paymentDescription: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class InvoicePaymentMethod extends DomainEntity<InvoicePaymentMethodProps> {
|
|
||||||
public static create(
|
|
||||||
props: InvoicePaymentMethodProps,
|
|
||||||
id?: UniqueID
|
|
||||||
): Result<InvoicePaymentMethod, Error> {
|
|
||||||
const item = new InvoicePaymentMethod(props, id);
|
|
||||||
|
|
||||||
return Result.ok(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
get paymentDescription(): string {
|
|
||||||
return this.props.paymentDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
getProps(): InvoicePaymentMethodProps {
|
|
||||||
return this.props;
|
|
||||||
}
|
|
||||||
|
|
||||||
toObjectString() {
|
|
||||||
return {
|
|
||||||
id: String(this.id),
|
|
||||||
payment_description: String(this.paymentDescription),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./supplier-invoice-tax.entity";
|
|
||||||
export * from "./supplier-invoice-taxes.collection";
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import type { TaxPercentage } from "@erp/core/api";
|
|
||||||
import { DomainEntity, type Percentage, type UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { InvoiceAmount } from "../../value-objects";
|
|
||||||
|
|
||||||
export type SupplierInvoiceTaxProps = {
|
|
||||||
taxableAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
ivaCode: string;
|
|
||||||
ivaPercentage: Percentage;
|
|
||||||
ivaAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
recCode: Maybe<string>;
|
|
||||||
recPercentage: Maybe<Percentage>;
|
|
||||||
recAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
retentionCode: Maybe<string>;
|
|
||||||
retentionPercentage: Maybe<Percentage>;
|
|
||||||
retentionAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
taxesAmount: InvoiceAmount;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class SupplierInvoiceTax extends DomainEntity<SupplierInvoiceTaxProps> {
|
|
||||||
public static create(
|
|
||||||
props: SupplierInvoiceTaxProps,
|
|
||||||
id?: UniqueID
|
|
||||||
): Result<SupplierInvoiceTax, Error> {
|
|
||||||
return Result.ok(new SupplierInvoiceTax(props, id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public get taxableAmount(): InvoiceAmount {
|
|
||||||
return this.props.taxableAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get ivaCode(): string {
|
|
||||||
return this.props.ivaCode;
|
|
||||||
}
|
|
||||||
public get ivaPercentage(): TaxPercentage {
|
|
||||||
return this.props.ivaPercentage;
|
|
||||||
}
|
|
||||||
public get ivaAmount(): InvoiceAmount {
|
|
||||||
return this.props.ivaAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get recCode(): Maybe<string> {
|
|
||||||
return this.props.recCode;
|
|
||||||
}
|
|
||||||
public get recPercentage(): Maybe<TaxPercentage> {
|
|
||||||
return this.props.recPercentage;
|
|
||||||
}
|
|
||||||
public get recAmount(): InvoiceAmount {
|
|
||||||
return this.props.recAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get retentionCode(): Maybe<string> {
|
|
||||||
return this.props.retentionCode;
|
|
||||||
}
|
|
||||||
public get retentionPercentage(): Maybe<TaxPercentage> {
|
|
||||||
return this.props.retentionPercentage;
|
|
||||||
}
|
|
||||||
public get retentionAmount(): InvoiceAmount {
|
|
||||||
return this.props.retentionAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get taxesAmount(): InvoiceAmount {
|
|
||||||
return this.props.taxesAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getProps(): SupplierInvoiceTaxProps {
|
|
||||||
return this.props;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
|
||||||
import { Collection } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import { InvoiceAmount } from "../../value-objects";
|
|
||||||
|
|
||||||
import type { SupplierInvoiceTax } from "./supplier-invoice-tax.entity";
|
|
||||||
|
|
||||||
export type SupplierInvoiceTaxesProps = {
|
|
||||||
taxes?: SupplierInvoiceTax[];
|
|
||||||
languageCode: LanguageCode;
|
|
||||||
currencyCode: CurrencyCode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class SupplierInvoiceTaxes extends Collection<SupplierInvoiceTax> {
|
|
||||||
private languageCode!: LanguageCode;
|
|
||||||
private currencyCode!: CurrencyCode;
|
|
||||||
|
|
||||||
constructor(props: SupplierInvoiceTaxesProps) {
|
|
||||||
super(props.taxes ?? []);
|
|
||||||
this.languageCode = props.languageCode;
|
|
||||||
this.currencyCode = props.currencyCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static create(props: SupplierInvoiceTaxesProps): SupplierInvoiceTaxes {
|
|
||||||
return new SupplierInvoiceTaxes(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTaxableAmount(): InvoiceAmount {
|
|
||||||
return this.items.reduce(
|
|
||||||
(acc, tax) => acc.add(tax.taxableAmount),
|
|
||||||
InvoiceAmount.zero(this.currencyCode.toString())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getIvaAmount(): InvoiceAmount {
|
|
||||||
return this.items.reduce(
|
|
||||||
(acc, tax) => acc.add(tax.ivaAmount),
|
|
||||||
InvoiceAmount.zero(this.currencyCode.toString())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getRecAmount(): InvoiceAmount {
|
|
||||||
return this.items.reduce(
|
|
||||||
(acc, tax) => acc.add(tax.recAmount),
|
|
||||||
InvoiceAmount.zero(this.currencyCode.toString())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getRetentionAmount(): InvoiceAmount {
|
|
||||||
return this.items.reduce(
|
|
||||||
(acc, tax) => acc.add(tax.retentionAmount),
|
|
||||||
InvoiceAmount.zero(this.currencyCode.toString())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTaxesAmount(): InvoiceAmount {
|
|
||||||
return this.items.reduce(
|
|
||||||
(acc, tax) => acc.add(tax.taxesAmount),
|
|
||||||
InvoiceAmount.zero(this.currencyCode.toString())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTransferredTaxesAmount(): InvoiceAmount {
|
|
||||||
return this.getIvaAmount().add(this.getRecAmount());
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNetTaxesAmount(): InvoiceAmount {
|
|
||||||
return this.getTransferredTaxesAmount().subtract(this.getRetentionAmount());
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTotalAmount(): InvoiceAmount {
|
|
||||||
return this.getTaxableAmount().add(this.getNetTaxesAmount());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./supplier-invoice-source-type.enum";
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Indica el origen de la factura.
|
|
||||||
*
|
|
||||||
* Reglas:
|
|
||||||
* - Determina cómo se ha creado la factura en el sistema
|
|
||||||
* - No implica calidad de datos ni estado de validación
|
|
||||||
* - No debe usarse para lógica de negocio compleja en MVP
|
|
||||||
*/
|
|
||||||
export enum SupplierInvoiceSourceType {
|
|
||||||
/**
|
|
||||||
* Creada manualmente por el usuario.
|
|
||||||
*/
|
|
||||||
MANUAL = "MANUAL",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creada a partir de un documento (PDF) subido.
|
|
||||||
* Puede haber sido enriquecida parcialmente por parsing.
|
|
||||||
*/
|
|
||||||
DOCUMENT = "DOCUMENT",
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export * from "./aggregates";
|
|
||||||
export * from "./entities";
|
|
||||||
export * from "./value-objects";
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export * from "./invoice-amount.vo";
|
|
||||||
export * from "./invoice-date.vo";
|
|
||||||
export * from "./invoice-number.vo";
|
|
||||||
export * from "./supplier-invoice-status.vo";
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
import { MoneyValue, type MoneyValueProps, type Percentage, type Quantity } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
type InvoiceAmountProps = Pick<MoneyValueProps, "value" | "currency_code">;
|
|
||||||
|
|
||||||
export class InvoiceAmount extends MoneyValue {
|
|
||||||
public static DEFAULT_SCALE = 2;
|
|
||||||
|
|
||||||
static create({ value, currency_code }: InvoiceAmountProps) {
|
|
||||||
const props = {
|
|
||||||
value: Number(value),
|
|
||||||
scale: InvoiceAmount.DEFAULT_SCALE,
|
|
||||||
currency_code,
|
|
||||||
};
|
|
||||||
return Result.ok(new InvoiceAmount(props));
|
|
||||||
}
|
|
||||||
|
|
||||||
static zero(currency_code: string) {
|
|
||||||
const props = {
|
|
||||||
value: 0,
|
|
||||||
currency_code,
|
|
||||||
};
|
|
||||||
return InvoiceAmount.create(props).data;
|
|
||||||
}
|
|
||||||
|
|
||||||
toObjectString() {
|
|
||||||
return {
|
|
||||||
value: String(this.value),
|
|
||||||
scale: String(this.scale),
|
|
||||||
currency_code: this.currencyCode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure fluent operations keep the subclass type
|
|
||||||
roundUsingScale(intermediateScale: number) {
|
|
||||||
const scaled = super.convertScale(intermediateScale);
|
|
||||||
const normalized = scaled.convertScale(InvoiceAmount.DEFAULT_SCALE);
|
|
||||||
const p = normalized.toPrimitive();
|
|
||||||
|
|
||||||
return new InvoiceAmount({
|
|
||||||
value: p.value,
|
|
||||||
currency_code: p.currency_code,
|
|
||||||
scale: InvoiceAmount.DEFAULT_SCALE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
add(addend: MoneyValue) {
|
|
||||||
const mv = super.add(addend);
|
|
||||||
const p = mv.toPrimitive();
|
|
||||||
return new InvoiceAmount({
|
|
||||||
value: p.value,
|
|
||||||
currency_code: p.currency_code,
|
|
||||||
scale: InvoiceAmount.DEFAULT_SCALE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
subtract(subtrahend: MoneyValue) {
|
|
||||||
const mv = super.subtract(subtrahend);
|
|
||||||
const p = mv.toPrimitive();
|
|
||||||
return new InvoiceAmount({
|
|
||||||
value: p.value,
|
|
||||||
currency_code: p.currency_code,
|
|
||||||
scale: InvoiceAmount.DEFAULT_SCALE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
multiply(multiplier: number | Quantity) {
|
|
||||||
const mv = super.multiply(multiplier);
|
|
||||||
const p = mv.toPrimitive();
|
|
||||||
return new InvoiceAmount({
|
|
||||||
value: p.value,
|
|
||||||
currency_code: p.currency_code,
|
|
||||||
scale: InvoiceAmount.DEFAULT_SCALE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
divide(divisor: number | Quantity) {
|
|
||||||
const mv = super.divide(divisor);
|
|
||||||
const p = mv.toPrimitive();
|
|
||||||
return new InvoiceAmount({
|
|
||||||
value: p.value,
|
|
||||||
currency_code: p.currency_code,
|
|
||||||
scale: InvoiceAmount.DEFAULT_SCALE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
percentage(percentage: number | Percentage) {
|
|
||||||
const mv = super.percentage(percentage);
|
|
||||||
const p = mv.toPrimitive();
|
|
||||||
return new InvoiceAmount({
|
|
||||||
value: p.value,
|
|
||||||
currency_code: p.currency_code,
|
|
||||||
scale: InvoiceAmount.DEFAULT_SCALE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
import { DomainValidationError, ValueObject } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
type ISupplierInvoiceStatusProps = {
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum SUPPLIER_INVOICE_STATUS {
|
|
||||||
DRAFT = "DRAFT",
|
|
||||||
NEEDS_REVIEW = "NEEDS_REVIEW",
|
|
||||||
CONFIRMED = "CONFIRMED",
|
|
||||||
CANCELLED = "CANCELLED",
|
|
||||||
}
|
|
||||||
|
|
||||||
const SUPPLIER_INVOICE_TRANSITIONS: Record<string, string[]> = {
|
|
||||||
[SUPPLIER_INVOICE_STATUS.DRAFT]: [
|
|
||||||
SUPPLIER_INVOICE_STATUS.NEEDS_REVIEW,
|
|
||||||
SUPPLIER_INVOICE_STATUS.CONFIRMED,
|
|
||||||
SUPPLIER_INVOICE_STATUS.CANCELLED,
|
|
||||||
],
|
|
||||||
[SUPPLIER_INVOICE_STATUS.NEEDS_REVIEW]: [
|
|
||||||
SUPPLIER_INVOICE_STATUS.DRAFT,
|
|
||||||
SUPPLIER_INVOICE_STATUS.CONFIRMED,
|
|
||||||
SUPPLIER_INVOICE_STATUS.CANCELLED,
|
|
||||||
],
|
|
||||||
[SUPPLIER_INVOICE_STATUS.CONFIRMED]: [
|
|
||||||
SUPPLIER_INVOICE_STATUS.NEEDS_REVIEW,
|
|
||||||
SUPPLIER_INVOICE_STATUS.CANCELLED,
|
|
||||||
],
|
|
||||||
[SUPPLIER_INVOICE_STATUS.CANCELLED]: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export class SupplierInvoiceStatus extends ValueObject<ISupplierInvoiceStatusProps> {
|
|
||||||
private static readonly ALLOWED_STATUSES = [
|
|
||||||
SUPPLIER_INVOICE_STATUS.DRAFT,
|
|
||||||
SUPPLIER_INVOICE_STATUS.NEEDS_REVIEW,
|
|
||||||
SUPPLIER_INVOICE_STATUS.CONFIRMED,
|
|
||||||
SUPPLIER_INVOICE_STATUS.CANCELLED,
|
|
||||||
];
|
|
||||||
|
|
||||||
private static readonly FIELD = "supplierInvoiceStatus";
|
|
||||||
private static readonly ERROR_CODE = "INVALID_SUPPLIER_INVOICE_STATUS";
|
|
||||||
|
|
||||||
public static create(value: string): Result<SupplierInvoiceStatus, Error> {
|
|
||||||
if (!SupplierInvoiceStatus.ALLOWED_STATUSES.includes(value as SUPPLIER_INVOICE_STATUS)) {
|
|
||||||
const detail = `Estado de la factura de proveedor no válido: ${value}`;
|
|
||||||
|
|
||||||
return Result.fail(
|
|
||||||
new DomainValidationError(
|
|
||||||
SupplierInvoiceStatus.ERROR_CODE,
|
|
||||||
SupplierInvoiceStatus.FIELD,
|
|
||||||
detail
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(
|
|
||||||
value === SUPPLIER_INVOICE_STATUS.NEEDS_REVIEW
|
|
||||||
? SupplierInvoiceStatus.needsReview()
|
|
||||||
: value === SUPPLIER_INVOICE_STATUS.CONFIRMED
|
|
||||||
? SupplierInvoiceStatus.confirmed()
|
|
||||||
: value === SUPPLIER_INVOICE_STATUS.CANCELLED
|
|
||||||
? SupplierInvoiceStatus.cancelled()
|
|
||||||
: SupplierInvoiceStatus.draft()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static draft(): SupplierInvoiceStatus {
|
|
||||||
return new SupplierInvoiceStatus({ value: SUPPLIER_INVOICE_STATUS.DRAFT });
|
|
||||||
}
|
|
||||||
|
|
||||||
public static needsReview(): SupplierInvoiceStatus {
|
|
||||||
return new SupplierInvoiceStatus({ value: SUPPLIER_INVOICE_STATUS.NEEDS_REVIEW });
|
|
||||||
}
|
|
||||||
|
|
||||||
public static confirmed(): SupplierInvoiceStatus {
|
|
||||||
return new SupplierInvoiceStatus({ value: SUPPLIER_INVOICE_STATUS.CONFIRMED });
|
|
||||||
}
|
|
||||||
|
|
||||||
public static cancelled(): SupplierInvoiceStatus {
|
|
||||||
return new SupplierInvoiceStatus({ value: SUPPLIER_INVOICE_STATUS.CANCELLED });
|
|
||||||
}
|
|
||||||
|
|
||||||
public isDraft(): boolean {
|
|
||||||
return this.props.value === SUPPLIER_INVOICE_STATUS.DRAFT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isNeedsReview(): boolean {
|
|
||||||
return this.props.value === SUPPLIER_INVOICE_STATUS.NEEDS_REVIEW;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isConfirmed(): boolean {
|
|
||||||
return this.props.value === SUPPLIER_INVOICE_STATUS.CONFIRMED;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isCancelled(): boolean {
|
|
||||||
return this.props.value === SUPPLIER_INVOICE_STATUS.CANCELLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
public canTransitionTo(nextStatus: string): boolean {
|
|
||||||
return SUPPLIER_INVOICE_TRANSITIONS[this.props.value].includes(nextStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getProps(): string {
|
|
||||||
return this.props.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public toPrimitive(): string {
|
|
||||||
return this.getProps();
|
|
||||||
}
|
|
||||||
|
|
||||||
public toString(): string {
|
|
||||||
return String(this.props.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import type { IModuleServer } from "@erp/core/api";
|
|
||||||
|
|
||||||
export const supplierInvoicesAPIModule: IModuleServer = {
|
|
||||||
name: "supplier-invoices",
|
|
||||||
version: "1.0.0",
|
|
||||||
dependencies: [],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fase de SETUP
|
|
||||||
* ----------------
|
|
||||||
* - Construye el dominio (una sola vez)
|
|
||||||
* - Define qué expone el módulo
|
|
||||||
* - NO conecta infraestructura
|
|
||||||
*/
|
|
||||||
async setup(params) {
|
|
||||||
const { env: ENV, app, database, baseRoutePath: API_BASE_PATH, logger } = params;
|
|
||||||
|
|
||||||
// 1) Dominio interno
|
|
||||||
const internal = buildSupplierInvoicesDependencies(params);
|
|
||||||
|
|
||||||
// 2) Servicios públicos (Application Services)
|
|
||||||
const supplierinvoicesServices: ISupplierInvoicePublicServices =
|
|
||||||
buildSupplierInvoicePublicServices(params, internal);
|
|
||||||
|
|
||||||
logger.info("🚀 Supplier invoices module dependencies registered", {
|
|
||||||
label: this.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Modelos Sequelize del módulo
|
|
||||||
models,
|
|
||||||
|
|
||||||
// Servicios expuestos a otros módulos
|
|
||||||
services: {
|
|
||||||
general: supplierinvoicesServices, // 'supplierinvoices:general'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Implementación privada del módulo
|
|
||||||
internal,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fase de START
|
|
||||||
* -------------
|
|
||||||
* - Conecta el módulo al runtime
|
|
||||||
* - Puede usar servicios e internals ya construidos
|
|
||||||
* - NO construye dominio
|
|
||||||
*/
|
|
||||||
async start(params) {
|
|
||||||
const { app, baseRoutePath, logger, getInternal } = params;
|
|
||||||
|
|
||||||
// Registro de rutas HTTP
|
|
||||||
supplierInvoicesRouter(params);
|
|
||||||
|
|
||||||
logger.info("🚀 Supplier invoices module started", {
|
|
||||||
label: this.name,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Warmup opcional (si lo necesitas en el futuro)
|
|
||||||
* ----------------------------------------------
|
|
||||||
* warmup(params) {
|
|
||||||
* ...
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
|
|
||||||
export default supplierInvoicesAPIModule;
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import supplierInvoiceModelInit from "./models/supplier-invoice.model";
|
|
||||||
import supplierInvoiceTaxesModelInit from "./models/supplier-invoice-tax.model";
|
|
||||||
|
|
||||||
export * from "./models";
|
|
||||||
|
|
||||||
// Array de inicializadores para que registerModels() lo use
|
|
||||||
export const models = [supplierInvoiceModelInit, supplierInvoiceTaxesModelInit];
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./sequelize-issued-invoice-domain.mapper";
|
|
||||||
@ -1,525 +0,0 @@
|
|||||||
import { DiscountPercentage, type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
|
||||||
import {
|
|
||||||
CurrencyCode,
|
|
||||||
LanguageCode,
|
|
||||||
TextValue,
|
|
||||||
UniqueID,
|
|
||||||
UtcDate,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
maybeToNullable,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import {
|
|
||||||
type InternalIssuedInvoiceProps,
|
|
||||||
InvoiceAmount,
|
|
||||||
InvoiceNumber,
|
|
||||||
InvoicePaymentMethod,
|
|
||||||
InvoiceSerie,
|
|
||||||
InvoiceStatus,
|
|
||||||
IssuedInvoice,
|
|
||||||
IssuedInvoiceItems,
|
|
||||||
IssuedInvoiceTaxes,
|
|
||||||
} from "../../../../../../domain";
|
|
||||||
import type {
|
|
||||||
CustomerInvoiceCreationAttributes,
|
|
||||||
CustomerInvoiceModel,
|
|
||||||
} from "../../../../../common";
|
|
||||||
|
|
||||||
import { SequelizeIssuedInvoiceItemDomainMapper } from "./sequelize-issued-invoice-item-domain.mapper";
|
|
||||||
import { SequelizeIssuedInvoiceRecipientDomainMapper } from "./sequelize-issued-invoice-recipient-domain.mapper";
|
|
||||||
import { SequelizeIssuedInvoiceTaxesDomainMapper } from "./sequelize-issued-invoice-taxes-domain.mapper";
|
|
||||||
import { SequelizeIssuedInvoiceVerifactuDomainMapper } from "./sequelize-verifactu-record-domain.mapper";
|
|
||||||
|
|
||||||
export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
|
|
||||||
CustomerInvoiceModel,
|
|
||||||
CustomerInvoiceCreationAttributes,
|
|
||||||
IssuedInvoice
|
|
||||||
> {
|
|
||||||
private _itemsMapper: SequelizeIssuedInvoiceItemDomainMapper;
|
|
||||||
private _recipientMapper: SequelizeIssuedInvoiceRecipientDomainMapper;
|
|
||||||
private _taxesMapper: SequelizeIssuedInvoiceTaxesDomainMapper;
|
|
||||||
private _verifactuMapper: SequelizeIssuedInvoiceVerifactuDomainMapper;
|
|
||||||
|
|
||||||
constructor(params: MapperParamsType) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this._itemsMapper = new SequelizeIssuedInvoiceItemDomainMapper(params); // Instanciar el mapper de items
|
|
||||||
this._recipientMapper = new SequelizeIssuedInvoiceRecipientDomainMapper();
|
|
||||||
this._taxesMapper = new SequelizeIssuedInvoiceTaxesDomainMapper(params);
|
|
||||||
this._verifactuMapper = new SequelizeIssuedInvoiceVerifactuDomainMapper();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _mapAttributesToDomain(raw: CustomerInvoiceModel, params?: MapperParamsType) {
|
|
||||||
const { errors } = params as {
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const invoiceId = extractOrPushError(UniqueID.create(raw.id), "id", errors);
|
|
||||||
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
|
||||||
|
|
||||||
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
|
||||||
|
|
||||||
// Para issued invoices, proforma_id debe estar relleno
|
|
||||||
const proformaId = extractOrPushError(
|
|
||||||
UniqueID.create(String(raw.proforma_id)),
|
|
||||||
"proforma_id",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
|
|
||||||
|
|
||||||
const series = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.series, (v) => InvoiceSerie.create(v)),
|
|
||||||
"series",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const invoiceNumber = extractOrPushError(
|
|
||||||
InvoiceNumber.create(raw.invoice_number),
|
|
||||||
"invoice_number",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fechas
|
|
||||||
const invoiceDate = extractOrPushError(
|
|
||||||
UtcDate.createFromISO(raw.invoice_date),
|
|
||||||
"invoice_date",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const operationDate = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.operation_date, (v) => UtcDate.createFromISO(v)),
|
|
||||||
"operation_date",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
// Idioma / divisa
|
|
||||||
const languageCode = extractOrPushError(
|
|
||||||
LanguageCode.create(raw.language_code),
|
|
||||||
"language_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const currencyCode = extractOrPushError(
|
|
||||||
CurrencyCode.create(raw.currency_code),
|
|
||||||
"currency_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
// Textos opcionales
|
|
||||||
const reference = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.reference, (value) => Result.ok(String(value))),
|
|
||||||
"reference",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const description = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.description, (value) => Result.ok(String(value))),
|
|
||||||
"description",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const notes = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.notes, (value) => TextValue.create(value)),
|
|
||||||
"notes",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
// Método de pago (VO opcional con id + descripción)
|
|
||||||
let paymentMethod = Maybe.none<InvoicePaymentMethod>();
|
|
||||||
|
|
||||||
if (!isNullishOrEmpty(raw.payment_method_id)) {
|
|
||||||
const paymentId = extractOrPushError(
|
|
||||||
UniqueID.create(String(raw.payment_method_id)),
|
|
||||||
"paymentMethod.id",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const paymentVO = extractOrPushError(
|
|
||||||
InvoicePaymentMethod.create(
|
|
||||||
{ paymentDescription: String(raw.payment_method_description ?? "") },
|
|
||||||
paymentId ?? undefined
|
|
||||||
),
|
|
||||||
"payment_method_description",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
if (paymentVO) {
|
|
||||||
paymentMethod = Maybe.some(paymentVO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const subtotalAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.subtotal_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"subtotal_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
// Total descuento de líneas
|
|
||||||
|
|
||||||
const itemsDiscountAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: Number(raw.items_discount_amount_value ?? 0),
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"items_discount_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
// % descuento global (VO)
|
|
||||||
const globalDiscountPercentage = extractOrPushError(
|
|
||||||
DiscountPercentage.create({
|
|
||||||
value: Number(raw.global_discount_percentage_value ?? 0),
|
|
||||||
}),
|
|
||||||
"global_discount_percentage_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const globalDiscountAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: Number(raw.global_discount_amount_value ?? 0),
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"global_discount_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalDiscountAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.total_discount_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"total_discount_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxableAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.taxable_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"taxable_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const ivaAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.iva_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"iva_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const recAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.rec_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"rec_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const retentionAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.retention_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"retention_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxesAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.taxes_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"taxes_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.total_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"total_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
invoiceId,
|
|
||||||
companyId,
|
|
||||||
customerId,
|
|
||||||
proformaId,
|
|
||||||
status,
|
|
||||||
series,
|
|
||||||
invoiceNumber,
|
|
||||||
invoiceDate,
|
|
||||||
operationDate,
|
|
||||||
reference,
|
|
||||||
description,
|
|
||||||
notes,
|
|
||||||
languageCode,
|
|
||||||
currencyCode,
|
|
||||||
paymentMethod,
|
|
||||||
|
|
||||||
subtotalAmount,
|
|
||||||
itemsDiscountAmount,
|
|
||||||
globalDiscountPercentage,
|
|
||||||
globalDiscountAmount,
|
|
||||||
totalDiscountAmount,
|
|
||||||
taxableAmount,
|
|
||||||
ivaAmount,
|
|
||||||
recAmount,
|
|
||||||
retentionAmount,
|
|
||||||
taxesAmount,
|
|
||||||
totalAmount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapToDomain(
|
|
||||||
raw: CustomerInvoiceModel,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<IssuedInvoice, Error> {
|
|
||||||
try {
|
|
||||||
const errors: ValidationErrorDetail[] = [];
|
|
||||||
|
|
||||||
// 1) Valores escalares (atributos generales)
|
|
||||||
const attributes = this._mapAttributesToDomain(raw, { errors, ...params });
|
|
||||||
|
|
||||||
// 2) Recipient (snapshot en la factura o include)
|
|
||||||
const recipientResult = this._recipientMapper.mapToDomain(raw, {
|
|
||||||
errors,
|
|
||||||
attributes,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3) Verifactu (snapshot en la factura o include)
|
|
||||||
const verifactuResult = this._verifactuMapper.mapToDomain(raw.verifactu, {
|
|
||||||
errors,
|
|
||||||
attributes,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4) Items (colección)
|
|
||||||
const itemsResults = this._itemsMapper.mapToDomainCollection(raw.items, raw.items.length, {
|
|
||||||
errors,
|
|
||||||
attributes,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 5) Taxes (colección)
|
|
||||||
const taxesResults = this._taxesMapper.mapToDomainCollection(raw.taxes, raw.taxes.length, {
|
|
||||||
errors,
|
|
||||||
attributes,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 6) Si hubo errores de mapeo, devolvemos colección de validación
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Customer invoice mapping failed [mapToDomain]", errors)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6) Construcción del agregado (Dominio)
|
|
||||||
|
|
||||||
const verifactu = verifactuResult.data;
|
|
||||||
|
|
||||||
const items = IssuedInvoiceItems.create({
|
|
||||||
items: itemsResults.data.getAll(),
|
|
||||||
languageCode: attributes.languageCode!,
|
|
||||||
currencyCode: attributes.currencyCode!,
|
|
||||||
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
|
||||||
});
|
|
||||||
|
|
||||||
const taxes = IssuedInvoiceTaxes.create({
|
|
||||||
taxes: taxesResults.data.getAll(),
|
|
||||||
languageCode: attributes.languageCode!,
|
|
||||||
currencyCode: attributes.currencyCode!,
|
|
||||||
});
|
|
||||||
|
|
||||||
const invoiceProps: InternalIssuedInvoiceProps = {
|
|
||||||
companyId: attributes.companyId!,
|
|
||||||
|
|
||||||
proformaId: attributes.proformaId!,
|
|
||||||
status: attributes.status!,
|
|
||||||
series: attributes.series!,
|
|
||||||
invoiceNumber: attributes.invoiceNumber!,
|
|
||||||
invoiceDate: attributes.invoiceDate!,
|
|
||||||
operationDate: attributes.operationDate!,
|
|
||||||
|
|
||||||
customerId: attributes.customerId!,
|
|
||||||
recipient: recipientResult.data,
|
|
||||||
|
|
||||||
reference: attributes.reference!,
|
|
||||||
description: attributes.description!,
|
|
||||||
notes: attributes.notes!,
|
|
||||||
|
|
||||||
languageCode: attributes.languageCode!,
|
|
||||||
currencyCode: attributes.currencyCode!,
|
|
||||||
|
|
||||||
subtotalAmount: attributes.subtotalAmount!,
|
|
||||||
|
|
||||||
itemsDiscountAmount: attributes.itemsDiscountAmount!,
|
|
||||||
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
|
||||||
globalDiscountAmount: attributes.globalDiscountAmount!,
|
|
||||||
totalDiscountAmount: attributes.totalDiscountAmount!,
|
|
||||||
|
|
||||||
taxableAmount: attributes.taxableAmount!,
|
|
||||||
ivaAmount: attributes.ivaAmount!,
|
|
||||||
recAmount: attributes.recAmount!,
|
|
||||||
retentionAmount: attributes.retentionAmount!,
|
|
||||||
|
|
||||||
taxesAmount: attributes.taxesAmount!,
|
|
||||||
totalAmount: attributes.totalAmount!,
|
|
||||||
|
|
||||||
paymentMethod: attributes.paymentMethod!,
|
|
||||||
|
|
||||||
taxes,
|
|
||||||
verifactu,
|
|
||||||
};
|
|
||||||
|
|
||||||
const invoiceId = attributes.invoiceId!;
|
|
||||||
const invoice = IssuedInvoice.rehydrate(invoiceProps, items, invoiceId);
|
|
||||||
|
|
||||||
return Result.ok(invoice);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
return Result.fail(err as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapToPersistence(
|
|
||||||
source: IssuedInvoice,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<CustomerInvoiceCreationAttributes, Error> {
|
|
||||||
const errors: ValidationErrorDetail[] = [];
|
|
||||||
|
|
||||||
// 1) Items
|
|
||||||
const itemsResult = this._itemsMapper.mapToPersistenceArray(source.items, {
|
|
||||||
errors,
|
|
||||||
parent: source,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (itemsResult.isFailure) {
|
|
||||||
errors.push({
|
|
||||||
path: "items",
|
|
||||||
message: itemsResult.error.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Taxes
|
|
||||||
const taxesResult = this._taxesMapper.mapToPersistenceArray(source.taxes, {
|
|
||||||
errors,
|
|
||||||
parent: source,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (taxesResult.isFailure) {
|
|
||||||
errors.push({
|
|
||||||
path: "taxes",
|
|
||||||
message: taxesResult.error.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Cliente
|
|
||||||
const recipient = this._recipientMapper.mapToPersistence(source.recipient, {
|
|
||||||
errors,
|
|
||||||
parent: source,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4) Verifactu
|
|
||||||
const verifactuResult = this._verifactuMapper.mapToPersistence(source.verifactu, {
|
|
||||||
errors,
|
|
||||||
parent: source,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 5) Si hubo errores de mapeo, devolvemos colección de validación
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Customer invoice mapping to persistence failed", errors)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = itemsResult.data;
|
|
||||||
const taxes = taxesResult.data;
|
|
||||||
const verifactu = verifactuResult.data;
|
|
||||||
|
|
||||||
const invoiceValues: Partial<CustomerInvoiceCreationAttributes> = {
|
|
||||||
// Identificación
|
|
||||||
id: source.id.toPrimitive(),
|
|
||||||
company_id: source.companyId.toPrimitive(),
|
|
||||||
|
|
||||||
// Flags / estado / serie / número
|
|
||||||
is_proforma: false,
|
|
||||||
status: source.status.toPrimitive(),
|
|
||||||
proforma_id: source.proformaId.toPrimitive(),
|
|
||||||
|
|
||||||
series: maybeToNullable(source.series, (v) => v.toPrimitive()),
|
|
||||||
invoice_number: source.invoiceNumber.toPrimitive(),
|
|
||||||
invoice_date: source.invoiceDate.toPrimitive(),
|
|
||||||
operation_date: maybeToNullable(source.operationDate, (v) => v.toPrimitive()),
|
|
||||||
language_code: source.languageCode.toPrimitive(),
|
|
||||||
currency_code: source.currencyCode.toPrimitive(),
|
|
||||||
|
|
||||||
reference: maybeToNullable(source.reference, (reference) => reference),
|
|
||||||
description: maybeToNullable(source.description, (description) => description),
|
|
||||||
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
|
|
||||||
|
|
||||||
payment_method_id: maybeToNullable(
|
|
||||||
source.paymentMethod,
|
|
||||||
(payment) => payment.toObjectString().id
|
|
||||||
),
|
|
||||||
payment_method_description: maybeToNullable(
|
|
||||||
source.paymentMethod,
|
|
||||||
(payment) => payment.toObjectString().payment_description
|
|
||||||
),
|
|
||||||
|
|
||||||
subtotal_amount_value: source.subtotalAmount.value,
|
|
||||||
subtotal_amount_scale: source.subtotalAmount.scale,
|
|
||||||
|
|
||||||
items_discount_amount_value: source.itemsDiscountAmount.value,
|
|
||||||
items_discount_amount_scale: source.itemsDiscountAmount.scale,
|
|
||||||
|
|
||||||
global_discount_percentage_value: source.globalDiscountPercentage.toPrimitive().value,
|
|
||||||
global_discount_percentage_scale: source.globalDiscountPercentage.toPrimitive().scale,
|
|
||||||
|
|
||||||
global_discount_amount_value: source.globalDiscountAmount.value,
|
|
||||||
global_discount_amount_scale: source.globalDiscountAmount.scale,
|
|
||||||
|
|
||||||
total_discount_amount_value: source.totalDiscountAmount.value,
|
|
||||||
total_discount_amount_scale: source.totalDiscountAmount.scale,
|
|
||||||
|
|
||||||
taxable_amount_value: source.taxableAmount.value,
|
|
||||||
taxable_amount_scale: source.taxableAmount.scale,
|
|
||||||
|
|
||||||
taxes_amount_value: source.taxesAmount.value,
|
|
||||||
taxes_amount_scale: source.taxesAmount.scale,
|
|
||||||
|
|
||||||
total_amount_value: source.totalAmount.value,
|
|
||||||
total_amount_scale: source.totalAmount.scale,
|
|
||||||
|
|
||||||
customer_id: source.customerId.toPrimitive(),
|
|
||||||
...recipient,
|
|
||||||
|
|
||||||
taxes,
|
|
||||||
items,
|
|
||||||
verifactu,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Result.ok<CustomerInvoiceCreationAttributes>(
|
|
||||||
invoiceValues as CustomerInvoiceCreationAttributes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,416 +0,0 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
|
||||||
import {
|
|
||||||
DiscountPercentage,
|
|
||||||
type MapperParamsType,
|
|
||||||
SequelizeDomainMapper,
|
|
||||||
TaxPercentage,
|
|
||||||
} from "@erp/core/api";
|
|
||||||
import {
|
|
||||||
UniqueID,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableOrEmptyString,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
maybeToNullable,
|
|
||||||
maybeToNullableString,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import {
|
|
||||||
type IIssuedInvoiceCreateProps,
|
|
||||||
type IIssuedInvoiceItemCreateProps,
|
|
||||||
type IssuedInvoice,
|
|
||||||
IssuedInvoiceItem,
|
|
||||||
ItemAmount,
|
|
||||||
ItemDescription,
|
|
||||||
ItemQuantity,
|
|
||||||
} from "../../../../../../domain";
|
|
||||||
import type {
|
|
||||||
CustomerInvoiceItemCreationAttributes,
|
|
||||||
CustomerInvoiceItemModel,
|
|
||||||
} from "../../../../../common";
|
|
||||||
|
|
||||||
export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMapper<
|
|
||||||
CustomerInvoiceItemModel,
|
|
||||||
CustomerInvoiceItemCreationAttributes,
|
|
||||||
IssuedInvoiceItem
|
|
||||||
> {
|
|
||||||
private readonly taxCatalog!: JsonTaxCatalogProvider;
|
|
||||||
|
|
||||||
constructor(params: MapperParamsType) {
|
|
||||||
super();
|
|
||||||
const { taxCatalog } = params as {
|
|
||||||
taxCatalog: JsonTaxCatalogProvider;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!taxCatalog) {
|
|
||||||
throw new Error('taxCatalog not defined ("SequelizeIssuedInvoiceItemDomainMapper")');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.taxCatalog = taxCatalog;
|
|
||||||
}
|
|
||||||
|
|
||||||
private mapAttributesToDomain(
|
|
||||||
raw: CustomerInvoiceItemModel,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Partial<IIssuedInvoiceItemCreateProps> & { itemId?: UniqueID } {
|
|
||||||
const { errors, index, attributes } = params as {
|
|
||||||
index: number;
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
attributes: Partial<IIssuedInvoiceCreateProps>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const itemId = extractOrPushError(
|
|
||||||
UniqueID.create(raw.item_id),
|
|
||||||
`items[${index}].item_id`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const description = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.description, (v) => ItemDescription.create(v)),
|
|
||||||
`items[${index}].description`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const quantity = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.quantity_value, (v) => ItemQuantity.create({ value: v })),
|
|
||||||
`items[${index}].quantity_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const unitAmount = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.unit_amount_value, (value) =>
|
|
||||||
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
|
|
||||||
),
|
|
||||||
`items[${index}].unit_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const subtotalAmount = extractOrPushError(
|
|
||||||
ItemAmount.create({
|
|
||||||
value: raw.subtotal_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`items[${index}].subtotal_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemDiscountPercentage = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.item_discount_percentage_value, (v) =>
|
|
||||||
DiscountPercentage.create({ value: v })
|
|
||||||
),
|
|
||||||
`items[${index}].item_discount_percentage_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemDiscountAmount = extractOrPushError(
|
|
||||||
ItemAmount.create({
|
|
||||||
value: raw.item_discount_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`items[${index}].item_discount_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const globalDiscountPercentage = extractOrPushError(
|
|
||||||
DiscountPercentage.create({ value: raw.global_discount_percentage_value }),
|
|
||||||
`items[${index}].global_discount_percentage_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const globalDiscountAmount = extractOrPushError(
|
|
||||||
ItemAmount.create({
|
|
||||||
value: raw.global_discount_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`items[${index}].global_discount_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalDiscountAmount = extractOrPushError(
|
|
||||||
ItemAmount.create({
|
|
||||||
value: raw.total_discount_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`items[${index}].total_discount_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxableAmount = extractOrPushError(
|
|
||||||
ItemAmount.create({
|
|
||||||
value: raw.taxable_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`items[${index}].taxable_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const ivaCode = maybeFromNullableOrEmptyString(raw.iva_code);
|
|
||||||
|
|
||||||
const ivaPercentage = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.iva_percentage_value, (value) => TaxPercentage.create({ value })),
|
|
||||||
`items[${index}].iva_percentage_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const ivaAmount = extractOrPushError(
|
|
||||||
ItemAmount.create({
|
|
||||||
value: raw.iva_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`items[${index}].iva_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const recCode = maybeFromNullableOrEmptyString(raw.rec_code);
|
|
||||||
|
|
||||||
const recPercentage = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.rec_percentage_value, (value) => TaxPercentage.create({ value })),
|
|
||||||
`items[${index}].rec_percentage_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const recAmount = extractOrPushError(
|
|
||||||
ItemAmount.create({
|
|
||||||
value: raw.rec_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`items[${index}].rec_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code);
|
|
||||||
|
|
||||||
const retentionPercentage = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.retention_percentage_value, (value) =>
|
|
||||||
TaxPercentage.create({ value })
|
|
||||||
),
|
|
||||||
`items[${index}].retention_percentage_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const retentionAmount = extractOrPushError(
|
|
||||||
ItemAmount.create({
|
|
||||||
value: raw.retention_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`items[${index}].retention_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxesAmount = extractOrPushError(
|
|
||||||
ItemAmount.create({
|
|
||||||
value: raw.taxes_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`items[${index}].taxes_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalAmount = extractOrPushError(
|
|
||||||
ItemAmount.create({
|
|
||||||
value: raw.total_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`items[${index}].total_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
itemId,
|
|
||||||
languageCode: attributes.languageCode,
|
|
||||||
currencyCode: attributes.currencyCode,
|
|
||||||
description,
|
|
||||||
|
|
||||||
quantity,
|
|
||||||
unitAmount,
|
|
||||||
subtotalAmount,
|
|
||||||
|
|
||||||
itemDiscountPercentage,
|
|
||||||
itemDiscountAmount,
|
|
||||||
globalDiscountPercentage,
|
|
||||||
globalDiscountAmount,
|
|
||||||
totalDiscountAmount,
|
|
||||||
|
|
||||||
taxableAmount,
|
|
||||||
|
|
||||||
ivaCode,
|
|
||||||
ivaPercentage,
|
|
||||||
ivaAmount,
|
|
||||||
|
|
||||||
recCode,
|
|
||||||
recPercentage,
|
|
||||||
recAmount,
|
|
||||||
|
|
||||||
retentionCode,
|
|
||||||
retentionPercentage,
|
|
||||||
retentionAmount,
|
|
||||||
|
|
||||||
taxesAmount,
|
|
||||||
totalAmount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapToDomain(
|
|
||||||
source: CustomerInvoiceItemModel,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<IssuedInvoiceItem, Error> {
|
|
||||||
const { errors, index } = params as {
|
|
||||||
index: number;
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
attributes: Partial<IIssuedInvoiceCreateProps>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1) Valores escalares (atributos generales)
|
|
||||||
const attributes = this.mapAttributesToDomain(source, params);
|
|
||||||
|
|
||||||
// Si hubo errores de mapeo, devolvemos colección de validación
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Customer invoice item mapping failed [mapToDomain]", errors)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Construcción del elemento de dominio
|
|
||||||
const itemId = attributes.itemId!;
|
|
||||||
const newItem = IssuedInvoiceItem.rehydrate(
|
|
||||||
{
|
|
||||||
description: attributes.description!,
|
|
||||||
|
|
||||||
quantity: attributes.quantity!,
|
|
||||||
unitAmount: attributes.unitAmount!,
|
|
||||||
|
|
||||||
subtotalAmount: attributes.subtotalAmount!,
|
|
||||||
|
|
||||||
itemDiscountPercentage: attributes.itemDiscountPercentage!,
|
|
||||||
itemDiscountAmount: attributes.itemDiscountAmount!,
|
|
||||||
|
|
||||||
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
|
||||||
globalDiscountAmount: attributes.globalDiscountAmount!,
|
|
||||||
|
|
||||||
totalDiscountAmount: attributes.totalDiscountAmount!,
|
|
||||||
|
|
||||||
taxableAmount: attributes.taxableAmount!,
|
|
||||||
|
|
||||||
ivaCode: attributes.ivaCode!,
|
|
||||||
ivaPercentage: attributes.ivaPercentage!,
|
|
||||||
ivaAmount: attributes.ivaAmount!,
|
|
||||||
|
|
||||||
recCode: attributes.recCode!,
|
|
||||||
recPercentage: attributes.recPercentage!,
|
|
||||||
recAmount: attributes.recAmount!,
|
|
||||||
|
|
||||||
retentionCode: attributes.retentionCode!,
|
|
||||||
retentionPercentage: attributes.retentionPercentage!,
|
|
||||||
retentionAmount: attributes.retentionAmount!,
|
|
||||||
|
|
||||||
taxesAmount: attributes.taxesAmount!,
|
|
||||||
totalAmount: attributes.totalAmount!,
|
|
||||||
|
|
||||||
languageCode: attributes.languageCode!,
|
|
||||||
currencyCode: attributes.currencyCode!,
|
|
||||||
},
|
|
||||||
itemId
|
|
||||||
);
|
|
||||||
|
|
||||||
return Result.ok(newItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapToPersistence(
|
|
||||||
source: IssuedInvoiceItem,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<CustomerInvoiceItemCreationAttributes, Error> {
|
|
||||||
const { errors, index, parent } = params as {
|
|
||||||
index: number;
|
|
||||||
parent: IssuedInvoice;
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
};
|
|
||||||
|
|
||||||
return Result.ok({
|
|
||||||
item_id: source.id.toPrimitive(),
|
|
||||||
invoice_id: parent.id.toPrimitive(),
|
|
||||||
position: index,
|
|
||||||
|
|
||||||
description: maybeToNullable(source.description, (v) => v.toPrimitive()),
|
|
||||||
|
|
||||||
quantity_value: maybeToNullable(source.quantity, (v) => v.toPrimitive().value),
|
|
||||||
quantity_scale:
|
|
||||||
maybeToNullable(source.quantity, (v) => v.toPrimitive().scale) ??
|
|
||||||
ItemQuantity.DEFAULT_SCALE,
|
|
||||||
|
|
||||||
unit_amount_value: maybeToNullable(source.unitAmount, (v) => v.toPrimitive().value),
|
|
||||||
unit_amount_scale:
|
|
||||||
maybeToNullable(source.unitAmount, (v) => v.toPrimitive().scale) ??
|
|
||||||
ItemAmount.DEFAULT_SCALE,
|
|
||||||
|
|
||||||
subtotal_amount_value: source.subtotalAmount.toPrimitive().value,
|
|
||||||
subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale,
|
|
||||||
|
|
||||||
item_discount_percentage_value: maybeToNullable(
|
|
||||||
source.itemDiscountPercentage,
|
|
||||||
(v) => v.toPrimitive().value
|
|
||||||
),
|
|
||||||
item_discount_percentage_scale:
|
|
||||||
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
|
||||||
DiscountPercentage.DEFAULT_SCALE,
|
|
||||||
|
|
||||||
item_discount_amount_value: source.itemDiscountAmount.toPrimitive().value,
|
|
||||||
item_discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale,
|
|
||||||
|
|
||||||
global_discount_percentage_value: source.globalDiscountPercentage.toPrimitive().value,
|
|
||||||
global_discount_percentage_scale:
|
|
||||||
source.globalDiscountPercentage.toPrimitive().scale ?? DiscountPercentage.DEFAULT_SCALE,
|
|
||||||
|
|
||||||
global_discount_amount_value: source.globalDiscountAmount.value,
|
|
||||||
global_discount_amount_scale: source.globalDiscountAmount.scale,
|
|
||||||
|
|
||||||
total_discount_amount_value: source.totalDiscountAmount.value,
|
|
||||||
total_discount_amount_scale: source.totalDiscountAmount.scale,
|
|
||||||
|
|
||||||
taxable_amount_value: source.taxableAmount.value,
|
|
||||||
taxable_amount_scale: source.taxableAmount.scale,
|
|
||||||
|
|
||||||
// IVA
|
|
||||||
iva_code: maybeToNullableString(source.ivaCode),
|
|
||||||
|
|
||||||
iva_percentage_value: maybeToNullable(source.ivaPercentage, (v) => v.toPrimitive().value),
|
|
||||||
iva_percentage_scale:
|
|
||||||
maybeToNullable(source.ivaPercentage, (v) => v.toPrimitive().scale) ?? 2,
|
|
||||||
|
|
||||||
iva_amount_value: source.ivaAmount.value,
|
|
||||||
iva_amount_scale: source.ivaAmount.scale,
|
|
||||||
|
|
||||||
// REC
|
|
||||||
rec_code: maybeToNullableString(source.recCode),
|
|
||||||
|
|
||||||
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
|
||||||
rec_percentage_scale:
|
|
||||||
maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ?? 2,
|
|
||||||
|
|
||||||
rec_amount_value: source.recAmount.value,
|
|
||||||
rec_amount_scale: source.recAmount.scale,
|
|
||||||
|
|
||||||
// RET
|
|
||||||
retention_code: maybeToNullableString(source.retentionCode),
|
|
||||||
|
|
||||||
retention_percentage_value: maybeToNullable(
|
|
||||||
source.retentionPercentage,
|
|
||||||
(v) => v.toPrimitive().value
|
|
||||||
),
|
|
||||||
retention_percentage_scale:
|
|
||||||
maybeToNullable(source.retentionPercentage, (v) => v.toPrimitive().scale) ?? 2,
|
|
||||||
|
|
||||||
retention_amount_value: source.retentionAmount.value,
|
|
||||||
retention_amount_scale: source.retentionAmount.scale,
|
|
||||||
|
|
||||||
//
|
|
||||||
taxes_amount_value: source.taxesAmount.value,
|
|
||||||
taxes_amount_scale: source.taxesAmount.scale,
|
|
||||||
|
|
||||||
//
|
|
||||||
total_amount_value: source.totalAmount.value,
|
|
||||||
total_amount_scale: source.totalAmount.scale,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,156 +0,0 @@
|
|||||||
import type { MapperParamsType } from "@erp/core/api";
|
|
||||||
import {
|
|
||||||
City,
|
|
||||||
Country,
|
|
||||||
Name,
|
|
||||||
PostalCode,
|
|
||||||
Province,
|
|
||||||
Street,
|
|
||||||
TINNumber,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
maybeToNullable,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import {
|
|
||||||
type IIssuedInvoiceCreateProps,
|
|
||||||
InvoiceRecipient,
|
|
||||||
type IssuedInvoice,
|
|
||||||
} from "../../../../../../domain";
|
|
||||||
import type { CustomerInvoiceModel } from "../../../../../common";
|
|
||||||
|
|
||||||
export class SequelizeIssuedInvoiceRecipientDomainMapper {
|
|
||||||
public mapToDomain(
|
|
||||||
source: CustomerInvoiceModel,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<Maybe<InvoiceRecipient>, Error> {
|
|
||||||
/**
|
|
||||||
* - Issued invoice -> snapshot de los datos (campos customer_*)
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { errors, attributes } = params as {
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
attributes: Partial<IIssuedInvoiceCreateProps>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const _name = source.customer_name!;
|
|
||||||
const _tin = source.customer_tin!;
|
|
||||||
const _street = source.customer_street!;
|
|
||||||
const _street2 = source.customer_street2!;
|
|
||||||
const _city = source.customer_city!;
|
|
||||||
const _postal_code = source.customer_postal_code!;
|
|
||||||
const _province = source.customer_province!;
|
|
||||||
const _country = source.customer_country!;
|
|
||||||
|
|
||||||
// Customer (snapshot)
|
|
||||||
const customerName = extractOrPushError(Name.create(_name!), "customer_name", errors);
|
|
||||||
|
|
||||||
const customerTin = extractOrPushError(TINNumber.create(_tin!), "customer_tin", errors);
|
|
||||||
|
|
||||||
const customerStreet = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_street, (value) => Street.create(value)),
|
|
||||||
"customer_street",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const customerStreet2 = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_street2, (value) => Street.create(value)),
|
|
||||||
"customer_street2",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const customerCity = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_city, (value) => City.create(value)),
|
|
||||||
"customer_city",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const customerProvince = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_province, (value) => Province.create(value)),
|
|
||||||
"customer_province",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const customerPostalCode = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_postal_code, (value) => PostalCode.create(value)),
|
|
||||||
"customer_postal_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const customerCountry = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_country, (value) => Country.create(value)),
|
|
||||||
"customer_country",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const createResult = InvoiceRecipient.create({
|
|
||||||
name: customerName!,
|
|
||||||
tin: customerTin!,
|
|
||||||
street: customerStreet!,
|
|
||||||
street2: customerStreet2!,
|
|
||||||
city: customerCity!,
|
|
||||||
postalCode: customerPostalCode!,
|
|
||||||
province: customerProvince!,
|
|
||||||
country: customerCountry!,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (createResult.isFailure) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Invoice recipient entity creation failed", [
|
|
||||||
{ path: "recipient", message: createResult.error.message },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(Maybe.some(createResult.data));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapea los datos del destinatario (recipient) de una factura de cliente
|
|
||||||
* al formato esperado por la capa de persistencia.
|
|
||||||
*
|
|
||||||
* Reglas:
|
|
||||||
* - Si la factura es proforma (`isProforma === true`), todos los campos de recipient son `null`.
|
|
||||||
* - Si la factura no es proforma (`isProforma === false`), debe existir `recipient`.
|
|
||||||
* En caso contrario, se agrega un error de validación.
|
|
||||||
*/
|
|
||||||
mapToPersistence(source: Maybe<InvoiceRecipient>, params?: MapperParamsType) {
|
|
||||||
const { errors, parent } = params as {
|
|
||||||
parent: IssuedInvoice;
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const { hasRecipient } = parent;
|
|
||||||
|
|
||||||
// Validación: facturas emitidas deben tener destinatario.
|
|
||||||
if (!hasRecipient) {
|
|
||||||
errors.push({
|
|
||||||
path: "recipient",
|
|
||||||
message: "[InvoiceRecipientDomainMapper] Issued customer invoice without recipient data",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si hay errores previos, devolvemos fallo de validación inmediatamente.
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Customer invoice mapping to persistence failed", errors)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const recipient = source.unwrap();
|
|
||||||
|
|
||||||
return {
|
|
||||||
customer_tin: recipient.tin.toPrimitive(),
|
|
||||||
customer_name: recipient.name.toPrimitive(),
|
|
||||||
customer_street: maybeToNullable(recipient.street, (v) => v.toPrimitive()),
|
|
||||||
customer_street2: maybeToNullable(recipient.street2, (v) => v.toPrimitive()),
|
|
||||||
customer_city: maybeToNullable(recipient.city, (v) => v.toPrimitive()),
|
|
||||||
customer_province: maybeToNullable(recipient.province, (v) => v.toPrimitive()),
|
|
||||||
customer_postal_code: maybeToNullable(recipient.postalCode, (v) => v.toPrimitive()),
|
|
||||||
customer_country: maybeToNullable(recipient.country, (v) => v.toPrimitive()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,249 +0,0 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
|
||||||
import {
|
|
||||||
DiscountPercentage,
|
|
||||||
type MapperParamsType,
|
|
||||||
SequelizeDomainMapper,
|
|
||||||
TaxPercentage,
|
|
||||||
} from "@erp/core/api";
|
|
||||||
import {
|
|
||||||
Percentage,
|
|
||||||
UniqueID,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableOrEmptyString,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
maybeToNullable,
|
|
||||||
maybeToNullableString,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import {
|
|
||||||
type IIssuedInvoiceCreateProps,
|
|
||||||
InvoiceAmount,
|
|
||||||
type IssuedInvoice,
|
|
||||||
IssuedInvoiceTax,
|
|
||||||
ItemAmount,
|
|
||||||
} from "../../../../../../domain";
|
|
||||||
import type {
|
|
||||||
CustomerInvoiceTaxCreationAttributes,
|
|
||||||
CustomerInvoiceTaxModel,
|
|
||||||
} from "../../../../../common";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapper para customer_invoice_taxes
|
|
||||||
*
|
|
||||||
* Domina estructuras:
|
|
||||||
* {
|
|
||||||
* tax: Tax
|
|
||||||
* taxableAmount: ItemAmount
|
|
||||||
* taxesAmount: ItemAmount
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* Cada fila = un impuesto agregado en toda la factura.
|
|
||||||
*/
|
|
||||||
export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapper<
|
|
||||||
CustomerInvoiceTaxModel,
|
|
||||||
CustomerInvoiceTaxCreationAttributes,
|
|
||||||
IssuedInvoiceTax
|
|
||||||
> {
|
|
||||||
private taxCatalog!: JsonTaxCatalogProvider;
|
|
||||||
|
|
||||||
constructor(params: MapperParamsType) {
|
|
||||||
super();
|
|
||||||
const { taxCatalog } = params as {
|
|
||||||
taxCatalog: JsonTaxCatalogProvider;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!taxCatalog) {
|
|
||||||
throw new Error('taxCatalog not defined ("SequelizeIssuedInvoiceTaxesDomainMapper")');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.taxCatalog = taxCatalog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapToDomain(
|
|
||||||
raw: CustomerInvoiceTaxModel,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<IssuedInvoiceTax, Error> {
|
|
||||||
const { errors, index, attributes } = params as {
|
|
||||||
index: number;
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
attributes: Partial<IIssuedInvoiceCreateProps>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const taxableAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.taxable_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`taxes[${index}].taxable_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const ivaCode = raw.iva_code;
|
|
||||||
|
|
||||||
// Una issued invoice debe traer IVA
|
|
||||||
const ivaPercentage = extractOrPushError(
|
|
||||||
TaxPercentage.create({
|
|
||||||
value: Number(raw.iva_percentage_value),
|
|
||||||
}),
|
|
||||||
`taxes[${index}].iva_percentage_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const ivaAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.iva_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`taxes[${index}].iva_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const recCode = maybeFromNullableOrEmptyString(raw.rec_code);
|
|
||||||
|
|
||||||
const recPercentage = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.rec_percentage_value, (value) => TaxPercentage.create({ value })),
|
|
||||||
`taxes[${index}].rec_percentage_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const recAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.rec_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`taxes[${index}].rec_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code);
|
|
||||||
|
|
||||||
const retentionPercentage = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.retention_percentage_value, (value) =>
|
|
||||||
TaxPercentage.create({ value })
|
|
||||||
),
|
|
||||||
`taxes[${index}].retention_percentage_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const retentionAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.retention_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`taxes[${index}].retention_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxesAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.taxes_amount_value,
|
|
||||||
currency_code: attributes.currencyCode?.code,
|
|
||||||
}),
|
|
||||||
`taxes[${index}].taxes_amount_value`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
// Si hubo errores de mapeo, devolvemos colección de validación
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Customer invoice tax mapping failed [mapToDomain]", errors)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Construcción del elemento de dominio
|
|
||||||
const createResult = IssuedInvoiceTax.create({
|
|
||||||
taxableAmount: taxableAmount!,
|
|
||||||
|
|
||||||
ivaCode: ivaCode!,
|
|
||||||
ivaPercentage: ivaPercentage!,
|
|
||||||
ivaAmount: ivaAmount!,
|
|
||||||
|
|
||||||
recCode: recCode,
|
|
||||||
recPercentage: recPercentage!,
|
|
||||||
recAmount: recAmount!,
|
|
||||||
|
|
||||||
retentionCode: retentionCode,
|
|
||||||
retentionPercentage: retentionPercentage!,
|
|
||||||
retentionAmount: retentionAmount!,
|
|
||||||
|
|
||||||
taxesAmount: taxesAmount!,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (createResult.isFailure) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Invoice tax group entity creation failed", [
|
|
||||||
{ path: `taxes[${index}]`, message: "Invoice tax group entity creation failed" },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return createResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapToPersistence(
|
|
||||||
source: IssuedInvoiceTax,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<CustomerInvoiceTaxCreationAttributes, Error> {
|
|
||||||
const { errors, parent } = params as {
|
|
||||||
parent: IssuedInvoice;
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const dto: CustomerInvoiceTaxCreationAttributes = {
|
|
||||||
tax_id: UniqueID.generateNewID().toPrimitive(),
|
|
||||||
invoice_id: parent.id.toPrimitive(),
|
|
||||||
|
|
||||||
// TAXABLE AMOUNT
|
|
||||||
taxable_amount_value: source.taxableAmount.value,
|
|
||||||
taxable_amount_scale: source.taxableAmount.scale,
|
|
||||||
|
|
||||||
// IVA
|
|
||||||
iva_code: source.ivaCode,
|
|
||||||
|
|
||||||
iva_percentage_value: source.ivaPercentage.value,
|
|
||||||
iva_percentage_scale: source.ivaPercentage.scale,
|
|
||||||
|
|
||||||
iva_amount_value: source.ivaAmount.value,
|
|
||||||
iva_amount_scale: source.ivaAmount.scale,
|
|
||||||
|
|
||||||
// REC
|
|
||||||
rec_code: maybeToNullableString(source.recCode),
|
|
||||||
|
|
||||||
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
|
||||||
rec_percentage_scale:
|
|
||||||
maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ??
|
|
||||||
DiscountPercentage.DEFAULT_SCALE,
|
|
||||||
|
|
||||||
rec_amount_value: source.recAmount.toPrimitive().value,
|
|
||||||
rec_amount_scale: source.recAmount.toPrimitive().scale ?? ItemAmount.DEFAULT_SCALE,
|
|
||||||
|
|
||||||
// RET
|
|
||||||
retention_code: maybeToNullableString(source.retentionCode),
|
|
||||||
|
|
||||||
retention_percentage_value: maybeToNullable(
|
|
||||||
source.retentionPercentage,
|
|
||||||
(v) => v.toPrimitive().value
|
|
||||||
),
|
|
||||||
retention_percentage_scale:
|
|
||||||
maybeToNullable(source.retentionPercentage, (v) => v.toPrimitive().scale) ??
|
|
||||||
Percentage.DEFAULT_SCALE,
|
|
||||||
|
|
||||||
retention_amount_value: source.retentionAmount.toPrimitive().value,
|
|
||||||
retention_amount_scale:
|
|
||||||
source.retentionAmount.toPrimitive().scale ?? ItemAmount.DEFAULT_SCALE,
|
|
||||||
|
|
||||||
// TOTAL
|
|
||||||
taxes_amount_value: source.taxesAmount.value,
|
|
||||||
taxes_amount_scale: source.taxesAmount.scale,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Result.ok(dto);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
return Result.fail(error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
import type { MapperParamsType } from "@erp/core/api";
|
|
||||||
import { SequelizeDomainMapper } from "@erp/core/api";
|
|
||||||
import {
|
|
||||||
URLAddress,
|
|
||||||
UniqueID,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
maybeToEmptyString,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import {
|
|
||||||
type IIssuedInvoiceCreateProps,
|
|
||||||
type IssuedInvoice,
|
|
||||||
VerifactuRecord,
|
|
||||||
VerifactuRecordEstado,
|
|
||||||
} from "../../../../../../domain";
|
|
||||||
import type {
|
|
||||||
VerifactuRecordCreationAttributes,
|
|
||||||
VerifactuRecordModel,
|
|
||||||
} from "../../../../../common";
|
|
||||||
|
|
||||||
export class SequelizeIssuedInvoiceVerifactuDomainMapper extends SequelizeDomainMapper<
|
|
||||||
VerifactuRecordModel,
|
|
||||||
VerifactuRecordCreationAttributes,
|
|
||||||
Maybe<VerifactuRecord>
|
|
||||||
> {
|
|
||||||
public mapToDomain(
|
|
||||||
source: VerifactuRecordModel,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<Maybe<VerifactuRecord>, Error> {
|
|
||||||
const { errors, attributes } = params as {
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
attributes: Partial<IIssuedInvoiceCreateProps>;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!source) {
|
|
||||||
return Result.ok(Maybe.none());
|
|
||||||
}
|
|
||||||
|
|
||||||
const recordId = extractOrPushError(UniqueID.create(source.id), "id", errors);
|
|
||||||
const estado = extractOrPushError(
|
|
||||||
VerifactuRecordEstado.create(source.estado),
|
|
||||||
"estado",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const qr = extractOrPushError(
|
|
||||||
maybeFromNullableResult(source.qr, (value) => Result.ok(String(value))),
|
|
||||||
"qr",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const url = extractOrPushError(
|
|
||||||
maybeFromNullableResult(source.url, (value) => URLAddress.create(value)),
|
|
||||||
"url",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const uuid = extractOrPushError(
|
|
||||||
maybeFromNullableResult(source.uuid, (value) => Result.ok(String(value))),
|
|
||||||
"uuid",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const operacion = extractOrPushError(
|
|
||||||
maybeFromNullableResult(source.operacion, (value) => Result.ok(String(value))),
|
|
||||||
"operacion",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Verifactu record mapping failed [mapToDTO]", errors)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const createResult = VerifactuRecord.create(
|
|
||||||
{
|
|
||||||
estado: estado!,
|
|
||||||
qrCode: qr!,
|
|
||||||
url: url!,
|
|
||||||
uuid: uuid!,
|
|
||||||
operacion: operacion!,
|
|
||||||
},
|
|
||||||
recordId!
|
|
||||||
);
|
|
||||||
|
|
||||||
if (createResult.isFailure) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Invoice verifactu entity creation failed", [
|
|
||||||
{ path: "verifactu", message: createResult.error.message },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(Maybe.some(createResult.data));
|
|
||||||
}
|
|
||||||
|
|
||||||
mapToPersistence(
|
|
||||||
source: Maybe<VerifactuRecord>,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<VerifactuRecordCreationAttributes, Error> {
|
|
||||||
const { errors, parent } = params as {
|
|
||||||
parent: IssuedInvoice;
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (source.isNone()) {
|
|
||||||
return Result.ok({
|
|
||||||
id: UniqueID.generateNewID().toPrimitive(),
|
|
||||||
invoice_id: parent.id.toPrimitive(),
|
|
||||||
estado: VerifactuRecordEstado.createPendiente().toPrimitive(),
|
|
||||||
qr: "",
|
|
||||||
url: "",
|
|
||||||
uuid: "",
|
|
||||||
operacion: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const verifactu = source.unwrap();
|
|
||||||
|
|
||||||
return Result.ok({
|
|
||||||
id: verifactu.id.toPrimitive(),
|
|
||||||
invoice_id: parent.id.toPrimitive(),
|
|
||||||
estado: verifactu.estado.toPrimitive(),
|
|
||||||
qr: maybeToEmptyString(verifactu.qrCode, (v) => v),
|
|
||||||
url: maybeToEmptyString(verifactu.url, (v) => v.toPrimitive()),
|
|
||||||
uuid: maybeToEmptyString(verifactu.uuid, (v) => v),
|
|
||||||
operacion: maybeToEmptyString(verifactu.operacion, (v) => v),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./domain";
|
|
||||||
export * from "./summary";
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./sequelize-issued-invoice-summary.mapper";
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
import { type MapperParamsType, SequelizeQueryMapper } from "@erp/core/api";
|
|
||||||
import {
|
|
||||||
City,
|
|
||||||
Country,
|
|
||||||
Name,
|
|
||||||
PostalCode,
|
|
||||||
Province,
|
|
||||||
Street,
|
|
||||||
TINNumber,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { IssuedInvoiceSummary } from "../../../../../../application";
|
|
||||||
import { InvoiceRecipient } from "../../../../../../domain";
|
|
||||||
import type { CustomerInvoiceModel } from "../../../../../common";
|
|
||||||
|
|
||||||
export class SequelizeIssuedInvoiceRecipientListMapper extends SequelizeQueryMapper<
|
|
||||||
CustomerInvoiceModel,
|
|
||||||
InvoiceRecipient
|
|
||||||
> {
|
|
||||||
public mapToReadModel(
|
|
||||||
raw: CustomerInvoiceModel,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<InvoiceRecipient, Error> {
|
|
||||||
/**
|
|
||||||
* - Issued invoice => snapshot de los datos (campos customer_*)
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { errors, attributes } = params as {
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
attributes: Partial<IssuedInvoiceSummary>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const { isProforma } = attributes;
|
|
||||||
|
|
||||||
if (isProforma && !raw.current_customer) {
|
|
||||||
errors.push({
|
|
||||||
path: "current_customer",
|
|
||||||
message: "Current customer not included in query (InvoiceRecipientListMapper)",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const _name = raw.customer_name!;
|
|
||||||
const _tin = raw.customer_tin!;
|
|
||||||
const _street = raw.customer_street!;
|
|
||||||
const _street2 = raw.customer_street2!;
|
|
||||||
const _city = raw.customer_city!;
|
|
||||||
const _postal_code = raw.customer_postal_code!;
|
|
||||||
const _province = raw.customer_province!;
|
|
||||||
const _country = raw.customer_country!;
|
|
||||||
|
|
||||||
// Customer (snapshot)
|
|
||||||
const customerName = extractOrPushError(Name.create(_name!), "customer_name", errors);
|
|
||||||
|
|
||||||
const customerTin = extractOrPushError(TINNumber.create(_tin!), "customer_tin", errors);
|
|
||||||
|
|
||||||
const customerStreet = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_street, (value) => Street.create(value)),
|
|
||||||
"customer_street",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const customerStreet2 = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_street2, (value) => Street.create(value)),
|
|
||||||
"customer_street2",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const customerCity = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_city, (value) => City.create(value)),
|
|
||||||
"customer_city",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const customerProvince = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_province, (value) => Province.create(value)),
|
|
||||||
"customer_province",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const customerPostalCode = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_postal_code, (value) => PostalCode.create(value)),
|
|
||||||
"customer_postal_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const customerCountry = extractOrPushError(
|
|
||||||
maybeFromNullableResult(_country, (value) => Country.create(value)),
|
|
||||||
"customer_country",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const createResult = InvoiceRecipient.create({
|
|
||||||
name: customerName!,
|
|
||||||
tin: customerTin!,
|
|
||||||
street: customerStreet!,
|
|
||||||
street2: customerStreet2!,
|
|
||||||
city: customerCity!,
|
|
||||||
postalCode: customerPostalCode!,
|
|
||||||
province: customerProvince!,
|
|
||||||
country: customerCountry!,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (createResult.isFailure) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Invoice recipient entity creation failed", [
|
|
||||||
{ path: "recipient", message: createResult.error.message },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(createResult.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,246 +0,0 @@
|
|||||||
import { type MapperParamsType, SequelizeQueryMapper } from "@erp/core/api";
|
|
||||||
import {
|
|
||||||
CurrencyCode,
|
|
||||||
LanguageCode,
|
|
||||||
UniqueID,
|
|
||||||
UtcDate,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { IssuedInvoiceSummary } from "../../../../../../application";
|
|
||||||
import {
|
|
||||||
InvoiceAmount,
|
|
||||||
InvoiceNumber,
|
|
||||||
InvoiceSerie,
|
|
||||||
InvoiceStatus,
|
|
||||||
type VerifactuRecord,
|
|
||||||
} from "../../../../../../domain";
|
|
||||||
import type { CustomerInvoiceModel } from "../../../../../common";
|
|
||||||
|
|
||||||
import { SequelizeIssuedInvoiceRecipientListMapper } from "./sequelize-issued-invoice-recipient-summary.mapper";
|
|
||||||
import { SequelizeVerifactuRecordSummaryMapper } from "./sequelize-verifactu-record-summary.mapper";
|
|
||||||
|
|
||||||
export class SequelizeIssuedInvoiceSummaryMapper extends SequelizeQueryMapper<
|
|
||||||
CustomerInvoiceModel,
|
|
||||||
IssuedInvoiceSummary
|
|
||||||
> {
|
|
||||||
private _recipientMapper: SequelizeIssuedInvoiceRecipientListMapper;
|
|
||||||
private _verifactuMapper: SequelizeVerifactuRecordSummaryMapper;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this._recipientMapper = new SequelizeIssuedInvoiceRecipientListMapper();
|
|
||||||
this._verifactuMapper = new SequelizeVerifactuRecordSummaryMapper();
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapToReadModel(
|
|
||||||
raw: CustomerInvoiceModel,
|
|
||||||
params?: MapperParamsType
|
|
||||||
): Result<IssuedInvoiceSummary, Error> {
|
|
||||||
const errors: ValidationErrorDetail[] = [];
|
|
||||||
|
|
||||||
// 1) Valores escalares (atributos generales)
|
|
||||||
const attributes = this._mapAttributesToReadModel(raw, { errors, ...params });
|
|
||||||
|
|
||||||
// 2) Recipient (snapshot en la factura o include)
|
|
||||||
const recipientResult = this._recipientMapper.mapToReadModel(raw, {
|
|
||||||
errors,
|
|
||||||
attributes,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (recipientResult.isFailure) {
|
|
||||||
errors.push({
|
|
||||||
path: "recipient",
|
|
||||||
message: recipientResult.error.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4) Verifactu record
|
|
||||||
let verifactu: Maybe<VerifactuRecord> = Maybe.none();
|
|
||||||
if (raw.verifactu) {
|
|
||||||
const verifactuResult = this._verifactuMapper.mapToReadModel(raw.verifactu, {
|
|
||||||
errors,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (verifactuResult.isFailure) {
|
|
||||||
errors.push({
|
|
||||||
path: "verifactu",
|
|
||||||
message: verifactuResult.error.message,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
verifactu = Maybe.some(verifactuResult.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5) Si hubo errores de mapeo, devolvemos colección de validación
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Customer invoice mapping failed [mapToDTO]", errors)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok({
|
|
||||||
id: attributes.invoiceId!,
|
|
||||||
companyId: attributes.companyId!,
|
|
||||||
isProforma: attributes.isProforma,
|
|
||||||
status: attributes.status!,
|
|
||||||
series: attributes.series!,
|
|
||||||
invoiceNumber: attributes.invoiceNumber!,
|
|
||||||
invoiceDate: attributes.invoiceDate!,
|
|
||||||
operationDate: attributes.operationDate!,
|
|
||||||
|
|
||||||
description: attributes.description!,
|
|
||||||
reference: attributes.reference!,
|
|
||||||
|
|
||||||
customerId: attributes.customerId!,
|
|
||||||
recipient: recipientResult.data,
|
|
||||||
|
|
||||||
languageCode: attributes.languageCode!,
|
|
||||||
currencyCode: attributes.currencyCode!,
|
|
||||||
|
|
||||||
subtotalAmount: attributes.subtotalAmount!,
|
|
||||||
totalDiscountAmount: attributes.totalDiscountAmount!,
|
|
||||||
taxableAmount: attributes.taxableAmount!,
|
|
||||||
taxesAmount: attributes.taxesAmount!,
|
|
||||||
totalAmount: attributes.totalAmount!,
|
|
||||||
|
|
||||||
verifactu,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _mapAttributesToReadModel(raw: CustomerInvoiceModel, params?: MapperParamsType) {
|
|
||||||
const { errors } = params as {
|
|
||||||
errors: ValidationErrorDetail[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const invoiceId = extractOrPushError(UniqueID.create(raw.id), "id", errors);
|
|
||||||
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
|
||||||
|
|
||||||
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
|
||||||
|
|
||||||
const isProforma = Boolean(raw.is_proforma);
|
|
||||||
|
|
||||||
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
|
|
||||||
|
|
||||||
const series = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.series, (value) => InvoiceSerie.create(value)),
|
|
||||||
"serie",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const invoiceNumber = extractOrPushError(
|
|
||||||
InvoiceNumber.create(raw.invoice_number),
|
|
||||||
"invoice_number",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const invoiceDate = extractOrPushError(
|
|
||||||
UtcDate.createFromISO(raw.invoice_date),
|
|
||||||
"invoice_date",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const operationDate = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.operation_date, (value) => UtcDate.createFromISO(value)),
|
|
||||||
"operation_date",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const reference = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.reference, (value) => Result.ok(String(value))),
|
|
||||||
"description",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const description = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.description, (value) => Result.ok(String(value))),
|
|
||||||
"description",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const languageCode = extractOrPushError(
|
|
||||||
LanguageCode.create(raw.language_code),
|
|
||||||
"language_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const currencyCode = extractOrPushError(
|
|
||||||
CurrencyCode.create(raw.currency_code),
|
|
||||||
"currency_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const subtotalAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.subtotal_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"subtotal_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalDiscountAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.total_discount_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"total_discount_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxableAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.taxable_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"taxable_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxesAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.taxes_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"taxes_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalAmount = extractOrPushError(
|
|
||||||
InvoiceAmount.create({
|
|
||||||
value: raw.total_amount_value,
|
|
||||||
currency_code: currencyCode?.code,
|
|
||||||
}),
|
|
||||||
"total_amount_value",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
invoiceId,
|
|
||||||
companyId,
|
|
||||||
customerId,
|
|
||||||
isProforma,
|
|
||||||
status,
|
|
||||||
series,
|
|
||||||
invoiceNumber,
|
|
||||||
invoiceDate,
|
|
||||||
operationDate,
|
|
||||||
reference,
|
|
||||||
description,
|
|
||||||
languageCode,
|
|
||||||
currencyCode,
|
|
||||||
|
|
||||||
subtotalAmount,
|
|
||||||
totalDiscountAmount,
|
|
||||||
taxableAmount,
|
|
||||||
taxesAmount,
|
|
||||||
totalAmount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./supplier-invoice.model";
|
|
||||||
export * from "./supplier-invoice-tax.model";
|
|
||||||
@ -1,241 +0,0 @@
|
|||||||
import {
|
|
||||||
type CreationOptional,
|
|
||||||
DataTypes,
|
|
||||||
type InferAttributes,
|
|
||||||
type InferCreationAttributes,
|
|
||||||
Model,
|
|
||||||
type NonAttribute,
|
|
||||||
type Sequelize,
|
|
||||||
} from "sequelize";
|
|
||||||
|
|
||||||
import type { SupplierInvoiceModel } from "./supplier-invoice.model";
|
|
||||||
|
|
||||||
export type SupplierInvoiceTaxCreationAttributes = InferCreationAttributes<
|
|
||||||
SupplierInvoiceTaxModel,
|
|
||||||
{ omit: "invoice" }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class SupplierInvoiceTaxModel extends Model<
|
|
||||||
InferAttributes<SupplierInvoiceTaxModel>,
|
|
||||||
InferCreationAttributes<SupplierInvoiceTaxModel, { omit: "invoice" }>
|
|
||||||
> {
|
|
||||||
declare tax_id: string;
|
|
||||||
declare invoice_id: string;
|
|
||||||
|
|
||||||
// Taxable amount (base imponible)
|
|
||||||
declare taxable_amount_value: number;
|
|
||||||
declare taxable_amount_scale: number;
|
|
||||||
|
|
||||||
declare iva_code: string;
|
|
||||||
declare iva_percentage_value: number;
|
|
||||||
declare iva_percentage_scale: number;
|
|
||||||
declare iva_amount_value: number;
|
|
||||||
declare iva_amount_scale: number;
|
|
||||||
|
|
||||||
declare rec_code: CreationOptional<string | null>;
|
|
||||||
declare rec_percentage_value: CreationOptional<number | null>;
|
|
||||||
declare rec_percentage_scale: number;
|
|
||||||
declare rec_amount_value: number;
|
|
||||||
declare rec_amount_scale: number;
|
|
||||||
|
|
||||||
declare retention_code: CreationOptional<string | null>;
|
|
||||||
declare retention_percentage_value: CreationOptional<number | null>;
|
|
||||||
declare retention_percentage_scale: number;
|
|
||||||
declare retention_amount_value: number;
|
|
||||||
declare retention_amount_scale: number;
|
|
||||||
|
|
||||||
// Total taxes amount / taxes total
|
|
||||||
declare taxes_amount_value: number;
|
|
||||||
declare taxes_amount_scale: number;
|
|
||||||
|
|
||||||
declare invoice: NonAttribute<SupplierInvoiceModel>;
|
|
||||||
|
|
||||||
static associate(database: Sequelize) {
|
|
||||||
const models = database.models;
|
|
||||||
const requiredModels = ["SupplierInvoiceModel", "SupplierInvoiceTaxModel"];
|
|
||||||
|
|
||||||
for (const name of requiredModels) {
|
|
||||||
if (!models[name]) {
|
|
||||||
throw new Error(`[SupplierInvoiceTaxModel.associate] Missing model: ${name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { SupplierInvoiceModel, SupplierInvoiceTaxModel } = models;
|
|
||||||
|
|
||||||
SupplierInvoiceTaxModel.belongsTo(SupplierInvoiceModel, {
|
|
||||||
as: "invoice",
|
|
||||||
foreignKey: "invoice_id",
|
|
||||||
targetKey: "id",
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
onUpdate: "CASCADE",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static hooks(_database: Sequelize) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (database: Sequelize) => {
|
|
||||||
SupplierInvoiceTaxModel.init(
|
|
||||||
{
|
|
||||||
tax_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
primaryKey: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
invoice_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
taxable_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
taxable_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
iva_code: {
|
|
||||||
type: DataTypes.STRING(40),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
iva_percentage_value: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
iva_percentage_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 2,
|
|
||||||
},
|
|
||||||
|
|
||||||
iva_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
iva_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
rec_code: {
|
|
||||||
type: DataTypes.STRING(40),
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
rec_percentage_value: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
rec_percentage_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 2,
|
|
||||||
},
|
|
||||||
|
|
||||||
rec_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
rec_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
retention_code: {
|
|
||||||
type: DataTypes.STRING(40),
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
retention_percentage_value: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
retention_percentage_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 2,
|
|
||||||
},
|
|
||||||
|
|
||||||
retention_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
retention_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
taxes_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
taxes_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize: database,
|
|
||||||
modelName: "SupplierInvoiceTaxModel",
|
|
||||||
tableName: "supplier_invoice_taxes",
|
|
||||||
|
|
||||||
underscored: true,
|
|
||||||
|
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
name: "idx_supplier_invoice_tax_invoice_id",
|
|
||||||
fields: ["invoice_id"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "uq_supplier_invoice_tax_signature",
|
|
||||||
fields: [
|
|
||||||
"invoice_id",
|
|
||||||
"iva_code",
|
|
||||||
"iva_percentage_value",
|
|
||||||
"iva_percentage_scale",
|
|
||||||
"rec_code",
|
|
||||||
"rec_percentage_value",
|
|
||||||
"rec_percentage_scale",
|
|
||||||
"retention_code",
|
|
||||||
"retention_percentage_value",
|
|
||||||
"retention_percentage_scale",
|
|
||||||
],
|
|
||||||
unique: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
whereMergeStrategy: "and",
|
|
||||||
defaultScope: {},
|
|
||||||
scopes: {},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return SupplierInvoiceTaxModel;
|
|
||||||
};
|
|
||||||
@ -1,333 +0,0 @@
|
|||||||
import {
|
|
||||||
type CreationOptional,
|
|
||||||
DataTypes,
|
|
||||||
type InferAttributes,
|
|
||||||
type InferCreationAttributes,
|
|
||||||
Model,
|
|
||||||
type NonAttribute,
|
|
||||||
type Sequelize,
|
|
||||||
} from "sequelize";
|
|
||||||
|
|
||||||
import type {
|
|
||||||
SupplierInvoiceTaxCreationAttributes,
|
|
||||||
SupplierInvoiceTaxModel,
|
|
||||||
} from "./supplier-invoice-tax.model";
|
|
||||||
|
|
||||||
export type SupplierInvoiceCreationAttributes = InferCreationAttributes<
|
|
||||||
SupplierInvoiceModel,
|
|
||||||
{ omit: "taxes" }
|
|
||||||
> & {
|
|
||||||
taxes?: SupplierInvoiceTaxCreationAttributes[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export class SupplierInvoiceModel extends Model<
|
|
||||||
InferAttributes<SupplierInvoiceModel>,
|
|
||||||
InferCreationAttributes<SupplierInvoiceModel, { omit: "taxes" }>
|
|
||||||
> {
|
|
||||||
declare id: string;
|
|
||||||
declare company_id: string;
|
|
||||||
|
|
||||||
declare supplier_id: string;
|
|
||||||
|
|
||||||
declare status: string;
|
|
||||||
declare source_type: string;
|
|
||||||
|
|
||||||
declare invoice_number: string;
|
|
||||||
declare invoice_date: string;
|
|
||||||
declare due_date: CreationOptional<string | null>;
|
|
||||||
|
|
||||||
declare currency_code: string;
|
|
||||||
|
|
||||||
// Método de pago
|
|
||||||
declare payment_method_id: CreationOptional<string | null>;
|
|
||||||
declare payment_method_description: CreationOptional<string | null>;
|
|
||||||
|
|
||||||
declare description: CreationOptional<string | null>;
|
|
||||||
declare notes: CreationOptional<string | null>;
|
|
||||||
declare supplier_invoice_category_id: CreationOptional<string | null>;
|
|
||||||
|
|
||||||
declare document_id: CreationOptional<string | null>;
|
|
||||||
|
|
||||||
declare taxable_amount_value: number;
|
|
||||||
declare taxable_amount_scale: number;
|
|
||||||
|
|
||||||
declare iva_amount_value: number;
|
|
||||||
declare iva_amount_scale: number;
|
|
||||||
|
|
||||||
declare rec_amount_value: number;
|
|
||||||
declare rec_amount_scale: number;
|
|
||||||
|
|
||||||
declare retention_amount_value: number;
|
|
||||||
declare retention_amount_scale: number;
|
|
||||||
|
|
||||||
declare taxes_amount_value: number;
|
|
||||||
declare taxes_amount_scale: number;
|
|
||||||
|
|
||||||
declare total_amount_value: number;
|
|
||||||
declare total_amount_scale: number;
|
|
||||||
|
|
||||||
declare version: number;
|
|
||||||
|
|
||||||
declare taxes: NonAttribute<SupplierInvoiceTaxModel[]>;
|
|
||||||
|
|
||||||
static associate(database: Sequelize) {
|
|
||||||
const models = database.models;
|
|
||||||
const requiredModels = ["SupplierInvoiceModel", "SupplierInvoiceTaxModel"];
|
|
||||||
|
|
||||||
for (const name of requiredModels) {
|
|
||||||
if (!models[name]) {
|
|
||||||
throw new Error(`[SupplierInvoiceModel.associate] Missing model: ${name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { SupplierInvoiceModel, SupplierInvoiceTaxModel } = models;
|
|
||||||
|
|
||||||
SupplierInvoiceModel.hasMany(SupplierInvoiceTaxModel, {
|
|
||||||
as: "taxes",
|
|
||||||
foreignKey: "invoice_id",
|
|
||||||
sourceKey: "id",
|
|
||||||
constraints: true,
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
onUpdate: "CASCADE",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static hooks(_database: Sequelize) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (database: Sequelize) => {
|
|
||||||
SupplierInvoiceModel.init(
|
|
||||||
{
|
|
||||||
id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
primaryKey: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
company_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
supplier_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
status: {
|
|
||||||
type: DataTypes.STRING(40),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
source_type: {
|
|
||||||
type: DataTypes.STRING(40),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
invoice_number: {
|
|
||||||
type: DataTypes.STRING(80),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
invoice_date: {
|
|
||||||
type: DataTypes.DATEONLY,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
due_date: {
|
|
||||||
type: DataTypes.DATEONLY,
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
currency_code: {
|
|
||||||
type: DataTypes.STRING(3),
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: "EUR",
|
|
||||||
},
|
|
||||||
|
|
||||||
payment_method_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
payment_method_description: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
description: {
|
|
||||||
type: DataTypes.STRING(500),
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
notes: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
supplier_invoice_category_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
document_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
taxable_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
taxable_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
iva_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
iva_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
rec_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
rec_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
retention_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
retention_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
taxes_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
taxes_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
total_amount_value: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
total_amount_scale: {
|
|
||||||
type: DataTypes.SMALLINT,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
version: {
|
|
||||||
type: DataTypes.INTEGER.UNSIGNED,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize: database,
|
|
||||||
modelName: "SupplierInvoiceModel",
|
|
||||||
tableName: "supplier_invoices",
|
|
||||||
|
|
||||||
underscored: true,
|
|
||||||
paranoid: true,
|
|
||||||
timestamps: true,
|
|
||||||
|
|
||||||
createdAt: "created_at",
|
|
||||||
updatedAt: "updated_at",
|
|
||||||
deletedAt: "deleted_at",
|
|
||||||
|
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
name: "uq_supplier_invoice_company_supplier_number",
|
|
||||||
fields: ["company_id", "supplier_id", "invoice_number"],
|
|
||||||
unique: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "idx_supplier_invoice_company_date",
|
|
||||||
fields: ["company_id", "deleted_at", { name: "invoice_date", order: "DESC" }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "idx_supplier_invoice_company_status",
|
|
||||||
fields: ["company_id", "status"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "idx_supplier_invoice_company_supplier",
|
|
||||||
fields: ["company_id", "supplier_id"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "idx_supplier_invoice_due_date",
|
|
||||||
fields: ["due_date"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "idx_supplier_invoice_document_id",
|
|
||||||
fields: ["document_id"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ft_supplier_invoice",
|
|
||||||
type: "FULLTEXT",
|
|
||||||
fields: ["invoice_number", "description", "notes"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
whereMergeStrategy: "and",
|
|
||||||
defaultScope: {},
|
|
||||||
scopes: {},
|
|
||||||
|
|
||||||
hooks: {
|
|
||||||
/**
|
|
||||||
* Incrementa la versión en cada update exitoso.
|
|
||||||
*
|
|
||||||
* Nota:
|
|
||||||
* - Si aplicas OCC en el repositorio con cláusula WHERE version = x,
|
|
||||||
* este hook sigue siendo útil.
|
|
||||||
* - Si prefieres controlar la versión solo desde dominio/repositorio,
|
|
||||||
* elimínalo para evitar dobles incrementos.
|
|
||||||
*/
|
|
||||||
beforeUpdate: (instance) => {
|
|
||||||
const current = instance.get("version") as number;
|
|
||||||
instance.set("version", current + 1);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return SupplierInvoiceModel;
|
|
||||||
};
|
|
||||||
@ -1,369 +0,0 @@
|
|||||||
import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } from "@erp/core/api";
|
|
||||||
import { type Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { type Collection, Result } from "@repo/rdx-utils";
|
|
||||||
import type { FindOptions, InferAttributes, OrderItem, Sequelize, Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { ISupplierInvoiceRepository, SupplierInvoiceSummary } from "../../../../application";
|
|
||||||
import type { SupplierInvoice } from "../../../../domain";
|
|
||||||
import type {
|
|
||||||
SequelizeSupplierInvoiceDomainMapper,
|
|
||||||
SequelizeSupplierInvoiceSummaryMapper,
|
|
||||||
} from "../mappers";
|
|
||||||
import { SupplierInvoiceModel, SupplierInvoiceTaxModel } from "../models";
|
|
||||||
|
|
||||||
export class SupplierInvoiceRepository
|
|
||||||
extends SequelizeRepository<SupplierInvoice>
|
|
||||||
implements ISupplierInvoiceRepository
|
|
||||||
{
|
|
||||||
constructor(
|
|
||||||
private readonly domainMapper: SequelizeSupplierInvoiceDomainMapper,
|
|
||||||
private readonly summaryMapper: SequelizeSupplierInvoiceSummaryMapper,
|
|
||||||
database: Sequelize
|
|
||||||
) {
|
|
||||||
super({ database });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Crea una nueva factura de proveedor junto con su desglose fiscal.
|
|
||||||
*/
|
|
||||||
public async create(
|
|
||||||
invoice: SupplierInvoice,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<void, Error>> {
|
|
||||||
try {
|
|
||||||
const dtoResult = this.domainMapper.mapToPersistence(invoice);
|
|
||||||
|
|
||||||
if (dtoResult.isFailure()) {
|
|
||||||
return Result.fail(dtoResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dto = dtoResult.value;
|
|
||||||
const { id, taxes, ...createPayload } = dto;
|
|
||||||
|
|
||||||
await SupplierInvoiceModel.create(
|
|
||||||
{
|
|
||||||
...createPayload,
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
{ transaction }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Array.isArray(taxes) && taxes.length > 0) {
|
|
||||||
await SupplierInvoiceTaxModel.bulkCreate(taxes, { transaction });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok();
|
|
||||||
} catch (error: unknown) {
|
|
||||||
return Result.fail(translateSequelizeError(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actualiza la cabecera y reemplaza completamente el desglose fiscal.
|
|
||||||
*
|
|
||||||
* Nota:
|
|
||||||
* - Este enfoque es simple y robusto para MVP.
|
|
||||||
* - Si más adelante necesitas actualización parcial de taxes,
|
|
||||||
* se puede optimizar.
|
|
||||||
*/
|
|
||||||
public async update(
|
|
||||||
invoice: SupplierInvoice,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<void, Error>> {
|
|
||||||
try {
|
|
||||||
const dtoResult = this.domainMapper.mapToPersistence(invoice);
|
|
||||||
|
|
||||||
if (dtoResult.isFailure()) {
|
|
||||||
return Result.fail(dtoResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dto = dtoResult.value;
|
|
||||||
const { id, company_id, version, taxes, ...updatePayload } = dto;
|
|
||||||
|
|
||||||
const [updatedRows] = await SupplierInvoiceModel.update(
|
|
||||||
{
|
|
||||||
...updatePayload,
|
|
||||||
version,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
where: {
|
|
||||||
id,
|
|
||||||
company_id,
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updatedRows === 0) {
|
|
||||||
return Result.fail(new EntityNotFoundError("SupplierInvoice", "id", id));
|
|
||||||
}
|
|
||||||
|
|
||||||
await SupplierInvoiceTaxModel.destroy({
|
|
||||||
where: { invoice_id: id },
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Array.isArray(taxes) && taxes.length > 0) {
|
|
||||||
await SupplierInvoiceTaxModel.bulkCreate(taxes, { transaction });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok();
|
|
||||||
} catch (error: unknown) {
|
|
||||||
return Result.fail(translateSequelizeError(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Guarda la factura creando o actualizando según exista previamente.
|
|
||||||
*
|
|
||||||
* Nota:
|
|
||||||
* - Mantengo save() como conveniencia.
|
|
||||||
* - La política sigue siendo explícita: primero existencia, luego create/update.
|
|
||||||
*/
|
|
||||||
public async save(
|
|
||||||
invoice: SupplierInvoice,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<void, Error>> {
|
|
||||||
const existsResult = await this.existsByIdInCompany(invoice.companyId, invoice.id, transaction);
|
|
||||||
|
|
||||||
if (existsResult.isFailure) {
|
|
||||||
return Result.fail(existsResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existsResult.data) {
|
|
||||||
return this.update(invoice, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.create(invoice, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Comprueba si existe una factura por id dentro de una empresa.
|
|
||||||
*/
|
|
||||||
public async existsByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
id: UniqueID,
|
|
||||||
transaction?: Transaction,
|
|
||||||
options: FindOptions<InferAttributes<SupplierInvoiceModel>> = {}
|
|
||||||
): Promise<Result<boolean, Error>> {
|
|
||||||
try {
|
|
||||||
const count = await SupplierInvoiceModel.count({
|
|
||||||
...options,
|
|
||||||
where: {
|
|
||||||
id: id.toString(),
|
|
||||||
company_id: companyId.toString(),
|
|
||||||
...(options.where ?? {}),
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
return Result.ok(count > 0);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
return Result.fail(translateSequelizeError(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Busca una factura por id dentro de una empresa.
|
|
||||||
*/
|
|
||||||
public async findByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
id: UniqueID,
|
|
||||||
transaction?: Transaction,
|
|
||||||
options: FindOptions<InferAttributes<SupplierInvoiceModel>> = {}
|
|
||||||
): Promise<Result<SupplierInvoice, Error>> {
|
|
||||||
try {
|
|
||||||
const normalizedOrder = Array.isArray(options.order)
|
|
||||||
? options.order
|
|
||||||
: options.order
|
|
||||||
? [options.order]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const normalizedInclude = Array.isArray(options.include)
|
|
||||||
? options.include
|
|
||||||
: options.include
|
|
||||||
? [options.include]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const mergedOptions: FindOptions<InferAttributes<SupplierInvoiceModel>> = {
|
|
||||||
...options,
|
|
||||||
where: {
|
|
||||||
...(options.where ?? {}),
|
|
||||||
id: id.toString(),
|
|
||||||
company_id: companyId.toString(),
|
|
||||||
},
|
|
||||||
order: [...normalizedOrder],
|
|
||||||
include: [
|
|
||||||
...normalizedInclude,
|
|
||||||
{
|
|
||||||
model: SupplierInvoiceTaxModel,
|
|
||||||
as: "taxes",
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
transaction,
|
|
||||||
};
|
|
||||||
|
|
||||||
const row = await SupplierInvoiceModel.findOne(mergedOptions);
|
|
||||||
|
|
||||||
if (!row) {
|
|
||||||
return Result.fail(new EntityNotFoundError("SupplierInvoice", "id", id.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.domainMapper.mapToDomain(row);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
return Result.fail(translateSequelizeError(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Busca una factura por proveedor + número dentro de una empresa.
|
|
||||||
*/
|
|
||||||
public async findBySupplierAndNumberInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
supplierId: UniqueID,
|
|
||||||
invoiceNumber: string,
|
|
||||||
transaction?: Transaction,
|
|
||||||
options: FindOptions<InferAttributes<SupplierInvoiceModel>> = {}
|
|
||||||
): Promise<Result<SupplierInvoice, Error>> {
|
|
||||||
try {
|
|
||||||
const normalizedInclude = Array.isArray(options.include)
|
|
||||||
? options.include
|
|
||||||
: options.include
|
|
||||||
? [options.include]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const row = await SupplierInvoiceModel.findOne({
|
|
||||||
...options,
|
|
||||||
where: {
|
|
||||||
...(options.where ?? {}),
|
|
||||||
company_id: companyId.toString(),
|
|
||||||
supplier_id: supplierId.toString(),
|
|
||||||
invoice_number: invoiceNumber,
|
|
||||||
},
|
|
||||||
include: [
|
|
||||||
...normalizedInclude,
|
|
||||||
{
|
|
||||||
model: SupplierInvoiceTaxModel,
|
|
||||||
as: "taxes",
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!row) {
|
|
||||||
return Result.fail(
|
|
||||||
new EntityNotFoundError(
|
|
||||||
"SupplierInvoice",
|
|
||||||
"invoice_number",
|
|
||||||
`${supplierId.toString()}:${invoiceNumber}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.domainMapper.mapToDomain(row);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
return Result.fail(translateSequelizeError(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Comprueba si existe una factura por proveedor + número dentro de una empresa.
|
|
||||||
*/
|
|
||||||
public async existsBySupplierAndNumberInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
supplierId: UniqueID,
|
|
||||||
invoiceNumber: string,
|
|
||||||
transaction?: Transaction,
|
|
||||||
options: FindOptions<InferAttributes<SupplierInvoiceModel>> = {}
|
|
||||||
): Promise<Result<boolean, Error>> {
|
|
||||||
try {
|
|
||||||
const count = await SupplierInvoiceModel.count({
|
|
||||||
...options,
|
|
||||||
where: {
|
|
||||||
...(options.where ?? {}),
|
|
||||||
company_id: companyId.toString(),
|
|
||||||
supplier_id: supplierId.toString(),
|
|
||||||
invoice_number: invoiceNumber,
|
|
||||||
},
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
return Result.ok(count > 0);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
return Result.fail(translateSequelizeError(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Busca facturas de proveedor mediante Criteria.
|
|
||||||
*/
|
|
||||||
public async findByCriteriaInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
criteria: Criteria,
|
|
||||||
transaction?: Transaction,
|
|
||||||
options: FindOptions<InferAttributes<SupplierInvoiceModel>> = {}
|
|
||||||
): Promise<Result<Collection<SupplierInvoiceSummary>, Error>> {
|
|
||||||
try {
|
|
||||||
const criteriaConverter = new CriteriaToSequelizeConverter();
|
|
||||||
|
|
||||||
const query = criteriaConverter.convert(criteria, {
|
|
||||||
searchableFields: ["invoice_number", "description", "internal_notes"],
|
|
||||||
mappings: {},
|
|
||||||
allowedFields: ["invoice_date", "due_date", "id", "created_at"],
|
|
||||||
enableFullText: true,
|
|
||||||
database: this.database,
|
|
||||||
strictMode: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const normalizedOrder = Array.isArray(options.order)
|
|
||||||
? options.order
|
|
||||||
: options.order
|
|
||||||
? [options.order]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const normalizedInclude = Array.isArray(options.include)
|
|
||||||
? options.include
|
|
||||||
: options.include
|
|
||||||
? [options.include]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
query.where = {
|
|
||||||
...query.where,
|
|
||||||
...(options.where ?? {}),
|
|
||||||
company_id: companyId.toString(),
|
|
||||||
deleted_at: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
query.order = [...(query.order as OrderItem[]), ...normalizedOrder];
|
|
||||||
|
|
||||||
query.include = [
|
|
||||||
...normalizedInclude,
|
|
||||||
{
|
|
||||||
model: SupplierInvoiceTaxModel,
|
|
||||||
as: "taxes",
|
|
||||||
required: false,
|
|
||||||
separate: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const [rows, count] = await Promise.all([
|
|
||||||
SupplierInvoiceModel.findAll({
|
|
||||||
...query,
|
|
||||||
transaction,
|
|
||||||
}),
|
|
||||||
SupplierInvoiceModel.count({
|
|
||||||
where: query.where,
|
|
||||||
distinct: true,
|
|
||||||
transaction,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return this.summaryMapper.mapToReadModelCollection(rows, count);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
return Result.fail(translateSequelizeError(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@erp/supplier-invoices/*": ["./src/*"]
|
|
||||||
},
|
|
||||||
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
||||||
"target": "ES2022",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
||||||
"module": "ESNext",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedSideEffectImports": true
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@erp/suppliers",
|
|
||||||
"description": "Suppliers",
|
|
||||||
"version": "0.5.0",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"sideEffects": false,
|
|
||||||
"scripts": {
|
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
||||||
"clean": "rimraf .turbo node_modules dist"
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
".": "./src/common/index.ts",
|
|
||||||
"./common": "./src/common/index.ts",
|
|
||||||
"./api": "./src/api/index.ts"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/express": "^4.17.21",
|
|
||||||
"typescript": "^5.9.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@erp/auth": "workspace:*",
|
|
||||||
"@erp/core": "workspace:*",
|
|
||||||
"@repo/i18next": "workspace:*",
|
|
||||||
"@repo/rdx-criteria": "workspace:*",
|
|
||||||
"@repo/rdx-ddd": "workspace:*",
|
|
||||||
"@repo/rdx-logger": "workspace:*",
|
|
||||||
"@repo/rdx-utils": "workspace:*",
|
|
||||||
"express": "^4.18.2",
|
|
||||||
"sequelize": "^6.37.5",
|
|
||||||
"zod": "^4.1.11"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export * from "./supplier-creator.di";
|
|
||||||
export * from "./supplier-finder.di";
|
|
||||||
export * from "./supplier-input-mappers.di";
|
|
||||||
export * from "./supplier-snapshot-builders.di";
|
|
||||||
export * from "./supplier-updater.di";
|
|
||||||
export * from "./supplier-use-cases.di";
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import type { ISupplierRepository } from "../repositories";
|
|
||||||
import { type ISupplierCreator, SupplierCreator } from "../services";
|
|
||||||
|
|
||||||
export const buildSupplierCreator = (params: {
|
|
||||||
repository: ISupplierRepository;
|
|
||||||
}): ISupplierCreator => {
|
|
||||||
const { repository } = params;
|
|
||||||
|
|
||||||
return new SupplierCreator({
|
|
||||||
repository,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import type { ISupplierRepository } from "../repositories";
|
|
||||||
import { type ISupplierFinder, SupplierFinder } from "../services";
|
|
||||||
|
|
||||||
export function buildSupplierFinder(params: { repository: ISupplierRepository }): ISupplierFinder {
|
|
||||||
const { repository } = params;
|
|
||||||
|
|
||||||
return new SupplierFinder(repository);
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
import type { ICatalogs } from "@erp/core/api";
|
|
||||||
|
|
||||||
import {
|
|
||||||
CreateSupplierInputMapper,
|
|
||||||
type ICreateSupplierInputMapper,
|
|
||||||
type IUpdateSupplierInputMapper,
|
|
||||||
UpdateSupplierInputMapper,
|
|
||||||
} from "../mappers";
|
|
||||||
|
|
||||||
export interface ISupplierInputMappers {
|
|
||||||
createInputMapper: ICreateSupplierInputMapper;
|
|
||||||
updateInputMapper: IUpdateSupplierInputMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const buildSupplierInputMappers = (catalogs: ICatalogs): ISupplierInputMappers => {
|
|
||||||
const { taxCatalog } = catalogs;
|
|
||||||
|
|
||||||
// Mappers el DTO a las props validadas (SupplierProps) y luego construir agregado
|
|
||||||
const createInputMapper = new CreateSupplierInputMapper({ taxCatalog });
|
|
||||||
const updateInputMapper = new UpdateSupplierInputMapper();
|
|
||||||
|
|
||||||
return {
|
|
||||||
createInputMapper,
|
|
||||||
updateInputMapper,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { SupplierFullSnapshotBuilder, SupplierSummarySnapshotBuilder } from "../snapshot-builders";
|
|
||||||
|
|
||||||
export function buildSupplierSnapshotBuilders() {
|
|
||||||
const fullSnapshotBuilder = new SupplierFullSnapshotBuilder();
|
|
||||||
const summarySnapshotBuilder = new SupplierSummarySnapshotBuilder();
|
|
||||||
|
|
||||||
return {
|
|
||||||
full: fullSnapshotBuilder,
|
|
||||||
summary: summarySnapshotBuilder,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import type { ISupplierRepository } from "../repositories";
|
|
||||||
import { type ISupplierUpdater, SupplierUpdater } from "../services";
|
|
||||||
|
|
||||||
export const buildSupplierUpdater = (params: {
|
|
||||||
repository: ISupplierRepository;
|
|
||||||
}): ISupplierUpdater => {
|
|
||||||
const { repository } = params;
|
|
||||||
|
|
||||||
return new SupplierUpdater({
|
|
||||||
repository,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
import type { ITransactionManager } from "@erp/core/api";
|
|
||||||
|
|
||||||
import type { ICreateSupplierInputMapper, IUpdateSupplierInputMapper } from "../mappers";
|
|
||||||
import type { ISupplierCreator, ISupplierFinder, ISupplierUpdater } from "../services";
|
|
||||||
import type {
|
|
||||||
ISupplierFullSnapshotBuilder,
|
|
||||||
ISupplierSummarySnapshotBuilder,
|
|
||||||
} from "../snapshot-builders";
|
|
||||||
import {
|
|
||||||
CreateSupplierUseCase,
|
|
||||||
GetSupplierByIdUseCase,
|
|
||||||
ListSuppliersUseCase,
|
|
||||||
UpdateSupplierUseCase,
|
|
||||||
} from "../use-cases";
|
|
||||||
|
|
||||||
export function buildGetSupplierByIdUseCase(deps: {
|
|
||||||
finder: ISupplierFinder;
|
|
||||||
fullSnapshotBuilder: ISupplierFullSnapshotBuilder;
|
|
||||||
transactionManager: ITransactionManager;
|
|
||||||
}) {
|
|
||||||
return new GetSupplierByIdUseCase(deps.finder, deps.fullSnapshotBuilder, deps.transactionManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildListSuppliersUseCase(deps: {
|
|
||||||
finder: ISupplierFinder;
|
|
||||||
summarySnapshotBuilder: ISupplierSummarySnapshotBuilder;
|
|
||||||
transactionManager: ITransactionManager;
|
|
||||||
}) {
|
|
||||||
return new ListSuppliersUseCase(
|
|
||||||
deps.finder,
|
|
||||||
deps.summarySnapshotBuilder,
|
|
||||||
deps.transactionManager
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildCreateSupplierUseCase(deps: {
|
|
||||||
creator: ISupplierCreator;
|
|
||||||
dtoMapper: ICreateSupplierInputMapper;
|
|
||||||
fullSnapshotBuilder: ISupplierFullSnapshotBuilder;
|
|
||||||
transactionManager: ITransactionManager;
|
|
||||||
}) {
|
|
||||||
return new CreateSupplierUseCase({
|
|
||||||
dtoMapper: deps.dtoMapper,
|
|
||||||
creator: deps.creator,
|
|
||||||
fullSnapshotBuilder: deps.fullSnapshotBuilder,
|
|
||||||
transactionManager: deps.transactionManager,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildUpdateSupplierUseCase(deps: {
|
|
||||||
updater: ISupplierUpdater;
|
|
||||||
dtoMapper: IUpdateSupplierInputMapper;
|
|
||||||
fullSnapshotBuilder: ISupplierFullSnapshotBuilder;
|
|
||||||
transactionManager: ITransactionManager;
|
|
||||||
}) {
|
|
||||||
return new UpdateSupplierUseCase({
|
|
||||||
dtoMapper: deps.dtoMapper,
|
|
||||||
updater: deps.updater,
|
|
||||||
fullSnapshotBuilder: deps.fullSnapshotBuilder,
|
|
||||||
transactionManager: deps.transactionManager,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*export function buildReportSupplierUseCase(deps: {
|
|
||||||
finder: ISupplierFinder;
|
|
||||||
fullSnapshotBuilder: ISupplierFullSnapshotBuilder;
|
|
||||||
reportSnapshotBuilder: ISupplierReportSnapshotBuilder;
|
|
||||||
documentService: SupplierDocumentGeneratorService;
|
|
||||||
transactionManager: ITransactionManager;
|
|
||||||
}) {
|
|
||||||
return new ReportSupplierUseCase(
|
|
||||||
deps.finder,
|
|
||||||
deps.fullSnapshotBuilder,
|
|
||||||
deps.reportSnapshotBuilder,
|
|
||||||
deps.documentService,
|
|
||||||
deps.transactionManager
|
|
||||||
);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
export function buildDeleteSupplierUseCase(deps: { finder: ISupplierFinder }) {
|
|
||||||
return new DeleteSupplierUseCase(deps.finder);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildIssueSupplierUseCase(deps: { finder: ISupplierFinder }) {
|
|
||||||
return new IssueSupplierUseCase(deps.finder);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildChangeStatusSupplierUseCase(deps: {
|
|
||||||
finder: ISupplierFinder;
|
|
||||||
transactionManager: ITransactionManager;
|
|
||||||
}) {
|
|
||||||
return new ChangeStatusSupplierUseCase(deps.finder, deps.transactionManager);
|
|
||||||
}*/
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export * from "./di";
|
|
||||||
export * from "./models";
|
|
||||||
export * from "./repositories";
|
|
||||||
export * from "./services";
|
|
||||||
export * from "./snapshot-builders";
|
|
||||||
export * from "./use-cases";
|
|
||||||
@ -1,217 +0,0 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
|
||||||
import {
|
|
||||||
City,
|
|
||||||
Country,
|
|
||||||
CurrencyCode,
|
|
||||||
DomainError,
|
|
||||||
EmailAddress,
|
|
||||||
LanguageCode,
|
|
||||||
Name,
|
|
||||||
PhoneNumber,
|
|
||||||
type PostalAddressProps,
|
|
||||||
PostalCode,
|
|
||||||
Province,
|
|
||||||
Street,
|
|
||||||
TINNumber,
|
|
||||||
URLAddress,
|
|
||||||
UniqueID,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { CreateSupplierRequestDTO } from "../../../common";
|
|
||||||
import { type ISupplierCreateProps, SupplierStatus } from "../../domain";
|
|
||||||
|
|
||||||
export interface ICreateSupplierInputMapper {
|
|
||||||
map(
|
|
||||||
dto: CreateSupplierRequestDTO,
|
|
||||||
params: { companyId: UniqueID }
|
|
||||||
): Result<{ id: UniqueID; props: ISupplierCreateProps }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreateSupplierInputMapper implements ICreateSupplierInputMapper {
|
|
||||||
private readonly taxCatalog: JsonTaxCatalogProvider;
|
|
||||||
|
|
||||||
constructor(params: { taxCatalog: JsonTaxCatalogProvider }) {
|
|
||||||
this.taxCatalog = params.taxCatalog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public map(
|
|
||||||
dto: CreateSupplierRequestDTO,
|
|
||||||
params: { companyId: UniqueID }
|
|
||||||
): Result<{ id: UniqueID; props: ISupplierCreateProps }> {
|
|
||||||
try {
|
|
||||||
const errors: ValidationErrorDetail[] = [];
|
|
||||||
const { companyId } = params;
|
|
||||||
|
|
||||||
const supplierId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
|
||||||
|
|
||||||
const status = SupplierStatus.active();
|
|
||||||
const isCompany = dto.is_company === "true";
|
|
||||||
|
|
||||||
const reference = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.reference, (value) => Name.create(value)),
|
|
||||||
"reference",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const name = extractOrPushError(Name.create(dto.name), "name", errors);
|
|
||||||
|
|
||||||
const tradeName = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.trade_name, (value) => Name.create(value)),
|
|
||||||
"trade_name",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const tinNumber = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.tin, (value) => TINNumber.create(value)),
|
|
||||||
"tin",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const street = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.street, (value) => Street.create(value)),
|
|
||||||
"street",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const street2 = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.street2, (value) => Street.create(value)),
|
|
||||||
"street2",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const city = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.city, (value) => City.create(value)),
|
|
||||||
"city",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const province = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.province, (value) => Province.create(value)),
|
|
||||||
"province",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const postalCode = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.postal_code, (value) => PostalCode.create(value)),
|
|
||||||
"postal_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const country = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.country, (value) => Country.create(value)),
|
|
||||||
"country",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const primaryEmailAddress = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.email_primary, (value) => EmailAddress.create(value)),
|
|
||||||
"email_primary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const secondaryEmailAddress = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.email_secondary, (value) => EmailAddress.create(value)),
|
|
||||||
"email_secondary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const primaryPhoneNumber = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.phone_primary, (value) => PhoneNumber.create(value)),
|
|
||||||
"phone_primary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const secondaryPhoneNumber = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.phone_secondary, (value) => PhoneNumber.create(value)),
|
|
||||||
"phone_secondary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const primaryMobileNumber = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.mobile_primary, (value) => PhoneNumber.create(value)),
|
|
||||||
"mobile_primary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const secondaryMobileNumber = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.mobile_secondary, (value) => PhoneNumber.create(value)),
|
|
||||||
"mobile_secondary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const faxNumber = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.fax, (value) => PhoneNumber.create(value)),
|
|
||||||
"fax",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const website = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.website, (value) => URLAddress.create(value)),
|
|
||||||
"website",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const languageCode = extractOrPushError(
|
|
||||||
LanguageCode.create(dto.language_code),
|
|
||||||
"language_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const currencyCode = extractOrPushError(
|
|
||||||
CurrencyCode.create(dto.currency_code),
|
|
||||||
"currency_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(new ValidationErrorCollection("Supplier props mapping failed", errors));
|
|
||||||
}
|
|
||||||
|
|
||||||
const postalAddressProps: PostalAddressProps = {
|
|
||||||
street: street!,
|
|
||||||
street2: street2!,
|
|
||||||
city: city!,
|
|
||||||
postalCode: postalCode!,
|
|
||||||
province: province!,
|
|
||||||
country: country!,
|
|
||||||
};
|
|
||||||
|
|
||||||
const supplierProps: ISupplierCreateProps = {
|
|
||||||
companyId,
|
|
||||||
status: status!,
|
|
||||||
reference: reference!,
|
|
||||||
|
|
||||||
isCompany: isCompany,
|
|
||||||
name: name!,
|
|
||||||
tradeName: tradeName!,
|
|
||||||
tin: tinNumber!,
|
|
||||||
|
|
||||||
address: postalAddressProps!,
|
|
||||||
|
|
||||||
emailPrimary: primaryEmailAddress!,
|
|
||||||
emailSecondary: secondaryEmailAddress!,
|
|
||||||
|
|
||||||
phonePrimary: primaryPhoneNumber!,
|
|
||||||
phoneSecondary: secondaryPhoneNumber!,
|
|
||||||
|
|
||||||
mobilePrimary: primaryMobileNumber!,
|
|
||||||
mobileSecondary: secondaryMobileNumber!,
|
|
||||||
|
|
||||||
fax: faxNumber!,
|
|
||||||
website: website!,
|
|
||||||
|
|
||||||
languageCode: languageCode!,
|
|
||||||
currencyCode: currencyCode!,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Result.ok({ id: supplierId!, props: supplierProps });
|
|
||||||
} catch (err: unknown) {
|
|
||||||
return Result.fail(new DomainError("Supplier props mapping failed", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./create-supplier-input.mapper";
|
|
||||||
export * from "./update-supplier-input.mapper";
|
|
||||||
@ -1,262 +0,0 @@
|
|||||||
import {
|
|
||||||
City,
|
|
||||||
Country,
|
|
||||||
CurrencyCode,
|
|
||||||
DomainError,
|
|
||||||
EmailAddress,
|
|
||||||
LanguageCode,
|
|
||||||
Name,
|
|
||||||
PhoneNumber,
|
|
||||||
type PostalAddressPatchProps,
|
|
||||||
PostalCode,
|
|
||||||
Province,
|
|
||||||
Street,
|
|
||||||
TINNumber,
|
|
||||||
URLAddress,
|
|
||||||
type UniqueID,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { UpdateSupplierByIdRequestDTO } from "../../../common";
|
|
||||||
import type { SupplierPatchProps } from "../../domain";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UpdateSupplierInputMapper
|
|
||||||
* Convierte el DTO a las props validadas (SupplierProps).
|
|
||||||
* No construye directamente el agregado.
|
|
||||||
* Tri-estado:
|
|
||||||
* - campo omitido → no se cambia
|
|
||||||
* - campo con valor null/"" → se quita el valor -> set(None()),
|
|
||||||
* - campo con valor no-vacío → se pone el nuevo valor -> set(Some(VO)).
|
|
||||||
*
|
|
||||||
* @param dto - DTO con los datos a cambiar en el cliente
|
|
||||||
* @returns Cambios en las propiedades del cliente
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface IUpdateSupplierInputMapper {
|
|
||||||
map(
|
|
||||||
dto: UpdateSupplierByIdRequestDTO,
|
|
||||||
params: { companyId: UniqueID }
|
|
||||||
): Result<SupplierPatchProps>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateSupplierInputMapper implements IUpdateSupplierInputMapper {
|
|
||||||
public map(dto: UpdateSupplierByIdRequestDTO, params: { companyId: UniqueID }) {
|
|
||||||
try {
|
|
||||||
const errors: ValidationErrorDetail[] = [];
|
|
||||||
const supplierPatchProps: SupplierPatchProps = {};
|
|
||||||
|
|
||||||
toPatchField(dto.reference).ifSet((reference) => {
|
|
||||||
supplierPatchProps.reference = extractOrPushError(
|
|
||||||
maybeFromNullableResult(reference, (value) => Name.create(value)),
|
|
||||||
"reference",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.is_company).ifSet((is_company) => {
|
|
||||||
if (isNullishOrEmpty(is_company)) {
|
|
||||||
errors.push({ path: "is_company", message: "is_company cannot be empty" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
supplierPatchProps.isCompany = extractOrPushError(
|
|
||||||
Result.ok(Boolean(is_company!)),
|
|
||||||
"is_company",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.name).ifSet((name) => {
|
|
||||||
if (isNullishOrEmpty(name)) {
|
|
||||||
errors.push({ path: "name", message: "Name cannot be empty" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
supplierPatchProps.name = extractOrPushError(Name.create(name!), "name", errors);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.trade_name).ifSet((trade_name) => {
|
|
||||||
supplierPatchProps.tradeName = extractOrPushError(
|
|
||||||
maybeFromNullableResult(trade_name, (value) => Name.create(value)),
|
|
||||||
"trade_name",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.tin).ifSet((tin) => {
|
|
||||||
supplierPatchProps.tin = extractOrPushError(
|
|
||||||
maybeFromNullableResult(tin, (value) => TINNumber.create(value)),
|
|
||||||
"tin",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.email_primary).ifSet((email_primary) => {
|
|
||||||
supplierPatchProps.emailPrimary = extractOrPushError(
|
|
||||||
maybeFromNullableResult(email_primary, (value) => EmailAddress.create(value)),
|
|
||||||
"email_primary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.email_secondary).ifSet((email_secondary) => {
|
|
||||||
supplierPatchProps.emailSecondary = extractOrPushError(
|
|
||||||
maybeFromNullableResult(email_secondary, (value) => EmailAddress.create(value)),
|
|
||||||
"email_secondary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.mobile_primary).ifSet((mobile_primary) => {
|
|
||||||
supplierPatchProps.mobilePrimary = extractOrPushError(
|
|
||||||
maybeFromNullableResult(mobile_primary, (value) => PhoneNumber.create(value)),
|
|
||||||
"mobile_primary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.mobile_secondary).ifSet((mobile_secondary) => {
|
|
||||||
supplierPatchProps.mobilePrimary = extractOrPushError(
|
|
||||||
maybeFromNullableResult(mobile_secondary, (value) => PhoneNumber.create(value)),
|
|
||||||
"mobile_secondary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.phone_primary).ifSet((phone_primary) => {
|
|
||||||
supplierPatchProps.phonePrimary = extractOrPushError(
|
|
||||||
maybeFromNullableResult(phone_primary, (value) => PhoneNumber.create(value)),
|
|
||||||
"phone_primary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.phone_secondary).ifSet((phone_secondary) => {
|
|
||||||
supplierPatchProps.phoneSecondary = extractOrPushError(
|
|
||||||
maybeFromNullableResult(phone_secondary, (value) => PhoneNumber.create(value)),
|
|
||||||
"phone_secondary",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.fax).ifSet((fax) => {
|
|
||||||
supplierPatchProps.fax = extractOrPushError(
|
|
||||||
maybeFromNullableResult(fax, (value) => PhoneNumber.create(value)),
|
|
||||||
"fax",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.website).ifSet((website) => {
|
|
||||||
supplierPatchProps.website = extractOrPushError(
|
|
||||||
maybeFromNullableResult(website, (value) => URLAddress.create(value)),
|
|
||||||
"website",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.language_code).ifSet((languageCode) => {
|
|
||||||
if (isNullishOrEmpty(languageCode)) {
|
|
||||||
errors.push({ path: "language_code", message: "Language code cannot be empty" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
supplierPatchProps.languageCode = extractOrPushError(
|
|
||||||
LanguageCode.create(languageCode!),
|
|
||||||
"language_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.currency_code).ifSet((currencyCode) => {
|
|
||||||
if (isNullishOrEmpty(currencyCode)) {
|
|
||||||
errors.push({ path: "currency_code", message: "Currency code cannot be empty" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
supplierPatchProps.currencyCode = extractOrPushError(
|
|
||||||
CurrencyCode.create(currencyCode!),
|
|
||||||
"currency_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// PostalAddress
|
|
||||||
const addressPatchProps = this.mapPostalAddress(dto, errors);
|
|
||||||
if (addressPatchProps) {
|
|
||||||
supplierPatchProps.address = addressPatchProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Supplier props mapping failed (update)", errors)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(supplierPatchProps);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
return Result.fail(new DomainError("Supplier props mapping failed", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public mapPostalAddress(
|
|
||||||
dto: UpdateSupplierByIdRequestDTO,
|
|
||||||
errors: ValidationErrorDetail[]
|
|
||||||
): PostalAddressPatchProps | undefined {
|
|
||||||
const postalAddressPatchProps: PostalAddressPatchProps = {};
|
|
||||||
|
|
||||||
toPatchField(dto.street).ifSet((street) => {
|
|
||||||
postalAddressPatchProps.street = extractOrPushError(
|
|
||||||
maybeFromNullableResult(street, (value) => Street.create(value)),
|
|
||||||
"street",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.street2).ifSet((street2) => {
|
|
||||||
postalAddressPatchProps.street2 = extractOrPushError(
|
|
||||||
maybeFromNullableResult(street2, (value) => Street.create(value)),
|
|
||||||
"street2",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.city).ifSet((city) => {
|
|
||||||
postalAddressPatchProps.city = extractOrPushError(
|
|
||||||
maybeFromNullableResult(city, (value) => City.create(value)),
|
|
||||||
"city",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.province).ifSet((province) => {
|
|
||||||
postalAddressPatchProps.province = extractOrPushError(
|
|
||||||
maybeFromNullableResult(province, (value) => Province.create(value)),
|
|
||||||
"province",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.postal_code).ifSet((postalCode) => {
|
|
||||||
postalAddressPatchProps.postalCode = extractOrPushError(
|
|
||||||
maybeFromNullableResult(postalCode, (value) => PostalCode.create(value)),
|
|
||||||
"postal_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.country).ifSet((country) => {
|
|
||||||
postalAddressPatchProps.country = extractOrPushError(
|
|
||||||
maybeFromNullableResult(country, (value) => Country.create(value)),
|
|
||||||
"country",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Object.keys(postalAddressPatchProps).length > 0 ? postalAddressPatchProps : undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./supplier-summary";
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import type {
|
|
||||||
CurrencyCode,
|
|
||||||
EmailAddress,
|
|
||||||
LanguageCode,
|
|
||||||
Name,
|
|
||||||
PhoneNumber,
|
|
||||||
PostalAddress,
|
|
||||||
TINNumber,
|
|
||||||
URLAddress,
|
|
||||||
UniqueID,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import type { Maybe } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
export type SupplierSummary = {
|
|
||||||
id: UniqueID;
|
|
||||||
companyId: UniqueID;
|
|
||||||
isActive: boolean;
|
|
||||||
reference: Maybe<Name>;
|
|
||||||
|
|
||||||
isCompany: boolean;
|
|
||||||
name: Name;
|
|
||||||
tradeName: Maybe<Name>;
|
|
||||||
tin: Maybe<TINNumber>;
|
|
||||||
|
|
||||||
address: PostalAddress;
|
|
||||||
|
|
||||||
emailPrimary: Maybe<EmailAddress>;
|
|
||||||
emailSecondary: Maybe<EmailAddress>;
|
|
||||||
phonePrimary: Maybe<PhoneNumber>;
|
|
||||||
phoneSecondary: Maybe<PhoneNumber>;
|
|
||||||
mobilePrimary: Maybe<PhoneNumber>;
|
|
||||||
mobileSecondary: Maybe<PhoneNumber>;
|
|
||||||
|
|
||||||
fax: Maybe<PhoneNumber>;
|
|
||||||
website: Maybe<URLAddress>;
|
|
||||||
|
|
||||||
languageCode: LanguageCode;
|
|
||||||
currencyCode: CurrencyCode;
|
|
||||||
};
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from './supplier-repository.interface';
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
|
||||||
import type { TINNumber, UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import type { Collection, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { Supplier } from "../../domain/aggregates";
|
|
||||||
import type { SupplierSummary } from "../models";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interfaz del repositorio para el agregado `Supplier`.
|
|
||||||
* El escopado multitenant está representado por `companyId`.
|
|
||||||
*/
|
|
||||||
export interface ISupplierRepository {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Crea un nuevo proveedor
|
|
||||||
*
|
|
||||||
* @param supplier - El proveedor nuevo a guardar.
|
|
||||||
* @param transaction - Transacción activa para la operación.
|
|
||||||
* @returns Result<void, Error>
|
|
||||||
*/
|
|
||||||
create(supplier: Supplier, transaction: unknown): Promise<Result<void, Error>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actualiza un proveedor existente.
|
|
||||||
*
|
|
||||||
* @param supplier - El proveedor a actualizar.
|
|
||||||
* @param transaction - Transacción activa para la operación.
|
|
||||||
* @returns Result<void, Error>
|
|
||||||
*/
|
|
||||||
update(supplier: Supplier, transaction: unknown): Promise<Result<void, Error>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Comprueba si existe un Supplier con un `id` dentro de una `company`.
|
|
||||||
*/
|
|
||||||
existsByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
id: UniqueID,
|
|
||||||
transaction: unknown
|
|
||||||
): Promise<Result<boolean, Error>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recupera un Supplier por su ID y companyId.
|
|
||||||
* Devuelve un `NotFoundError` si no se encuentra.
|
|
||||||
*/
|
|
||||||
getByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
id: UniqueID,
|
|
||||||
transaction: unknown
|
|
||||||
): Promise<Result<Supplier, Error>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recupera un Supplier por su TIN y companyId.
|
|
||||||
* Devuelve un `NotFoundError` si no se encuentra.
|
|
||||||
*/
|
|
||||||
getByTINInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
tin: TINNumber,
|
|
||||||
transaction?: unknown
|
|
||||||
): Promise<Result<Supplier, Error>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recupera múltiples suppliers dentro de una empresa
|
|
||||||
* según un criterio dinámico (búsqueda, paginación, etc.).
|
|
||||||
* El resultado está encapsulado en un objeto `Collection<T>`.
|
|
||||||
*/
|
|
||||||
findByCriteriaInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
criteria: Criteria,
|
|
||||||
transaction: unknown
|
|
||||||
): Promise<Result<Collection<SupplierSummary>, Error>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Elimina un Supplier por su ID, dentro de una empresa.
|
|
||||||
* Retorna `void` si se elimina correctamente, o `NotFoundError` si no existía.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
deleteByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
id: UniqueID,
|
|
||||||
transaction: unknown
|
|
||||||
): Promise<Result<boolean, Error>>;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export * from "./supplier-creator.service";
|
|
||||||
export * from "./supplier-finder.service";
|
|
||||||
export * from "./supplier-public-services.interface";
|
|
||||||
export * from "./supplier-updater.service";
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import { DuplicateEntityError } from "@erp/core/api";
|
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import { type ISupplierCreateProps, Supplier } from "../../domain";
|
|
||||||
import type { ISupplierRepository } from "../repositories";
|
|
||||||
import { SupplierNotExistsInCompanySpecification } from "../specs";
|
|
||||||
|
|
||||||
export interface ISupplierCreator {
|
|
||||||
create(params: {
|
|
||||||
companyId: UniqueID;
|
|
||||||
id: UniqueID;
|
|
||||||
props: ISupplierCreateProps;
|
|
||||||
unknown: unknown;
|
|
||||||
}): Promise<Result<Supplier, Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type SupplierCreatorDeps = {
|
|
||||||
repository: ISupplierRepository;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class SupplierCreator implements ISupplierCreator {
|
|
||||||
private readonly repository: ISupplierRepository;
|
|
||||||
|
|
||||||
constructor(deps: SupplierCreatorDeps) {
|
|
||||||
this.repository = deps.repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
async create(params: {
|
|
||||||
companyId: UniqueID;
|
|
||||||
id: UniqueID;
|
|
||||||
props: ISupplierCreateProps;
|
|
||||||
unknown: unknown;
|
|
||||||
}): Promise<Result<Supplier, Error>> {
|
|
||||||
const { companyId, id, props, unknown } = params;
|
|
||||||
|
|
||||||
// 1. Verificar unicidad
|
|
||||||
const spec = new SupplierNotExistsInCompanySpecification(this.repository, companyId, unknown);
|
|
||||||
|
|
||||||
const isNew = await spec.isSatisfiedBy(id);
|
|
||||||
|
|
||||||
if (!isNew) {
|
|
||||||
return Result.fail(new DuplicateEntityError("Supplier", "id", String(id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Crear agregado
|
|
||||||
const createResult = Supplier.create(
|
|
||||||
{
|
|
||||||
...props,
|
|
||||||
companyId,
|
|
||||||
},
|
|
||||||
id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (createResult.isFailure) {
|
|
||||||
return createResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newSupplier = createResult.data;
|
|
||||||
|
|
||||||
// 3. Persistir agregado
|
|
||||||
const saveResult = await this.repository.create(newSupplier, unknown);
|
|
||||||
|
|
||||||
if (saveResult.isFailure) {
|
|
||||||
return Result.fail(saveResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(newSupplier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
|
||||||
import type { TINNumber, UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import type { Collection, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { Supplier } from "../../domain";
|
|
||||||
import type { SupplierSummary } from "../models";
|
|
||||||
import type { ISupplierRepository } from "../repositories";
|
|
||||||
|
|
||||||
export interface ISupplierFinder {
|
|
||||||
findSupplierById(
|
|
||||||
companyId: UniqueID,
|
|
||||||
supplierId: UniqueID,
|
|
||||||
unknown?: unknown
|
|
||||||
): Promise<Result<Supplier, Error>>;
|
|
||||||
|
|
||||||
findSupplierByTIN(
|
|
||||||
companyId: UniqueID,
|
|
||||||
tin: TINNumber,
|
|
||||||
unknown?: unknown
|
|
||||||
): Promise<Result<Supplier, Error>>;
|
|
||||||
|
|
||||||
supplierExists(
|
|
||||||
companyId: UniqueID,
|
|
||||||
invoiceId: UniqueID,
|
|
||||||
unknown?: unknown
|
|
||||||
): Promise<Result<boolean, Error>>;
|
|
||||||
|
|
||||||
findSuppliersByCriteria(
|
|
||||||
companyId: UniqueID,
|
|
||||||
criteria: Criteria,
|
|
||||||
unknown?: unknown
|
|
||||||
): Promise<Result<Collection<SupplierSummary>, Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SupplierFinder implements ISupplierFinder {
|
|
||||||
constructor(private readonly repository: ISupplierRepository) {}
|
|
||||||
|
|
||||||
async findSupplierById(
|
|
||||||
companyId: UniqueID,
|
|
||||||
supplierId: UniqueID,
|
|
||||||
unknown?: unknown
|
|
||||||
): Promise<Result<Supplier, Error>> {
|
|
||||||
return this.repository.getByIdInCompany(companyId, supplierId, unknown);
|
|
||||||
}
|
|
||||||
|
|
||||||
findSupplierByTIN(
|
|
||||||
companyId: UniqueID,
|
|
||||||
tin: TINNumber,
|
|
||||||
unknown?: unknown
|
|
||||||
): Promise<Result<Supplier, Error>> {
|
|
||||||
return this.repository.getByTINInCompany(companyId, tin, unknown);
|
|
||||||
}
|
|
||||||
|
|
||||||
async supplierExists(
|
|
||||||
companyId: UniqueID,
|
|
||||||
supplierId: UniqueID,
|
|
||||||
unknown?: unknown
|
|
||||||
): Promise<Result<boolean, Error>> {
|
|
||||||
return this.repository.existsByIdInCompany(companyId, supplierId, unknown);
|
|
||||||
}
|
|
||||||
|
|
||||||
async findSuppliersByCriteria(
|
|
||||||
companyId: UniqueID,
|
|
||||||
criteria: Criteria,
|
|
||||||
unknown?: unknown
|
|
||||||
): Promise<Result<Collection<SupplierSummary>, Error>> {
|
|
||||||
return this.repository.findByCriteriaInCompany(companyId, criteria, unknown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import type { TINNumber, UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import type { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { ISupplierCreateProps, Supplier } from "../../domain";
|
|
||||||
|
|
||||||
export interface ISupplierServicesContext {
|
|
||||||
transaction: unknown;
|
|
||||||
companyId: UniqueID;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISupplierPublicServices {
|
|
||||||
findSupplierByTIN: (
|
|
||||||
tin: TINNumber,
|
|
||||||
context: ISupplierServicesContext
|
|
||||||
) => Promise<Result<Supplier, Error>>;
|
|
||||||
|
|
||||||
createSupplier: (
|
|
||||||
id: UniqueID,
|
|
||||||
props: ISupplierCreateProps,
|
|
||||||
context: ISupplierServicesContext
|
|
||||||
) => Promise<Result<Supplier, Error>>;
|
|
||||||
}
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { Supplier, SupplierPatchProps } from "../../domain";
|
|
||||||
import type { ISupplierRepository } from "../repositories";
|
|
||||||
|
|
||||||
export interface ISupplierUpdater {
|
|
||||||
update(params: {
|
|
||||||
companyId: UniqueID;
|
|
||||||
id: UniqueID;
|
|
||||||
props: SupplierPatchProps;
|
|
||||||
transaction: unknown;
|
|
||||||
}): Promise<Result<Supplier, Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type SupplierUpdaterDeps = {
|
|
||||||
repository: ISupplierRepository;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class SupplierUpdater implements ISupplierUpdater {
|
|
||||||
private readonly repository: ISupplierRepository;
|
|
||||||
|
|
||||||
constructor(deps: SupplierUpdaterDeps) {
|
|
||||||
this.repository = deps.repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(params: {
|
|
||||||
companyId: UniqueID;
|
|
||||||
id: UniqueID;
|
|
||||||
props: SupplierPatchProps;
|
|
||||||
transaction: unknown;
|
|
||||||
}): Promise<Result<Supplier, Error>> {
|
|
||||||
const { companyId, id, props, transaction } = params;
|
|
||||||
|
|
||||||
// Recuperar agregado existente
|
|
||||||
const existingResult = await this.repository.getByIdInCompany(companyId, id, transaction);
|
|
||||||
|
|
||||||
if (existingResult.isFailure) {
|
|
||||||
return Result.fail(existingResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const supplier = existingResult.data;
|
|
||||||
|
|
||||||
// Aplicar cambios en el agregado
|
|
||||||
const updateResult = supplier.update(props);
|
|
||||||
|
|
||||||
if (updateResult.isFailure) {
|
|
||||||
return Result.fail(updateResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Persistir cambios
|
|
||||||
const saveResult = await this.repository.update(supplier, transaction);
|
|
||||||
|
|
||||||
if (saveResult.isFailure) {
|
|
||||||
return Result.fail(saveResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(supplier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./supplier-snapshot.interface";
|
|
||||||
export * from "./supplier-snapshot-builder";
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
|
||||||
import { maybeToEmptyString } from "@repo/rdx-ddd";
|
|
||||||
|
|
||||||
import type { Supplier } from "../../../domain";
|
|
||||||
|
|
||||||
import type { ISupplierFullSnapshot } from "./supplier-snapshot.interface";
|
|
||||||
|
|
||||||
export interface ISupplierFullSnapshotBuilder
|
|
||||||
extends ISnapshotBuilder<Supplier, ISupplierFullSnapshot> {}
|
|
||||||
|
|
||||||
export class SupplierFullSnapshotBuilder implements ISupplierFullSnapshotBuilder {
|
|
||||||
toOutput(supplier: Supplier): ISupplierFullSnapshot {
|
|
||||||
const address = supplier.address.toPrimitive();
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: supplier.id.toPrimitive(),
|
|
||||||
company_id: supplier.companyId.toPrimitive(),
|
|
||||||
status: supplier.isActive ? "active" : "inactive",
|
|
||||||
reference: maybeToEmptyString(supplier.reference, (value) => value.toPrimitive()),
|
|
||||||
|
|
||||||
is_company: String(supplier.isCompany),
|
|
||||||
name: supplier.name.toPrimitive(),
|
|
||||||
trade_name: maybeToEmptyString(supplier.tradeName, (value) => value.toPrimitive()),
|
|
||||||
tin: maybeToEmptyString(supplier.tin, (value) => value.toPrimitive()),
|
|
||||||
|
|
||||||
street: maybeToEmptyString(address.street, (value) => value.toPrimitive()),
|
|
||||||
street2: maybeToEmptyString(address.street2, (value) => value.toPrimitive()),
|
|
||||||
city: maybeToEmptyString(address.city, (value) => value.toPrimitive()),
|
|
||||||
province: maybeToEmptyString(address.province, (value) => value.toPrimitive()),
|
|
||||||
postal_code: maybeToEmptyString(address.postalCode, (value) => value.toPrimitive()),
|
|
||||||
country: maybeToEmptyString(address.country, (value) => value.toPrimitive()),
|
|
||||||
|
|
||||||
email_primary: maybeToEmptyString(supplier.emailPrimary, (value) => value.toPrimitive()),
|
|
||||||
email_secondary: maybeToEmptyString(supplier.emailSecondary, (value) => value.toPrimitive()),
|
|
||||||
|
|
||||||
phone_primary: maybeToEmptyString(supplier.phonePrimary, (value) => value.toPrimitive()),
|
|
||||||
phone_secondary: maybeToEmptyString(supplier.phoneSecondary, (value) => value.toPrimitive()),
|
|
||||||
|
|
||||||
mobile_primary: maybeToEmptyString(supplier.mobilePrimary, (value) => value.toPrimitive()),
|
|
||||||
mobile_secondary: maybeToEmptyString(supplier.mobileSecondary, (value) =>
|
|
||||||
value.toPrimitive()
|
|
||||||
),
|
|
||||||
|
|
||||||
fax: maybeToEmptyString(supplier.fax, (value) => value.toPrimitive()),
|
|
||||||
website: maybeToEmptyString(supplier.website, (value) => value.toPrimitive()),
|
|
||||||
|
|
||||||
language_code: supplier.languageCode.toPrimitive(),
|
|
||||||
currency_code: supplier.currencyCode.toPrimitive(),
|
|
||||||
|
|
||||||
metadata: {
|
|
||||||
entity: "supplier",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
export interface ISupplierFullSnapshot {
|
|
||||||
id: string;
|
|
||||||
company_id: string;
|
|
||||||
status: string;
|
|
||||||
reference: string;
|
|
||||||
|
|
||||||
is_company: string;
|
|
||||||
name: string;
|
|
||||||
trade_name: string;
|
|
||||||
tin: string;
|
|
||||||
|
|
||||||
street: string;
|
|
||||||
street2: string;
|
|
||||||
city: string;
|
|
||||||
province: string;
|
|
||||||
postal_code: string;
|
|
||||||
country: string;
|
|
||||||
|
|
||||||
email_primary: string;
|
|
||||||
email_secondary: string;
|
|
||||||
|
|
||||||
phone_primary: string;
|
|
||||||
phone_secondary: string;
|
|
||||||
|
|
||||||
mobile_primary: string;
|
|
||||||
mobile_secondary: string;
|
|
||||||
|
|
||||||
fax: string;
|
|
||||||
website: string;
|
|
||||||
|
|
||||||
language_code: string;
|
|
||||||
currency_code: string;
|
|
||||||
|
|
||||||
metadata?: Record<string, string>;
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./domain";
|
|
||||||
export * from "./summary";
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user