Repaso de proformas
This commit is contained in:
parent
ba67750e17
commit
6ea2e2df5b
@ -63,6 +63,7 @@ export class ReportProformaUseCase {
|
|||||||
if (documentResult.isFailure) {
|
if (documentResult.isFailure) {
|
||||||
return Result.fail(documentResult.error);
|
return Result.fail(documentResult.error);
|
||||||
}
|
}
|
||||||
|
11;
|
||||||
|
|
||||||
// 5. Devolver artefacto firmado
|
// 5. Devolver artefacto firmado
|
||||||
return Result.ok({
|
return Result.ok({
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
CreateProformaRequestSchema,
|
CreateProformaRequestSchema,
|
||||||
GetProformaByIdRequestSchema,
|
GetProformaByIdRequestSchema,
|
||||||
IssueProformaByIdParamsRequestSchema,
|
IssueProformaByIdResponseSchema,
|
||||||
ListProformasRequestSchema,
|
ListProformasRequestSchema,
|
||||||
ReportProformaByIdParamsRequestSchema,
|
ReportProformaByIdParamsRequestSchema,
|
||||||
ReportProformaByIdQueryRequestSchema,
|
ReportProformaByIdQueryRequestSchema,
|
||||||
@ -148,7 +148,7 @@ export const proformasRouter = (params: StartParams) => {
|
|||||||
"/:proforma_id/issue",
|
"/:proforma_id/issue",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
|
|
||||||
validateRequest(IssueProformaByIdParamsRequestSchema, "params"),
|
validateRequest(IssueProformaByIdResponseSchema, "params"),
|
||||||
|
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.issueProforma(publicServices);
|
const useCase = deps.useCases.issueProforma(publicServices);
|
||||||
|
|||||||
@ -3,3 +3,7 @@ import { z } from "zod/v4";
|
|||||||
export const IssueProformaByIdParamsRequestSchema = z.object({
|
export const IssueProformaByIdParamsRequestSchema = z.object({
|
||||||
proforma_id: z.string(),
|
proforma_id: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type IssueProformaByIdParamsRequestDTO = z.infer<
|
||||||
|
typeof IssueProformaByIdParamsRequestSchema
|
||||||
|
>;
|
||||||
|
|||||||
@ -1,6 +1,16 @@
|
|||||||
import { MoneySchema, PercentageSchema, QuantitySchema } from "@erp/core";
|
import { NumericStringSchema, PercentageSchema } from "@erp/core";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const UpdateProformaItemRequestSchema = z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
position: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
quantity: NumericStringSchema.optional(),
|
||||||
|
unit_amount: NumericStringSchema.optional(),
|
||||||
|
item_discount_percentage: PercentageSchema.optional(),
|
||||||
|
taxes: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export const UpdateProformaByIdParamsRequestSchema = z.object({
|
export const UpdateProformaByIdParamsRequestSchema = z.object({
|
||||||
proforma_id: z.string(),
|
proforma_id: z.string(),
|
||||||
});
|
});
|
||||||
@ -20,22 +30,7 @@ export const UpdateProformaByIdRequestSchema = z.object({
|
|||||||
language_code: z.string().optional(),
|
language_code: z.string().optional(),
|
||||||
currency_code: z.string().optional(),
|
currency_code: z.string().optional(),
|
||||||
|
|
||||||
items: z
|
items: z.array(UpdateProformaItemRequestSchema).default([]),
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
is_valued: z.string().optional(),
|
|
||||||
|
|
||||||
description: z.string().optional(),
|
|
||||||
quantity: QuantitySchema.optional(),
|
|
||||||
unit_amount: MoneySchema.optional(),
|
|
||||||
|
|
||||||
discount_percentage: PercentageSchema.optional(),
|
|
||||||
|
|
||||||
tax_codes: z.array(z.string()).default([]),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional()
|
|
||||||
.default([]),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type UpdateProformaByIdRequestDTO = Partial<z.infer<typeof UpdateProformaByIdRequestSchema>>;
|
export type UpdateProformaByIdRequestDTO = Partial<z.infer<typeof UpdateProformaByIdRequestSchema>>;
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
export * from "./create-proforma.response.dto";
|
export * from "./create-proforma.response.dto";
|
||||||
export * from "./get-proforma-by-id.response.dto";
|
export * from "./get-proforma-by-id.response.dto";
|
||||||
|
export * from "./issue-proforma-by-id.response.dto";
|
||||||
export * from "./list-proformas.response.dto";
|
export * from "./list-proformas.response.dto";
|
||||||
|
export * from "./report-proforma-by-id.response.dto";
|
||||||
|
export * from "./update-proformas-by-id.response.dto";
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const IssueProformaByIdResponseSchema = z.object({
|
||||||
|
issuedinvoice_id: z.string(),
|
||||||
|
proforma_id: z.string(),
|
||||||
|
customer_id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type IssueProformaByIdResponseDTO = z.infer<typeof IssueProformaByIdResponseSchema>;
|
||||||
@ -10,7 +10,7 @@ export const ListProformasResponseSchema = createPaginatedListSchema(
|
|||||||
z.object({
|
z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
company_id: z.uuid(),
|
company_id: z.uuid(),
|
||||||
is_proforma: z.boolean(),
|
is_proforma: z.string(),
|
||||||
|
|
||||||
customer_id: z.string(),
|
customer_id: z.string(),
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export type ReportProformaByIdResponseDTO = Blob;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import {
|
||||||
|
type GetProformaByIdResponseDTO,
|
||||||
|
GetProformaByIdResponseSchema,
|
||||||
|
} from "./get-proforma-by-id.response.dto";
|
||||||
|
|
||||||
|
export const UpdateProformaByIdResponseSchema = GetProformaByIdResponseSchema;
|
||||||
|
export type UpdateProformaByIdResponseDTO = GetProformaByIdResponseDTO;
|
||||||
@ -3,7 +3,7 @@ import { lazy } from "react";
|
|||||||
import { Outlet, type RouteObject } from "react-router-dom";
|
import { Outlet, type RouteObject } from "react-router-dom";
|
||||||
|
|
||||||
const ProformaLayout = lazy(() =>
|
const ProformaLayout = lazy(() =>
|
||||||
import("./proformas/shared").then((m) => ({ default: m.ProformaLayout }))
|
import("./proformas/shared2").then((m) => ({ default: m.ProformaLayout }))
|
||||||
);
|
);
|
||||||
|
|
||||||
const ProformasListPage = lazy(() =>
|
const ProformasListPage = lazy(() =>
|
||||||
|
|||||||
@ -38,8 +38,8 @@ import { useFieldArray, useForm } from "react-hook-form";
|
|||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
|
|
||||||
import { useTranslation } from "../../i18n";
|
import { useTranslation } from "../../i18n";
|
||||||
import { CustomerInvoicePricesCard } from "../../proformas/shared/ui/components";
|
import { CustomerInvoicePricesCard } from "../../proformas/shared2/ui/components";
|
||||||
import { CustomerInvoiceItemsCardEditor } from "../../proformas/shared/ui/components/items";
|
import { CustomerInvoiceItemsCardEditor } from "../../proformas/shared2/ui/components/items";
|
||||||
|
|
||||||
import type { CustomerInvoiceData } from "./customer-invoice.schema";
|
import type { CustomerInvoiceData } from "./customer-invoice.schema";
|
||||||
import { formatCurrency } from "./utils";
|
import { formatCurrency } from "./utils";
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { useTranslation } from "../../../i18n";
|
import { useTranslation } from "../../../i18n";
|
||||||
import type { ProformaListRow } from "../../shared";
|
import type { ProformaListRow } from "../../shared2";
|
||||||
import type { PROFORMA_STATUS } from "../../shared/entities";
|
import type { PROFORMA_STATUS } from "../../shared2/entities";
|
||||||
import { useChangeProformaStatusMutation } from "../../shared/hooks";
|
import { useChangeProformaStatusMutation } from "../../shared2/hooks";
|
||||||
|
|
||||||
interface ChangeStatusDialogState {
|
interface ChangeStatusDialogState {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
XCircleIcon,
|
XCircleIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import type { ProformaStatus } from "../../shared";
|
import type { ProformaStatus } from "../../shared2";
|
||||||
|
|
||||||
export const getProformaStatusButtonVariant = (
|
export const getProformaStatusButtonVariant = (
|
||||||
status: ProformaStatus
|
status: ProformaStatus
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { useTranslation } from "../../../i18n";
|
import { useTranslation } from "../../../i18n";
|
||||||
import { PROFORMA_STATUS, PROFORMA_STATUS_TRANSITIONS, type ProformaListRow } from "../../shared";
|
import { PROFORMA_STATUS, PROFORMA_STATUS_TRANSITIONS, type ProformaListRow } from "../../shared2";
|
||||||
import { getProformaStatusIcon } from "../helpers";
|
import { getProformaStatusIcon } from "../helpers";
|
||||||
|
|
||||||
import { StatusNode, TimelineConnector } from "./components";
|
import { StatusNode, TimelineConnector } from "./components";
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { cn } from "@repo/shadcn-ui/lib/utils";
|
|||||||
import { CheckCircle2, type LucideIcon } from "lucide-react";
|
import { CheckCircle2, type LucideIcon } from "lucide-react";
|
||||||
|
|
||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
import type { PROFORMA_STATUS } from "../../../shared";
|
import type { PROFORMA_STATUS } from "../../../shared2";
|
||||||
|
|
||||||
interface StatusNodeProps {
|
interface StatusNodeProps {
|
||||||
status: PROFORMA_STATUS;
|
status: PROFORMA_STATUS;
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./proforma-create-form.entity";
|
||||||
|
export * from "./proforma-create-form.schema";
|
||||||
|
export * from "./proforma-create-form-default";
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Valor por defecto para el formulario de creación de proformas.
|
||||||
|
*
|
||||||
|
* Reglas:
|
||||||
|
* - debe ser un objeto que cumpla con la interfaz ProformaCreateForm
|
||||||
|
* - debe tener valores por defecto razonables para cada campo, evitando campos vacíos o nulos cuando sea posible
|
||||||
|
* - el shape del objeto debe coincidir exactamente con el de ProformaCreateForm, sin campos adicionales ni transformaciones
|
||||||
|
* - orientado a la UI, no a la API ni al dominio, es decir, debe ser un objeto que se pueda usar directamente para inicializar un formulario en la interfaz de usuario
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const defaultProformaCreateForm: ProformaCreateForm = {
|
||||||
|
invoiceNumber: "",
|
||||||
|
series: "",
|
||||||
|
|
||||||
|
invoiceDate: "",
|
||||||
|
operationDate: "",
|
||||||
|
|
||||||
|
customerId: "",
|
||||||
|
|
||||||
|
reference: "",
|
||||||
|
isCompany: true,
|
||||||
|
name: "",
|
||||||
|
tradeName: "",
|
||||||
|
tin: "",
|
||||||
|
|
||||||
|
defaultTaxes: [],
|
||||||
|
|
||||||
|
street: "",
|
||||||
|
street2: "",
|
||||||
|
city: "",
|
||||||
|
province: "",
|
||||||
|
postalCode: "",
|
||||||
|
country: "es",
|
||||||
|
|
||||||
|
primaryEmail: "",
|
||||||
|
secondaryEmail: "",
|
||||||
|
primaryPhone: "",
|
||||||
|
secondaryPhone: "",
|
||||||
|
primaryMobile: "",
|
||||||
|
secondaryMobile: "",
|
||||||
|
|
||||||
|
fax: "",
|
||||||
|
website: "",
|
||||||
|
|
||||||
|
legalRecord: "",
|
||||||
|
|
||||||
|
languageCode: "es",
|
||||||
|
currencyCode: "EUR",
|
||||||
|
};
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* ProformaCreateForm representa el shape de datos del formulario de creación de proforma.
|
||||||
|
* Es decir, los campos que se muestran en el formulario y que el usuario puede editar.
|
||||||
|
*
|
||||||
|
* Este shape es específico para la UI y no tiene por qué coincidir
|
||||||
|
* con el shape del dominio ni con el de la API.
|
||||||
|
*
|
||||||
|
* Debe cumplir las siguientes reglas:
|
||||||
|
* - nombres en camelCase
|
||||||
|
* - tipos orientados a UI/form
|
||||||
|
* - sin campos de solo lectura que no se editen
|
||||||
|
* - sin shape DTO
|
||||||
|
* - sin detalles impuestos por el widget
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ProformaItemForm } from "../../shared/entities";
|
||||||
|
|
||||||
|
export interface ProformaCreateForm {
|
||||||
|
series: string;
|
||||||
|
|
||||||
|
invoiceDate: string;
|
||||||
|
operationDate: string;
|
||||||
|
|
||||||
|
customerId: string;
|
||||||
|
|
||||||
|
reference: string;
|
||||||
|
notes: string;
|
||||||
|
|
||||||
|
languageCode: string;
|
||||||
|
currencyCode: string;
|
||||||
|
|
||||||
|
globalDiscountPercentage: number;
|
||||||
|
|
||||||
|
paymentMethod: string;
|
||||||
|
|
||||||
|
items: ProformaItemForm[];
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Este esquema es para validar los datos del formulario de creación de cliente.
|
||||||
|
* No tiene por qué coincidir con el shape de la entidad ni con el de la API.
|
||||||
|
* Solo define los campos que se muestran en el formulario y sus validaciones.
|
||||||
|
*
|
||||||
|
* Reglas:
|
||||||
|
* - no meter transformaciones silenciosas raras en el esquema (ej: .toUpperCase())
|
||||||
|
* - nombres en camelCase
|
||||||
|
* - tipos orientados a UI/form
|
||||||
|
* - sin campos de solo lectura que no se editen
|
||||||
|
* - sin shape DTO
|
||||||
|
* - sin detalles impuestos por el widget
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const CustomerCreateFormSchema = z.object({
|
||||||
|
reference: z.string(),
|
||||||
|
isCompany: z.boolean(),
|
||||||
|
name: z.string().min(1, "El nombre es obligatorio"),
|
||||||
|
tradeName: z.string(),
|
||||||
|
tin: z.string(),
|
||||||
|
|
||||||
|
defaultTaxes: z.array(z.string()),
|
||||||
|
|
||||||
|
street: z.string(),
|
||||||
|
street2: z.string(),
|
||||||
|
city: z.string(),
|
||||||
|
province: z.string(),
|
||||||
|
postalCode: z.string(),
|
||||||
|
country: z.string().min(1, "El país es obligatorio"),
|
||||||
|
|
||||||
|
primaryEmail: z.email("Email inválido"),
|
||||||
|
secondaryEmail: z.email("Email inválido"),
|
||||||
|
|
||||||
|
primaryPhone: z.string(),
|
||||||
|
secondaryPhone: z.string(),
|
||||||
|
primaryMobile: z.string(),
|
||||||
|
secondaryMobile: z.string(),
|
||||||
|
|
||||||
|
fax: z.string(),
|
||||||
|
website: z.url("URL inválida"),
|
||||||
|
|
||||||
|
legalRecord: z.string(),
|
||||||
|
|
||||||
|
languageCode: z.string().min(1, "El idioma es obligatorio"),
|
||||||
|
currencyCode: z.string().min(1, "La moneda es obligatoria"),
|
||||||
|
});
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
export interface ProformaItemForm {
|
||||||
|
id: string;
|
||||||
|
position: string;
|
||||||
|
description: string;
|
||||||
|
quantity: number;
|
||||||
|
unitAmount: number;
|
||||||
|
discountPercentage: number;
|
||||||
|
taxes: string;
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useTranslation } from "../../../i18n";
|
import { useTranslation } from "../../../i18n";
|
||||||
import { type ProformaListRow, useDeleteProformaMutation } from "../../shared";
|
import { type ProformaListRow, useDeleteProformaMutation } from "../../shared2";
|
||||||
|
|
||||||
interface DeleteProformaDialogState {
|
interface DeleteProformaDialogState {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
import type { ProformaListRow } from "../../../shared";
|
import type { ProformaListRow } from "../../../shared2";
|
||||||
|
|
||||||
interface DeleteProformaDialogProps {
|
interface DeleteProformaDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export * from "./issue-proforma-invoice.api";
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import type { IDataSource } from "@erp/core/client";
|
|
||||||
|
|
||||||
export interface IssueProformaInvoiceResponse {
|
|
||||||
invoiceId: string;
|
|
||||||
proformaId: string;
|
|
||||||
customerId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function issueProformaInvoiceApi(
|
|
||||||
dataSource: IDataSource,
|
|
||||||
proformaId: string
|
|
||||||
): Promise<IssueProformaInvoiceResponse> {
|
|
||||||
return dataSource.custom<IssueProformaInvoiceResponse>({
|
|
||||||
path: `proformas/${proformaId}/issue`,
|
|
||||||
method: "put",
|
|
||||||
data: {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -3,11 +3,11 @@ import React from "react";
|
|||||||
import { useChangeProformaStatusDialogController } from "../../change-status";
|
import { useChangeProformaStatusDialogController } from "../../change-status";
|
||||||
import { useDeleteProformaDialogController } from "../../delete";
|
import { useDeleteProformaDialogController } from "../../delete";
|
||||||
import { useProformaIssueDialogController } from "../../issue-proforma";
|
import { useProformaIssueDialogController } from "../../issue-proforma";
|
||||||
import type { ProformaListRow } from "../../shared";
|
import type { ProformaListRow } from "../../shared2";
|
||||||
import {
|
import {
|
||||||
type PROFORMA_STATUS,
|
type PROFORMA_STATUS,
|
||||||
PROFORMA_STATUS_TRANSITIONS,
|
PROFORMA_STATUS_TRANSITIONS,
|
||||||
} from "../../shared/entities/proforma-status.entity";
|
} from "../../shared2/entities/proforma-status.entity";
|
||||||
|
|
||||||
import { useListProformasController } from "./use-list-proformas.controller";
|
import { useListProformasController } from "./use-list-proformas.controller";
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { CriteriaDTO } from "@erp/core";
|
|||||||
import { useDebounce } from "@repo/rdx-ui/components";
|
import { useDebounce } from "@repo/rdx-ui/components";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
|
||||||
import { useListProformasQuery } from "../../shared";
|
import { useListProformasQuery } from "../../shared2";
|
||||||
|
|
||||||
export const useListProformasController = () => {
|
export const useListProformasController = () => {
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { ColumnDef } from "@tanstack/react-table";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { useTranslation } from "../../../../../i18n";
|
import { useTranslation } from "../../../../../i18n";
|
||||||
import type { ProformaList, ProformaListRow } from "../../../../shared";
|
import type { ProformaList, ProformaListRow } from "../../../../shared2";
|
||||||
|
|
||||||
interface ProformasGridProps {
|
interface ProformasGridProps {
|
||||||
data?: ProformaList;
|
data?: ProformaList;
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import {
|
|||||||
PROFORMA_STATUS_TRANSITIONS,
|
PROFORMA_STATUS_TRANSITIONS,
|
||||||
type ProformaListRow,
|
type ProformaListRow,
|
||||||
type ProformaStatus,
|
type ProformaStatus,
|
||||||
} from "../../../../shared";
|
} from "../../../../shared2";
|
||||||
import { ProformaStatusBadge } from "../../components";
|
import { ProformaStatusBadge } from "../../components";
|
||||||
|
|
||||||
type GridActionHandlers = {
|
type GridActionHandlers = {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
getProformaStatusColor,
|
getProformaStatusColor,
|
||||||
getProformaStatusIcon,
|
getProformaStatusIcon,
|
||||||
} from "../../../change-status/helpers";
|
} from "../../../change-status/helpers";
|
||||||
import type { ProformaStatus } from "../../../shared";
|
import type { ProformaStatus } from "../../../shared2";
|
||||||
|
|
||||||
export type ProformaStatusBadgeProps = {
|
export type ProformaStatusBadgeProps = {
|
||||||
status: string | ProformaStatus; // permitir cualquier valor
|
status: string | ProformaStatus; // permitir cualquier valor
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
|
|
||||||
import { useTranslation } from "../../../i18n";
|
import { useTranslation } from "../../../i18n";
|
||||||
import { ProformaDtoAdapter } from "../../adapters";
|
import { ProformaDtoAdapter } from "../../adapters";
|
||||||
import { useUpdateProforma } from "../../shared/hooks/use-proforma-update-mutation";
|
import { useUpdateProforma } from "../../shared2/hooks/use-proforma-update-mutation";
|
||||||
import {
|
import {
|
||||||
type Proforma,
|
type Proforma,
|
||||||
type ProformaFormData,
|
type ProformaFormData,
|
||||||
|
|||||||
@ -0,0 +1,129 @@
|
|||||||
|
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
||||||
|
|
||||||
|
import type { GetProformaByIdResponseDTO } from "../../../../common";
|
||||||
|
import type {
|
||||||
|
Proforma,
|
||||||
|
ProformaItem,
|
||||||
|
ProformaRecipient,
|
||||||
|
ProformaStatus,
|
||||||
|
ProformaTaxSummary,
|
||||||
|
} from "../entities";
|
||||||
|
|
||||||
|
export const GetProformaByIdAdapter = {
|
||||||
|
fromDto(dto: GetProformaByIdResponseDTO): Proforma {
|
||||||
|
return {
|
||||||
|
id: dto.id,
|
||||||
|
companyId: dto.company_id,
|
||||||
|
isProforma: dto.is_proforma === "1",
|
||||||
|
|
||||||
|
invoiceNumber: dto.invoice_number,
|
||||||
|
status: dto.status as ProformaStatus,
|
||||||
|
series: dto.series,
|
||||||
|
|
||||||
|
invoiceDate: dto.invoice_date,
|
||||||
|
operationDate: dto.operation_date,
|
||||||
|
|
||||||
|
reference: dto.reference,
|
||||||
|
description: dto.description,
|
||||||
|
notes: dto.notes,
|
||||||
|
|
||||||
|
languageCode: dto.language_code,
|
||||||
|
currencyCode: dto.currency_code,
|
||||||
|
|
||||||
|
customerId: dto.customer_id,
|
||||||
|
recipient: mapRecipient(dto.recipient),
|
||||||
|
taxes: dto.taxes.map(mapTaxSummary),
|
||||||
|
|
||||||
|
paymentMethod: dto.payment_method?.payment_id ?? "",
|
||||||
|
|
||||||
|
subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount),
|
||||||
|
|
||||||
|
itemsDiscountAmount: MoneyDTOHelper.toNumber(dto.items_discount_amount),
|
||||||
|
globalDiscountPercentage: PercentageDTOHelper.toNumber(dto.discount_percentage),
|
||||||
|
discountAmount: MoneyDTOHelper.toNumber(dto.discount_amount),
|
||||||
|
|
||||||
|
taxableAmount: MoneyDTOHelper.toNumber(dto.taxable_amount),
|
||||||
|
ivaAmount: MoneyDTOHelper.toNumber(dto.iva_amount),
|
||||||
|
recAmount: MoneyDTOHelper.toNumber(dto.rec_amount),
|
||||||
|
retentionAmount: MoneyDTOHelper.toNumber(dto.retention_amount),
|
||||||
|
taxesAmount: MoneyDTOHelper.toNumber(dto.taxes_amount),
|
||||||
|
|
||||||
|
totalAmount: MoneyDTOHelper.toNumber(dto.total_amount),
|
||||||
|
|
||||||
|
items: dto.items.map(mapItem),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapItem = (dto: GetProformaByIdResponseDTO["items"][number]): ProformaItem => {
|
||||||
|
return {
|
||||||
|
id: dto.id,
|
||||||
|
position: dto.position,
|
||||||
|
description: dto.description,
|
||||||
|
|
||||||
|
quantity: QuantityDTOHelper.toNumber(dto.quantity),
|
||||||
|
unitAmount: MoneyDTOHelper.toNumber(dto.unit_amount),
|
||||||
|
|
||||||
|
subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount),
|
||||||
|
|
||||||
|
itemDiscountPercentage: PercentageDTOHelper.toNumber(dto.item_discount_percentage),
|
||||||
|
itemDiscountAmount: MoneyDTOHelper.toNumber(dto.item_discount_amount),
|
||||||
|
|
||||||
|
globalDiscountPercentage: PercentageDTOHelper.toNumber(dto.global_discount_percentage),
|
||||||
|
globalDiscountAmount: MoneyDTOHelper.toNumber(dto.global_discount_amount),
|
||||||
|
|
||||||
|
taxableAmount: MoneyDTOHelper.toNumber(dto.taxable_amount),
|
||||||
|
|
||||||
|
ivaCode: dto.iva_code,
|
||||||
|
ivaPercentage: PercentageDTOHelper.toNumber(dto.iva_percentage),
|
||||||
|
ivaAmount: MoneyDTOHelper.toNumber(dto.iva_amount),
|
||||||
|
|
||||||
|
recCode: dto.rec_code,
|
||||||
|
recPercentage: PercentageDTOHelper.toNumber(dto.rec_percentage),
|
||||||
|
recAmount: MoneyDTOHelper.toNumber(dto.rec_amount),
|
||||||
|
|
||||||
|
retentionCode: dto.retention_code,
|
||||||
|
retentionPercentage: PercentageDTOHelper.toNumber(dto.retention_percentage),
|
||||||
|
retentionAmount: MoneyDTOHelper.toNumber(dto.retention_amount),
|
||||||
|
|
||||||
|
taxesAmount: MoneyDTOHelper.toNumber(dto.taxes_amount),
|
||||||
|
totalAmount: MoneyDTOHelper.toNumber(dto.total_amount),
|
||||||
|
|
||||||
|
// Nota: el DTO de detalle actual no incluye `taxes` por línea.
|
||||||
|
// Dejamos fallback explícito para no inventar parseos.
|
||||||
|
taxes: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const mapRecipient = (dto: GetProformaByIdResponseDTO["recipient"]): ProformaRecipient => {
|
||||||
|
return {
|
||||||
|
id: dto.id,
|
||||||
|
name: dto.name,
|
||||||
|
tin: dto.tin,
|
||||||
|
street: dto.street,
|
||||||
|
street2: dto.street2,
|
||||||
|
city: dto.city,
|
||||||
|
province: dto.province,
|
||||||
|
postalCode: dto.postal_code,
|
||||||
|
country: dto.country,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapTaxSummary = (dto: GetProformaByIdResponseDTO["taxes"][number]): ProformaTaxSummary => {
|
||||||
|
return {
|
||||||
|
taxableAmount: MoneyDTOHelper.toNumber(dto.taxable_amount),
|
||||||
|
|
||||||
|
ivaCode: dto.iva_code,
|
||||||
|
ivaPercentage: PercentageDTOHelper.toNumber(dto.iva_percentage),
|
||||||
|
ivaAmount: MoneyDTOHelper.toNumber(dto.iva_amount),
|
||||||
|
|
||||||
|
recCode: dto.rec_code,
|
||||||
|
recPercentage: PercentageDTOHelper.toNumber(dto.rec_percentage),
|
||||||
|
recAmount: MoneyDTOHelper.toNumber(dto.rec_amount),
|
||||||
|
|
||||||
|
retentionCode: dto.retention_code,
|
||||||
|
retentionPercentage: PercentageDTOHelper.toNumber(dto.retention_percentage),
|
||||||
|
retentionAmount: MoneyDTOHelper.toNumber(dto.retention_amount),
|
||||||
|
|
||||||
|
taxesAmount: MoneyDTOHelper.toNumber(dto.taxes_amount),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./list-proformas.adapter";
|
|
||||||
@ -1,101 +1,98 @@
|
|||||||
import { MoneyDTOHelper, PercentageDTOHelper, formatCurrency } from "@erp/core";
|
import { MoneyDTOHelper, PercentageDTOHelper, formatCurrency } from "@erp/core";
|
||||||
|
|
||||||
import type { ListProformasResponseDTO } from "../../../../common";
|
import type { ListProformasResponseDTO } from "../../../../common";
|
||||||
import type { ProformaList, ProformaListRow } from "../entities";
|
import type { ProformaList, ProformaListRow, ProformaStatus } from "../entities";
|
||||||
|
|
||||||
|
export const ListProformasAdapter = {
|
||||||
|
fromDto(dto: ListProformasResponseDTO): ProformaList {
|
||||||
|
return {
|
||||||
|
items: dto.items.map(ProformaListRowAdapter.fromDto),
|
||||||
|
page: dto.page,
|
||||||
|
per_page: dto.per_page,
|
||||||
|
total_pages: dto.total_pages,
|
||||||
|
total_items: dto.total_items,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
type ListProformasItemDTO = ListProformasResponseDTO["items"][number];
|
type ListProformasItemDTO = ListProformasResponseDTO["items"][number];
|
||||||
|
|
||||||
export const ListProformasAdapter = {
|
const ProformaListRowAdapter = {
|
||||||
fromDTO(pageDto: ListProformasResponseDTO, context?: unknown): ProformaList {
|
fromDto(dto: ListProformasItemDTO): ProformaListRow {
|
||||||
return {
|
return {
|
||||||
//...pageDto,
|
id: dto.id,
|
||||||
page: pageDto.page,
|
companyId: dto.company_id,
|
||||||
per_page: pageDto.per_page,
|
isProforma: dto.is_proforma === "1",
|
||||||
total_pages: pageDto.total_pages,
|
|
||||||
total_items: pageDto.total_items,
|
|
||||||
items: pageDto.items.map((row) => ListProformasRowAdapter.fromDTO(row, context)),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ListProformasRowAdapter = {
|
invoiceNumber: dto.invoice_number,
|
||||||
fromDTO(rowDto: ListProformasItemDTO, context?: unknown): ProformaListRow {
|
status: dto.status as ProformaStatus,
|
||||||
return {
|
series: dto.series,
|
||||||
//...rowDto,
|
|
||||||
id: rowDto.id,
|
|
||||||
company_id: rowDto.company_id,
|
|
||||||
|
|
||||||
customer_id: rowDto.company_id,
|
invoiceDate: dto.invoice_date,
|
||||||
|
operationDate: dto.operation_date,
|
||||||
|
|
||||||
invoice_number: rowDto.invoice_number,
|
languageCode: dto.language_code,
|
||||||
status: rowDto.status,
|
currencyCode: dto.currency_code,
|
||||||
series: rowDto.series,
|
|
||||||
|
|
||||||
invoice_date: rowDto.invoice_date,
|
|
||||||
operation_date: rowDto.operation_date,
|
|
||||||
|
|
||||||
language_code: rowDto.language_code,
|
|
||||||
currency_code: rowDto.currency_code,
|
|
||||||
|
|
||||||
reference: rowDto.reference,
|
|
||||||
description: rowDto.description,
|
|
||||||
|
|
||||||
|
reference: dto.reference,
|
||||||
|
description: dto.description,
|
||||||
recipient: {
|
recipient: {
|
||||||
tin: rowDto.recipient.tin,
|
id: dto.customer_id,
|
||||||
name: rowDto.recipient.name,
|
tin: dto.recipient.tin,
|
||||||
|
name: dto.recipient.name,
|
||||||
|
|
||||||
street: rowDto.recipient.street,
|
street: dto.recipient.street,
|
||||||
street2: rowDto.recipient.street2,
|
street2: dto.recipient.street2,
|
||||||
city: rowDto.recipient.city,
|
city: dto.recipient.city,
|
||||||
province: rowDto.recipient.province,
|
province: dto.recipient.province,
|
||||||
postal_code: rowDto.recipient.postal_code,
|
postalCode: dto.recipient.postal_code,
|
||||||
country: rowDto.recipient.country,
|
country: dto.recipient.country,
|
||||||
},
|
},
|
||||||
|
|
||||||
subtotal_amount: MoneyDTOHelper.toNumber(rowDto.subtotal_amount),
|
subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount),
|
||||||
subtotal_amount_fmt: formatCurrency(
|
subtotalAmountFmt: formatCurrency(
|
||||||
MoneyDTOHelper.toNumber(rowDto.subtotal_amount),
|
MoneyDTOHelper.toNumber(dto.subtotal_amount),
|
||||||
Number(rowDto.total_amount.scale || 2),
|
Number(dto.total_amount.scale || 2),
|
||||||
rowDto.currency_code,
|
dto.currency_code,
|
||||||
rowDto.language_code
|
dto.language_code
|
||||||
),
|
),
|
||||||
|
|
||||||
discount_percentage: PercentageDTOHelper.toNumber(rowDto.discount_percentage),
|
discountPercentage: PercentageDTOHelper.toNumber(dto.discount_percentage),
|
||||||
discount_percentage_fmt: PercentageDTOHelper.toNumericString(rowDto.discount_percentage),
|
discountPercentageFmt: PercentageDTOHelper.toNumericString(dto.discount_percentage),
|
||||||
|
|
||||||
discount_amount: MoneyDTOHelper.toNumber(rowDto.discount_amount),
|
discountAmount: MoneyDTOHelper.toNumber(dto.discount_amount),
|
||||||
discount_amount_fmt: formatCurrency(
|
discountAmountFmt: formatCurrency(
|
||||||
MoneyDTOHelper.toNumber(rowDto.discount_amount),
|
MoneyDTOHelper.toNumber(dto.discount_amount),
|
||||||
Number(rowDto.total_amount.scale || 2),
|
Number(dto.total_amount.scale || 2),
|
||||||
rowDto.currency_code,
|
dto.currency_code,
|
||||||
rowDto.language_code
|
dto.language_code
|
||||||
),
|
),
|
||||||
|
|
||||||
taxable_amount: MoneyDTOHelper.toNumber(rowDto.taxable_amount),
|
taxableAmount: MoneyDTOHelper.toNumber(dto.taxable_amount),
|
||||||
taxable_amount_fmt: formatCurrency(
|
taxableAmountFmt: formatCurrency(
|
||||||
MoneyDTOHelper.toNumber(rowDto.taxable_amount),
|
MoneyDTOHelper.toNumber(dto.taxable_amount),
|
||||||
Number(rowDto.total_amount.scale || 2),
|
Number(dto.total_amount.scale || 2),
|
||||||
rowDto.currency_code,
|
dto.currency_code,
|
||||||
rowDto.language_code
|
dto.language_code
|
||||||
),
|
),
|
||||||
|
|
||||||
taxes_amount: MoneyDTOHelper.toNumber(rowDto.taxes_amount),
|
taxesAmount: MoneyDTOHelper.toNumber(dto.taxes_amount),
|
||||||
taxes_amount_fmt: formatCurrency(
|
taxesAmountFmt: formatCurrency(
|
||||||
MoneyDTOHelper.toNumber(rowDto.taxes_amount),
|
MoneyDTOHelper.toNumber(dto.taxes_amount),
|
||||||
Number(rowDto.total_amount.scale || 2),
|
Number(dto.total_amount.scale || 2),
|
||||||
rowDto.currency_code,
|
dto.currency_code,
|
||||||
rowDto.language_code
|
dto.language_code
|
||||||
),
|
),
|
||||||
|
|
||||||
total_amount: MoneyDTOHelper.toNumber(rowDto.total_amount),
|
totalAmount: MoneyDTOHelper.toNumber(dto.total_amount),
|
||||||
total_amount_fmt: formatCurrency(
|
totalAmountFmt: formatCurrency(
|
||||||
MoneyDTOHelper.toNumber(rowDto.total_amount),
|
MoneyDTOHelper.toNumber(dto.total_amount),
|
||||||
Number(rowDto.total_amount.scale || 2),
|
Number(dto.total_amount.scale || 2),
|
||||||
rowDto.currency_code,
|
dto.currency_code,
|
||||||
rowDto.language_code
|
dto.language_code
|
||||||
),
|
),
|
||||||
|
|
||||||
linked_invoice_id: rowDto.linked_invoice_id,
|
linkedInvoiceId: dto.linked_invoice_id,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,42 @@
|
|||||||
|
import type { Proforma, ProformaListRow } from "../entities";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilizado por el gestor de caché para actualizar algunos campos del objeto ProfromaListRow.
|
||||||
|
* Adaptador para transformar un objeto Proforma a un objeto ProformaListRowPatch,
|
||||||
|
* que es una versión parcial de ProformaListRow.
|
||||||
|
|
||||||
|
* Reglas de adaptación:
|
||||||
|
* - los campos se asignan directamente
|
||||||
|
* - los campos son opcionales excepto el id.
|
||||||
|
*
|
||||||
|
* @param proforma - objeto Proforma a adaptar.
|
||||||
|
* @returns {ProformaListRowPatch} Objeto adaptado a ProformaListRowPatch.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ProformaListRowPatch = Partial<ProformaListRow> & Pick<ProformaListRow, "id">;
|
||||||
|
|
||||||
|
export const ProformaToListRowPatchAdapter = {
|
||||||
|
fromProforma(proforma: Proforma): ProformaListRowPatch {
|
||||||
|
return {
|
||||||
|
id: proforma.id,
|
||||||
|
customerId: proforma.customerId,
|
||||||
|
invoiceNumber: proforma.invoiceNumber,
|
||||||
|
status: proforma.status,
|
||||||
|
series: proforma.series,
|
||||||
|
invoiceDate: proforma.invoiceDate,
|
||||||
|
operationDate: proforma.operationDate,
|
||||||
|
languageCode: proforma.languageCode,
|
||||||
|
currencyCode: proforma.currencyCode,
|
||||||
|
reference: proforma.reference,
|
||||||
|
description: proforma.description,
|
||||||
|
recipientName: proforma.recipient.name,
|
||||||
|
recipientTin: proforma.recipient.tin,
|
||||||
|
subtotalAmount: proforma.subtotalAmount,
|
||||||
|
discountPercentage: proforma.discountPercentage,
|
||||||
|
discountAmount: proforma.discountAmount,
|
||||||
|
taxableAmount: proforma.taxableAmount,
|
||||||
|
taxesAmount: proforma.taxesAmount,
|
||||||
|
totalAmount: proforma.totalAmount,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,17 +1,25 @@
|
|||||||
import type { IDataSource } from "@erp/core/client";
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
export interface ChangeStatusResponse {
|
import type { ChangeStatusProformaByIdRequestDTO } from "../../../../common";
|
||||||
success: boolean;
|
|
||||||
|
export interface ChangeProformaStatusByIdParams {
|
||||||
|
id: string;
|
||||||
|
data: ChangeStatusProformaByIdRequestDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ChangeProformaStatusByIdResult = void;
|
||||||
|
|
||||||
export async function changeProformaStatusById(
|
export async function changeProformaStatusById(
|
||||||
dataSource: IDataSource,
|
dataSource: IDataSource,
|
||||||
proformaId: string,
|
params: ChangeProformaStatusByIdParams
|
||||||
newStatus: string
|
): Promise<ChangeProformaStatusByIdResult> {
|
||||||
): Promise<ChangeStatusResponse> {
|
const { id, data } = params;
|
||||||
return dataSource.custom<ChangeStatusResponse>({
|
|
||||||
path: `proformas/${proformaId}/status`,
|
if (!id) throw new Error("proformaId is required");
|
||||||
|
|
||||||
|
return dataSource.custom<ChangeStatusProformaByIdRequestDTO, ChangeProformaStatusByIdResult>({
|
||||||
|
path: `proformas/${id}/status`,
|
||||||
method: "patch",
|
method: "patch",
|
||||||
data: { new_status: newStatus },
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { CreateProformaRequestDTO, CreateProformaResponseDTO } from "../../../../common";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea una nueva proforma en el sistema utilizando la fuente de datos proporcionada.
|
||||||
|
*
|
||||||
|
* @param dataSource - La fuente de datos para interactuar con la API.
|
||||||
|
* @param params - Los parámetros necesarios para crear la proforma.
|
||||||
|
* @returns Una promesa que resuelve con los detalles de la proforma creada.
|
||||||
|
* @throws Error si el ID de la proforma no es proporcionado o si la creación falla.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface CreateProformaParams {
|
||||||
|
id: string;
|
||||||
|
data: CreateProformaRequestDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateProformaResult = CreateProformaResponseDTO;
|
||||||
|
|
||||||
|
export const createProforma = (
|
||||||
|
dataSource: IDataSource,
|
||||||
|
params: CreateProformaParams
|
||||||
|
): Promise<CreateProformaResult> => {
|
||||||
|
const { id, data } = params;
|
||||||
|
|
||||||
|
if (!id) throw new Error("proformaId is required");
|
||||||
|
|
||||||
|
return dataSource.createOne<CreateProformaRequestDTO, CreateProformaResponseDTO>(
|
||||||
|
"proformas",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elimina una proforma existente en el sistema utilizando la fuente de datos proporcionada.
|
||||||
|
*
|
||||||
|
* @param dataSource - La fuente de datos para interactuar con la API.
|
||||||
|
* @param params - Los parámetros necesarios para eliminar la proforma.
|
||||||
|
* @returns Una promesa que se resuelve cuando la proforma ha sido eliminada exitosamente.
|
||||||
|
* @throws Error si el ID de la proforma no es proporcionado o si la eliminación falla.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface DeleteProformaByIdParams {
|
||||||
|
id: string;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeleteProformaByIdResult = void;
|
||||||
|
|
||||||
|
export const deleteProformaById = (
|
||||||
|
dataSource: IDataSource,
|
||||||
|
params: DeleteProformaByIdParams
|
||||||
|
): Promise<DeleteProformaByIdResult> => {
|
||||||
|
const { id, signal } = params;
|
||||||
|
|
||||||
|
if (!id) throw new Error("proformaId is required");
|
||||||
|
|
||||||
|
return dataSource.deleteOne("proformas", id, { signal });
|
||||||
|
};
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import type { IDataSource } from "@erp/core/client";
|
|
||||||
|
|
||||||
export async function deleteProformaById(
|
|
||||||
dataSource: IDataSource,
|
|
||||||
proformaId: string
|
|
||||||
): Promise<void> {
|
|
||||||
await dataSource.deleteOne("proformas", proformaId);
|
|
||||||
}
|
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { GetProformaByIdResponseDTO } from "../../../../common";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recupera los detalles de una proforma específica utilizando su ID a través de la fuente de datos proporcionada.
|
||||||
|
*
|
||||||
|
* @param dataSource - La fuente de datos para interactuar con la API.
|
||||||
|
* @param params - Los parámetros necesarios para obtener la proforma, incluyendo su ID.
|
||||||
|
* @returns Una promesa que resuelve con los detalles de la proforma solicitada.
|
||||||
|
* @throws Error si el ID de la proforma no es proporcionado o si la recuperación falla.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface GetProformaByIdParams {
|
||||||
|
id: string;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetProformaByIdResult = GetProformaByIdResponseDTO;
|
||||||
|
|
||||||
|
export function getProformaById(
|
||||||
|
dataSource: IDataSource,
|
||||||
|
params: GetProformaByIdParams
|
||||||
|
): Promise<GetProformaByIdResult> {
|
||||||
|
const { id, signal } = params;
|
||||||
|
|
||||||
|
if (!id) throw new Error("proformaId is required");
|
||||||
|
|
||||||
|
return dataSource.getOne<GetProformaByIdResponseDTO>("proformas", id, { signal });
|
||||||
|
}
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import type { IDataSource } from "@erp/core/client";
|
|
||||||
|
|
||||||
import type { Proforma } from "../entities";
|
|
||||||
|
|
||||||
export async function getProformaById(dataSource: IDataSource, signal: AbortSignal, id?: string) {
|
|
||||||
if (!id) throw new Error("proformaId is required");
|
|
||||||
const response = dataSource.getOne<Proforma>("proformas", id, { signal });
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
@ -1,4 +1,7 @@
|
|||||||
export * from "./change-proforma-status-by-id.api";
|
export * from "./change-proforma-status-by-id.api";
|
||||||
export * from "./delete-proforma-by-ip.api";
|
export * from "./create-proforma.api";
|
||||||
export * from "./get-proforma-by-ip.api";
|
export * from "./delete-proforma-by-id.api";
|
||||||
export * from "./list-proformas.api";
|
export * from "./get-proforma-by-id.api";
|
||||||
|
export * from "./issue-proforma-by-id.api";
|
||||||
|
export * from "./list-proformas-by-criteria.api";
|
||||||
|
export * from "./update-proforma-by-id.api";
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
import type { IssueProformaByIdResponseDTO } from "@erp/customer-invoices/common";
|
||||||
|
|
||||||
|
export interface IssueProformaByIdParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IssueProformaByIdResult = IssueProformaByIdResponseDTO;
|
||||||
|
|
||||||
|
export const issueProformaById = (
|
||||||
|
dataSource: IDataSource,
|
||||||
|
params: IssueProformaByIdParams
|
||||||
|
): Promise<IssueProformaByIdResult> => {
|
||||||
|
const { id } = params;
|
||||||
|
|
||||||
|
if (!id) throw new Error("proformaId is required");
|
||||||
|
|
||||||
|
return dataSource.custom<Record<string, never>, IssueProformaByIdResponseDTO>({
|
||||||
|
path: `proformas/${id}/issue`,
|
||||||
|
method: "put",
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import type { CriteriaDTO } from "@erp/core";
|
||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { ListProformasResponseDTO } from "../../../../common";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recupera una lista de proformas del sistema utilizando la
|
||||||
|
* fuente de datos proporcionada y los criterios de búsqueda especificados.
|
||||||
|
*
|
||||||
|
* @param dataSource - La fuente de datos para interactuar con la API.
|
||||||
|
* @param params - Los parámetros necesarios para listar las proformas, incluyendo los criterios de búsqueda.
|
||||||
|
* @returns Una promesa que resuelve con una lista de proformas que cumplen con los criterios especificados.
|
||||||
|
* @throws Error si la recuperación de la lista de proformas falla.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ListProformasByCriteriaParams = {
|
||||||
|
criteria?: CriteriaDTO;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListProformasResult = ListProformasResponseDTO;
|
||||||
|
|
||||||
|
export function getListProformasByCriteria(
|
||||||
|
dataSource: IDataSource,
|
||||||
|
params: ListProformasByCriteriaParams
|
||||||
|
): Promise<ListProformasResult> {
|
||||||
|
const { criteria, signal } = params || { criteria: undefined, signal: undefined };
|
||||||
|
return dataSource.getList<ListProformasResponseDTO>("proformas", {
|
||||||
|
signal,
|
||||||
|
...criteria,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import type { CriteriaDTO } from "@erp/core";
|
|
||||||
import type { IDataSource } from "@erp/core/client";
|
|
||||||
|
|
||||||
import type { ListProformasResponseDTO } from "../../../../common";
|
|
||||||
|
|
||||||
export async function getListProformas(
|
|
||||||
dataSource: IDataSource,
|
|
||||||
signal: AbortSignal,
|
|
||||||
criteria: CriteriaDTO
|
|
||||||
) {
|
|
||||||
const response = dataSource.getList<ListProformasResponseDTO>("proformas", {
|
|
||||||
signal,
|
|
||||||
...criteria,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ReportProformaByIdQueryRequestDTO,
|
||||||
|
ReportProformaByIdResponseDTO,
|
||||||
|
} from "../../../../common";
|
||||||
|
|
||||||
|
export type ProformaReportFormat = "PDF" | "HTML" | "JSON";
|
||||||
|
|
||||||
|
export interface ReportProformaByIdParams {
|
||||||
|
id: string;
|
||||||
|
data: ReportProformaByIdQueryRequestDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ReportProformaByIdResult = ReportProformaByIdResponseDTO;
|
||||||
|
|
||||||
|
export const reportProformaById = (
|
||||||
|
dataSource: IDataSource,
|
||||||
|
params: ReportProformaByIdParams
|
||||||
|
): Promise<ReportProformaByIdResult> => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
data: { format = "PDF" },
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
if (!id) throw new Error("proformaId is required");
|
||||||
|
|
||||||
|
return dataSource.custom<ReportProformaByIdQueryRequestDTO, ReportProformaByIdResponseDTO>({
|
||||||
|
path: `proformas/${id}/report`,
|
||||||
|
method: "get",
|
||||||
|
data: {
|
||||||
|
format,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
UpdateProformaByIdRequestDTO,
|
||||||
|
UpdateProformaByIdResponseDTO,
|
||||||
|
} from "../../../../common";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actualiza una proforma existente en el sistema utilizando la fuente de datos proporcionada.
|
||||||
|
*
|
||||||
|
* @param dataSource - La fuente de datos para interactuar con la API.
|
||||||
|
* @param params - Los parámetros necesarios para actualizar la proforma.
|
||||||
|
* @returns Una promesa que resuelve con los detalles de la proforma actualizada.
|
||||||
|
* @throws Error si el ID de la proforma no es proporcionado o si la actualización falla.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface UpdateProformaByIdParams {
|
||||||
|
id: string;
|
||||||
|
data: UpdateProformaByIdRequestDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateProformaByIdResult = UpdateProformaByIdResponseDTO;
|
||||||
|
|
||||||
|
export const updateProformaById = (
|
||||||
|
dataSource: IDataSource,
|
||||||
|
params: UpdateProformaByIdParams
|
||||||
|
): Promise<UpdateProformaByIdResult> => {
|
||||||
|
const { id, data } = params;
|
||||||
|
|
||||||
|
if (!id) throw new Error("proformaId is required");
|
||||||
|
|
||||||
|
return dataSource.updateOne<UpdateProformaByIdRequestDTO, UpdateProformaByIdResponseDTO>(
|
||||||
|
"proformas",
|
||||||
|
id,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./payment-method-options.constants";
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export const PAYMENT_METHOD_OPTIONS = [
|
||||||
|
{ label: "Transferencia", value: "6" },
|
||||||
|
{ label: "Transferencia bancaria", value: "15" },
|
||||||
|
{ label: "Domiciliación bancaria", value: "14" },
|
||||||
|
];
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./proforma-item-form.entity";
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
export interface ProformaItemForm {
|
||||||
|
id: string;
|
||||||
|
position: string;
|
||||||
|
description: string;
|
||||||
|
quantity: number; // scale = 2
|
||||||
|
unitAmount: number; // pendiente confirmar escala exacta de importe
|
||||||
|
discountPercentage: number; // 0..100
|
||||||
|
taxes: string;
|
||||||
|
}
|
||||||
@ -1,3 +1,8 @@
|
|||||||
|
export * from "./forms/proforma-item-form.entity";
|
||||||
|
export * from "./proforma.entity";
|
||||||
|
export * from "./proforma-item.entity";
|
||||||
export * from "./proforma-list.entity";
|
export * from "./proforma-list.entity";
|
||||||
export * from "./proforma-list-row.entity";
|
export * from "./proforma-list-row.entity";
|
||||||
|
export * from "./proforma-recipient.entity";
|
||||||
export * from "./proforma-status.entity";
|
export * from "./proforma-status.entity";
|
||||||
|
export * from "./proforma-tax-summary.entity";
|
||||||
|
|||||||
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Interface que representa una línea de proforma en el sistema,
|
||||||
|
* adaptada desde la respuesta de la API.
|
||||||
|
* Contiene todos los campos detallados de la línea.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ProformaItem {
|
||||||
|
id: string;
|
||||||
|
position: string;
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
quantity: number;
|
||||||
|
unitAmount: number;
|
||||||
|
|
||||||
|
subtotalAmount: number;
|
||||||
|
|
||||||
|
itemDiscountPercentage: number;
|
||||||
|
itemDiscountAmount: number;
|
||||||
|
|
||||||
|
globalDiscountPercentage: number;
|
||||||
|
globalDiscountAmount: number;
|
||||||
|
|
||||||
|
taxableAmount: number;
|
||||||
|
|
||||||
|
ivaCode: string;
|
||||||
|
ivaPercentage: number;
|
||||||
|
ivaAmount: number;
|
||||||
|
|
||||||
|
recCode: string;
|
||||||
|
recPercentage: number;
|
||||||
|
recAmount: number;
|
||||||
|
|
||||||
|
retentionCode: string;
|
||||||
|
retentionPercentage: number;
|
||||||
|
retentionAmount: number;
|
||||||
|
|
||||||
|
taxesAmount: number;
|
||||||
|
|
||||||
|
totalAmount: number;
|
||||||
|
|
||||||
|
taxes: string;
|
||||||
|
}
|
||||||
@ -1,51 +1,50 @@
|
|||||||
|
import type { ProformaRecipient } from "./proforma-recipient.entity";
|
||||||
|
import type { ProformaStatus } from "./proforma-status.entity";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface que representa una fila de la lista de
|
||||||
|
* proformas en el sistema, adaptada desde la respuesta de la API.
|
||||||
|
* Contiene los campos justos para mostrar
|
||||||
|
* la información básica de cada proforma en la lista.
|
||||||
|
*/
|
||||||
|
|
||||||
export interface ProformaListRow {
|
export interface ProformaListRow {
|
||||||
id: string;
|
id: string;
|
||||||
company_id: string;
|
companyId: string;
|
||||||
|
isProforma: boolean;
|
||||||
|
|
||||||
customer_id: string;
|
invoiceNumber: string;
|
||||||
|
status: ProformaStatus;
|
||||||
invoice_number: string;
|
|
||||||
status: string;
|
|
||||||
series: string;
|
series: string;
|
||||||
|
|
||||||
invoice_date: string;
|
invoiceDate: string;
|
||||||
operation_date: string;
|
operationDate: string;
|
||||||
|
|
||||||
language_code: string;
|
languageCode: string;
|
||||||
currency_code: string;
|
currencyCode: string;
|
||||||
|
|
||||||
reference: string;
|
reference: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
recipient: {
|
recipient: ProformaRecipient;
|
||||||
tin: string;
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
street: string;
|
subtotalAmount: number;
|
||||||
street2: string;
|
subtotalAmountFmt: string;
|
||||||
city: string;
|
|
||||||
province: string;
|
|
||||||
postal_code: string;
|
|
||||||
country: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
subtotal_amount: number;
|
discountPercentage: number;
|
||||||
subtotal_amount_fmt: string;
|
discountPercentageFmt: string;
|
||||||
|
|
||||||
discount_percentage: number;
|
discountAmount: number;
|
||||||
discount_percentage_fmt: string;
|
discountAmountFmt: string;
|
||||||
|
|
||||||
discount_amount: number;
|
taxableAmount: number;
|
||||||
discount_amount_fmt: string;
|
taxableAmountFmt: string;
|
||||||
|
|
||||||
taxable_amount: number;
|
taxesAmount: number;
|
||||||
taxable_amount_fmt: string;
|
taxesAmountFmt: string;
|
||||||
|
|
||||||
taxes_amount: number;
|
totalAmount: number;
|
||||||
taxes_amount_fmt: string;
|
totalAmountFmt: string;
|
||||||
|
|
||||||
total_amount: number;
|
linkedInvoiceId: string;
|
||||||
total_amount_fmt: string;
|
|
||||||
|
|
||||||
linked_invoice_id: string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import type { ProformaListRow } from "./proforma-list-row.entity";
|
import type { ProformaListRow } from "./proforma-list-row.entity";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface que representa la respuesta paginada de una lista de proformas,
|
||||||
|
* adaptada desde la respuesta de la API.
|
||||||
|
*/
|
||||||
|
|
||||||
export interface ProformaList {
|
export interface ProformaList {
|
||||||
items: ProformaListRow[];
|
items: ProformaListRow[];
|
||||||
total_pages: number;
|
total_pages: number;
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Interface que representa el destinatario de una proforma en el sistema,
|
||||||
|
* adaptada desde la respuesta de la API.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ProformaRecipient {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
tin: string;
|
||||||
|
|
||||||
|
street: string;
|
||||||
|
street2: string;
|
||||||
|
|
||||||
|
city: string;
|
||||||
|
province: string;
|
||||||
|
postalCode: string;
|
||||||
|
country: string;
|
||||||
|
}
|
||||||
@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Enumeración que representa
|
||||||
|
* los posibles estados de una proforma en el sistema.
|
||||||
|
*/
|
||||||
|
|
||||||
export enum PROFORMA_STATUS {
|
export enum PROFORMA_STATUS {
|
||||||
DRAFT = "draft",
|
DRAFT = "draft",
|
||||||
SENT = "sent",
|
SENT = "sent",
|
||||||
@ -6,13 +11,4 @@ export enum PROFORMA_STATUS {
|
|||||||
ISSUED = "issued",
|
ISSUED = "issued",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transiciones válidas según reglas del dominio
|
|
||||||
export const PROFORMA_STATUS_TRANSITIONS: Record<PROFORMA_STATUS, PROFORMA_STATUS[]> = {
|
|
||||||
[PROFORMA_STATUS.DRAFT]: [PROFORMA_STATUS.SENT],
|
|
||||||
[PROFORMA_STATUS.SENT]: [PROFORMA_STATUS.APPROVED, PROFORMA_STATUS.REJECTED],
|
|
||||||
[PROFORMA_STATUS.APPROVED]: [PROFORMA_STATUS.ISSUED, PROFORMA_STATUS.DRAFT],
|
|
||||||
[PROFORMA_STATUS.REJECTED]: [PROFORMA_STATUS.DRAFT],
|
|
||||||
[PROFORMA_STATUS.ISSUED]: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ProformaStatus = `${PROFORMA_STATUS}`;
|
export type ProformaStatus = `${PROFORMA_STATUS}`;
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Interface que representa el resumen de impuestos
|
||||||
|
* de una proforma en el sistema,
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ProformaTaxSummary {
|
||||||
|
taxableAmount: number;
|
||||||
|
|
||||||
|
ivaCode: string;
|
||||||
|
ivaPercentage: number;
|
||||||
|
ivaAmount: number;
|
||||||
|
|
||||||
|
recCode: string;
|
||||||
|
recPercentage: number;
|
||||||
|
recAmount: number;
|
||||||
|
|
||||||
|
retentionCode: string;
|
||||||
|
retentionPercentage: number;
|
||||||
|
retentionAmount: number;
|
||||||
|
|
||||||
|
taxesAmount: number;
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import type { ProformaItem } from "./proforma-item.entity";
|
||||||
|
import type { ProformaRecipient } from "./proforma-recipient.entity";
|
||||||
|
import type { ProformaStatus } from "./proforma-status.entity";
|
||||||
|
import type { ProformaTaxSummary } from "./proforma-tax-summary.entity";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface que representa una proforma en el sistema,
|
||||||
|
* adaptada desde la respuesta de la API.
|
||||||
|
* Contiene todos los campos detallados de la proforma.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Proforma {
|
||||||
|
id: string;
|
||||||
|
companyId: string;
|
||||||
|
isProforma: boolean;
|
||||||
|
|
||||||
|
invoiceNumber: string;
|
||||||
|
status: ProformaStatus;
|
||||||
|
series: string;
|
||||||
|
|
||||||
|
invoiceDate: string;
|
||||||
|
operationDate: string;
|
||||||
|
|
||||||
|
reference: string;
|
||||||
|
description: string;
|
||||||
|
notes: string;
|
||||||
|
|
||||||
|
languageCode: string;
|
||||||
|
currencyCode: string;
|
||||||
|
|
||||||
|
customerId: string;
|
||||||
|
recipient: ProformaRecipient;
|
||||||
|
|
||||||
|
taxes: ProformaTaxSummary[];
|
||||||
|
|
||||||
|
paymentMethod?: string;
|
||||||
|
|
||||||
|
subtotalAmount: number;
|
||||||
|
|
||||||
|
itemsDiscountAmount: number;
|
||||||
|
globalDiscountPercentage: number;
|
||||||
|
discountAmount: number;
|
||||||
|
|
||||||
|
taxableAmount: number;
|
||||||
|
ivaAmount: number;
|
||||||
|
recAmount: number;
|
||||||
|
retentionAmount: number;
|
||||||
|
taxesAmount: number;
|
||||||
|
|
||||||
|
totalAmount: number;
|
||||||
|
|
||||||
|
items: ProformaItem[];
|
||||||
|
}
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export * from "./use-change-proforma-status-mutation";
|
|
||||||
export * from "./use-delete-proforma-mutation";
|
|
||||||
export * from "./use-list-proformas-query";
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import { useDataSource } from "@erp/core/hooks";
|
|
||||||
import {
|
|
||||||
type DefaultError,
|
|
||||||
type QueryKey,
|
|
||||||
type UseQueryResult,
|
|
||||||
useQuery,
|
|
||||||
} from "@tanstack/react-query";
|
|
||||||
|
|
||||||
import { type Proforma, getProformaById } from "../../api";
|
|
||||||
|
|
||||||
export const PROFORMA_QUERY_KEY = (proformaId?: string): QueryKey => [
|
|
||||||
"proformas:detail",
|
|
||||||
{
|
|
||||||
proformaId,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
type ProformaQueryOptions = {
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useProformaGetQuery = (
|
|
||||||
proformaId?: string,
|
|
||||||
options?: ProformaQueryOptions
|
|
||||||
): UseQueryResult<Proforma, DefaultError> => {
|
|
||||||
const dataSource = useDataSource();
|
|
||||||
const enabled = options?.enabled ?? Boolean(proformaId);
|
|
||||||
|
|
||||||
return useQuery<Proforma, DefaultError>({
|
|
||||||
queryKey: PROFORMA_QUERY_KEY(proformaId),
|
|
||||||
queryFn: async ({ signal }) => getProformaById(dataSource, signal, proformaId),
|
|
||||||
enabled,
|
|
||||||
placeholderData: (previousData, _previousQuery) => previousData, // Mantener datos previos mientras se carga nueva datos (antiguo `keepPreviousData`)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/*export function invalidateProformaDetailCache(qc: QueryClient, id: string) {
|
|
||||||
return qc.invalidateQueries({
|
|
||||||
queryKey: getProformaQueryKey(id ?? "unknown"),
|
|
||||||
exact: Boolean(id),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setProformaDetailCache(qc: QueryClient, id: string, data: unknown) {
|
|
||||||
qc.setQueryData(getProformaQueryKey(id), data);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
import { useDataSource } from "@erp/core/hooks";
|
|
||||||
import { ValidationErrorCollection } from "@repo/rdx-ddd";
|
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
import { UpdateProformaByIdRequestSchema } from "../../../../common";
|
|
||||||
import type { ProformaFormData } from "../../update/types";
|
|
||||||
|
|
||||||
export const PROFORMA_UPDATE_KEY = ["proformas", "update"] as const;
|
|
||||||
|
|
||||||
type UpdateProformaContext = {};
|
|
||||||
|
|
||||||
type UpdateProformaPayload = {
|
|
||||||
id: string;
|
|
||||||
data: Partial<ProformaFormData>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useProformaUpdateMutation = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const dataSource = useDataSource();
|
|
||||||
const schema = UpdateProformaByIdRequestSchema;
|
|
||||||
|
|
||||||
return useMutation<Proforma, DefaultError, UpdateProformaPayload, UpdateProformaContext>({
|
|
||||||
mutationKey: PROFORMA_UPDATE_KEY,
|
|
||||||
|
|
||||||
mutationFn: async (payload) => {
|
|
||||||
const { id: proformaId, data } = payload;
|
|
||||||
if (!proformaId) {
|
|
||||||
throw new Error("proformaId is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = schema.safeParse(data);
|
|
||||||
if (!result.success) {
|
|
||||||
throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error));
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = await dataSource.updateOne("proformas", proformaId, data);
|
|
||||||
return updated as Proforma;
|
|
||||||
},
|
|
||||||
|
|
||||||
onSuccess: (updated: Proforma, variables) => {
|
|
||||||
const { id: proformaId } = updated;
|
|
||||||
|
|
||||||
// Invalida el listado para refrescar desde servidor
|
|
||||||
//invalidateProformaListCache(queryClient);
|
|
||||||
|
|
||||||
// Actualiza detalle
|
|
||||||
//setProformaDetailCache(queryClient, proformaId, updated);
|
|
||||||
|
|
||||||
// Actualiza todas las páginas donde aparezca
|
|
||||||
//upsertProformaIntoListCaches(queryClient, { ...updated });
|
|
||||||
},
|
|
||||||
|
|
||||||
onSettled: () => {
|
|
||||||
// Refresca todos los listados
|
|
||||||
//invalidateProformaListCache(queryClient);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export * from "./api";
|
|
||||||
export * from "./entities";
|
|
||||||
export * from "./hooks";
|
|
||||||
export * from "./ui";
|
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./use-change-proforma-status-mutation";
|
||||||
|
export * from "./use-delete-proforma-mutation";
|
||||||
|
export * from "./use-list-proformas-query";
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { useDataSource } from "@erp/core/hooks";
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { changeProformaStatusById } from "../api/change-proforma-status-by-id.api";
|
import { changeProformaStatusById } from "../../shared/api/change-proforma-status-by-id.api";
|
||||||
import type { PROFORMA_STATUS } from "../entities";
|
import type { PROFORMA_STATUS } from "../entities";
|
||||||
|
|
||||||
import { LIST_PROFORMAS_QUERY_KEY_PREFIX } from "./use-list-proformas-query";
|
import { LIST_PROFORMAS_QUERY_KEY_PREFIX } from "./use-list-proformas-query";
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import {
|
||||||
|
type DefaultError,
|
||||||
|
type QueryKey,
|
||||||
|
type UseQueryResult,
|
||||||
|
useQuery,
|
||||||
|
} from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { type Proforma, getProformaById } from "../../api";
|
||||||
|
|
||||||
|
export const PROFORMA_QUERY_KEY = (proformaId?: string): QueryKey => [
|
||||||
|
"proformas:detail",
|
||||||
|
{
|
||||||
|
proformaId,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
type ProformaQueryOptions = {
|
||||||
|
enabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useProformaGetQuery = (
|
||||||
|
proformaId?: string,
|
||||||
|
options?: ProformaQueryOptions
|
||||||
|
): UseQueryResult<Proforma, DefaultError> => {
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
const enabled = options?.enabled ?? Boolean(proformaId);
|
||||||
|
|
||||||
|
return useQuery<Proforma, DefaultError>({
|
||||||
|
queryKey: PROFORMA_QUERY_KEY(proformaId),
|
||||||
|
queryFn: async ({ signal }) => getProformaById(dataSource, signal, proformaId),
|
||||||
|
enabled,
|
||||||
|
placeholderData: (previousData, _previousQuery) => previousData, // Mantener datos previos mientras se carga nueva datos (antiguo `keepPreviousData`)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*export function invalidateProformaDetailCache(qc: QueryClient, id: string) {
|
||||||
|
return qc.invalidateQueries({
|
||||||
|
queryKey: getProformaQueryKey(id ?? "unknown"),
|
||||||
|
exact: Boolean(id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setProformaDetailCache(qc: QueryClient, id: string, data: unknown) {
|
||||||
|
qc.setQueryData(getProformaQueryKey(id), data);
|
||||||
|
}
|
||||||
|
*/
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import { ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { UpdateProformaByIdRequestSchema } from "../../../../common";
|
||||||
|
import type { ProformaFormData } from "../../update/types";
|
||||||
|
|
||||||
|
export const PROFORMA_UPDATE_KEY = ["proformas", "update"] as const;
|
||||||
|
|
||||||
|
type UpdateProformaContext = {};
|
||||||
|
|
||||||
|
type UpdateProformaPayload = {
|
||||||
|
id: string;
|
||||||
|
data: Partial<ProformaFormData>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useProformaUpdateMutation = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
const schema = UpdateProformaByIdRequestSchema;
|
||||||
|
|
||||||
|
return useMutation<Proforma, DefaultError, UpdateProformaPayload, UpdateProformaContext>({
|
||||||
|
mutationKey: PROFORMA_UPDATE_KEY,
|
||||||
|
|
||||||
|
mutationFn: async (payload) => {
|
||||||
|
const { id: proformaId, data } = payload;
|
||||||
|
if (!proformaId) {
|
||||||
|
throw new Error("proformaId is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = schema.safeParse(data);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error));
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await dataSource.updateOne("proformas", proformaId, data);
|
||||||
|
return updated as Proforma;
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess: (updated: Proforma, variables) => {
|
||||||
|
const { id: proformaId } = updated;
|
||||||
|
|
||||||
|
// Invalida el listado para refrescar desde servidor
|
||||||
|
//invalidateProformaListCache(queryClient);
|
||||||
|
|
||||||
|
// Actualiza detalle
|
||||||
|
//setProformaDetailCache(queryClient, proformaId, updated);
|
||||||
|
|
||||||
|
// Actualiza todas las páginas donde aparezca
|
||||||
|
//upsertProformaIntoListCaches(queryClient, { ...updated });
|
||||||
|
},
|
||||||
|
|
||||||
|
onSettled: () => {
|
||||||
|
// Refresca todos los listados
|
||||||
|
//invalidateProformaListCache(queryClient);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./api";
|
||||||
|
export * from "./entities";
|
||||||
|
export * from "./hooks";
|
||||||
|
export * from "./ui";
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./proforma-update-form.entity";
|
||||||
|
export * from "./proforma-update-form.schema";
|
||||||
|
export * from "./proforma-update-form-defaults";
|
||||||
|
export * from "./proforma-update-patch.entity";
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
export interface ProformaItemUpdateForm {
|
||||||
|
id: string;
|
||||||
|
position: string;
|
||||||
|
description: string;
|
||||||
|
quantity: number;
|
||||||
|
unitAmount: number;
|
||||||
|
discountPercentage: number;
|
||||||
|
taxes: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import type { CustomerUpdateForm } from "./proforma-update-form.entity";
|
||||||
|
|
||||||
|
export const defaultCustomerUpdateForm: CustomerUpdateForm = {
|
||||||
|
reference: "",
|
||||||
|
isCompany: true,
|
||||||
|
name: "",
|
||||||
|
tradeName: "",
|
||||||
|
tin: "",
|
||||||
|
|
||||||
|
defaultTaxes: [],
|
||||||
|
|
||||||
|
street: "",
|
||||||
|
street2: "",
|
||||||
|
city: "",
|
||||||
|
province: "",
|
||||||
|
postalCode: "",
|
||||||
|
country: "es",
|
||||||
|
|
||||||
|
primaryEmail: "",
|
||||||
|
secondaryEmail: "",
|
||||||
|
primaryPhone: "",
|
||||||
|
secondaryPhone: "",
|
||||||
|
primaryMobile: "",
|
||||||
|
secondaryMobile: "",
|
||||||
|
|
||||||
|
fax: "",
|
||||||
|
website: "",
|
||||||
|
|
||||||
|
legalRecord: "",
|
||||||
|
|
||||||
|
languageCode: "es",
|
||||||
|
currencyCode: "EUR",
|
||||||
|
};
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* ProformaUpdateForm representa el shape de datos del formulario de actualización de proformas.
|
||||||
|
* Es decir, los campos que se muestran en el formulario y que el usuario puede editar.
|
||||||
|
*
|
||||||
|
* Es específico de la UI y no tiene por qué coincidir
|
||||||
|
* con el shape del dominio ni con el de la API.
|
||||||
|
*
|
||||||
|
* Debe cumplir las siguientes reglas:
|
||||||
|
* - nombres en camelCase
|
||||||
|
* - tipos orientados a UI/form
|
||||||
|
* - sin campos de solo lectura que no se editen
|
||||||
|
* - sin shape DTO
|
||||||
|
* - sin detalles impuestos por el widget
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ProformaItemForm } from "../../shared/entities";
|
||||||
|
|
||||||
|
export interface ProformaUpdateForm {
|
||||||
|
series: string;
|
||||||
|
|
||||||
|
invoiceDate: string;
|
||||||
|
operationDate: string;
|
||||||
|
|
||||||
|
customerId: string;
|
||||||
|
|
||||||
|
reference: string;
|
||||||
|
notes: string;
|
||||||
|
|
||||||
|
languageCode: string;
|
||||||
|
currencyCode: string;
|
||||||
|
|
||||||
|
globalDiscountPercentage: number;
|
||||||
|
|
||||||
|
paymentMethod: string;
|
||||||
|
|
||||||
|
items: ProformaItemForm[];
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Este esquema es para validar los datos del formulario de actualización de proformas.
|
||||||
|
* No tiene por qué coincidir con el shape de la entidad ni con el de la API.
|
||||||
|
* Solo define los campos que se muestran en el formulario y sus validaciones.
|
||||||
|
*
|
||||||
|
* Reglas:
|
||||||
|
* - no meter transformaciones silenciosas raras en el esquema (ej: .toUpperCase())
|
||||||
|
* - nombres en camelCase
|
||||||
|
* - tipos orientados a UI/form
|
||||||
|
* - sin campos de solo lectura que no se editen
|
||||||
|
* - sin shape DTO
|
||||||
|
* - sin detalles impuestos por el widget
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const ProformaItemFormSchema = z.object({
|
||||||
|
description: z.string().max(2000).optional().default(""),
|
||||||
|
quantity: z.any(), //NumericStringSchema.optional(),
|
||||||
|
unit_amount: z.any(), //NumericStringSchema.optional(),
|
||||||
|
|
||||||
|
subtotal_amount: z.any(), //z.number(),
|
||||||
|
discount_percentage: z.any(), //NumericStringSchema.optional(),
|
||||||
|
discount_amount: z.number(),
|
||||||
|
taxable_amount: z.number(),
|
||||||
|
|
||||||
|
tax_codes: z.array(z.string()).default([]),
|
||||||
|
|
||||||
|
taxes_amount: z.number(),
|
||||||
|
total_amount: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ProformaFormSchema = z.object({
|
||||||
|
invoice_number: z.string().optional(),
|
||||||
|
series: z.string().optional(),
|
||||||
|
|
||||||
|
invoice_date: z.string().optional(),
|
||||||
|
operation_date: z.string().optional(),
|
||||||
|
|
||||||
|
customer_id: z.string().optional(),
|
||||||
|
recipient: z
|
||||||
|
.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
tin: z.string().optional(),
|
||||||
|
street: z.string().optional(),
|
||||||
|
street2: z.string().optional(),
|
||||||
|
city: z.string().optional(),
|
||||||
|
province: z.string().optional(),
|
||||||
|
postal_code: z.string().optional(),
|
||||||
|
country: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
reference: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
notes: z.string().optional(),
|
||||||
|
|
||||||
|
language_code: z
|
||||||
|
.string({
|
||||||
|
error: "El idioma es obligatorio",
|
||||||
|
})
|
||||||
|
.min(1, "Debe indicar un idioma")
|
||||||
|
.toUpperCase() // asegura mayúsculas
|
||||||
|
.default("es"),
|
||||||
|
|
||||||
|
currency_code: z
|
||||||
|
.string({
|
||||||
|
error: "La moneda es obligatoria",
|
||||||
|
})
|
||||||
|
.min(1, "La moneda no puede estar vacía")
|
||||||
|
.toUpperCase() // asegura mayúsculas
|
||||||
|
.default("EUR"),
|
||||||
|
|
||||||
|
taxes: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
tax_code: z.string(),
|
||||||
|
tax_label: z.string(),
|
||||||
|
taxable_amount: z.number(),
|
||||||
|
taxes_amount: z.number(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
items: z.array(ProformaItemFormSchema).optional(),
|
||||||
|
|
||||||
|
subtotal_amount: z.number(),
|
||||||
|
items_discount_amount: z.number(),
|
||||||
|
discount_percentage: z.number(),
|
||||||
|
discount_amount: z.number(),
|
||||||
|
taxable_amount: z.number(),
|
||||||
|
taxes_amount: z.number(),
|
||||||
|
total_amount: z.number(),
|
||||||
|
});
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import type { ProformaUpdateForm } from "./proforma-update-form.entity";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProformaUpdatePatch representa los cambios que se van a aplicar a una proforma.
|
||||||
|
* Se representa con las mismas propiedades que ProformaUpdateForm,
|
||||||
|
* pero todas ellas son opcionales.
|
||||||
|
*
|
||||||
|
* A la API solo hay que enviar los campos que han cambiado.
|
||||||
|
*
|
||||||
|
* Reglas:
|
||||||
|
* - debe ser un Partial de ProformaUpdateForm
|
||||||
|
* - no debe tener campos adicionales ni transformaciones
|
||||||
|
* - debe ser un shape orientado a la API, no a la UI ni al dominio
|
||||||
|
* - sin shape DTO, solo tipos simples y directos
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ProformaUpdatePatch = Partial<ProformaUpdateForm>;
|
||||||
Loading…
Reference in New Issue
Block a user