.
This commit is contained in:
parent
5bc9920538
commit
3a0262cbf7
@ -2,6 +2,9 @@ import { type ModuleParams, type SetupParams, buildTransactionManager } from "@e
|
|||||||
import type { Sequelize } from "sequelize";
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type IPaymentMethodPublicFinder,
|
||||||
|
PaymentMethodPublicFinder,
|
||||||
|
PaymentMethodPublicModelMapper,
|
||||||
buildPaymentMethodCreator,
|
buildPaymentMethodCreator,
|
||||||
buildPaymentMethodDeleter,
|
buildPaymentMethodDeleter,
|
||||||
buildPaymentMethodFinder,
|
buildPaymentMethodFinder,
|
||||||
@ -11,8 +14,6 @@ import {
|
|||||||
buildPaymentMethodUpdater,
|
buildPaymentMethodUpdater,
|
||||||
} from "../../../application";
|
} from "../../../application";
|
||||||
import type { IPaymentMethodRepository } from "../../../application/payment-methods/repositories";
|
import type { IPaymentMethodRepository } from "../../../application/payment-methods/repositories";
|
||||||
import type { IPaymentMethodFinder } from "../../../application/payment-methods/services";
|
|
||||||
import { PaymentMethodFinder } from "../../../application/payment-methods/services";
|
|
||||||
import {
|
import {
|
||||||
CreatePaymentMethodUseCase,
|
CreatePaymentMethodUseCase,
|
||||||
DeletePaymentMethodByIdUseCase,
|
DeletePaymentMethodByIdUseCase,
|
||||||
@ -116,8 +117,13 @@ export const buildPaymentMethodsDependencies = (
|
|||||||
export const buildPaymentMethodsPublicServices = (
|
export const buildPaymentMethodsPublicServices = (
|
||||||
_params: SetupParams,
|
_params: SetupParams,
|
||||||
deps: PaymentMethodsInternalDeps
|
deps: PaymentMethodsInternalDeps
|
||||||
): { finder: IPaymentMethodFinder } => {
|
): { finder: IPaymentMethodPublicFinder } => {
|
||||||
|
const mapper = new PaymentMethodPublicModelMapper();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
finder: new PaymentMethodFinder(deps.repository),
|
finder: new PaymentMethodPublicFinder({
|
||||||
|
repository: deps.repository,
|
||||||
|
mapper,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export abstract class SequelizeDomainMapper<TModel extends Model, TModelAttribut
|
|||||||
public abstract mapToPersistence(
|
public abstract mapToPersistence(
|
||||||
domain: TEntity,
|
domain: TEntity,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<TModelAttributes, Error> | Promise<Result<TModelAttributes, Error>>;
|
): Result<TModelAttributes, Error>;
|
||||||
|
|
||||||
public mapToDomainCollection(
|
public mapToDomainCollection(
|
||||||
raws: (TModel | TModelAttributes)[],
|
raws: (TModel | TModelAttributes)[],
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./invoice-payment-method-read.model";
|
||||||
|
export * from "./invoice-tax-regime-full-read.model";
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Datos del método de pago que completan a la proforma / issued-invoice
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface InvoicePaymentMethodReadModel {
|
||||||
|
id: UniqueID;
|
||||||
|
name: string;
|
||||||
|
description: Maybe<string>;
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Datos del régimen de pago que completan a la proforma / issued-invoice
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface InvocieTaxRegimeFullReadModel {
|
||||||
|
code: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
@ -1,10 +1,8 @@
|
|||||||
import { buildCatalogs } from "@erp/core/api";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type IProformaToIssuedInvoiceConverter,
|
type IProformaToIssuedInvoiceConverter,
|
||||||
ProformaToIssuedInvoiceConverter,
|
ProformaToIssuedInvoiceConverter,
|
||||||
} from "../services";
|
} from "../services";
|
||||||
|
|
||||||
export function buildProformaToIssuedInvoicePropsConverter(): IProformaToIssuedInvoiceConverter {
|
export const buildProformaToIssuedInvoicePropsConverter = (): IProformaToIssuedInvoiceConverter => {
|
||||||
return new ProformaToIssuedInvoiceConverter(buildCatalogs());
|
return new ProformaToIssuedInvoiceConverter();
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from "./issued-invoice-summary";
|
export * from "./issued-invoice-summary";
|
||||||
|
export * from "./proforma-issue-read.model";
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
import type { Proforma } from "../../../domain";
|
||||||
|
import type { InvoicePaymentMethodReadModel } from "../../common/models";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modelo de lectura usado exclusivamente durante la emisión de una proforma.
|
||||||
|
*
|
||||||
|
* Combina la proforma validada con los datos externos que deben materializarse
|
||||||
|
* como snapshot histórico dentro de la factura emitida.
|
||||||
|
*/
|
||||||
|
export interface ProformaIssueReadModel {
|
||||||
|
proforma: Proforma;
|
||||||
|
paymentMethod: InvoicePaymentMethodReadModel;
|
||||||
|
}
|
||||||
@ -1,8 +1,6 @@
|
|||||||
// modules/customer-invoices/src/api/application/issued-invoices/services/proforma-to-issued-invoice-props-converter.ts
|
// modules/customer-invoices/src/api/application/issued-invoices/services/proforma-to-issued-invoice-props-converter.ts
|
||||||
|
|
||||||
import type { JsonPaymentCatalogProvider } from "@erp/core";
|
import { UtcDate } from "@repo/rdx-ddd";
|
||||||
import type { ICatalogs } from "@erp/core/api";
|
|
||||||
import { DomainError, UtcDate } from "@repo/rdx-ddd";
|
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -14,9 +12,10 @@ import {
|
|||||||
IssuedInvoiceTaxes,
|
IssuedInvoiceTaxes,
|
||||||
type Proforma,
|
type Proforma,
|
||||||
} from "../../../domain";
|
} from "../../../domain";
|
||||||
|
import type { ProformaIssueReadModel } from "../models";
|
||||||
|
|
||||||
export interface IProformaToIssuedInvoiceConverter {
|
export interface IProformaToIssuedInvoiceConverter {
|
||||||
toCreateProps(proforma: Proforma): Result<IIssuedInvoiceCreateProps, Error>;
|
toCreateProps(source: ProformaIssueReadModel): Result<IIssuedInvoiceCreateProps, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,29 +26,25 @@ export interface IProformaToIssuedInvoiceConverter {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoiceConverter {
|
export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoiceConverter {
|
||||||
private readonly paymentCatalog: JsonPaymentCatalogProvider;
|
public toCreateProps(source: ProformaIssueReadModel): Result<IIssuedInvoiceCreateProps, Error> {
|
||||||
|
const { proforma } = source;
|
||||||
|
|
||||||
constructor(catalogs: ICatalogs) {
|
const itemsResult = this.resolveItems(proforma);
|
||||||
this.paymentCatalog = catalogs.paymentCatalog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public toCreateProps(proforma: Proforma): Result<IIssuedInvoiceCreateProps, Error> {
|
if (itemsResult.isFailure) {
|
||||||
const itemsOrResult = this.resolveItems(proforma);
|
return Result.fail(itemsResult.error);
|
||||||
|
|
||||||
if (itemsOrResult.isFailure) {
|
|
||||||
return Result.fail(itemsOrResult.error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const taxesOrResult = this.resolveTaxes(proforma);
|
const taxesResult = this.resolveTaxes(proforma);
|
||||||
|
|
||||||
if (taxesOrResult.isFailure) {
|
if (taxesResult.isFailure) {
|
||||||
return Result.fail(taxesOrResult.error);
|
return Result.fail(taxesResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const paymentOrResult = this.resolvePayment(proforma);
|
const paymentResult = this.resolvePayment(source);
|
||||||
|
|
||||||
if (paymentOrResult.isFailure) {
|
if (paymentResult.isFailure) {
|
||||||
return Result.fail(paymentOrResult.error);
|
return Result.fail(paymentResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const proformaTotals = proforma.totals();
|
const proformaTotals = proforma.totals();
|
||||||
@ -74,17 +69,17 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
|||||||
notes: proforma.notes,
|
notes: proforma.notes,
|
||||||
reference: proforma.reference,
|
reference: proforma.reference,
|
||||||
|
|
||||||
paymentMethod: paymentOrResult.data,
|
paymentMethod: paymentResult.data,
|
||||||
|
|
||||||
customerId: proforma.customerId,
|
customerId: proforma.customerId,
|
||||||
recipient: proforma.recipient.getOrUndefined()!,
|
recipient: proforma.recipient.getOrUndefined()!,
|
||||||
|
|
||||||
items: itemsOrResult.data,
|
items: itemsResult.data,
|
||||||
|
|
||||||
taxes: IssuedInvoiceTaxes.create({
|
taxes: IssuedInvoiceTaxes.create({
|
||||||
currencyCode: proforma.currencyCode,
|
currencyCode: proforma.currencyCode,
|
||||||
languageCode: proforma.languageCode,
|
languageCode: proforma.languageCode,
|
||||||
taxes: taxesOrResult.data,
|
taxes: taxesResult.data,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
subtotalAmount: proformaTotals.subtotalAmount,
|
subtotalAmount: proformaTotals.subtotalAmount,
|
||||||
@ -111,13 +106,13 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
|||||||
const issuedItems: IssuedInvoiceItem[] = [];
|
const issuedItems: IssuedInvoiceItem[] = [];
|
||||||
|
|
||||||
for (const item of proforma.items.getAll()) {
|
for (const item of proforma.items.getAll()) {
|
||||||
const itemOrResult = this.resolveItem(proforma, item);
|
const itemResult = this.resolveItem(proforma, item);
|
||||||
|
|
||||||
if (itemOrResult.isFailure) {
|
if (itemResult.isFailure) {
|
||||||
return Result.fail(itemOrResult.error);
|
return Result.fail(itemResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
issuedItems.push(itemOrResult.data);
|
issuedItems.push(itemResult.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(issuedItems);
|
return Result.ok(issuedItems);
|
||||||
@ -173,13 +168,13 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
|||||||
const issuedTaxes: IssuedInvoiceTax[] = [];
|
const issuedTaxes: IssuedInvoiceTax[] = [];
|
||||||
|
|
||||||
for (const tax of proforma.taxes().getAll()) {
|
for (const tax of proforma.taxes().getAll()) {
|
||||||
const taxOrResult = this.resolveTax(tax);
|
const taxResult = this.resolveTax(tax);
|
||||||
|
|
||||||
if (taxOrResult.isFailure) {
|
if (taxResult.isFailure) {
|
||||||
return Result.fail(taxOrResult.error);
|
return Result.fail(taxResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
issuedTaxes.push(taxOrResult.data);
|
issuedTaxes.push(taxResult.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(issuedTaxes);
|
return Result.ok(issuedTaxes);
|
||||||
@ -203,34 +198,25 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
|||||||
recAmount: tax.recAmount,
|
recAmount: tax.recAmount,
|
||||||
|
|
||||||
retentionCode: tax.retentionCode,
|
retentionCode: tax.retentionCode,
|
||||||
retentionAmount: tax.retentionAmount,
|
|
||||||
retentionPercentage: tax.retentionPercentage,
|
retentionPercentage: tax.retentionPercentage,
|
||||||
|
retentionAmount: tax.retentionAmount,
|
||||||
|
|
||||||
taxesAmount: tax.taxesAmount,
|
taxesAmount: tax.taxesAmount,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolvePayment(proforma: Proforma): Result<InvoicePaymentMethod, Error> {
|
private resolvePayment(source: ProformaIssueReadModel): Result<InvoicePaymentMethod, Error> {
|
||||||
const paymentId = proforma.paymentMethodId.unwrap();
|
const paymentMethodResult = InvoicePaymentMethod.create(
|
||||||
|
|
||||||
const existingPaymentResult = this.paymentCatalog.findById(paymentId.toString());
|
|
||||||
if (existingPaymentResult.isNone()) {
|
|
||||||
return Result.fail(
|
|
||||||
new DomainError("Missing payment method [ProformaToIssuedInvoiceConverter]")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const paymentMethodOrError = InvoicePaymentMethod.create(
|
|
||||||
{
|
{
|
||||||
name: existingPaymentResult.unwrap().description,
|
name: source.paymentMethod.name,
|
||||||
},
|
},
|
||||||
paymentId
|
source.paymentMethod.id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (paymentMethodOrError.isFailure) {
|
if (paymentMethodResult.isFailure) {
|
||||||
return Result.fail(paymentMethodOrError.error);
|
return Result.fail(paymentMethodResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(paymentMethodOrError.data);
|
return Result.ok(paymentMethodResult.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,20 @@
|
|||||||
|
import type { IPaymentMethodPublicFinder } from "@erp/catalogs/api";
|
||||||
|
|
||||||
import type { IProformaToIssuedInvoiceConverter } from "../../issued-invoices";
|
import type { IProformaToIssuedInvoiceConverter } from "../../issued-invoices";
|
||||||
import type { IProformaRepository } from "../repositories";
|
import { type IProformaIssuer, ProformaIssueReadModelAssembler, ProformaIssuer } from "../services";
|
||||||
import { type IProformaIssuer, ProformaIssuer } from "../services";
|
|
||||||
|
|
||||||
export const buildProformaIssuer = (params: {
|
export const buildProformaIssuer = (params: {
|
||||||
proformaConverter: IProformaToIssuedInvoiceConverter;
|
proformaConverter: IProformaToIssuedInvoiceConverter;
|
||||||
repository: IProformaRepository;
|
|
||||||
}): IProformaIssuer => {
|
}): IProformaIssuer => {
|
||||||
const { proformaConverter, repository } = params;
|
|
||||||
|
|
||||||
return new ProformaIssuer({
|
return new ProformaIssuer({
|
||||||
proformaConverter,
|
proformaConverter: params.proformaConverter,
|
||||||
repository,
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildProformaIssueReadModelAssembler = (params: {
|
||||||
|
paymentMethodFinder: IPaymentMethodPublicFinder;
|
||||||
|
}) => {
|
||||||
|
return new ProformaIssueReadModelAssembler({
|
||||||
|
paymentMethodFinder: params.paymentMethodFinder,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,10 +2,12 @@ import type { ITransactionManager } from "@erp/core/api";
|
|||||||
|
|
||||||
import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
||||||
import type { ICreateProformaInputMapper, IUpdateProformaInputMapper } from "../mappers";
|
import type { ICreateProformaInputMapper, IUpdateProformaInputMapper } from "../mappers";
|
||||||
|
import type { IProformaRepository } from "../repositories";
|
||||||
import type {
|
import type {
|
||||||
IProformaCreator,
|
IProformaCreator,
|
||||||
IProformaFinder,
|
IProformaFinder,
|
||||||
IProformaFullReadModelAssembler,
|
IProformaFullReadModelAssembler,
|
||||||
|
IProformaIssueReadModelAssembler,
|
||||||
IProformaIssuer,
|
IProformaIssuer,
|
||||||
IProformaStatusChanger,
|
IProformaStatusChanger,
|
||||||
IProformaUpdater,
|
IProformaUpdater,
|
||||||
@ -92,18 +94,25 @@ export function buildIssueProformaUseCase(deps: {
|
|||||||
};
|
};
|
||||||
finder: IProformaFinder;
|
finder: IProformaFinder;
|
||||||
issuer: IProformaIssuer;
|
issuer: IProformaIssuer;
|
||||||
|
issueReadModelAssembler: IProformaIssueReadModelAssembler;
|
||||||
|
repository: IProformaRepository;
|
||||||
transactionManager: ITransactionManager;
|
transactionManager: ITransactionManager;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
finder,
|
finder,
|
||||||
issuer,
|
issuer,
|
||||||
|
issueReadModelAssembler,
|
||||||
|
repository,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
publicServices: { issuedInvoiceServices },
|
publicServices: { issuedInvoiceServices },
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
return new IssueProformaUseCase({
|
return new IssueProformaUseCase({
|
||||||
issuedInvoiceServices,
|
issuedInvoiceServices,
|
||||||
finder,
|
finder,
|
||||||
issuer,
|
issuer,
|
||||||
|
issueReadModelAssembler,
|
||||||
|
repository,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,10 @@
|
|||||||
import type { Maybe } from "@repo/rdx-utils";
|
import type { Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { Proforma } from "../../../domain";
|
import type { Proforma } from "../../../domain";
|
||||||
|
import type {
|
||||||
/**
|
InvocieTaxRegimeFullReadModel,
|
||||||
* Datos del método de pago que completan a la proforma.
|
InvoicePaymentMethodReadModel,
|
||||||
*/
|
} from "../../common/models";
|
||||||
export interface ProformaPaymentMethodReadModel {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: Maybe<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Datos del régimen de pago que completan a la proforma.
|
|
||||||
*/
|
|
||||||
export interface ProformaTaxRegimeFullReadModel {
|
|
||||||
code: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modelo de una proforma con datos accesorios.
|
* Modelo de una proforma con datos accesorios.
|
||||||
@ -27,6 +14,6 @@ export interface ProformaTaxRegimeFullReadModel {
|
|||||||
*/
|
*/
|
||||||
export interface ProformaFullReadModel {
|
export interface ProformaFullReadModel {
|
||||||
proforma: Proforma;
|
proforma: Proforma;
|
||||||
paymentMethod: Maybe<ProformaPaymentMethodReadModel>;
|
paymentMethod: Maybe<InvoicePaymentMethodReadModel>;
|
||||||
taxRegime: Maybe<ProformaTaxRegimeFullReadModel>;
|
taxRegime: Maybe<InvocieTaxRegimeFullReadModel>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,4 +40,10 @@ export interface IProformaRepository {
|
|||||||
newStatus: InvoiceStatus,
|
newStatus: InvoiceStatus,
|
||||||
transaction: unknown
|
transaction: unknown
|
||||||
): Promise<Result<boolean, Error>>;
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
|
markAsIssuedByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: unknown
|
||||||
|
): Promise<Result<void, Error>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from "./proforma-full-read-model.assembler";
|
export * from "./proforma-full-read-model.assembler";
|
||||||
|
export * from "./proforma-issue-read-model.assembler";
|
||||||
|
|||||||
@ -0,0 +1,61 @@
|
|||||||
|
import type { IPaymentMethodPublicFinder } from "@erp/catalogs/api";
|
||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { Proforma } from "../../../../domain";
|
||||||
|
import type { ProformaIssueReadModel } from "../../../issued-invoices";
|
||||||
|
|
||||||
|
export interface IProformaIssueReadModelAssembler {
|
||||||
|
assemble(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
proforma: Proforma;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<ProformaIssueReadModel, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepara los datos externos necesarios para emitir una proforma.
|
||||||
|
*
|
||||||
|
* Esta pieza materializa snapshots externos que pasarán a formar parte histórica
|
||||||
|
* de la factura emitida.
|
||||||
|
*/
|
||||||
|
export class ProformaIssueReadModelAssembler implements IProformaIssueReadModelAssembler {
|
||||||
|
public constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
paymentMethodFinder: IPaymentMethodPublicFinder;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async assemble(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
proforma: Proforma;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<ProformaIssueReadModel, Error>> {
|
||||||
|
if (params.proforma.paymentMethodId.isNone()) {
|
||||||
|
return Result.fail(new Error("Payment method is required to issue proforma"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentMethodId = params.proforma.paymentMethodId.unwrap();
|
||||||
|
|
||||||
|
const paymentMethodResult = await this.deps.paymentMethodFinder.getByIdInCompany({
|
||||||
|
companyId: params.companyId,
|
||||||
|
id: paymentMethodId,
|
||||||
|
transaction: params.transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (paymentMethodResult.isFailure) {
|
||||||
|
return Result.fail(paymentMethodResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentMethod = paymentMethodResult.data;
|
||||||
|
|
||||||
|
return Result.ok({
|
||||||
|
proforma: params.proforma,
|
||||||
|
paymentMethod: {
|
||||||
|
id: paymentMethod.id,
|
||||||
|
name: paymentMethod.name,
|
||||||
|
description: paymentMethod.description,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,5 +8,5 @@ export * from "./proforma-finder";
|
|||||||
export * from "./proforma-issuer";
|
export * from "./proforma-issuer";
|
||||||
export * from "./proforma-number-generator.interface";
|
export * from "./proforma-number-generator.interface";
|
||||||
export * from "./proforma-public-services.interface";
|
export * from "./proforma-public-services.interface";
|
||||||
export * from "./proforma-status-charger";
|
export * from "./proforma-status-changer";
|
||||||
export * from "./proforma-updater";
|
export * from "./proforma-updater";
|
||||||
|
|||||||
@ -1,65 +1,34 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
// modules/customer-invoices/src/api/application/proformas/services/proforma-issuer.ts
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { IIssuedInvoiceCreateProps, Proforma } from "../../../domain";
|
import type { IIssuedInvoiceCreateProps } from "../../../domain";
|
||||||
import type { IProformaToIssuedInvoiceConverter } from "../../issued-invoices";
|
import type {
|
||||||
import type { IProformaRepository } from "../repositories";
|
IProformaToIssuedInvoiceConverter,
|
||||||
|
ProformaIssueReadModel,
|
||||||
|
} from "../../issued-invoices";
|
||||||
|
|
||||||
export interface IProformaIssuerParams {
|
export interface IProformaIssuerParams {
|
||||||
companyId: UniqueID;
|
source: ProformaIssueReadModel;
|
||||||
proforma: Proforma;
|
|
||||||
issuedInvoiceId: UniqueID;
|
|
||||||
transaction: unknown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProformaIssuer {
|
export interface IProformaIssuer {
|
||||||
issueProforma(params: IProformaIssuerParams): Promise<Result<IIssuedInvoiceCreateProps, Error>>;
|
issueProforma(params: IProformaIssuerParams): Result<IIssuedInvoiceCreateProps, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProformaIssuerDeps = {
|
|
||||||
proformaConverter: IProformaToIssuedInvoiceConverter;
|
|
||||||
repository: IProformaRepository;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ProformaIssuer implements IProformaIssuer {
|
export class ProformaIssuer implements IProformaIssuer {
|
||||||
private readonly proformaConverter: IProformaToIssuedInvoiceConverter;
|
public constructor(
|
||||||
private readonly repository: IProformaRepository;
|
private readonly deps: {
|
||||||
|
proformaConverter: IProformaToIssuedInvoiceConverter;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(deps: ProformaIssuerDeps) {
|
public issueProforma(params: IProformaIssuerParams): Result<IIssuedInvoiceCreateProps, Error> {
|
||||||
this.proformaConverter = deps.proformaConverter;
|
const issueResult = params.source.proforma.markAsIssued();
|
||||||
this.repository = deps.repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async issueProforma(
|
|
||||||
params: IProformaIssuerParams
|
|
||||||
): Promise<Result<IIssuedInvoiceCreateProps, Error>> {
|
|
||||||
const { proforma, companyId, transaction } = params;
|
|
||||||
|
|
||||||
// Cambiamos el estado de la proforma a 'issued'
|
|
||||||
const issueResult = proforma.issue();
|
|
||||||
if (issueResult.isFailure) {
|
if (issueResult.isFailure) {
|
||||||
return Result.fail(issueResult.error);
|
return Result.fail(issueResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persistir
|
return this.deps.proformaConverter.toCreateProps(params.source);
|
||||||
const updateStatusResult = await this.repository.updateStatusByIdInCompany(
|
|
||||||
companyId,
|
|
||||||
proforma.id,
|
|
||||||
proforma.status,
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateStatusResult.isFailure) {
|
|
||||||
return Result.fail(updateStatusResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generamos las propiedades de la factura a partir de la proforma
|
|
||||||
const propsResult = this.proformaConverter.toCreateProps(proforma);
|
|
||||||
|
|
||||||
if (propsResult.isFailure) {
|
|
||||||
return Result.fail(propsResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(propsResult.data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { Proforma } from "../../../domain";
|
import { InvoiceStatus, type Proforma } from "../../../domain";
|
||||||
import { InvoiceStatus } from "../../../domain";
|
|
||||||
import type { IProformaRepository } from "../repositories";
|
import type { IProformaRepository } from "../repositories";
|
||||||
|
|
||||||
export interface IProformaStatusChanger {
|
export interface IProformaStatusChanger {
|
||||||
@ -35,13 +35,11 @@ export class ChangeStatusProformaUseCase {
|
|||||||
return Result.fail(proformaIdResult.error);
|
return Result.fail(proformaIdResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const proformaId = proformaIdResult.data;
|
|
||||||
|
|
||||||
return this.deps.transactionManager.complete(async (transaction) => {
|
return this.deps.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
const changeResult = await this.deps.statusChanger.changeStatus({
|
const changeResult = await this.deps.statusChanger.changeStatus({
|
||||||
companyId,
|
companyId,
|
||||||
id: proformaId,
|
id: proformaIdResult.data,
|
||||||
newStatus: new_status!,
|
newStatus: new_status!,
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,12 @@ import { UniqueID } from "@repo/rdx-ddd";
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
||||||
import type { IProformaFinder, IProformaIssuer } from "../services";
|
import type { IProformaRepository } from "../repositories";
|
||||||
|
import type {
|
||||||
|
IProformaFinder,
|
||||||
|
IProformaIssueReadModelAssembler,
|
||||||
|
IProformaIssuer,
|
||||||
|
} from "../services";
|
||||||
|
|
||||||
type IssueProformaUseCaseInput = {
|
type IssueProformaUseCaseInput = {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
@ -11,13 +16,13 @@ type IssueProformaUseCaseInput = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caso de uso: Conversión de una issuedinvoice a factura definitiva.
|
* Caso de uso: conversión de una proforma en factura definitiva.
|
||||||
*
|
*
|
||||||
* - Recupera la proforma
|
* - Recupera la proforma.
|
||||||
* - Valida su estado ("approved")
|
* - Valida su emisión mediante dominio.
|
||||||
* - Genera la factura definitiva (nueva entidad)
|
* - Crea la factura definitiva.
|
||||||
* - Marca la proforma como "issued"
|
* - Marca la proforma como emitida.
|
||||||
* - Persiste ambas dentro de la misma transacción
|
* - Ejecuta todo dentro de una única transacción.
|
||||||
*/
|
*/
|
||||||
export class IssueProformaUseCase {
|
export class IssueProformaUseCase {
|
||||||
public constructor(
|
public constructor(
|
||||||
@ -25,6 +30,8 @@ export class IssueProformaUseCase {
|
|||||||
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
||||||
finder: IProformaFinder;
|
finder: IProformaFinder;
|
||||||
issuer: IProformaIssuer;
|
issuer: IProformaIssuer;
|
||||||
|
issueReadModelAssembler: IProformaIssueReadModelAssembler;
|
||||||
|
repository: IProformaRepository;
|
||||||
transactionManager: ITransactionManager;
|
transactionManager: ITransactionManager;
|
||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
@ -32,10 +39,13 @@ export class IssueProformaUseCase {
|
|||||||
public execute(params: IssueProformaUseCaseInput) {
|
public execute(params: IssueProformaUseCaseInput) {
|
||||||
const { proforma_id, companyId } = params;
|
const { proforma_id, companyId } = params;
|
||||||
|
|
||||||
const proformaIdOrError = UniqueID.create(proforma_id);
|
const proformaIdResult = UniqueID.create(proforma_id);
|
||||||
if (proformaIdOrError.isFailure) return Result.fail(proformaIdOrError.error);
|
|
||||||
|
|
||||||
const proformaId = proformaIdOrError.data;
|
if (proformaIdResult.isFailure) {
|
||||||
|
return Result.fail(proformaIdResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proformaId = proformaIdResult.data;
|
||||||
|
|
||||||
return this.deps.transactionManager.complete(async (transaction) => {
|
return this.deps.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
@ -46,28 +56,36 @@ export class IssueProformaUseCase {
|
|||||||
transaction
|
transaction
|
||||||
);
|
);
|
||||||
|
|
||||||
if (proformaResult.isFailure) return Result.fail(proformaResult.error);
|
if (proformaResult.isFailure) {
|
||||||
|
return Result.fail(proformaResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
const proforma = proformaResult.data;
|
const proforma = proformaResult.data;
|
||||||
|
|
||||||
// 2. Generamos la factura definitiva y la guardamos
|
// 2. Generamos la factura definitiva
|
||||||
const issuedInvoiceId = UniqueID.generateNewID();
|
const issuedInvoiceId = UniqueID.generateNewID();
|
||||||
const createPropsOrError = await this.deps.issuer.issueProforma({
|
|
||||||
|
const issueReadModelResult = await this.deps.issueReadModelAssembler.assemble({
|
||||||
companyId,
|
companyId,
|
||||||
issuedInvoiceId,
|
|
||||||
proforma,
|
proforma,
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (createPropsOrError.isFailure) {
|
if (issueReadModelResult.isFailure) {
|
||||||
return Result.fail(createPropsOrError.error);
|
return Result.fail(issueReadModelResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const createProps = createPropsOrError.data;
|
const createPropsResult = this.deps.issuer.issueProforma({
|
||||||
|
source: issueReadModelResult.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (createPropsResult.isFailure) {
|
||||||
|
return Result.fail(createPropsResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
// Creamos y guardamos en persistencia la factura definitiva
|
|
||||||
const invoiceResult = await this.deps.issuedInvoiceServices.createIssuedInvoice(
|
const invoiceResult = await this.deps.issuedInvoiceServices.createIssuedInvoice(
|
||||||
issuedInvoiceId,
|
issuedInvoiceId,
|
||||||
createProps,
|
createPropsResult.data,
|
||||||
{
|
{
|
||||||
companyId,
|
companyId,
|
||||||
transaction,
|
transaction,
|
||||||
@ -78,12 +96,21 @@ export class IssueProformaUseCase {
|
|||||||
return Result.fail(invoiceResult.error);
|
return Result.fail(invoiceResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dto = {
|
const markAsIssuedResult = await this.deps.repository.markAsIssuedByIdInCompany(
|
||||||
|
companyId,
|
||||||
|
proformaId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (markAsIssuedResult.isFailure) {
|
||||||
|
return Result.fail(markAsIssuedResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok({
|
||||||
issuedinvoice_id: issuedInvoiceId.toString(),
|
issuedinvoice_id: issuedInvoiceId.toString(),
|
||||||
proforma_id: proformaId.toString(),
|
proforma_id: proformaId.toString(),
|
||||||
customer_id: proforma.customerId.toString(),
|
customer_id: proforma.customerId.toString(),
|
||||||
};
|
});
|
||||||
return Result.ok(dto);
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
return Result.fail(error as Error);
|
return Result.fail(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,29 +25,19 @@ const INVOICE_TRANSITIONS: Record<string, string[]> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
|
export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
|
||||||
private static readonly ALLOWED_STATUSES = ["draft", "sent", "approved", "rejected", "issued"];
|
private static readonly ALLOWED_STATUSES = Object.values(INVOICE_STATUS);
|
||||||
private static readonly FIELD = "invoiceStatus";
|
private static readonly FIELD = "invoiceStatus";
|
||||||
private static readonly ERROR_CODE = "INVALID_INVOICE_STATUS";
|
private static readonly ERROR_CODE = "INVALID_INVOICE_STATUS";
|
||||||
|
|
||||||
static create(value: string): Result<InvoiceStatus, Error> {
|
static create(value: string): Result<InvoiceStatus, Error> {
|
||||||
if (!InvoiceStatus.ALLOWED_STATUSES.includes(value)) {
|
if (!InvoiceStatus.ALLOWED_STATUSES.includes(value as INVOICE_STATUS)) {
|
||||||
const detail = `Estado de la factura no válido: ${value}`;
|
const detail = `Estado de la factura no válido: ${value}`;
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new DomainValidationError(InvoiceStatus.ERROR_CODE, InvoiceStatus.FIELD, detail)
|
new DomainValidationError(InvoiceStatus.ERROR_CODE, InvoiceStatus.FIELD, detail)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(
|
return Result.ok(new InvoiceStatus({ value: value as INVOICE_STATUS }));
|
||||||
value === "rejected"
|
|
||||||
? InvoiceStatus.rejected()
|
|
||||||
: value === "sent"
|
|
||||||
? InvoiceStatus.sent()
|
|
||||||
: value === "issued"
|
|
||||||
? InvoiceStatus.issued()
|
|
||||||
: value === "approved"
|
|
||||||
? InvoiceStatus.approved()
|
|
||||||
: InvoiceStatus.draft()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static draft(): InvoiceStatus {
|
public static draft(): InvoiceStatus {
|
||||||
@ -94,10 +84,18 @@ export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
|
|||||||
return INVOICE_TRANSITIONS[this.props.value].includes(nextStatus);
|
return INVOICE_TRANSITIONS[this.props.value].includes(nextStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isIssued(): boolean {
|
isIssued(): boolean {
|
||||||
return this.props.value === INVOICE_STATUS.ISSUED;
|
return this.props.value === INVOICE_STATUS.ISSUED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isRejected(): boolean {
|
||||||
|
return this.props.value === INVOICE_STATUS.REJECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSent(): boolean {
|
||||||
|
return this.props.value === INVOICE_STATUS.SENT;
|
||||||
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return String(this.props.value);
|
return String(this.props.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import {
|
|||||||
} from "../entities";
|
} from "../entities";
|
||||||
import { InvalidProformaTransitionError, ProformaItemMismatch } from "../errors";
|
import { InvalidProformaTransitionError, ProformaItemMismatch } from "../errors";
|
||||||
import type { IProformaTaxTotals, ProformaCalculationContext } from "../services";
|
import type { IProformaTaxTotals, ProformaCalculationContext } from "../services";
|
||||||
|
import { canManuallyTransitionProformaStatus } from "../services";
|
||||||
import { ProformaItemTaxes } from "../value-objects";
|
import { ProformaItemTaxes } from "../value-objects";
|
||||||
|
|
||||||
export interface IProformaCreateProps {
|
export interface IProformaCreateProps {
|
||||||
@ -301,6 +302,12 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
return this.taxRegimeCode.isSome();
|
return this.taxRegimeCode.isSome();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cambia manualmente el estado de la proforma.
|
||||||
|
*
|
||||||
|
* No permite marcar una proforma como `issued`, porque esa transición implica
|
||||||
|
* crear una factura emitida y debe ejecutarse mediante el caso de uso de emisión.
|
||||||
|
*/
|
||||||
public changeStatus(nextStatus: InvoiceStatus): Result<boolean, Error> {
|
public changeStatus(nextStatus: InvoiceStatus): Result<boolean, Error> {
|
||||||
const currentStatus = this.status;
|
const currentStatus = this.status;
|
||||||
|
|
||||||
@ -308,17 +315,7 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
return Result.ok(false);
|
return Result.ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextStatus.toPrimitive() === INVOICE_STATUS.ISSUED) {
|
if (!canManuallyTransitionProformaStatus({ currentStatus, nextStatus })) {
|
||||||
return Result.fail(
|
|
||||||
new InvalidProformaTransitionError(
|
|
||||||
currentStatus.toPrimitive(),
|
|
||||||
nextStatus.toPrimitive(),
|
|
||||||
this.id.toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentStatus.canTransitionTo(nextStatus)) {
|
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new InvalidProformaTransitionError(
|
new InvalidProformaTransitionError(
|
||||||
currentStatus.toPrimitive(),
|
currentStatus.toPrimitive(),
|
||||||
@ -333,21 +330,39 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
return Result.ok(true);
|
return Result.ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public issue(): Result<void, Error> {
|
public markAsIssued(): Result<void, Error> {
|
||||||
|
const currentStatus = this.status;
|
||||||
|
|
||||||
|
if (currentStatus.isIssued()) {
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
// Antes de cambiar el estado de la proforma,
|
// Antes de cambiar el estado de la proforma,
|
||||||
// comprobamos que se cumplen las condiciones
|
// comprobamos que se cumplen las condiciones
|
||||||
// necesarias.
|
// necesarias.
|
||||||
|
|
||||||
if (!this.props.status.canTransitionTo("issued")) {
|
if (!currentStatus.isApproved()) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new DomainValidationError(
|
new InvalidProformaTransitionError(
|
||||||
"INVALID_STATE",
|
currentStatus.toPrimitive(),
|
||||||
"status",
|
INVOICE_STATUS.ISSUED,
|
||||||
"Proforma cannot be issued from current state"
|
this.id.toString()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validationResult = this.validateCanBeIssued();
|
||||||
|
|
||||||
|
if (validationResult.isFailure) {
|
||||||
|
return Result.fail(validationResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.status = InvoiceStatus.issued();
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateCanBeIssued(): Result<void, Error> {
|
||||||
if (this.series.isNone()) {
|
if (this.series.isNone()) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new DomainValidationError(
|
new DomainValidationError(
|
||||||
@ -444,12 +459,11 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
new DomainValidationError(
|
new DomainValidationError(
|
||||||
"LINKED_INVOICE_NOT_ALLOWED",
|
"LINKED_INVOICE_NOT_ALLOWED",
|
||||||
"linkedInvoiceId",
|
"linkedInvoiceId",
|
||||||
"Proforma cannot be linked to an invoice"
|
"Proforma is already linked to an invoice"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.status = InvoiceStatus.issued();
|
|
||||||
return Result.ok();
|
return Result.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./proforma-compare-tax-totals";
|
export * from "./proforma-compare-tax-totals";
|
||||||
export * from "./proforma-compute-tax-groups";
|
export * from "./proforma-compute-tax-groups";
|
||||||
export * from "./proforma-items-totals-calculator";
|
export * from "./proforma-items-totals-calculator";
|
||||||
|
export * from "./proforma-manual-status-transitions";
|
||||||
export * from "./proforma-taxes-calculator";
|
export * from "./proforma-taxes-calculator";
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { INVOICE_STATUS, type InvoiceStatus } from "../..";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transiciones manuales permitidas para una proforma.
|
||||||
|
*
|
||||||
|
* No incluye `approved -> issued` porque emitir una proforma crea una factura emitida
|
||||||
|
* y debe pasar siempre por el caso de uso específico de emisión.
|
||||||
|
*/
|
||||||
|
const PROFORMA_MANUAL_STATUS_TRANSITIONS: Readonly<
|
||||||
|
Record<INVOICE_STATUS, readonly INVOICE_STATUS[]>
|
||||||
|
> = {
|
||||||
|
[INVOICE_STATUS.DRAFT]: [INVOICE_STATUS.SENT],
|
||||||
|
[INVOICE_STATUS.SENT]: [INVOICE_STATUS.APPROVED, INVOICE_STATUS.REJECTED],
|
||||||
|
[INVOICE_STATUS.APPROVED]: [INVOICE_STATUS.REJECTED, INVOICE_STATUS.DRAFT],
|
||||||
|
[INVOICE_STATUS.REJECTED]: [INVOICE_STATUS.DRAFT],
|
||||||
|
[INVOICE_STATUS.ISSUED]: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export function canManuallyTransitionProformaStatus(params: {
|
||||||
|
currentStatus: InvoiceStatus;
|
||||||
|
nextStatus: InvoiceStatus;
|
||||||
|
}): boolean {
|
||||||
|
const current = params.currentStatus.toPrimitive() as INVOICE_STATUS;
|
||||||
|
const next = params.nextStatus.toPrimitive() as INVOICE_STATUS;
|
||||||
|
|
||||||
|
return PROFORMA_MANUAL_STATUS_TRANSITIONS[current].includes(next);
|
||||||
|
}
|
||||||
@ -14,11 +14,11 @@ import {
|
|||||||
} from "../../../application";
|
} from "../../../application";
|
||||||
import type { Proforma } from "../../../domain";
|
import type { Proforma } from "../../../domain";
|
||||||
|
|
||||||
import { resolveProformaCatalogsDeps } from "./proforma-catalog-deps.di";
|
|
||||||
import { buildProformaNumberGenerator } from "./proforma-number-generator.di";
|
import { buildProformaNumberGenerator } from "./proforma-number-generator.di";
|
||||||
import { buildProformaPersistenceMappers } from "./proforma-persistence-mappers.di";
|
import { buildProformaPersistenceMappers } from "./proforma-persistence-mappers.di";
|
||||||
import { buildProformaRepository } from "./proforma-repositories.di";
|
import { buildProformaRepository } from "./proforma-repositories.di";
|
||||||
import type { ProformasInternalDeps } from "./proformas.di";
|
import type { ProformasInternalDeps } from "./proformas.di";
|
||||||
|
import { resolveProformaCatalogsDeps } from "./proforrma-catalog-deps.di";
|
||||||
|
|
||||||
type ProformaServicesContext = {
|
type ProformaServicesContext = {
|
||||||
transaction: Transaction;
|
transaction: Transaction;
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
buildProformaCreator,
|
buildProformaCreator,
|
||||||
buildProformaFinder,
|
buildProformaFinder,
|
||||||
buildProformaInputMappers,
|
buildProformaInputMappers,
|
||||||
|
buildProformaIssueReadModelAssembler,
|
||||||
buildProformaIssuer,
|
buildProformaIssuer,
|
||||||
buildProformaReadModelAssemblers,
|
buildProformaReadModelAssemblers,
|
||||||
buildProformaSnapshotBuilders,
|
buildProformaSnapshotBuilders,
|
||||||
@ -104,7 +105,10 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
|
|
||||||
const issuer = buildProformaIssuer({
|
const issuer = buildProformaIssuer({
|
||||||
proformaConverter: proformaToIssuedInvoiceConverter,
|
proformaConverter: proformaToIssuedInvoiceConverter,
|
||||||
repository,
|
});
|
||||||
|
|
||||||
|
const issueReadModelAssembler = buildProformaIssueReadModelAssembler({
|
||||||
|
paymentMethodFinder: catalogs.paymentMethod.finder,
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentGeneratorPipeline = buildProformaDocumentService(params);
|
const documentGeneratorPipeline = buildProformaDocumentService(params);
|
||||||
@ -159,6 +163,8 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
publicServices,
|
publicServices,
|
||||||
finder,
|
finder,
|
||||||
issuer,
|
issuer,
|
||||||
|
issueReadModelAssembler,
|
||||||
|
repository,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@ -15,24 +15,24 @@ type ProformaCatalogsDeps = {
|
|||||||
|
|
||||||
export function resolveProformaCatalogsDeps(params: ModuleParams): ProformaCatalogsDeps {
|
export function resolveProformaCatalogsDeps(params: ModuleParams): ProformaCatalogsDeps {
|
||||||
const taxDefinition =
|
const taxDefinition =
|
||||||
params.getService<CatalogsPublicServicesType["taxDefinitions"]>("catalogs:taxDefinition");
|
params.getService<CatalogsPublicServicesType["taxDefinitions"]>("catalogs:taxDefinitions");
|
||||||
|
|
||||||
if (!taxDefinition?.finder) {
|
if (!taxDefinition?.finder) {
|
||||||
throw new Error("Missing public service: catalogs:taxDefinition.finder");
|
throw new Error("Missing public service: catalogs:taxDefinitions.finder");
|
||||||
}
|
}
|
||||||
|
|
||||||
const taxRegime =
|
const taxRegime =
|
||||||
params.getService<CatalogsPublicServicesType["taxRegimes"]>("catalogs:taxRegime");
|
params.getService<CatalogsPublicServicesType["taxRegimes"]>("catalogs:taxRegimes");
|
||||||
|
|
||||||
if (!taxRegime?.finder) {
|
if (!taxRegime?.finder) {
|
||||||
throw new Error("Missing public service: catalogs:taxRegime.finder");
|
throw new Error("Missing public service: catalogs:taxRegimes.finder");
|
||||||
}
|
}
|
||||||
|
|
||||||
const paymentMethod =
|
const paymentMethod =
|
||||||
params.getService<CatalogsPublicServicesType["paymentMethods"]>("catalogs:paymentMethod");
|
params.getService<CatalogsPublicServicesType["paymentMethods"]>("catalogs:paymentMethods");
|
||||||
|
|
||||||
if (!paymentMethod?.finder) {
|
if (!paymentMethod?.finder) {
|
||||||
throw new Error("Missing public service: catalogs:paymentMethod.finder");
|
throw new Error("Missing public service: catalogs:paymentMethods.finder");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -275,7 +275,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async mapToPersistence(
|
public mapToPersistence(
|
||||||
source: Proforma,
|
source: Proforma,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<CustomerInvoiceCreationAttributes, Error> {
|
): Result<CustomerInvoiceCreationAttributes, Error> {
|
||||||
@ -350,7 +350,10 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
|
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
|
||||||
|
|
||||||
payment_method_id: maybeToNullable(source.paymentMethodId, (value) => value.toPrimitive()),
|
payment_method_id: maybeToNullable(source.paymentMethodId, (value) => value.toPrimitive()),
|
||||||
|
payment_method_description: null,
|
||||||
|
|
||||||
tax_regime_code: maybeToNullable(source.taxRegimeCode, (value) => value),
|
tax_regime_code: maybeToNullable(source.taxRegimeCode, (value) => value),
|
||||||
|
tax_regime_description: null,
|
||||||
|
|
||||||
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||||
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
|
// modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-item-domain.mapper.ts
|
||||||
import {
|
import {
|
||||||
DiscountPercentage,
|
DiscountPercentage,
|
||||||
type MapperParamsType,
|
type MapperParamsType,
|
||||||
SequelizeDomainMapper,
|
SequelizeDomainMapper,
|
||||||
Tax,
|
Tax,
|
||||||
|
type TaxCalculationBehavior,
|
||||||
|
type TaxGroup,
|
||||||
|
TaxPercentage,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
UniqueID,
|
UniqueID,
|
||||||
@ -12,7 +16,7 @@ import {
|
|||||||
maybeFromNullableResult,
|
maybeFromNullableResult,
|
||||||
maybeToNullable,
|
maybeToNullable,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type IProformaCreateProps,
|
type IProformaCreateProps,
|
||||||
@ -54,14 +58,14 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
);
|
);
|
||||||
|
|
||||||
const description = extractOrPushError(
|
const description = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.description, (v) => ItemDescription.create(v)),
|
maybeFromNullableResult(raw.description, (value) => ItemDescription.create(value)),
|
||||||
`items[${index}].description`,
|
`items[${index}].description`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const quantity = extractOrPushError(
|
const quantity = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.quantity_value, (v) =>
|
maybeFromNullableResult(raw.quantity_value, (value) =>
|
||||||
ItemQuantity.create({ value: v, scale: raw.quantity_scale })
|
ItemQuantity.create({ value, scale: raw.quantity_scale })
|
||||||
),
|
),
|
||||||
`items[${index}].quantity_value`,
|
`items[${index}].quantity_value`,
|
||||||
errors
|
errors
|
||||||
@ -80,9 +84,9 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
);
|
);
|
||||||
|
|
||||||
const itemDiscountPercentage = extractOrPushError(
|
const itemDiscountPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.item_discount_percentage_value, (v) =>
|
maybeFromNullableResult(raw.item_discount_percentage_value, (value) =>
|
||||||
DiscountPercentage.create({
|
DiscountPercentage.create({
|
||||||
value: v,
|
value,
|
||||||
scale: raw.item_discount_percentage_scale,
|
scale: raw.item_discount_percentage_scale,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@ -91,22 +95,41 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
);
|
);
|
||||||
|
|
||||||
const iva = extractOrPushError(
|
const iva = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.iva_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
|
this.mapTaxToDomain({
|
||||||
`items[${index}].iva_code`,
|
code: raw.iva_code,
|
||||||
|
percentageValue: raw.iva_percentage_value,
|
||||||
|
percentageScale: raw.iva_percentage_scale,
|
||||||
|
group: "iva",
|
||||||
|
calculationBehavior: "additive",
|
||||||
|
fieldPath: `items[${index}].iva`,
|
||||||
|
}),
|
||||||
|
`items[${index}].iva`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const rec = extractOrPushError(
|
const rec = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.rec_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
|
this.mapTaxToDomain({
|
||||||
`items[${index}].rec_code`,
|
code: raw.rec_code,
|
||||||
|
percentageValue: raw.rec_percentage_value,
|
||||||
|
percentageScale: raw.rec_percentage_scale,
|
||||||
|
group: "surcharge",
|
||||||
|
calculationBehavior: "additive",
|
||||||
|
fieldPath: `items[${index}].rec`,
|
||||||
|
}),
|
||||||
|
`items[${index}].rec`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const retention = extractOrPushError(
|
const retention = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.retention_code, (code) =>
|
this.mapTaxToDomain({
|
||||||
Tax.createFromCode(code, this._taxCatalog)
|
code: raw.retention_code,
|
||||||
),
|
percentageValue: raw.retention_percentage_value,
|
||||||
`items[${index}].retention_code`,
|
percentageScale: raw.retention_percentage_scale,
|
||||||
|
group: "retention",
|
||||||
|
calculationBehavior: "subtractive",
|
||||||
|
fieldPath: `items[${index}].retention`,
|
||||||
|
}),
|
||||||
|
`items[${index}].retention`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -122,55 +145,94 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private mapTaxToDomain(params: {
|
||||||
|
code: string | null;
|
||||||
|
percentageValue: number | null;
|
||||||
|
percentageScale?: number;
|
||||||
|
group: TaxGroup;
|
||||||
|
calculationBehavior: TaxCalculationBehavior;
|
||||||
|
fieldPath: string;
|
||||||
|
}): Result<Maybe<Tax>, Error> {
|
||||||
|
if (params.code === null || params.code.trim() === "") {
|
||||||
|
return Result.ok(Maybe.none());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.percentageValue === null) {
|
||||||
|
return Result.fail(
|
||||||
|
new Error(`${params.fieldPath}.percentage_value is required when tax code is present`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const percentageResult = TaxPercentage.create({
|
||||||
|
value: params.percentageValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (percentageResult.isFailure) {
|
||||||
|
return Result.fail(percentageResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const taxResult = Tax.create({
|
||||||
|
code: params.code,
|
||||||
|
name: params.code,
|
||||||
|
rate: percentageResult.data,
|
||||||
|
group: params.group,
|
||||||
|
calculationBehavior: params.calculationBehavior,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (taxResult.isFailure) {
|
||||||
|
return Result.fail(taxResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(Maybe.some(taxResult.data));
|
||||||
|
}
|
||||||
|
|
||||||
public mapToDomain(
|
public mapToDomain(
|
||||||
raw: CustomerInvoiceItemModel,
|
raw: CustomerInvoiceItemModel,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<ProformaItem, Error> {
|
): Result<ProformaItem, Error> {
|
||||||
const { errors, index } = params as {
|
const { errors } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
parent: Partial<IProformaCreateProps>;
|
parent: Partial<IProformaCreateProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1) Valores escalares (atributos generales)
|
|
||||||
const attributes = this.mapAttributesToDomain(raw, params);
|
const attributes = this.mapAttributesToDomain(raw, params);
|
||||||
|
|
||||||
// Si hubo errores de mapeo, devolvemos colección de validación
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new ValidationErrorCollection("Customer invoice item mapping failed [mapToDomain]", errors)
|
new ValidationErrorCollection("Customer invoice item mapping failed [mapToDomain]", errors)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Construcción del elemento de taxes
|
|
||||||
const taxesResult = ProformaItemTaxes.create({
|
const taxesResult = ProformaItemTaxes.create({
|
||||||
iva: attributes.iva!,
|
iva: attributes.iva!,
|
||||||
rec: attributes.rec!,
|
rec: attributes.rec!,
|
||||||
retention: attributes.retention!,
|
retention: attributes.retention!,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2) Construcción del elemento de dominio
|
if (taxesResult.isFailure) {
|
||||||
const itemId = attributes.itemId!;
|
return Result.fail(taxesResult.error);
|
||||||
const newItem = ProformaItem.rehydrate(
|
}
|
||||||
|
|
||||||
|
const item = ProformaItem.rehydrate(
|
||||||
{
|
{
|
||||||
description: attributes.description!,
|
description: attributes.description!,
|
||||||
quantity: attributes.quantity!,
|
quantity: attributes.quantity!,
|
||||||
unitAmount: attributes.unitAmount!,
|
unitAmount: attributes.unitAmount!,
|
||||||
|
|
||||||
itemDiscountPercentage: attributes.itemDiscountPercentage!,
|
itemDiscountPercentage: attributes.itemDiscountPercentage!,
|
||||||
taxes: taxesResult.data,
|
taxes: taxesResult.data,
|
||||||
},
|
},
|
||||||
itemId
|
attributes.itemId!
|
||||||
);
|
);
|
||||||
|
|
||||||
return Result.ok(newItem);
|
return Result.ok(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapToPersistence(
|
public mapToPersistence(
|
||||||
source: ProformaItem,
|
source: ProformaItem,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<CustomerInvoiceItemCreationAttributes, Error> {
|
): Result<CustomerInvoiceItemCreationAttributes, Error> {
|
||||||
const { errors, index, parent } = params as {
|
const { index, parent } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
parent: Proforma;
|
parent: Proforma;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
@ -187,34 +249,32 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
invoice_id: parent.id.toPrimitive(),
|
invoice_id: parent.id.toPrimitive(),
|
||||||
position: index,
|
position: index,
|
||||||
|
|
||||||
description: maybeToNullable(source.description, (v) => v.toPrimitive()),
|
description: maybeToNullable(source.description, (value) => value.toPrimitive()),
|
||||||
|
|
||||||
quantity_value: maybeToNullable(source.quantity, (v) => v.toPrimitive().value),
|
quantity_value: maybeToNullable(source.quantity, (value) => value.toPrimitive().value),
|
||||||
quantity_scale:
|
quantity_scale:
|
||||||
maybeToNullable(source.quantity, (v) => v.toPrimitive().scale) ??
|
maybeToNullable(source.quantity, (value) => value.toPrimitive().scale) ??
|
||||||
ItemQuantity.DEFAULT_SCALE,
|
ItemQuantity.DEFAULT_SCALE,
|
||||||
|
|
||||||
unit_amount_value: maybeToNullable(source.unitAmount, (v) => v.toPrimitive().value),
|
unit_amount_value: maybeToNullable(source.unitAmount, (value) => value.toPrimitive().value),
|
||||||
unit_amount_scale:
|
unit_amount_scale:
|
||||||
maybeToNullable(source.unitAmount, (v) => v.toPrimitive().scale) ??
|
maybeToNullable(source.unitAmount, (value) => value.toPrimitive().scale) ??
|
||||||
ItemAmount.DEFAULT_SCALE,
|
ItemAmount.DEFAULT_SCALE,
|
||||||
|
|
||||||
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||||
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||||
|
|
||||||
//
|
|
||||||
item_discount_percentage_value: maybeToNullable(
|
item_discount_percentage_value: maybeToNullable(
|
||||||
source.itemDiscountPercentage,
|
source.itemDiscountPercentage,
|
||||||
(v) => v.toPrimitive().value
|
(value) => value.toPrimitive().value
|
||||||
),
|
),
|
||||||
item_discount_percentage_scale:
|
item_discount_percentage_scale:
|
||||||
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
maybeToNullable(source.itemDiscountPercentage, (value) => value.toPrimitive().scale) ??
|
||||||
DiscountPercentage.DEFAULT_SCALE,
|
DiscountPercentage.DEFAULT_SCALE,
|
||||||
|
|
||||||
item_discount_amount_value: allAmounts.itemDiscountAmount.value,
|
item_discount_amount_value: allAmounts.itemDiscountAmount.value,
|
||||||
item_discount_amount_scale: allAmounts.itemDiscountAmount.scale,
|
item_discount_amount_scale: allAmounts.itemDiscountAmount.scale,
|
||||||
|
|
||||||
//
|
|
||||||
global_discount_percentage_value: parent.globalDiscountPercentage.toPrimitive().value,
|
global_discount_percentage_value: parent.globalDiscountPercentage.toPrimitive().value,
|
||||||
global_discount_percentage_scale:
|
global_discount_percentage_scale:
|
||||||
parent.globalDiscountPercentage.toPrimitive().scale ?? DiscountPercentage.DEFAULT_SCALE,
|
parent.globalDiscountPercentage.toPrimitive().scale ?? DiscountPercentage.DEFAULT_SCALE,
|
||||||
@ -222,52 +282,40 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
global_discount_amount_value: allAmounts.globalDiscountAmount.value,
|
global_discount_amount_value: allAmounts.globalDiscountAmount.value,
|
||||||
global_discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
global_discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
||||||
|
|
||||||
//
|
|
||||||
total_discount_amount_value: allAmounts.totalDiscountAmount.value,
|
total_discount_amount_value: allAmounts.totalDiscountAmount.value,
|
||||||
total_discount_amount_scale: allAmounts.totalDiscountAmount.scale,
|
total_discount_amount_scale: allAmounts.totalDiscountAmount.scale,
|
||||||
|
|
||||||
//
|
|
||||||
taxable_amount_value: allAmounts.taxableAmount.value,
|
taxable_amount_value: allAmounts.taxableAmount.value,
|
||||||
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
||||||
|
|
||||||
// IVA
|
iva_code: maybeToNullable(source.taxes.iva, (value) => value.code),
|
||||||
iva_code: maybeToNullable(source.taxes.iva, (v) => v.code),
|
iva_percentage_value: maybeToNullable(source.taxes.iva, (value) => value.percentage.value),
|
||||||
|
|
||||||
iva_percentage_value: maybeToNullable(source.taxes.iva, (v) => v.percentage.value),
|
|
||||||
iva_percentage_scale:
|
iva_percentage_scale:
|
||||||
maybeToNullable(source.taxes.iva, (v) => v.percentage.scale) ?? Tax.DEFAULT_SCALE,
|
maybeToNullable(source.taxes.iva, (value) => value.percentage.scale) ?? Tax.DEFAULT_SCALE,
|
||||||
|
|
||||||
iva_amount_value: allAmounts.ivaAmount.value,
|
iva_amount_value: allAmounts.ivaAmount.value,
|
||||||
iva_amount_scale: allAmounts.ivaAmount.scale,
|
iva_amount_scale: allAmounts.ivaAmount.scale,
|
||||||
|
|
||||||
// REC
|
rec_code: maybeToNullable(source.taxes.rec, (value) => value.code),
|
||||||
rec_code: maybeToNullable(source.taxes.rec, (v) => v.code),
|
rec_percentage_value: maybeToNullable(source.taxes.rec, (value) => value.percentage.value),
|
||||||
|
|
||||||
rec_percentage_value: maybeToNullable(source.taxes.rec, (v) => v.percentage.value),
|
|
||||||
rec_percentage_scale:
|
rec_percentage_scale:
|
||||||
maybeToNullable(source.taxes.rec, (v) => v.percentage.scale) ?? Tax.DEFAULT_SCALE,
|
maybeToNullable(source.taxes.rec, (value) => value.percentage.scale) ?? Tax.DEFAULT_SCALE,
|
||||||
|
|
||||||
rec_amount_value: allAmounts.recAmount.value,
|
rec_amount_value: allAmounts.recAmount.value,
|
||||||
rec_amount_scale: allAmounts.recAmount.scale,
|
rec_amount_scale: allAmounts.recAmount.scale,
|
||||||
|
|
||||||
// RET
|
retention_code: maybeToNullable(source.taxes.retention, (value) => value.code),
|
||||||
retention_code: maybeToNullable(source.taxes.retention, (v) => v.code),
|
|
||||||
|
|
||||||
retention_percentage_value: maybeToNullable(
|
retention_percentage_value: maybeToNullable(
|
||||||
source.taxes.retention,
|
source.taxes.retention,
|
||||||
(v) => v.percentage.value
|
(value) => value.percentage.value
|
||||||
),
|
),
|
||||||
retention_percentage_scale:
|
retention_percentage_scale:
|
||||||
maybeToNullable(source.taxes.retention, (v) => v.percentage.scale) ?? Tax.DEFAULT_SCALE,
|
maybeToNullable(source.taxes.retention, (value) => value.percentage.scale) ??
|
||||||
|
Tax.DEFAULT_SCALE,
|
||||||
retention_amount_value: allAmounts.retentionAmount.value,
|
retention_amount_value: allAmounts.retentionAmount.value,
|
||||||
retention_amount_scale: allAmounts.retentionAmount.scale,
|
retention_amount_scale: allAmounts.retentionAmount.scale,
|
||||||
|
|
||||||
//
|
|
||||||
taxes_amount_value: allAmounts.taxesAmount.value,
|
taxes_amount_value: allAmounts.taxesAmount.value,
|
||||||
taxes_amount_scale: allAmounts.taxesAmount.scale,
|
taxes_amount_scale: allAmounts.taxesAmount.scale,
|
||||||
|
|
||||||
//
|
|
||||||
total_amount_value: allAmounts.totalAmount.value,
|
total_amount_value: allAmounts.totalAmount.value,
|
||||||
total_amount_scale: allAmounts.totalAmount.scale,
|
total_amount_scale: allAmounts.totalAmount.scale,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { type Collection, Result } from "@repo/rdx-utils";
|
|||||||
import type { FindOptions, InferAttributes, OrderItem, Sequelize, Transaction } from "sequelize";
|
import type { FindOptions, InferAttributes, OrderItem, Sequelize, Transaction } from "sequelize";
|
||||||
|
|
||||||
import type { IProformaRepository, ProformaSummary } from "../../../../../application";
|
import type { IProformaRepository, ProformaSummary } from "../../../../../application";
|
||||||
import type { InvoiceStatus, Proforma } from "../../../../../domain";
|
import { INVOICE_STATUS, type InvoiceStatus, type Proforma } from "../../../../../domain";
|
||||||
import {
|
import {
|
||||||
CustomerInvoiceItemModel,
|
CustomerInvoiceItemModel,
|
||||||
CustomerInvoiceModel,
|
CustomerInvoiceModel,
|
||||||
@ -241,6 +241,41 @@ export class ProformaRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async markAsIssuedByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<void, Error>> {
|
||||||
|
try {
|
||||||
|
const [affectedRows] = await CustomerInvoiceModel.update(
|
||||||
|
{
|
||||||
|
status: INVOICE_STATUS.ISSUED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
id: id.toString(),
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
is_proforma: true,
|
||||||
|
status: INVOICE_STATUS.APPROVED,
|
||||||
|
},
|
||||||
|
transaction: transaction as Transaction,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (affectedRows !== 1) {
|
||||||
|
return Result.fail(
|
||||||
|
new Error(
|
||||||
|
`Proforma ${id.toString()} could not be marked as issued because it is not approved or does not exist`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Busca una factura por su identificador único.
|
* Busca una factura por su identificador único.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user