Supplier invoices (incompleto)
This commit is contained in:
parent
936c440cf3
commit
96ddb559b2
33
modules/supplier-invoices/package.json
Normal file
33
modules/supplier-invoices/package.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
modules/supplier-invoices/src/api/application/index.ts
Normal file
6
modules/supplier-invoices/src/api/application/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from "./di";
|
||||||
|
export * from "./models";
|
||||||
|
export * from "./repositories";
|
||||||
|
export * from "./services";
|
||||||
|
export * from "./snapshot-builders";
|
||||||
|
export * from "./use-cases";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./supplier-invoice-summary";
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./supplier-invoice-repository.interface";
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
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>>;
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./supplier-invoice.aggregate";
|
||||||
@ -0,0 +1,313 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./invoice-payment-method";
|
||||||
|
export * from "./supplier-invoice-taxes";
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./supplier-invoice-tax.entity";
|
||||||
|
export * from "./supplier-invoice-taxes.collection";
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
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
modules/supplier-invoices/src/api/domain/enums/index.ts
Normal file
1
modules/supplier-invoices/src/api/domain/enums/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./supplier-invoice-source-type.enum";
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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",
|
||||||
|
}
|
||||||
3
modules/supplier-invoices/src/api/domain/index.ts
Normal file
3
modules/supplier-invoices/src/api/domain/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./aggregates";
|
||||||
|
export * from "./entities";
|
||||||
|
export * from "./value-objects";
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./invoice-amount.vo";
|
||||||
|
export * from "./invoice-date.vo";
|
||||||
|
export * from "./invoice-number.vo";
|
||||||
|
export * from "./supplier-invoice-status.vo";
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
70
modules/supplier-invoices/src/api/infrastucture/index.ts
Normal file
70
modules/supplier-invoices/src/api/infrastucture/index.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
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];
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./sequelize-issued-invoice-domain.mapper";
|
||||||
@ -0,0 +1,525 @@
|
|||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,416 @@
|
|||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
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()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,249 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,135 @@
|
|||||||
|
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),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./domain";
|
||||||
|
export * from "./summary";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./sequelize-issued-invoice-summary.mapper";
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,246 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./supplier-invoice.model";
|
||||||
|
export * from "./supplier-invoice-tax.model";
|
||||||
@ -0,0 +1,241 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
@ -0,0 +1,333 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
@ -0,0 +1,372 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
modules/supplier-invoices/tsconfig.json
Normal file
33
modules/supplier-invoices/tsconfig.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"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"]
|
||||||
|
}
|
||||||
@ -691,6 +691,46 @@ importers:
|
|||||||
specifier: ^5.9.3
|
specifier: ^5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
|
modules/supplier-invoices:
|
||||||
|
dependencies:
|
||||||
|
'@erp/auth':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../auth
|
||||||
|
'@erp/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../core
|
||||||
|
'@repo/i18next':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/i18n
|
||||||
|
'@repo/rdx-criteria':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-criteria
|
||||||
|
'@repo/rdx-ddd':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-ddd
|
||||||
|
'@repo/rdx-logger':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-logger
|
||||||
|
'@repo/rdx-utils':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-utils
|
||||||
|
express:
|
||||||
|
specifier: ^4.18.2
|
||||||
|
version: 4.21.2
|
||||||
|
sequelize:
|
||||||
|
specifier: ^6.37.5
|
||||||
|
version: 6.37.7(mysql2@3.15.3)(pg-hstore@2.3.4)
|
||||||
|
zod:
|
||||||
|
specifier: ^4.1.11
|
||||||
|
version: 4.1.12
|
||||||
|
devDependencies:
|
||||||
|
'@types/express':
|
||||||
|
specifier: ^4.17.21
|
||||||
|
version: 4.17.25
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.9.3
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages/i18n:
|
packages/i18n:
|
||||||
dependencies:
|
dependencies:
|
||||||
i18next:
|
i18next:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user