feat: Enhance proforma issuance with tax regime support

- Added tax regime to ProformaIssueReadModel and related services.
- Implemented tax regime resolution in ProformaToIssuedInvoiceConverter.
- Updated IssuedInvoiceFullSnapshotBuilder to include tax regime details.
- Enhanced ProformaIssueReadModelAssembler to handle tax regime fetching.
- Introduced IssueProformaResponseSnapshotBuilder for response mapping.
- Updated routes and controllers to accommodate new proforma issuance logic.
- Refactored request and response DTOs to include tax regime information.
- Added command structure for issuing proformas.
This commit is contained in:
David Arranz 2026-07-09 13:07:48 +02:00
parent 9d7e18d73d
commit 1c0235de75
30 changed files with 337 additions and 57 deletions

View File

@ -1,5 +1,8 @@
import type { Proforma } from "../../../domain";
import type { InvoicePaymentMethodReadModel } from "../../common/models";
import type {
InvocieTaxRegimeFullReadModel,
InvoicePaymentMethodReadModel,
} from "../../common/models";
/**
* Modelo de lectura usado exclusivamente durante la emisión de una proforma.
@ -10,4 +13,5 @@ import type { InvoicePaymentMethodReadModel } from "../../common/models";
export interface ProformaIssueReadModel {
proforma: Proforma;
paymentMethod: InvoicePaymentMethodReadModel;
taxRegime: InvocieTaxRegimeFullReadModel;
}

View File

@ -7,6 +7,7 @@ import {
type IIssuedInvoiceCreateProps,
InvoicePaymentMethod,
InvoiceStatus,
InvoiceTaxRegime,
IssuedInvoiceItem,
IssuedInvoiceRecipient,
IssuedInvoiceTax,
@ -57,6 +58,12 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
return Result.fail(paymentResult.error);
}
const taxRegimeResult = this.resolveTaxRegime(source);
if (taxRegimeResult.isFailure) {
return Result.fail(taxRegimeResult.error);
}
const proformaTotals = proforma.totals();
return Result.ok({
@ -78,6 +85,7 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
reference: proforma.reference,
paymentMethod: paymentResult.data,
taxRegime: taxRegimeResult.data,
customerId: proforma.customerId,
recipient: recipientResult.data,
@ -251,4 +259,17 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
return Result.ok(paymentMethodResult.data);
}
private resolveTaxRegime(source: ProformaIssueReadModel): Result<InvoiceTaxRegime, Error> {
const taxRegimeResult = InvoiceTaxRegime.create({
code: source.taxRegime.code,
description: source.taxRegime.description || source.taxRegime.code,
});
if (taxRegimeResult.isFailure) {
return Result.fail(taxRegimeResult.error);
}
return Result.ok(taxRegimeResult.data);
}
}

View File

@ -31,6 +31,11 @@ export class IssuedInvoiceFullSnapshotBuilder implements IIssuedInvoiceFullSnaps
payment_description: invoice.paymentMethod.name.toString(),
};
const tax_regime = {
code: invoice.taxRegime.code,
description: invoice.taxRegime.description,
};
return {
id: invoice.id.toString(),
company_id: invoice.companyId.toString(),
@ -58,6 +63,7 @@ export class IssuedInvoiceFullSnapshotBuilder implements IIssuedInvoiceFullSnaps
taxes,
payment_method,
tax_regime,
subtotal_amount: invoice.subtotalAmount.toObjectString(),

View File

@ -44,6 +44,10 @@ export interface IIssuedInvoiceFullSnapshot {
payment_id: string;
payment_description: string;
} | null;
tax_regime: {
code: string;
description: string | null;
};
subtotal_amount: { value: string; scale: string; currency_code: string };

View File

@ -0,0 +1 @@
export * from "./issue-proforma.command";

View File

@ -0,0 +1,5 @@
export type IssueProformaCommand = {
companyId: string;
proformaId: string;
actorUserId?: string;
};

View File

@ -1,4 +1,4 @@
import type { IPaymentMethodPublicFinder } from "@erp/catalogs/api";
import type { IPaymentMethodPublicFinder, ITaxRegimePublicFinder } from "@erp/catalogs/api";
import type { IProformaToIssuedInvoiceConverter } from "../../issued-invoices";
import { type IProformaIssuer, ProformaIssueReadModelAssembler, ProformaIssuer } from "../services";
@ -13,8 +13,10 @@ export const buildProformaIssuer = (params: {
export const buildProformaIssueReadModelAssembler = (params: {
paymentMethodFinder: IPaymentMethodPublicFinder;
taxRegimeFinder: ITaxRegimePublicFinder;
}) => {
return new ProformaIssueReadModelAssembler({
paymentMethodFinder: params.paymentMethodFinder,
taxRegimeFinder: params.taxRegimeFinder,
});
};

View File

@ -1,6 +1,7 @@
// application/issued-invoices/di/snapshot-builders.di.ts
import {
IssueProformaResponseSnapshotBuilder,
ProformaFullSnapshotBuilder,
ProformaItemReportSnapshotBuilder,
ProformaItemsFullSnapshotBuilder,
@ -32,9 +33,11 @@ export function buildProformaSnapshotBuilders() {
itemsReportBuilder,
taxesReportBuilder
);
const issueResponseSnapshotBuilder = new IssueProformaResponseSnapshotBuilder();
return {
full: fullSnapshotBuilder,
issue: issueResponseSnapshotBuilder,
summary: summarySnapshotBuilder,
report: reportSnapshotBuilder,
};

View File

@ -16,6 +16,7 @@ import type {
} from "../services";
import type {
IProformaFullSnapshotBuilder,
IIssueProformaResponseSnapshotBuilder,
IProformaReportSnapshotBuilder,
IProformaSummarySnapshotBuilder,
} from "../snapshot-builders";
@ -121,6 +122,7 @@ export function buildIssueProformaUseCase(deps: {
finder: IProformaFinder;
issuer: IProformaIssuer;
issueReadModelAssembler: IProformaIssueReadModelAssembler;
issueResponseSnapshotBuilder: IIssueProformaResponseSnapshotBuilder;
repository: IProformaRepository;
transactionManager: ITransactionManager;
}) {
@ -128,6 +130,7 @@ export function buildIssueProformaUseCase(deps: {
finder,
issuer,
issueReadModelAssembler,
issueResponseSnapshotBuilder,
repository,
transactionManager,
publicServices: { issuedInvoiceServices },
@ -138,6 +141,7 @@ export function buildIssueProformaUseCase(deps: {
finder,
issuer,
issueReadModelAssembler,
issueResponseSnapshotBuilder,
repository,
transactionManager,
});

View File

@ -1,3 +1,4 @@
export * from "./commands";
export * from "./di";
export * from "./errors";
export * from "./mappers";

View File

@ -15,7 +15,6 @@ import { Maybe, NumberHelper, Result } from "@repo/rdx-utils";
import type { CreateProformaRequestDTO } from "../../../../common";
import {
InvoiceNumber,
type InvoiceRecipient,
InvoiceSerie,
InvoiceStatus,
@ -59,12 +58,6 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper {
errors
);
const invoiceNumber = extractOrPushError(
InvoiceNumber.create(dto.invoice_number),
"invoice_number",
errors
);
const series = extractOrPushError(
maybeFromNullableResult(dto.series, (value) => InvoiceSerie.create(value)),
"series",

View File

@ -1,4 +1,4 @@
import type { IPaymentMethodPublicFinder } from "@erp/catalogs/api";
import type { IPaymentMethodPublicFinder, ITaxRegimePublicFinder } from "@erp/catalogs/api";
import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
@ -23,6 +23,7 @@ export class ProformaIssueReadModelAssembler implements IProformaIssueReadModelA
public constructor(
private readonly deps: {
paymentMethodFinder: IPaymentMethodPublicFinder;
taxRegimeFinder: ITaxRegimePublicFinder;
}
) {}
@ -47,6 +48,12 @@ export class ProformaIssueReadModelAssembler implements IProformaIssueReadModelA
return Result.fail(paymentMethodResult.error);
}
const taxRegimeResult = await this.resolveTaxRegime(params);
if (taxRegimeResult.isFailure) {
return Result.fail(taxRegimeResult.error);
}
const paymentMethod = paymentMethodResult.data;
return Result.ok({
@ -56,6 +63,43 @@ export class ProformaIssueReadModelAssembler implements IProformaIssueReadModelA
name: paymentMethod.name,
description: paymentMethod.description,
},
taxRegime: taxRegimeResult.data,
});
}
private async resolveTaxRegime(params: {
companyId: UniqueID;
proforma: Proforma;
transaction?: unknown;
}) {
if (params.proforma.taxRegimeCode.isNone()) {
return Result.fail(new Error("Tax regime is required to issue proforma"));
}
const taxRegimeCode = params.proforma.taxRegimeCode.unwrap();
const taxRegimeResult = await this.deps.taxRegimeFinder.findByCodeInCompany({
companyId: params.companyId,
code: taxRegimeCode,
transaction: params.transaction,
});
if (taxRegimeResult.isFailure) {
return Result.fail(taxRegimeResult.error);
}
if (taxRegimeResult.data.isNone()) {
return Result.ok({
code: taxRegimeCode,
description: "",
});
}
const taxRegime = taxRegimeResult.data.unwrap();
return Result.ok({
code: taxRegime.code,
description: taxRegime.description,
});
}
}

View File

@ -1,3 +1,4 @@
export * from "./full";
export * from "./issue";
export * from "./report";
export * from "./summary";

View File

@ -0,0 +1 @@
export * from "./issue-proforma-response.snapshot-builder";

View File

@ -0,0 +1,39 @@
import type { ISnapshotBuilder } from "@erp/core/api";
import type { IssueProformaByIdResponseDTO } from "../../../../../common";
import type { IssuedInvoice } from "../../../../domain";
export interface IIssueProformaResponseSnapshotBuilder
extends ISnapshotBuilder<IssuedInvoice, IssueProformaByIdResponseDTO> {}
export class IssueProformaResponseSnapshotBuilder
implements IIssueProformaResponseSnapshotBuilder
{
toOutput(invoice: IssuedInvoice): IssueProformaByIdResponseDTO {
return {
issued_invoice: {
id: invoice.id.toString(),
proforma_id: invoice.linkedProformaId.toString(),
series: invoice.series.toString(),
invoice_number: invoice.invoiceNumber.toString(),
invoice_date: invoice.invoiceDate.toDateString(),
status: invoice.status.toPrimitive(),
recipient: {
name: invoice.recipient.name.toString(),
tin: invoice.recipient.tin.toString(),
},
tax_regime: {
code: invoice.taxRegime.code,
description: invoice.taxRegime.description,
},
totals: {
taxable_base: invoice.taxableAmount.toObjectString(),
tax_total: invoice.taxesAmount.toObjectString(),
retention_total: invoice.retentionAmount.toObjectString(),
grand_total: invoice.totalAmount.toObjectString(),
currency_code: invoice.currencyCode.toPrimitive(),
},
},
};
}
}

View File

@ -3,17 +3,14 @@ import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
import type { IssueProformaCommand } from "../commands";
import type { IProformaRepository } from "../repositories";
import type {
IProformaFinder,
IProformaIssueReadModelAssembler,
IProformaIssuer,
} from "../services";
type IssueProformaUseCaseInput = {
companyId: UniqueID;
proforma_id: string;
};
import type { IIssueProformaResponseSnapshotBuilder } from "../snapshot-builders";
/**
* Caso de uso: conversión de una proforma en factura definitiva.
@ -31,15 +28,22 @@ export class IssueProformaUseCase {
finder: IProformaFinder;
issuer: IProformaIssuer;
issueReadModelAssembler: IProformaIssueReadModelAssembler;
issueResponseSnapshotBuilder: IIssueProformaResponseSnapshotBuilder;
repository: IProformaRepository;
transactionManager: ITransactionManager;
}
) {}
public execute(params: IssueProformaUseCaseInput) {
const { proforma_id, companyId } = params;
public execute(command: IssueProformaCommand) {
const companyIdResult = UniqueID.create(command.companyId);
const proformaIdResult = UniqueID.create(proforma_id);
if (companyIdResult.isFailure) {
return Result.fail(companyIdResult.error);
}
const companyId = companyIdResult.data;
const proformaIdResult = UniqueID.create(command.proformaId);
if (proformaIdResult.isFailure) {
return Result.fail(proformaIdResult.error);
@ -106,11 +110,7 @@ export class IssueProformaUseCase {
return Result.fail(markAsIssuedResult.error);
}
return Result.ok({
issuedinvoice_id: issuedInvoiceId.toString(),
proforma_id: proformaId.toString(),
customer_id: proforma.customerId.toString(),
});
return Result.ok(this.deps.issueResponseSnapshotBuilder.toOutput(invoiceResult.data));
} catch (error: unknown) {
return Result.fail(error as Error);
}

View File

@ -17,6 +17,7 @@ import type {
InvoicePaymentMethod,
InvoiceSerie,
InvoiceStatus,
InvoiceTaxRegime,
} from "../../common";
import {
type IIssuedInvoiceItemCreateProps,
@ -51,6 +52,7 @@ export interface IIssuedInvoiceCreateProps {
currencyCode: CurrencyCode;
paymentMethod: InvoicePaymentMethod;
taxRegime: InvoiceTaxRegime;
items: IIssuedInvoiceItemCreateProps[];
taxes: IssuedInvoiceTaxes;
@ -139,6 +141,16 @@ export class IssuedInvoice
);
}
if (!props.taxRegime) {
return Result.fail(
new DomainValidationError(
"MISSING_TAX_REGIME",
"taxRegime",
"Issued invoice requires tax regime"
)
);
}
return Result.ok();
}
@ -222,6 +234,10 @@ export class IssuedInvoice
return this.props.paymentMethod;
}
public get taxRegime(): InvoiceTaxRegime {
return this.props.taxRegime;
}
public get languageCode(): LanguageCode {
return this.props.languageCode;
}

View File

@ -1,6 +1,7 @@
import { DiscountPercentage, type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import {
CurrencyCode,
DomainValidationError,
LanguageCode,
TextValue,
UniqueID,
@ -20,6 +21,7 @@ import {
InvoicePaymentMethod,
InvoiceSerie,
InvoiceStatus,
InvoiceTaxRegime,
IssuedInvoice,
IssuedInvoiceItems,
IssuedInvoiceTaxes,
@ -154,6 +156,23 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
}
}
const taxRegime = extractOrPushError(
!isNullishOrEmpty(raw.tax_regime_code)
? InvoiceTaxRegime.create({
code: String(raw.tax_regime_code),
description: String(raw.tax_regime_description ?? raw.tax_regime_code),
})
: Result.fail(
new DomainValidationError(
"MISSING_TAX_REGIME",
"tax_regime",
"Issued invoice requires persisted tax regime"
)
),
"tax_regime",
errors
);
const subtotalAmount = extractOrPushError(
InvoiceAmount.create({
value: raw.subtotal_amount_value,
@ -270,6 +289,7 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
languageCode,
currencyCode,
paymentMethod,
taxRegime,
subtotalAmount,
itemsDiscountAmount,
@ -384,6 +404,7 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
totalAmount: attributes.totalAmount!,
paymentMethod: attributes.paymentMethod!,
taxRegime: attributes.taxRegime!,
taxes,
verifactu,
@ -483,6 +504,8 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
payment_method_id: source.paymentMethod.toObjectString().id,
payment_method_description: source.paymentMethod.toObjectString().name,
tax_regime_code: source.taxRegime.code,
tax_regime_description: source.taxRegime.description,
subtotal_amount_value: source.subtotalAmount.value,
subtotal_amount_scale: source.subtotalAmount.scale,

View File

@ -117,6 +117,7 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
const issueReadModelAssembler = buildProformaIssueReadModelAssembler({
paymentMethodFinder: catalogs.paymentMethod.finder,
taxRegimeFinder: catalogs.taxRegime.finder,
});
const companyReportProfileFinder = new CompanyReportProfileFinder({
@ -188,6 +189,7 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
finder,
issuer,
issueReadModelAssembler,
issueResponseSnapshotBuilder: snapshotBuilders.issue,
repository,
transactionManager,
}),

View File

@ -5,11 +5,16 @@ import {
requireCompanyContextGuard,
} from "@erp/core/api";
import { IssueProformaByIdResponseSchema } from "../../../../../common";
import type { IssueProformaUseCase } from "../../../../application/proformas";
import type { IIssueProformaByIdRequestMapper } from "../mappers";
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts";
export class IssueProformaController extends ExpressController {
public constructor(private readonly useCase: IssueProformaUseCase) {
public constructor(
private readonly useCase: IssueProformaUseCase,
private readonly requestMapper: IIssueProformaByIdRequestMapper
) {
super();
this.errorMapper = proformasApiErrorMapper;
@ -27,15 +32,26 @@ export class IssueProformaController extends ExpressController {
return this.forbiddenError("Tenant ID not found");
}
const { proforma_id } = this.req.params;
if (!proforma_id) {
const params = this.req.params;
const proformaId = params.id ?? params.proforma_id;
if (!proformaId) {
return this.invalidInputError("Proforma ID missing");
}
const result = await this.useCase.execute({ proforma_id, companyId });
const command = this.requestMapper.map({
companyId: companyId.toString(),
params: params as
| { id: string }
| {
proforma_id: string;
},
});
const result = await this.useCase.execute(command);
return result.match(
(data) => this.ok(data),
(data) => this.ok(IssueProformaByIdResponseSchema.parse(data)),
(err) => this.handleError(err)
);
}

View File

@ -1,2 +1,3 @@
export * from "./controllers";
export * from "./mappers";
export * from "./proformas.routes";

View File

@ -0,0 +1 @@
export * from "./issue-proforma-by-id-request.mapper";

View File

@ -0,0 +1,27 @@
import type {
IssueProformaByIdParamsRequestDTO,
LegacyIssueProformaByIdParamsRequestDTO,
} from "../../../../../common";
import type { IssueProformaCommand } from "../../../../application/proformas";
type IssueProformaRequestMapperInput = {
companyId: string;
params: IssueProformaByIdParamsRequestDTO | LegacyIssueProformaByIdParamsRequestDTO;
actorUserId?: string;
};
export interface IIssueProformaByIdRequestMapper {
map(params: IssueProformaRequestMapperInput): IssueProformaCommand;
}
export class IssueProformaByIdRequestMapper implements IIssueProformaByIdRequestMapper {
map(params: IssueProformaRequestMapperInput): IssueProformaCommand {
const proformaId = "id" in params.params ? params.params.id : params.params.proforma_id;
return {
companyId: params.companyId,
proformaId,
actorUserId: params.actorUserId,
};
}
}

View File

@ -15,6 +15,7 @@ import {
ChangeStatusProformaByIdParamsRequestSchema,
ChangeStatusProformaByIdRequestSchema,
CreateProformaRequestSchema,
LegacyIssueProformaByIdParamsRequestSchema,
GetProformaByIdRequestSchema,
IssueProformaByIdParamsRequestSchema,
ListProformasRequestSchema,
@ -27,6 +28,7 @@ import type { IIssuedInvoicePublicServices } from "../../../application";
import { ChangeStatusProformaController } from "./controllers/change-status-proforma.controller";
import { CreateProformaController } from "./controllers/create-proforma.controller";
import { IssueProformaByIdRequestMapper } from "./mappers";
import { UpdateProformaController } from "./controllers/update-proforma.controller";
export const proformasRouter = (params: StartParams) => {
@ -141,17 +143,29 @@ export const proformasRouter = (params: StartParams) => {
}
);
const issueProformaHandler = (req: Request, res: Response, next: NextFunction) => {
const useCase = deps.useCases.issueProforma(publicServices);
const requestMapper = new IssueProformaByIdRequestMapper();
const controller = new IssueProformaController(useCase, requestMapper);
return controller.execute(req, res, next);
};
router.post(
"/:id/issue",
validateRequest(IssueProformaByIdParamsRequestSchema, "params"),
issueProformaHandler
);
router.post(
"/:proforma_id/issue",
validateRequest(LegacyIssueProformaByIdParamsRequestSchema, "params"),
issueProformaHandler
);
router.put(
"/:proforma_id/issue",
//checkTabContext,
validateRequest(IssueProformaByIdParamsRequestSchema, "params"),
(req: Request, res: Response, next: NextFunction) => {
const useCase = deps.useCases.issueProforma(publicServices);
const controller = new IssueProformaController(useCase);
return controller.execute(req, res, next);
}
validateRequest(LegacyIssueProformaByIdParamsRequestSchema, "params"),
issueProformaHandler
);
app.use(`${config.server.apiBasePath}/proformas`, router);

View File

@ -1,6 +1,6 @@
import type { UniqueID } from "@repo/rdx-ddd";
import { type Maybe, Result } from "@repo/rdx-utils";
import { type Transaction, type WhereOptions, literal } from "sequelize";
import { type Transaction, type WhereOptions } from "sequelize";
import type { IProformaNumberGenerator } from "../../../../../application/proformas";
import { InvoiceNumber, type InvoiceSerie } from "../../../../../domain";
@ -11,6 +11,9 @@ import { CustomerInvoiceModel } from "../../../../common/persistence";
* La serie elegida en la proforma solo referencia la futura InvoiceSeries.
*/
export class SequelizeProformaNumberGenerator implements IProformaNumberGenerator {
private static readonly PREFIX = "PF-";
private static readonly DIGITS = 4;
public async getNextForCompany(
companyId: UniqueID,
_series: Maybe<InvoiceSerie>,
@ -22,24 +25,20 @@ export class SequelizeProformaNumberGenerator implements IProformaNumberGenerato
};
try {
const lastInvoice = await CustomerInvoiceModel.findOne({
const existingInvoices = await CustomerInvoiceModel.findAll({
attributes: ["invoice_number"],
where,
// Orden numérico real: CAST(... AS UNSIGNED)
order: [literal("CAST(invoice_number AS UNSIGNED) DESC")],
transaction,
raw: true,
// Bloqueo opcional para evitar carreras si estás dentro de una TX
lock: transaction.LOCK.UPDATE, // requiere InnoDB y TX abierta
lock: transaction.LOCK.UPDATE,
});
let nextValue = "0001"; // valor inicial por defecto
const maxSequence = existingInvoices.reduce((max, currentInvoice) => {
const currentSequence = this.extractSequence(currentInvoice.invoice_number);
return Math.max(max, currentSequence);
}, 0);
if (lastInvoice) {
const current = Number(lastInvoice.invoice_number);
const next = Number.isFinite(current) && current > 0 ? current + 1 : 1;
nextValue = String(next).padStart(4, "0");
}
const nextValue = this.formatSequence(maxSequence + 1);
const numberResult = InvoiceNumber.create(nextValue);
if (numberResult.isFailure) {
@ -50,9 +49,27 @@ export class SequelizeProformaNumberGenerator implements IProformaNumberGenerato
} catch (error) {
return Result.fail(
new Error(
`Error generating invoice number for company ${companyId}: ${(error as Error).message}`
`Error generating proforma number for company ${companyId}: ${(error as Error).message}`
)
);
}
}
private extractSequence(invoiceNumber: string): number {
const normalized = invoiceNumber.trim().toUpperCase();
if (normalized.startsWith(SequelizeProformaNumberGenerator.PREFIX)) {
const suffix = normalized.slice(SequelizeProformaNumberGenerator.PREFIX.length);
const parsed = Number(suffix);
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
}
const parsed = Number(normalized);
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
}
private formatSequence(sequence: number): string {
const padded = String(sequence).padStart(SequelizeProformaNumberGenerator.DIGITS, "0");
return `${SequelizeProformaNumberGenerator.PREFIX}${padded}`;
}
}

View File

@ -28,7 +28,7 @@ export type CreateProformaItemRequestDTO = z.infer<typeof CreateProformaItemRequ
export const CreateProformaRequestSchema = z.object({
id: z.uuid(),
invoice_number: z.string(),
invoice_number: z.string().nullable().optional(),
series: z.string().nullable(),
invoice_date: IsoDateSchema,

View File

@ -1,9 +1,17 @@
import { z } from "zod/v4";
export const IssueProformaByIdParamsRequestSchema = z.object({
proforma_id: z.string(),
id: z.string(),
});
export type IssueProformaByIdParamsRequestDTO = z.infer<
typeof IssueProformaByIdParamsRequestSchema
>;
export const LegacyIssueProformaByIdParamsRequestSchema = z.object({
proforma_id: z.string(),
});
export type LegacyIssueProformaByIdParamsRequestDTO = z.infer<
typeof LegacyIssueProformaByIdParamsRequestSchema
>;

View File

@ -46,7 +46,7 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
payment_method: PaymentMethodRefSchema.nullable(),
tax_regime: TaxRegimeRefSchema.nullable(),
tax_regime: TaxRegimeRefSchema,
subtotal_amount: MoneySchema,

View File

@ -1,9 +1,35 @@
import { CurrencyCodeSchema, IsoDateSchema, MoneySchema } from "@erp/core";
import { z } from "zod/v4";
import { IssuedInvoiceStatusSchema, TaxRegimeRefSchema } from "../../shared";
export const IssueProformaIssuedInvoiceRecipientSchema = z.object({
name: z.string(),
tin: z.string().nullable(),
});
export const IssueProformaIssuedInvoiceTotalsSchema = z.object({
taxable_base: MoneySchema,
tax_total: MoneySchema,
retention_total: MoneySchema,
grand_total: MoneySchema,
currency_code: CurrencyCodeSchema,
});
export const IssueProformaIssuedInvoiceSchema = z.object({
id: z.uuid(),
proforma_id: z.uuid().nullable(),
series: z.string(),
invoice_number: z.string(),
invoice_date: IsoDateSchema,
status: IssuedInvoiceStatusSchema,
recipient: IssueProformaIssuedInvoiceRecipientSchema,
tax_regime: TaxRegimeRefSchema,
totals: IssueProformaIssuedInvoiceTotalsSchema,
});
export const IssueProformaByIdResponseSchema = z.object({
issuedinvoice_id: z.string(),
proforma_id: z.string(),
customer_id: z.string(),
issued_invoice: IssueProformaIssuedInvoiceSchema,
});
export type IssueProformaByIdResponseDTO = z.infer<typeof IssueProformaByIdResponseSchema>;

View File

@ -2,7 +2,7 @@ import { z } from "zod/v4";
export const TaxRegimeRefSchema = z.object({
code: z.string(),
description: z.string(),
description: z.string().nullable(),
});
export type TaxRegimeRefDTO = z.infer<typeof TaxRegimeRefSchema>;