v0.6.0 - Subida a producción

This commit is contained in:
David Arranz 2026-04-21 15:51:57 +02:00
parent 67dc91eced
commit 330240deaa
56 changed files with 320 additions and 239 deletions

2
.vscode/launch.json vendored
View File

@ -1,5 +1,5 @@
{
"version": "0.5.0",
"version": "0.6.0",
"configurations": [
{
"name": "WEB: Vite (Chrome)",

View File

@ -1,6 +1,6 @@
{
"name": "@erp/factuges-server",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"scripts": {
"build": "tsup src/index.ts --config tsup.config.ts",
@ -37,8 +37,6 @@
"@erp/core": "workspace:*",
"@erp/customer-invoices": "workspace:*",
"@erp/customers": "workspace:*",
"@erp/factuges": "workspace:*",
"@erp/suppliers": "workspace:*",
"@repo/rdx-logger": "workspace:*",
"@repo/rdx-utils": "workspace:*",
"bcrypt": "^6.0.0",

View File

@ -1,7 +1,7 @@
import customerInvoicesAPIModule from "@erp/customer-invoices/api";
import customersAPIModule from "@erp/customers/api";
import factuGESAPIModule from "@erp/factuges/api";
import suppliersAPIModule from "@erp/suppliers/api";
//import suppliersAPIModule from "@erp/suppliers/api";
import { registerModule } from "./lib";
@ -9,6 +9,5 @@ export const registerModules = () => {
//registerModule(authAPIModule);
registerModule(customersAPIModule);
registerModule(customerInvoicesAPIModule);
registerModule(factuGESAPIModule);
registerModule(suppliersAPIModule);
//registerModule(suppliersAPIModule);
};

View File

@ -1,7 +1,7 @@
{
"name": "@erp/factuges-web",
"private": true,
"version": "0.5.0",
"version": "0.6.0",
"type": "module",
"scripts": {
"dev": "vite --host --clearScreen false",

View File

@ -1,6 +1,5 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},

View File

@ -4,7 +4,6 @@
"compilerOptions": {
"resolveJsonModule": true,
"esModuleInterop": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}

View File

@ -1,6 +1,6 @@
{
"name": "@erp/auth",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,7 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@erp/auth/*": ["./src/*"]
},

View File

@ -1,6 +1,6 @@
{
"name": "@erp/core",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,3 +0,0 @@
export const formatDate = (value: string) => {
return new Date(value).toLocaleDateString();
};

View File

@ -1,3 +1,2 @@
export * from "./date-func";
export * from "./form-utils";
export * from "./http-url-utils";

View File

@ -1,7 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@erp/core/*": ["./src/*"]
},

View File

@ -1,6 +1,6 @@
{
"name": "@erp/customer-invoices",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,5 +1,5 @@
import type { UniqueID } from "@repo/rdx-ddd";
import type { Maybe, Result } from "@repo/rdx-utils";
import type { Result } from "@repo/rdx-utils";
import type { InvoiceNumber, InvoiceSerie } from "../../../domain";
@ -13,7 +13,7 @@ export interface IIssuedInvoiceNumberGenerator {
*/
getNextForCompany(
companyId: UniqueID,
series: Maybe<InvoiceSerie>,
series: InvoiceSerie,
transaction: unknown
): Promise<Result<InvoiceNumber, Error>>;
}

View File

@ -98,15 +98,15 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
companyId: proforma.companyId,
status: InvoiceStatus.issued(),
series: proforma.series,
proformaId: proforma.id,
series: proforma.series.getOrUndefined()!,
linkedProformaId: proforma.id,
invoiceNumber: proforma.invoiceNumber,
invoiceDate: UtcDate.today(), // La fecha de la factura es la fecha de emisión, no la de la proforma
operationDate: proforma.operationDate,
description: proforma.description,
description: proforma.description.getOrUndefined()!,
languageCode: proforma.languageCode,
currencyCode: proforma.currencyCode,
@ -116,7 +116,7 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
paymentMethod: proforma.paymentMethod,
customerId: proforma.customerId,
recipient: proforma.recipient,
recipient: proforma.recipient.getOrUndefined()!,
items: issuedItems,

View File

@ -53,9 +53,7 @@ export class IssuedInvoiceFullSnapshotBuilder implements IIssuedInvoiceFullSnaps
customer_id: invoice.customerId.toString(),
recipient,
linked_proforma_id: maybeToNullable(invoice.linkedProformaId, (linkedId) =>
linkedId.toString()
),
linked_proforma_id: invoice.linkedProformaId.toString(),
taxes,

View File

@ -1,2 +1 @@
export * from "./proforma-summary-snapshot.interface";
export * from "./proforma-summary-snapshot-builder";

View File

@ -1,15 +1,14 @@
import type { ISnapshotBuilder } from "@erp/core/api";
import { maybeToEmptyString, maybeToNullable } from "@repo/rdx-ddd";
import type { ProformaSummaryDTO } from "../../../../../common";
import type { ProformaSummary } from "../../models";
import type { IProformaSummarySnapshot } from "./proforma-summary-snapshot.interface";
export interface IProformaSummarySnapshotBuilder
extends ISnapshotBuilder<ProformaSummary, IProformaSummarySnapshot> {}
extends ISnapshotBuilder<ProformaSummary, ProformaSummaryDTO> {}
export class ProformaSummarySnapshotBuilder implements IProformaSummarySnapshotBuilder {
toOutput(proforma: ProformaSummary): IProformaSummarySnapshot {
toOutput(proforma: ProformaSummary): ProformaSummaryDTO {
const recipient = proforma.recipient.toObjectString();
return {
@ -18,7 +17,7 @@ export class ProformaSummarySnapshotBuilder implements IProformaSummarySnapshotB
is_proforma: proforma.isProforma,
invoice_number: proforma.invoiceNumber.toString(),
status: proforma.status.toPrimitive(),
status: proforma.status.toPrimitive() as ProformaSummaryDTO["status"],
series: maybeToNullable(proforma.series, (value) => value.toString()),
invoice_date: proforma.invoiceDate.toDateString(),

View File

@ -1,42 +0,0 @@
/**
* Fijarse en ListProformasResponseDTO["items"]
*/
export interface IProformaSummarySnapshot {
id: string;
company_id: string;
is_proforma: boolean;
invoice_number: string;
status: string;
series: string | null;
invoice_date: string;
operation_date: string | null;
language_code: string;
currency_code: string;
reference: string | null;
description: string | null;
customer_id: string;
recipient: {
id: string;
tin: string;
name: string;
street: string | null;
street2: string | null;
city: string | null;
postal_code: string | null;
province: string | null;
country: string | null;
};
subtotal_amount: { value: string; scale: string; currency_code: string };
total_discount_amount: { value: string; scale: string; currency_code: string };
taxable_amount: { value: string; scale: string; currency_code: string };
taxes_amount: { value: string; scale: string; currency_code: string };
total_amount: { value: string; scale: string; currency_code: string };
linked_invoice_id: string | null;
}

View File

@ -32,7 +32,7 @@ export interface IIssuedInvoiceCreateProps {
companyId: UniqueID;
status: InvoiceStatus;
linkedProformaId: Maybe<UniqueID>; // <- id de la proforma padre en caso de issue
linkedProformaId: UniqueID; // <- id de la proforma padre en caso de issue
series: InvoiceSerie;
invoiceNumber: InvoiceNumber;
@ -178,7 +178,7 @@ export class IssuedInvoice
return this.props.customerId;
}
public get linkedProformaId(): Maybe<UniqueID> {
public get linkedProformaId(): UniqueID {
return this.props.linkedProformaId;
}

View File

@ -304,6 +304,97 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
);
}
if (this.series.isNone()) {
return Result.fail(
new DomainValidationError(
"MISSING_SERIES",
"series",
"Series is required to issue the proforma"
)
);
}
if (this.description.isNone()) {
return Result.fail(
new DomainValidationError(
"MISSING_DESCRIPTION",
"description",
"Description is required to issue the proforma"
)
);
}
if (this.recipient.isNone()) {
return Result.fail(
new DomainValidationError(
"MISSING_RECIPIENT",
"recipient",
"Recipient is required to issue the proforma"
)
);
}
if (this.items.size() === 0) {
return Result.fail(
new DomainValidationError(
"NO_ITEMS",
"items",
"At least one item is required to issue the proforma"
)
);
}
const invalidItem = this.items.find((item) => !item.isValued());
if (invalidItem) {
return Result.fail(
new DomainValidationError(
"INVALID_ITEM",
"items",
`Item at position ${invalidItem.id} is not valid for invoicing`
)
);
}
if (this.paymentMethod.isNone()) {
return Result.fail(
new DomainValidationError(
"MISSING_PAYMENT_METHOD",
"paymentMethod",
"Payment method is required to issue the proforma"
)
);
}
/*if (this.operationDate.isSome() && this.operationDate.unwrap() > new Date()) {
return Result.fail(
new DomainValidationError(
"INVALID_OPERATION_DATE",
"operationDate",
"Operation date cannot be in the future"
)
);
}
if (this.operationDate.isSome() && this.operationDate.unwrap() < this.invoiceDate) {
return Result.fail(
new DomainValidationError(
"INVALID_OPERATION_DATE",
"operationDate",
"Operation date cannot be before invoice date"
)
);
}*/
if (this.linkedInvoiceId.isSome()) {
return Result.fail(
new DomainValidationError(
"LINKED_INVOICE_NOT_ALLOWED",
"linkedInvoiceId",
"Proforma cannot be linked to an invoice"
)
);
}
this.props.status = InvoiceStatus.issued();
return Result.ok();
}

View File

@ -64,7 +64,7 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
const linkedProformaId = extractOrPushError(
maybeFromNullableResult(raw.proforma_id, (v) => UniqueID.create(String(v))),
UniqueID.create(String(raw.proforma_id)),
"proforma_id",
errors
);
@ -435,6 +435,7 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
}
// 3) Cliente
console.debug(source.recipient);
const recipient = this._recipientMapper.mapToPersistence(source.recipient, {
errors,
parent: source,
@ -450,6 +451,7 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
// 5) Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) {
console.error("Errors mapping issued invoice to persistence:", errors);
return Result.fail(
new ValidationErrorCollection("Customer invoice mapping to persistence failed", errors)
);
@ -467,7 +469,7 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
// Flags / estado / serie / número
is_proforma: false,
status: source.status.toPrimitive(),
proforma_id: maybeToNullable(source.linkedProformaId, (v) => v.toPrimitive()),
proforma_id: source.linkedProformaId.toPrimitive(),
series: source.series.toPrimitive(),
invoice_number: source.invoiceNumber.toPrimitive(),

View File

@ -15,11 +15,7 @@ import {
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import {
type IIssuedInvoiceCreateProps,
InvoiceRecipient,
type IssuedInvoice,
} from "../../../../../../domain";
import { type IIssuedInvoiceCreateProps, InvoiceRecipient } from "../../../../../../domain";
import type { CustomerInvoiceModel } from "../../../../../common";
export class SequelizeIssuedInvoiceRecipientDomainMapper {
@ -118,27 +114,10 @@ export class SequelizeIssuedInvoiceRecipientDomainMapper {
* En caso contrario, se agrega un error de validación.
*/
mapToPersistence(source: InvoiceRecipient, params?: MapperParamsType) {
const { errors, parent } = params as {
/*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;

View File

@ -1,5 +1,5 @@
import type { UniqueID } from "@repo/rdx-ddd";
import { type Maybe, Result } from "@repo/rdx-utils";
import { Result } from "@repo/rdx-utils";
import { type Transaction, type WhereOptions, literal } from "sequelize";
import type { IIssuedInvoiceNumberGenerator } from "../../../../../application/issued-invoices";
@ -12,7 +12,7 @@ import { CustomerInvoiceModel } from "../../../../common/persistence";
export class SequelizeIssuedInvoiceNumberGenerator implements IIssuedInvoiceNumberGenerator {
public async getNextForCompany(
companyId: UniqueID,
series: Maybe<InvoiceSerie>,
series: InvoiceSerie,
transaction: Transaction
): Promise<Result<InvoiceNumber, Error>> {
const where: WhereOptions = {
@ -20,14 +20,7 @@ export class SequelizeIssuedInvoiceNumberGenerator implements IIssuedInvoiceNumb
is_proforma: false,
};
series.match(
(serieVO) => {
where.series = serieVO.toString();
},
() => {
where.series = null;
}
);
where.series = series.toString();
try {
const lastInvoice = await CustomerInvoiceModel.findOne({

View File

@ -1,46 +1,8 @@
import {
CurrencyCodeSchema,
IsoDateSchema,
LanguageCodeSchema,
MoneySchema,
PercentageSchema,
createPaginatedListSchema,
} from "@erp/core";
import { z } from "zod/v4";
import { createPaginatedListSchema } from "@erp/core";
import type { z } from "zod/v4";
import { ProformaRecipientSummarySchema, ProformaStatusSchema } from "../../shared/proforma";
import { ProformaSummarySchema } from "../../shared/proforma";
export const ListProformasResponseSchema = createPaginatedListSchema(
z.object({
id: z.uuid(),
company_id: z.uuid(),
is_proforma: z.boolean(),
invoice_number: z.string(),
status: ProformaStatusSchema,
series: z.string().nullable(),
invoice_date: IsoDateSchema,
operation_date: IsoDateSchema.nullable(),
language_code: LanguageCodeSchema,
currency_code: CurrencyCodeSchema,
reference: z.string().nullable(),
description: z.string().nullable(),
customer_id: z.uuid(),
recipient: ProformaRecipientSummarySchema,
subtotal_amount: MoneySchema,
discount_percentage: PercentageSchema,
discount_amount: MoneySchema,
taxable_amount: MoneySchema,
taxes_amount: MoneySchema,
total_amount: MoneySchema,
linked_invoice_id: z.uuid().nullable(),
})
);
export const ListProformasResponseSchema = createPaginatedListSchema(ProformaSummarySchema);
export type ListProformasResponseDTO = z.infer<typeof ListProformasResponseSchema>;

View File

@ -1,3 +1,4 @@
export * from "./proforma-item-detail.dto";
export * from "./proforma-recipient-summary.dto";
export * from "./proforma-status.dto";
export * from "./proforma-summary.dto";

View File

@ -0,0 +1,37 @@
import { CurrencyCodeSchema, IsoDateSchema, LanguageCodeSchema, MoneySchema } from "@erp/core";
import { z } from "zod/v4";
import { ProformaRecipientSummarySchema } from "./proforma-recipient-summary.dto";
import { ProformaStatusSchema } from "./proforma-status.dto";
export const ProformaSummarySchema = z.object({
id: z.uuid(),
company_id: z.uuid(),
is_proforma: z.boolean(),
invoice_number: z.string(),
status: ProformaStatusSchema,
series: z.string().nullable(),
invoice_date: IsoDateSchema,
operation_date: IsoDateSchema.nullable(),
language_code: LanguageCodeSchema,
currency_code: CurrencyCodeSchema,
reference: z.string().nullable(),
description: z.string().nullable(),
customer_id: z.uuid(),
recipient: ProformaRecipientSummarySchema,
subtotal_amount: MoneySchema,
total_discount_amount: MoneySchema,
taxable_amount: MoneySchema,
taxes_amount: MoneySchema,
total_amount: MoneySchema,
linked_invoice_id: z.uuid().nullable(),
});
export type ProformaSummaryDTO = z.infer<typeof ProformaSummarySchema>;

View File

@ -1,4 +1,4 @@
import { formatDate } from "@erp/core/client";
import { DateHelper } from "@erp/core";
import { ReactQRCode } from "@lglab/react-qr-code";
import { DataTableColumnHeader } from "@repo/rdx-ui/components";
import {
@ -205,7 +205,7 @@ export function useIssuedInvoicesGridColumns(
),
cell: ({ row }) => (
<div className="font-medium text-left tabular-nums">
{formatDate(row.original.invoiceDate)}
{DateHelper.format(row.original.invoiceDate)}
</div>
),
enableSorting: false,
@ -227,7 +227,7 @@ export function useIssuedInvoicesGridColumns(
),
cell: ({ row }) => (
<div className="font-medium text-left tabular-nums">
{formatDate(row.original.operationDate)}
{DateHelper.format(row.original.operationDate)}
</div>
),
enableSorting: false,
@ -284,6 +284,27 @@ export function useIssuedInvoicesGridColumns(
},
},
// Taxable amount
{
accessorKey: "taxableAmountFmt",
header: ({ column }) => (
<DataTableColumnHeader
className="text-right tabular-nums"
column={column}
title={t("pages.issued_invoices.list.grid_columns.taxable_amount", "Base imponible")}
/>
),
cell: ({ row }) => (
<div className="font-medium text-right tabular-nums">{row.original.taxableAmountFmt}</div>
),
enableSorting: false,
size: 120,
minSize: 100,
meta: {
title: t("pages.issued_invoices.list.grid_columns.taxable_amount"),
},
},
// Taxes amount
{
accessorKey: "taxes_amount_fmt",

View File

@ -1,3 +1,4 @@
import { DateHelper } from "@erp/core";
import {
Button,
Tooltip,
@ -159,6 +160,10 @@ export function useProformasGridColumns(
accessorKey: "series",
header: "Serie",
},
{
accessorKey: "reference",
header: "Referencia",
},
{
accessorKey: "invoiceDate",
header: ({ column }) => {
@ -173,6 +178,17 @@ export function useProformasGridColumns(
</Button>
);
},
cell: ({ row }) => (
<div className="font-medium text-left tabular-nums">
{DateHelper.format(row.original.invoiceDate)}
</div>
),
enableSorting: false,
size: 140,
minSize: 120,
meta: {
title: t("pages.issued_invoices.list.grid_columns.invoice_date"),
},
},
{
accessorKey: "operationDate",
@ -193,30 +209,43 @@ export function useProformasGridColumns(
accessorKey: "subtotalAmountFmt",
header: () => <div className="text-right">Subtotal</div>,
cell: ({ row }) => (
<div className="text-right tabular-nums">{row.getValue("subtotalAmountFmt")}</div>
<div className="text-right tabular-nums font-medium">
{row.getValue("subtotalAmountFmt")}
</div>
),
},
{
accessorKey: "discountAmountFmt",
accessorKey: "totalDiscountAmountFmt",
header: () => <div className="text-right">Descuentos</div>,
cell: ({ row }) => (
<div className="text-right tabular-nums">{row.getValue("discountAmountFmt")}</div>
<div className="text-right tabular-nums font-medium">
{row.getValue("totalDiscountAmountFmt")}
</div>
),
},
{
accessorKey: "taxableAmountFmt",
header: () => <div className="text-right">Base imponible</div>,
cell: ({ row }) => (
<div className="text-right tabular-nums font-medium">
{row.getValue("taxableAmountFmt")}
</div>
),
},
{
accessorKey: "taxesAmountFmt",
header: () => <div className="text-right">Impuestos</div>,
cell: ({ row }) => (
<div className="text-right tabular-nums">{row.getValue("taxesAmountFmt")}</div>
<div className="text-right tabular-nums font-medium">
{row.getValue("taxesAmountFmt")}
</div>
),
},
{
accessorKey: "totalAmountFmt",
header: () => <div className="text-right">Importe total</div>,
cell: ({ row }) => (
<div className="text-right tabular-nums font-medium">
{row.getValue("totalAmountFmt")}
</div>
<div className="text-right tabular-nums font-bold">{row.getValue("totalAmountFmt")}</div>
),
},
{

View File

@ -1,28 +1,52 @@
import { MoneyDTOHelper, PercentageDTOHelper, formatCurrency } from "@erp/core";
import { MoneyDTOHelper, formatCurrency } from "@erp/core";
import type { ListProformasResponseDTO } from "../../../../common";
import type { ListProformasResult } from "../api";
import type { ProformaList, ProformaListRow, ProformaStatus } from "../entities";
/**
* Adaptador para transformar los datos de la API de ListProformasResponseDTO
* a la entidad ProformaList utilizada en la aplicación.
* Reglas de adaptación:
* - page, per_page, total_pages, total_items se asignan directamente.
* - items se transforma utilizando ProformaListRowAdapter para cada elemento.
*
* @param pageDto - lista de proformas desde la API.
* @param context - Contexto adicional opcional para la adaptación.
* @returns {ProformaList} Objeto adaptado a ProformaList.
*/
export const ListProformasAdapter = {
fromDto(dto: ListProformasResponseDTO): ProformaList {
fromDto(dto: ListProformasResult, context?: unknown): ProformaList {
return {
items: dto.items.map(ProformaListRowAdapter.fromDto),
page: dto.page,
perPage: dto.per_page,
totalPages: dto.total_pages,
totalItems: dto.total_items,
items: dto.items.map((rowDto) => ProformaListRowAdapter.fromDto(rowDto, context)),
};
},
};
type ListProformasItemDTO = ListProformasResponseDTO["items"][number];
/**
* Adaptador para transformar los items de la API de ListProformasResult a la entidad ProformaListRow.
* Reglas de adaptación:
* - id, company_id, status, reference se asignan directamente.
* - is_proforma se convierte a booleano (true si es "1", false si es "0").
*
* @param rowDto - item de proforma desde la API.
* @param context - Contexto adicional opcional para la adaptación.
* @returns {ProformaListRow} Objeto adaptado a ProformaListRow.
*/
type ListProformasItemOutput = ListProformasResponseDTO["items"][number];
const ProformaListRowAdapter = {
fromDto(dto: ListProformasItemDTO): ProformaListRow {
fromDto(dto: ListProformasItemOutput, context?: unknown): ProformaListRow {
return {
id: dto.id,
companyId: dto.company_id,
isProforma: dto.is_proforma === "1",
isProforma: dto.is_proforma,
invoiceNumber: dto.invoice_number,
status: dto.status as ProformaStatus,
@ -57,12 +81,9 @@ const ProformaListRowAdapter = {
dto.language_code
),
discountPercentage: PercentageDTOHelper.toNumber(dto.discount_percentage),
discountPercentageFmt: PercentageDTOHelper.toNumericString(dto.discount_percentage),
discountAmount: MoneyDTOHelper.toNumber(dto.discount_amount),
discountAmountFmt: formatCurrency(
MoneyDTOHelper.toNumber(dto.discount_amount),
totalDiscountAmount: MoneyDTOHelper.toNumber(dto.total_discount_amount),
totalDiscountAmountFmt: formatCurrency(
MoneyDTOHelper.toNumber(dto.total_discount_amount),
Number(dto.total_amount.scale || 2),
dto.currency_code,
dto.language_code

View File

@ -15,27 +15,24 @@ export interface ProformaListRow {
invoiceNumber: string;
status: ProformaStatus;
series: string;
series: string | null;
invoiceDate: string;
operationDate: string;
operationDate: string | null;
languageCode: string;
currencyCode: string;
reference: string;
description: string;
reference: string | null;
description: string | null;
recipient: ProformaRecipient;
subtotalAmount: number;
subtotalAmountFmt: string;
discountPercentage: number;
discountPercentageFmt: string;
discountAmount: number;
discountAmountFmt: string;
totalDiscountAmount: number;
totalDiscountAmountFmt: string;
taxableAmount: number;
taxableAmountFmt: string;
@ -46,5 +43,5 @@ export interface ProformaListRow {
totalAmount: number;
totalAmountFmt: string;
linkedInvoiceId: string;
linkedInvoiceId: string | null;
}

View File

@ -5,14 +5,14 @@
export interface ProformaRecipient {
id: string;
name: string;
tin: string;
name: string | null;
tin: string | null;
street: string;
street2: string;
street: string | null;
street2: string | null;
city: string;
province: string;
postalCode: string;
country: string;
city: string | null;
province: string | null;
postalCode: string | null;
country: string | null;
}

View File

@ -1,7 +1,8 @@
import type { CustomerSelectionOption } from "@erp/customers";
import { FormSectionCard } from "@repo/rdx-ui/components";
import { useTranslation } from "../../../../i18n";
import { FormSectionCard, SelectedRecipientSummary } from "../blocks";
import { SelectedRecipientSummary } from "../blocks";
interface ProformaUpdateRecipientEditorProps {
disabled?: boolean;

View File

@ -1,7 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@erp/customer-invoices/*": ["./src/*"]
},

View File

@ -1,7 +1,7 @@
{
"name": "@erp/customers",
"description": "Customers",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,3 +1,4 @@
import { DateHelper } from "@erp/core";
import { Badge, Button } from "@repo/shadcn-ui/components";
import { FileTextIcon } from "lucide-react";
@ -69,7 +70,7 @@ export const CustomerProformasSection = ({
>
<div className="min-w-0">
<p className="text-sm font-medium text-foreground truncate">{pro.number}</p>
<p className="text-xs text-muted-foreground">{formatDate(pro.date)}</p>
<p className="text-xs text-muted-foreground">{DateHelper.format(pro.date)}</p>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="text-sm font-medium">{formatCurrency(pro.amount)}</span>

View File

@ -1,7 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@erp/customers/*": ["./src/*"]
},
@ -28,11 +27,6 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": [
"src",
"../customer-invoices/src/web/proformas/update/ui/blocks/selected-recipient/customer-view-dialog.tsx",
"../customer-invoices/src/web/proformas/update/ui/blocks/selected-recipient/customer-card.tsx",
"../customer-invoices/src/web/proformas/update/ui/blocks/selected-recipient/selected-recipient-empty-card.tsx"
],
"include": ["src"],
"exclude": ["node_modules"]
}

View File

@ -1,6 +1,6 @@
{
"name": "@erp/factuges",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -4,10 +4,10 @@ import {
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import type { CreateProformaFromFactugesUseCase } from "@erp/factuges/api/application";
import type { CreateProformaFromFactugesRequestDTO } from "@erp/factuges/common";
import type { CreateProformaFromFactugesRequestDTO } from "../../../../common/dto/request/create-proforma-from-factuges.request.dto.js";
import type { CreateProformaFromFactugesUseCase } from "../../../application/use-cases/create-proforma-from-factuges.use-case.js";
import { factugesApiErrorMapper } from "../factuges-api-error-mapper.js";
import { factugesApiErrorMapper } from "../factuges-api-error-mapper";
export class CreateProformaFromFactugesController extends ExpressController {
public constructor(private readonly useCase: CreateProformaFromFactugesUseCase) {

View File

@ -1,7 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@erp/factuges/*": ["./src/*"]
},

View File

@ -1,7 +1,7 @@
{
"name": "@erp/supplier-invoices",
"description": "Supplier invoices",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,7 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@erp/supplier-invoices/*": ["./src/*"]
},

View File

@ -1,7 +1,7 @@
{
"name": "@erp/suppliers",
"description": "Suppliers",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,7 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@erp/suppliers/*": ["./src/*"]
},

View File

@ -1,6 +1,6 @@
{
"name": "@repo/rdx-criteria",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,6 +1,6 @@
{
"name": "@repo/rdx-ddd",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,5 +1,6 @@
import { ZodError } from "zod/v4";
import { ValidationErrorCollection, ValidationErrorDetail } from "../errors";
import type { ZodError } from "zod/v4";
import { ValidationErrorCollection, type ValidationErrorDetail } from "../errors";
export function translateZodValidationError<T>(
message: string,

View File

@ -1,6 +1,8 @@
import { Result } from "@repo/rdx-utils";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";
interface UtcDateProps {
@ -101,4 +103,15 @@ export class UtcDate extends ValueObject<UtcDateProps> {
equals(other: UtcDate): boolean {
return this.toISOString() === other.toISOString();
}
/**
* Determina si la fecha representada
* es una fecha futura en comparación con la fecha actual.
* @returns
*/
isFuture(currentDate?: UtcDate): boolean {
const now = currentDate ? currentDate : UtcDate.today();
return this.date.getTime() > now.getTime();
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@repo/rdx-logger",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,7 +1,6 @@
{
"extends": "@repo/typescript-config/react-library.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@repo/rdx-ui/*": ["./src/*"]
},

View File

@ -1,6 +1,6 @@
{
"name": "@repo/rdx-utils",
"version": "0.5.0",
"version": "0.6.0",
"private": true,
"type": "module",
"sideEffects": false,

View File

@ -1,8 +1,9 @@
{
"extends": "@repo/typescript-config/buildless.json",
"compilerOptions": {
"types": ["node"],
"rootDir": "src"
},
"include": ["src", "../../modules/core/src/api/application/mappers/patch-collector.ts"],
"include": ["src"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}

View File

@ -1,16 +1,21 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Root",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@erp/core/*": ["modules/core/src/*"],
"@erp/auth/*": ["modules/auth/src/*"],
"@erp/customers/*": ["modules/customers/src/*"],
"@erp/customer-invoices/*": ["modules/customer-invoices/src/*"]
"@erp/core/*": [
"modules/core/src/*"
],
"@erp/auth/*": [
"modules/auth/src/*"
],
"@erp/customers/*": [
"modules/customers/src/*"
],
"@erp/customer-invoices/*": [
"modules/customer-invoices/src/*"
]
},
"target": "ES2021",
"module": "CommonJS",
"moduleResolution": "Node",
@ -20,4 +25,4 @@
"skipLibCheck": true,
"allowUnreachableCode": true
}
}
}

View File

@ -65,12 +65,6 @@ importers:
'@erp/customers':
specifier: workspace:*
version: link:../../modules/customers
'@erp/factuges':
specifier: workspace:*
version: link:../../modules/factuges
'@erp/suppliers':
specifier: workspace:*
version: link:../../modules/supplier
'@repo/rdx-logger':
specifier: workspace:*
version: link:../../packages/rdx-logger