.
This commit is contained in:
parent
47dffacf03
commit
0129c5963c
1
apps/web/src/lib/data-source/index.ts
Normal file
1
apps/web/src/lib/data-source/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "../../../../../modules/core/src/web/lib/data-source/errors";
|
||||
@ -26,6 +26,10 @@
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"allowUnreachableCode": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": [
|
||||
"src",
|
||||
"../../modules/core/src/web/lib/data-source/errors",
|
||||
"../../modules/core/src/web/lib/data-source/axios/normalize-axios-error.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@ -24,7 +24,6 @@ export class CreatePaymentMethodUseCase {
|
||||
|
||||
public execute(params: CreatePaymentMethodUseCaseInput) {
|
||||
const { dto, companyId } = params;
|
||||
console.log("Executing CreatePaymentMethodUseCase with params:", params);
|
||||
|
||||
const mappedPropsResult = this.deps.dtoMapper.map(dto, { companyId });
|
||||
if (mappedPropsResult.isFailure) {
|
||||
|
||||
@ -10,7 +10,7 @@ import type {
|
||||
|
||||
export class TaxDefinitionPublicModelMapper {
|
||||
public toPublicModel(taxDefinition: TaxDefinition): TaxDefinitionPublicModel {
|
||||
console.log(taxDefinition.description)
|
||||
|
||||
return {
|
||||
id: taxDefinition.id,
|
||||
companyId: Maybe.some(taxDefinition.companyId),
|
||||
|
||||
@ -80,8 +80,6 @@ export class SequelizeTaxDefinitionDomainMapper extends SequelizeDomainMapper<
|
||||
|
||||
const allowedSurchargeCodes = extractOrPushError(
|
||||
maybeFromNullableResult(raw.allowed_surcharge_codes, (v) => {
|
||||
console.log(v);
|
||||
|
||||
const values = v.split(";");
|
||||
if (!Array.isArray(values))
|
||||
return Result.fail(new Error("Invalid allowed_surcharge_codes"));
|
||||
|
||||
@ -59,6 +59,7 @@ export abstract class ExpressController {
|
||||
|
||||
await this.executeImpl();
|
||||
} catch (error: unknown) {
|
||||
console.error("Error in ExpressController.execute:", error);
|
||||
this.handleError(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
74
modules/core/src/web/components/error-state.tsx
Normal file
74
modules/core/src/web/components/error-state.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
// packages/rdx-ui/src/components/error-state.tsx
|
||||
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import { AlertCircle, RefreshCw } from "lucide-react";
|
||||
import type React from "react";
|
||||
|
||||
export interface ErrorStateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
||||
title: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
reference?: string;
|
||||
retryLabel?: string;
|
||||
isRetrying?: boolean;
|
||||
onRetry?: () => void;
|
||||
}
|
||||
|
||||
export const ErrorState = ({
|
||||
title,
|
||||
description,
|
||||
reference,
|
||||
retryLabel = "Reintentar",
|
||||
isRetrying = false,
|
||||
onRetry,
|
||||
className,
|
||||
...props
|
||||
}: ErrorStateProps) => {
|
||||
return (
|
||||
<div
|
||||
aria-live="polite"
|
||||
className={cn(
|
||||
"flex min-h-64 flex-col items-center justify-center rounded-lg border border-destructive/30 bg-destructive/5 px-6 py-10 text-center",
|
||||
className
|
||||
)}
|
||||
role="alert"
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="mb-4 flex size-11 items-center justify-center rounded-full bg-destructive/10 text-destructive"
|
||||
>
|
||||
<AlertCircle className="size-5" />
|
||||
</div>
|
||||
|
||||
<h2 className="text-base font-semibold text-foreground">{title}</h2>
|
||||
|
||||
{description ? (
|
||||
<div className="mt-2 max-w-xl text-sm text-muted-foreground">{description}</div>
|
||||
) : null}
|
||||
|
||||
{reference ? (
|
||||
<p className="mt-3 text-xs text-muted-foreground">
|
||||
Referencia:{" "}
|
||||
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-[0.75rem] text-foreground">
|
||||
{reference}
|
||||
</code>
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{onRetry ? (
|
||||
<Button
|
||||
className="mt-5"
|
||||
disabled={isRetrying}
|
||||
onClick={onRetry}
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<RefreshCw aria-hidden="true" className={cn("size-4", isRetrying && "animate-spin")} />
|
||||
|
||||
<span aria-live="polite">{isRetrying ? "Reintentando…" : retryLabel}</span>
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./error-alert";
|
||||
export * from "./error-state";
|
||||
export * from "./form";
|
||||
export * from "./not-found-card";
|
||||
export * from "./page-header";
|
||||
|
||||
@ -3,13 +3,23 @@ import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
import type { ICustomParams, IDataSource } from "../datasource.interface";
|
||||
|
||||
import { defaultAxiosRequestConfig } from "./create-axios-instance";
|
||||
import { normalizeAxiosError } from "./normalize-axios-error";
|
||||
|
||||
const executeRequest = async <R>(request: () => Promise<AxiosResponse<R>>): Promise<R> => {
|
||||
try {
|
||||
const response = await request();
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw normalizeAxiosError(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const createAxiosDataSource = (client: AxiosInstance): IDataSource => {
|
||||
if (!client) {
|
||||
throw new Error("[Axios] Se esperaba una instancia de Axios.");
|
||||
}
|
||||
|
||||
if (!client.getUri || typeof client.getUri !== "function") {
|
||||
if (typeof client.getUri !== "function") {
|
||||
throw new Error("[Axios] La instancia proporcionada no es una instancia de Axios válida.");
|
||||
}
|
||||
|
||||
@ -21,117 +31,83 @@ export const createAxiosDataSource = (client: AxiosInstance): IDataSource => {
|
||||
getBaseUrl: () => client.getUri(),
|
||||
|
||||
getList: async <R>(resource: string, params?: Record<string, unknown>): Promise<R> => {
|
||||
const { signal, ...rest } = (params ?? {}) as Record<string, unknown> & {
|
||||
const { signal, ...queryParams } = (params ?? {}) as Record<string, unknown> & {
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
|
||||
const res = await client.get<R>(resource, {
|
||||
signal,
|
||||
params: rest,
|
||||
});
|
||||
|
||||
return res.data;
|
||||
return executeRequest(() =>
|
||||
client.get<R>(resource, {
|
||||
signal,
|
||||
params: queryParams,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
getOne: async <R>(
|
||||
resource: string,
|
||||
id: string | number,
|
||||
params?: Record<string, unknown>
|
||||
): Promise<R> => {
|
||||
const res = await client.get<R>(`${resource}/${id}`, params);
|
||||
return res.data;
|
||||
},
|
||||
): Promise<R> =>
|
||||
executeRequest(() => client.get<R>(`${resource}/${id}`, params as AxiosRequestConfig)),
|
||||
|
||||
getMany: async <R>(resource: string, ids: Array<string | number>): Promise<R> => {
|
||||
const res = await client.get<R>(resource, {
|
||||
params: { ids },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
},
|
||||
getMany: async <R>(resource: string, ids: Array<string | number>): Promise<R> =>
|
||||
executeRequest(() =>
|
||||
client.get<R>(resource, {
|
||||
params: { ids },
|
||||
})
|
||||
),
|
||||
|
||||
createOne: async <TData, R>(
|
||||
resource: string,
|
||||
data: TData,
|
||||
params?: Record<string, unknown>
|
||||
): Promise<R> => {
|
||||
const res = await client.post<R, AxiosResponse<R>, TData>(
|
||||
resource,
|
||||
data,
|
||||
params as AxiosRequestConfig<TData>
|
||||
);
|
||||
|
||||
return res.data;
|
||||
},
|
||||
): Promise<R> =>
|
||||
executeRequest(() =>
|
||||
client.post<R, AxiosResponse<R>, TData>(resource, data, params as AxiosRequestConfig<TData>)
|
||||
),
|
||||
|
||||
updateOne: async <TData, R>(
|
||||
resource: string,
|
||||
id: string | number,
|
||||
data: TData,
|
||||
params?: Record<string, unknown>
|
||||
): Promise<R> => {
|
||||
const url = `${resource}/${id}`;
|
||||
|
||||
const res = await client.put<R, AxiosResponse<R>, TData>(
|
||||
url,
|
||||
data,
|
||||
params as AxiosRequestConfig<TData>
|
||||
);
|
||||
|
||||
return res.data;
|
||||
},
|
||||
): Promise<R> =>
|
||||
executeRequest(() =>
|
||||
client.put<R, AxiosResponse<R>, TData>(
|
||||
`${resource}/${id}`,
|
||||
data,
|
||||
params as AxiosRequestConfig<TData>
|
||||
)
|
||||
),
|
||||
|
||||
deleteOne: async <R = void>(
|
||||
resource: string,
|
||||
id: string | number,
|
||||
params?: Record<string, unknown>
|
||||
): Promise<R> => {
|
||||
const res = await client.delete<R>(`${resource}/${id}`, params);
|
||||
return res.data;
|
||||
},
|
||||
): Promise<R> =>
|
||||
executeRequest(() => client.delete<R>(`${resource}/${id}`, params as AxiosRequestConfig)),
|
||||
|
||||
custom: async <TData, R>(customParams: ICustomParams<TData>): Promise<R> => {
|
||||
const { url, path, method, responseType, headers, signal, data, ...payload } = customParams;
|
||||
|
||||
const requestUrl = path ? `${client.getUri()}/${path}` : url;
|
||||
if (!requestUrl) throw new Error('"url" or "path" param is missing');
|
||||
|
||||
if (!requestUrl) {
|
||||
throw new Error('"url" or "path" param is missing');
|
||||
}
|
||||
|
||||
const config: AxiosRequestConfig<TData> = {
|
||||
url: requestUrl,
|
||||
method,
|
||||
responseType,
|
||||
signal,
|
||||
...payload,
|
||||
...defaultAxiosRequestConfig,
|
||||
...payload,
|
||||
headers,
|
||||
data,
|
||||
};
|
||||
|
||||
let customResponse: AxiosResponse<R, TData>;
|
||||
|
||||
switch (method) {
|
||||
case "put":
|
||||
case "post":
|
||||
case "patch":
|
||||
customResponse = await client.request<R, AxiosResponse<R>, TData>({
|
||||
...config,
|
||||
data,
|
||||
});
|
||||
break;
|
||||
|
||||
case "delete":
|
||||
customResponse = await client.delete<R, AxiosResponse<R>, TData>(requestUrl, {
|
||||
...config,
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
customResponse = await client.get<R, AxiosResponse<R>, TData>(requestUrl, {
|
||||
...config,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return customResponse.data;
|
||||
return executeRequest(() => client.request<R, AxiosResponse<R>, TData>(config));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
// modules/core/src/web/lib/data-source/axios/normalize-axios-error.ts
|
||||
|
||||
import axios, { AxiosError } from "axios";
|
||||
|
||||
import {
|
||||
ApiClientError,
|
||||
type ApiClientErrorKind,
|
||||
isApiProblemDetails,
|
||||
} from "../../../../../../../apps/web/src/lib/data-source";
|
||||
|
||||
const getErrorKind = (status: number): ApiClientErrorKind => {
|
||||
switch (status) {
|
||||
case 401:
|
||||
return "unauthorized";
|
||||
case 403:
|
||||
return "forbidden";
|
||||
case 404:
|
||||
return "not-found";
|
||||
case 409:
|
||||
return "conflict";
|
||||
case 422:
|
||||
return "validation";
|
||||
case 503:
|
||||
return "unavailable";
|
||||
default:
|
||||
return status >= 500 ? "server" : "unknown";
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeProblemError = (error: AxiosError, data: unknown): ApiClientError => {
|
||||
if (!isApiProblemDetails(data)) {
|
||||
return new ApiClientError({
|
||||
kind: error.response?.status && error.response.status >= 500 ? "server" : "unknown",
|
||||
status: error.response?.status,
|
||||
message: "The server returned an unexpected error response.",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
return new ApiClientError({
|
||||
kind: getErrorKind(data.status),
|
||||
status: data.status,
|
||||
title: data.title,
|
||||
detail: data.detail,
|
||||
code: data.code,
|
||||
source: data.source,
|
||||
instance: data.instance,
|
||||
correlationId: data.correlationId,
|
||||
issues: data.errors,
|
||||
problem: data,
|
||||
message: data.detail || data.title,
|
||||
cause: error,
|
||||
});
|
||||
};
|
||||
|
||||
export const normalizeAxiosError = (error: unknown): Error => {
|
||||
if (!axios.isAxiosError(error)) {
|
||||
return error instanceof Error
|
||||
? error
|
||||
: new ApiClientError({
|
||||
kind: "unknown",
|
||||
message: "An unexpected error occurred.",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
if (error.code === AxiosError.ERR_CANCELED) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (error.code === AxiosError.ECONNABORTED) {
|
||||
return new ApiClientError({
|
||||
kind: "timeout",
|
||||
message: "The request timed out.",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
if (!error.response) {
|
||||
return new ApiClientError({
|
||||
kind: "network",
|
||||
message: "The server could not be reached.",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
return normalizeProblemError(error, error.response.data);
|
||||
};
|
||||
@ -0,0 +1,65 @@
|
||||
// modules/core/src/web/lib/data-source/errors/api-client-error.ts
|
||||
|
||||
import type { ApiProblemDetails, ApiProblemIssue, ApiProblemSource } from "./api-problem-details";
|
||||
|
||||
export type ApiClientErrorKind =
|
||||
| "unauthorized"
|
||||
| "forbidden"
|
||||
| "not-found"
|
||||
| "conflict"
|
||||
| "validation"
|
||||
| "unavailable"
|
||||
| "server"
|
||||
| "network"
|
||||
| "timeout"
|
||||
| "unknown";
|
||||
|
||||
export interface ApiClientErrorOptions {
|
||||
kind: ApiClientErrorKind;
|
||||
message: string;
|
||||
|
||||
status?: number;
|
||||
title?: string;
|
||||
detail?: string;
|
||||
code?: string;
|
||||
source?: ApiProblemSource;
|
||||
instance?: string;
|
||||
correlationId?: string;
|
||||
issues?: ApiProblemIssue[];
|
||||
problem?: ApiProblemDetails;
|
||||
cause?: unknown;
|
||||
}
|
||||
|
||||
export class ApiClientError extends Error {
|
||||
readonly kind: ApiClientErrorKind;
|
||||
readonly status?: number;
|
||||
readonly title?: string;
|
||||
readonly detail?: string;
|
||||
readonly code?: string;
|
||||
readonly source?: ApiProblemSource;
|
||||
readonly instance?: string;
|
||||
readonly correlationId?: string;
|
||||
readonly issues: readonly ApiProblemIssue[];
|
||||
readonly problem?: ApiProblemDetails;
|
||||
|
||||
constructor(options: ApiClientErrorOptions) {
|
||||
super(options.message, { cause: options.cause });
|
||||
|
||||
this.name = "ApiClientError";
|
||||
this.kind = options.kind;
|
||||
this.status = options.status;
|
||||
this.title = options.title;
|
||||
this.detail = options.detail;
|
||||
this.code = options.code;
|
||||
this.source = options.source;
|
||||
this.instance = options.instance;
|
||||
this.correlationId = options.correlationId;
|
||||
this.issues = options.issues ?? [];
|
||||
this.problem = options.problem;
|
||||
|
||||
Object.setPrototypeOf(this, ApiClientError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export const isApiClientError = (error: unknown): error is ApiClientError =>
|
||||
error instanceof ApiClientError;
|
||||
@ -0,0 +1,26 @@
|
||||
export type ApiProblemPathSegment = string | number;
|
||||
|
||||
export interface ApiProblemIssue {
|
||||
path?: ApiProblemPathSegment[];
|
||||
code?: string;
|
||||
message: string;
|
||||
|
||||
origin?: string;
|
||||
format?: string;
|
||||
}
|
||||
|
||||
export type ApiProblemSource = "request" | "response" | "domain" | "infrastructure";
|
||||
|
||||
export interface ApiProblemDetails {
|
||||
type: string;
|
||||
title: string;
|
||||
status: number;
|
||||
detail: string;
|
||||
|
||||
instance?: string;
|
||||
method?: string;
|
||||
correlationId?: string;
|
||||
code?: string;
|
||||
source?: ApiProblemSource;
|
||||
errors?: ApiProblemIssue[];
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
import { isApiClientError } from "./api-client-error";
|
||||
|
||||
export interface UserErrorMessage {
|
||||
title: string;
|
||||
description: string;
|
||||
reference?: string;
|
||||
}
|
||||
|
||||
export const getUserErrorMessage = (error: unknown): UserErrorMessage => {
|
||||
if (!isApiClientError(error)) {
|
||||
return {
|
||||
title: "No se ha podido completar la operación",
|
||||
description: "Se ha producido un error inesperado.",
|
||||
};
|
||||
}
|
||||
|
||||
const reference = error.correlationId;
|
||||
|
||||
switch (error.kind) {
|
||||
case "unauthorized":
|
||||
return {
|
||||
title: "La sesión no es válida",
|
||||
description: "Vuelve a iniciar sesión para continuar.",
|
||||
reference,
|
||||
};
|
||||
|
||||
case "forbidden":
|
||||
return {
|
||||
title: "Acceso no permitido",
|
||||
description: "No tienes permisos para realizar esta operación.",
|
||||
reference,
|
||||
};
|
||||
|
||||
case "not-found":
|
||||
return {
|
||||
title: "Recurso no encontrado",
|
||||
description: "El recurso solicitado ya no existe o no está disponible.",
|
||||
reference,
|
||||
};
|
||||
|
||||
case "conflict":
|
||||
return {
|
||||
title: "No se puede completar la operación",
|
||||
description: error.detail || "El estado actual del recurso impide realizarla.",
|
||||
reference,
|
||||
};
|
||||
|
||||
case "validation":
|
||||
if (error.source === "request") {
|
||||
return {
|
||||
title: "Revisa los datos introducidos",
|
||||
description: "Algunos datos enviados no son válidos.",
|
||||
reference,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: "No se han podido procesar los datos",
|
||||
description:
|
||||
"El servidor ha devuelto información inconsistente. Inténtalo de nuevo o comunica la incidencia.",
|
||||
reference,
|
||||
};
|
||||
|
||||
case "network":
|
||||
return {
|
||||
title: "Sin conexión con el servidor",
|
||||
description: "Comprueba la conexión de red e inténtalo de nuevo.",
|
||||
};
|
||||
|
||||
case "timeout":
|
||||
return {
|
||||
title: "La operación está tardando demasiado",
|
||||
description: "El servidor no ha respondido dentro del tiempo esperado.",
|
||||
};
|
||||
|
||||
case "unavailable":
|
||||
return {
|
||||
title: "Servicio temporalmente no disponible",
|
||||
description: "Inténtalo de nuevo dentro de unos instantes.",
|
||||
reference,
|
||||
};
|
||||
|
||||
case "server":
|
||||
case "unknown":
|
||||
default:
|
||||
return {
|
||||
title: "No se ha podido completar la operación",
|
||||
description: "Se ha producido un error interno. Inténtalo de nuevo.",
|
||||
reference,
|
||||
};
|
||||
}
|
||||
};
|
||||
4
modules/core/src/web/lib/data-source/errors/index.ts
Normal file
4
modules/core/src/web/lib/data-source/errors/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./api-client-error";
|
||||
export * from "./api-problem-details";
|
||||
export * from "./get-user-error-message";
|
||||
export * from "./is-api-problem-details";
|
||||
@ -0,0 +1,47 @@
|
||||
import type {
|
||||
ApiProblemDetails,
|
||||
ApiProblemIssue,
|
||||
ApiProblemPathSegment,
|
||||
} from "./api-problem-details";
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null;
|
||||
|
||||
const isPathSegment = (value: unknown): value is ApiProblemPathSegment =>
|
||||
typeof value === "string" || typeof value === "number";
|
||||
|
||||
const isApiProblemIssue = (value: unknown): value is ApiProblemIssue => {
|
||||
if (!isRecord(value) || typeof value.message !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.path !== undefined && !(Array.isArray(value.path) && value.path.every(isPathSegment))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const isApiProblemDetails = (value: unknown): value is ApiProblemDetails => {
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof value.type !== "string" ||
|
||||
typeof value.title !== "string" ||
|
||||
typeof value.status !== "number" ||
|
||||
typeof value.detail !== "string"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
value.errors !== undefined &&
|
||||
!(Array.isArray(value.errors) && value.errors.every(isApiProblemIssue))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
export * from "./axios";
|
||||
export * from "./build-text-filters";
|
||||
export * from "./datasource.interface";
|
||||
export * from "./errors";
|
||||
|
||||
@ -4,9 +4,9 @@ import type { Maybe } from "@repo/rdx-utils";
|
||||
import type {
|
||||
InvoiceAmount,
|
||||
InvoiceNumber,
|
||||
InvoiceRecipient,
|
||||
InvoiceSerie,
|
||||
InvoiceStatus,
|
||||
IssuedInvoiceRecipient,
|
||||
VerifactuRecord,
|
||||
} from "../../../domain";
|
||||
|
||||
@ -14,7 +14,6 @@ export type IssuedInvoiceSummary = {
|
||||
id: UniqueID;
|
||||
companyId: UniqueID;
|
||||
|
||||
isProforma: boolean;
|
||||
invoiceNumber: InvoiceNumber;
|
||||
status: InvoiceStatus;
|
||||
series: Maybe<InvoiceSerie>;
|
||||
@ -26,7 +25,7 @@ export type IssuedInvoiceSummary = {
|
||||
description: Maybe<string>;
|
||||
|
||||
customerId: UniqueID;
|
||||
recipient: InvoiceRecipient;
|
||||
recipient: IssuedInvoiceRecipient;
|
||||
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
@ -31,16 +31,15 @@ export class IssuedInvoiceSummarySnapshotBuilder implements IIssuedInvoiceSummar
|
||||
return {
|
||||
id: invoice.id.toString(),
|
||||
company_id: invoice.companyId.toString(),
|
||||
is_proforma: invoice.isProforma,
|
||||
|
||||
invoice_number: invoice.invoiceNumber.toString(),
|
||||
status: invoice.status.toPrimitive(),
|
||||
series: maybeToEmptyString(invoice.series, (v) => v.toString()),
|
||||
|
||||
invoice_date: invoice.invoiceDate.toDateString(),
|
||||
operation_date: maybeToEmptyString(invoice.operationDate, (v) => v.toDateString()),
|
||||
reference: maybeToEmptyString(invoice.reference, (v) => v.toString()),
|
||||
description: maybeToEmptyString(invoice.description, (v) => v.toString()),
|
||||
operation_date: maybeToNullable(invoice.operationDate, (v) => v.toDateString()),
|
||||
reference: maybeToNullable(invoice.reference, (v) => v.toString()),
|
||||
description: maybeToNullable(invoice.description, (v) => v.toString()),
|
||||
|
||||
customer_id: invoice.customerId.toString(),
|
||||
|
||||
|
||||
@ -4,20 +4,19 @@
|
||||
export interface IIssuedInvoiceSummarySnapshot {
|
||||
id: string;
|
||||
company_id: string;
|
||||
is_proforma: boolean;
|
||||
|
||||
invoice_number: string;
|
||||
status: string;
|
||||
series: string;
|
||||
|
||||
invoice_date: string;
|
||||
operation_date: string;
|
||||
operation_date: string | null;
|
||||
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
|
||||
reference: string | null;
|
||||
description: string;
|
||||
description: string | null;
|
||||
|
||||
customer_id: string;
|
||||
recipient: {
|
||||
|
||||
@ -15,12 +15,12 @@ import { Maybe, NumberHelper, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { CreateProformaRequestDTO } from "../../../../common";
|
||||
import {
|
||||
type InvoiceRecipient,
|
||||
InvoiceSerie,
|
||||
InvoiceStatus,
|
||||
ItemAmount,
|
||||
ItemDescription,
|
||||
ItemQuantity,
|
||||
type ProformaRecipient,
|
||||
} from "../../../domain";
|
||||
import type {
|
||||
ProformaCreateInputProps,
|
||||
@ -64,8 +64,8 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
||||
errors
|
||||
);
|
||||
|
||||
const invoiceDate = extractOrPushError(
|
||||
UtcDate.createFromISO(dto.invoice_date),
|
||||
const proformaDate = extractOrPushError(
|
||||
UtcDate.createFromISO(dto.proforma_date),
|
||||
"invoice_date",
|
||||
errors
|
||||
);
|
||||
@ -142,11 +142,11 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
||||
//invoiceNumber: invoiceNumber!,
|
||||
series: series!,
|
||||
|
||||
proformaDate: invoiceDate!,
|
||||
proformaDate: proformaDate!,
|
||||
operationDate: operationDate!,
|
||||
|
||||
customerId: customerId!,
|
||||
recipient: Maybe.none<InvoiceRecipient>(),
|
||||
recipient: Maybe.none<ProformaRecipient>(),
|
||||
|
||||
reference: reference!,
|
||||
description: description!,
|
||||
@ -169,6 +169,7 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
||||
props,
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
console.error(err);
|
||||
return Result.fail(new DomainError("Proforma props mapping failed", { cause: err }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ import type { ProformaItemTaxCodesInput } from "./proforma-item-tax-codes-input.
|
||||
* - Se encargan de validar y convertir los datos recibidos en formatos adecuados para el dominio.
|
||||
*/
|
||||
|
||||
export type ProformaCreateInputProps = Omit<IProformaCreateProps, "items" | "invoiceNumber"> & {
|
||||
export type ProformaCreateInputProps = Omit<IProformaCreateProps, "items" | "proformaReference"> & {
|
||||
items: ProformaItemCreateInputProps[];
|
||||
};
|
||||
|
||||
|
||||
@ -77,8 +77,6 @@ export class ProformaUpdater implements IProformaUpdater {
|
||||
}): Promise<Result<ProformaPatchProps, Error>> {
|
||||
const { patch, companyId, currentInvoiceDate } = params;
|
||||
|
||||
console.log(patch);
|
||||
|
||||
if (patch.taxRegimeCode !== undefined) {
|
||||
const taxRegimeResult = await this.deps.taxResolver.ensureTaxRegimeByCode({
|
||||
companyId,
|
||||
|
||||
@ -10,7 +10,6 @@ export interface IProformaSummarySnapshotBuilder
|
||||
export class ProformaSummarySnapshotBuilder implements IProformaSummarySnapshotBuilder {
|
||||
toOutput(proforma: ProformaSummary): ProformaSummaryDTO {
|
||||
const recipient = proforma.recipient.toObjectString();
|
||||
console.log("Proforma => ", proforma);
|
||||
|
||||
return {
|
||||
id: proforma.id.toString(),
|
||||
|
||||
@ -19,48 +19,85 @@ export class IssuedInvoiceItemModel extends Model<
|
||||
InferAttributes<IssuedInvoiceItemModel>,
|
||||
InferCreationAttributes<IssuedInvoiceItemModel, { omit: "issued_invoice" }>
|
||||
> {
|
||||
declare id: string;
|
||||
declare item_id: string;
|
||||
declare issued_invoice_id: string;
|
||||
|
||||
declare position: number;
|
||||
|
||||
declare description: CreationOptional<string | null>;
|
||||
|
||||
declare quantity_value: CreationOptional<number | null>;
|
||||
declare quantity_scale: number;
|
||||
|
||||
declare unit_amount_value: CreationOptional<number | null>;
|
||||
declare unit_amount_scale: number;
|
||||
|
||||
// Subtotal (cantidad * importe unitario)
|
||||
declare subtotal_amount_value: number;
|
||||
declare subtotal_amount_scale: number;
|
||||
|
||||
// Discount percentage
|
||||
declare item_discount_percentage_value: CreationOptional<number | null>;
|
||||
declare item_discount_percentage_scale: number;
|
||||
|
||||
// Discount amount
|
||||
declare item_discount_amount_value: number;
|
||||
declare item_discount_amount_scale: number;
|
||||
|
||||
// Porcentaje de descuento global proporcional a esta línea.
|
||||
declare global_discount_percentage_value: number;
|
||||
declare global_discount_percentage_scale: number;
|
||||
|
||||
// Importe del descuento global para esta línea
|
||||
declare global_discount_amount_value: number;
|
||||
declare global_discount_amount_scale: number;
|
||||
|
||||
// Suma de los dos descuentos: el de la linea + el global
|
||||
declare total_discount_amount_value: number;
|
||||
declare total_discount_amount_scale: number;
|
||||
|
||||
// Taxable amount (base imponible tras los dos descuentos)
|
||||
declare taxable_amount_value: number;
|
||||
declare taxable_amount_scale: number;
|
||||
|
||||
// IVA percentage
|
||||
declare iva_code: CreationOptional<string | null>;
|
||||
|
||||
declare iva_percentage_value: CreationOptional<number | null>;
|
||||
declare iva_percentage_scale: number;
|
||||
|
||||
// IVA amount
|
||||
declare iva_amount_value: number;
|
||||
declare iva_amount_scale: number;
|
||||
|
||||
// Recargo de equivalencia percentage
|
||||
declare rec_code: CreationOptional<string | null>;
|
||||
|
||||
declare rec_percentage_value: CreationOptional<number | null>;
|
||||
declare rec_percentage_scale: number;
|
||||
|
||||
// Recargo de equivalencia amount
|
||||
declare rec_amount_value: number;
|
||||
declare rec_amount_scale: number;
|
||||
|
||||
// Retention percentage
|
||||
declare retention_code: CreationOptional<string | null>;
|
||||
|
||||
declare retention_percentage_value: CreationOptional<number | null>;
|
||||
declare retention_percentage_scale: number;
|
||||
|
||||
declare retention_amount_value: number;
|
||||
declare retention_amount_scale: number;
|
||||
|
||||
// Total taxes amount / taxes total
|
||||
declare taxes_amount_value: number;
|
||||
declare taxes_amount_scale: number;
|
||||
|
||||
// Total
|
||||
declare total_amount_value: number;
|
||||
declare total_amount_scale: number;
|
||||
|
||||
// Relaciones
|
||||
declare issued_invoice: NonAttribute<IssuedInvoiceModel>;
|
||||
|
||||
static associate(database: Sequelize) {
|
||||
@ -88,10 +125,9 @@ export class IssuedInvoiceItemModel extends Model<
|
||||
export default (database: Sequelize) => {
|
||||
IssuedInvoiceItemModel.init(
|
||||
{
|
||||
id: {
|
||||
item_id: {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
field: "item_id",
|
||||
charset: "utf8mb4",
|
||||
collate: "utf8mb4_bin",
|
||||
} as any,
|
||||
@ -112,96 +148,117 @@ export default (database: Sequelize) => {
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
quantity_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
quantity_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
unit_amount_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
unit_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
subtotal_amount_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
subtotal_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
item_discount_percentage_value: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
item_discount_percentage_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
item_discount_amount_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
item_discount_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
global_discount_percentage_value: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
global_discount_percentage_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
global_discount_amount_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
global_discount_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
total_discount_amount_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
total_discount_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
taxable_amount_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
taxable_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
// IVA %
|
||||
|
||||
iva_code: {
|
||||
type: DataTypes.STRING(40),
|
||||
allowNull: true,
|
||||
@ -217,6 +274,7 @@ export default (database: Sequelize) => {
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
iva_amount_value: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
@ -227,11 +285,14 @@ export default (database: Sequelize) => {
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
// REC %
|
||||
rec_code: {
|
||||
type: DataTypes.STRING(40),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
rec_percentage_value: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: true,
|
||||
@ -242,6 +303,7 @@ export default (database: Sequelize) => {
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
rec_amount_value: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
@ -252,11 +314,14 @@ export default (database: Sequelize) => {
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
// Retención %
|
||||
retention_code: {
|
||||
type: DataTypes.STRING(40),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
retention_percentage_value: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: true,
|
||||
@ -267,6 +332,7 @@ export default (database: Sequelize) => {
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
retention_amount_value: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
@ -277,21 +343,25 @@ export default (database: Sequelize) => {
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
taxes_amount_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
taxes_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
total_amount_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
total_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
@ -302,11 +372,15 @@ export default (database: Sequelize) => {
|
||||
sequelize: database,
|
||||
modelName: "IssuedInvoiceItemModel",
|
||||
tableName: "issued_invoice_items",
|
||||
|
||||
charset: "utf8mb4",
|
||||
collate: "utf8mb4_unicode_ci",
|
||||
underscored: true,
|
||||
|
||||
indexes: [{ fields: ["issued_invoice_id"] }, { fields: ["issued_invoice_id", "position"] }],
|
||||
whereMergeStrategy: "and",
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {
|
||||
order: [["position", "ASC"]],
|
||||
},
|
||||
|
||||
@ -51,43 +51,68 @@ export class IssuedInvoiceModel extends Model<
|
||||
|
||||
declare reference: CreationOptional<string | null>;
|
||||
declare description: string;
|
||||
|
||||
declare notes: CreationOptional<string | null>;
|
||||
|
||||
declare language_code: CreationOptional<string>;
|
||||
declare currency_code: CreationOptional<string>;
|
||||
|
||||
// Método de pago
|
||||
declare payment_method_id: CreationOptional<string | null>;
|
||||
declare payment_method_name: CreationOptional<string | null>;
|
||||
declare payment_method_description: CreationOptional<string | null>;
|
||||
|
||||
// Plazo de pago
|
||||
declare payment_term_id: CreationOptional<string | null>;
|
||||
declare payment_term_name: CreationOptional<string | null>;
|
||||
declare payment_term_description: CreationOptional<string | null>;
|
||||
declare payment_term_snapshot_json: CreationOptional<string | null>;
|
||||
|
||||
// Régimen fiscal
|
||||
declare tax_regime_code: string;
|
||||
declare tax_regime_description: string;
|
||||
|
||||
// Subtotal
|
||||
declare subtotal_amount_value: number;
|
||||
declare subtotal_amount_scale: number;
|
||||
|
||||
// Items discount amount (suma de descuentos individuales por ítem)
|
||||
declare items_discount_amount_value: number;
|
||||
declare items_discount_amount_scale: number;
|
||||
|
||||
// Global/header discount percentage
|
||||
declare global_discount_percentage_value: number;
|
||||
declare global_discount_percentage_scale: number;
|
||||
|
||||
// Global/header discount amount
|
||||
declare global_discount_amount_value: number;
|
||||
declare global_discount_amount_scale: number;
|
||||
|
||||
// Total discount amount (subtotal - descuentos)
|
||||
declare total_discount_amount_value: number;
|
||||
declare total_discount_amount_scale: number;
|
||||
|
||||
// Taxable amount (base imponible)
|
||||
declare taxable_amount_value: number;
|
||||
declare taxable_amount_scale: number;
|
||||
declare iva_amount_value: CreationOptional<number | null>;
|
||||
|
||||
// IVA amount
|
||||
declare iva_amount_value: number;
|
||||
declare iva_amount_scale: number;
|
||||
declare rec_amount_value: CreationOptional<number | null>;
|
||||
|
||||
// Recargo de equivalencia amount
|
||||
declare rec_amount_value: number;
|
||||
declare rec_amount_scale: number;
|
||||
declare retention_amount_value: CreationOptional<number | null>;
|
||||
|
||||
// Retention amount
|
||||
declare retention_amount_value: number;
|
||||
declare retention_amount_scale: number;
|
||||
|
||||
// Total taxes amount / taxes total
|
||||
declare taxes_amount_value: number;
|
||||
declare taxes_amount_scale: number;
|
||||
|
||||
// Total
|
||||
declare total_amount_value: number;
|
||||
declare total_amount_scale: number;
|
||||
|
||||
|
||||
@ -76,18 +76,18 @@ export class ProformaModel extends Model<
|
||||
declare total_amount_scale: number;
|
||||
|
||||
declare customer_id: string;
|
||||
declare customer_tin: CreationOptional<string | null>;
|
||||
/*declare customer_tin: CreationOptional<string | null>;
|
||||
declare customer_name: CreationOptional<string | null>;
|
||||
declare customer_street: CreationOptional<string | null>;
|
||||
declare customer_street2: CreationOptional<string | null>;
|
||||
declare customer_city: CreationOptional<string | null>;
|
||||
declare customer_province: CreationOptional<string | null>;
|
||||
declare customer_postal_code: CreationOptional<string | null>;
|
||||
declare customer_country: CreationOptional<string | null>;
|
||||
declare customer_country: CreationOptional<string | null>;*/
|
||||
|
||||
declare items: NonAttribute<ProformaItemModel[]>;
|
||||
declare taxes: NonAttribute<ProformaTaxModel[]>;
|
||||
declare current_customer?: NonAttribute<CustomerModel | null>;
|
||||
declare current_customer: NonAttribute<CustomerModel>;
|
||||
declare linked_invoice?: NonAttribute<IssuedInvoiceModel | null>;
|
||||
|
||||
static associate(database: Sequelize) {
|
||||
@ -352,7 +352,7 @@ export default (database: Sequelize) => {
|
||||
collate: "utf8mb4_bin",
|
||||
} as any,
|
||||
|
||||
customer_tin: {
|
||||
/*customer_tin: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
@ -391,7 +391,7 @@ export default (database: Sequelize) => {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
},*/
|
||||
},
|
||||
{
|
||||
sequelize: database,
|
||||
|
||||
@ -52,7 +52,7 @@ export const issuedInvoicesRouter = (params: StartParams) => {
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:invoice_id/report",
|
||||
"/:invoice_id/report/pdf",
|
||||
//checkTabContext,
|
||||
validateRequest(ReportIssueInvoiceByIdQueryRequestSchema, "query"),
|
||||
validateRequest(ReportIssueInvoiceByIdParamsRequestSchema, "params"),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export * from "./sequelize-issued-invoice-domain.mapper";
|
||||
export * from "./sequelize-issued-invoice-v2-domain.mapper";
|
||||
export * from "./sequelize-issued-invoice-v2-item-domain.mapper";
|
||||
/*export * from "./sequelize-issued-invoice-v2-item-domain.mapper";
|
||||
export * from "./sequelize-issued-invoice-v2-recipient-domain.mapper";
|
||||
export * from "./sequelize-issued-invoice-v2-taxes-domain.mapper";
|
||||
export * from "./sequelize-issued-invoice-v2-verifactu-domain.mapper";
|
||||
*/
|
||||
|
||||
@ -98,7 +98,11 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
"reference",
|
||||
errors
|
||||
);
|
||||
const description = extractOrPushError(Result.ok(String(raw.description ?? "")), "description", errors);
|
||||
const description = extractOrPushError(
|
||||
Result.ok(String(raw.description ?? "")),
|
||||
"description",
|
||||
errors
|
||||
);
|
||||
const notes = extractOrPushError(
|
||||
maybeFromNullableResult(raw.notes, (value) => TextValue.create(value)),
|
||||
"notes",
|
||||
@ -124,18 +128,18 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
}
|
||||
|
||||
const taxRegime = extractOrPushError(
|
||||
!isNullishOrEmpty(raw.tax_regime_code)
|
||||
? InvoiceTaxRegime.create({
|
||||
code: String(raw.tax_regime_code),
|
||||
description: String(raw.tax_regime_description ?? raw.tax_regime_code),
|
||||
})
|
||||
: Result.fail(
|
||||
isNullishOrEmpty(raw.tax_regime_code)
|
||||
? Result.fail(
|
||||
new DomainValidationError(
|
||||
"MISSING_TAX_REGIME",
|
||||
"tax_regime",
|
||||
"Issued invoice requires persisted tax regime"
|
||||
)
|
||||
),
|
||||
)
|
||||
: InvoiceTaxRegime.create({
|
||||
code: String(raw.tax_regime_code),
|
||||
description: String(raw.tax_regime_description ?? raw.tax_regime_code),
|
||||
}),
|
||||
"tax_regime",
|
||||
errors
|
||||
);
|
||||
@ -167,7 +171,10 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
errors
|
||||
);
|
||||
const totalDiscountAmount = extractOrPushError(
|
||||
InvoiceAmount.create({ value: raw.total_discount_amount_value, currency_code: currencyCode?.code }),
|
||||
InvoiceAmount.create({
|
||||
value: raw.total_discount_amount_value,
|
||||
currency_code: currencyCode?.code,
|
||||
}),
|
||||
"total_discount_amount_value",
|
||||
errors
|
||||
);
|
||||
@ -187,7 +194,10 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
errors
|
||||
);
|
||||
const retentionAmount = extractOrPushError(
|
||||
InvoiceAmount.create({ value: raw.retention_amount_value, currency_code: currencyCode?.code }),
|
||||
InvoiceAmount.create({
|
||||
value: raw.retention_amount_value,
|
||||
currency_code: currencyCode?.code,
|
||||
}),
|
||||
"retention_amount_value",
|
||||
errors
|
||||
);
|
||||
@ -233,8 +243,13 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
};
|
||||
}
|
||||
|
||||
public mapToDomain(raw: IssuedInvoiceModel, params?: MapperParamsType): Result<IssuedInvoice, Error> {
|
||||
public mapToDomain(
|
||||
raw: IssuedInvoiceModel,
|
||||
params?: MapperParamsType
|
||||
): Result<IssuedInvoice, Error> {
|
||||
try {
|
||||
console.debug(raw);
|
||||
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
const attributes = this._mapAttributesToDomain(raw, { errors, ...params });
|
||||
const recipientResult = this._recipientMapper.mapToDomain(raw, {
|
||||
@ -319,23 +334,27 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
||||
params?: MapperParamsType
|
||||
): Result<IssuedInvoiceCreationAttributes, Error> {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
|
||||
const itemsResult = this._itemsMapper.mapToPersistenceArray(source.items, {
|
||||
errors,
|
||||
parent: source,
|
||||
...params,
|
||||
});
|
||||
|
||||
if (itemsResult.isFailure) errors.push({ path: "items", message: itemsResult.error.message });
|
||||
const taxesResult = this._taxesMapper.mapToPersistenceArray(source.taxes, {
|
||||
errors,
|
||||
parent: source,
|
||||
...params,
|
||||
});
|
||||
|
||||
if (taxesResult.isFailure) errors.push({ path: "taxes", message: taxesResult.error.message });
|
||||
const recipient = this._recipientMapper.mapToPersistence(source.recipient, {
|
||||
errors,
|
||||
parent: source,
|
||||
...params,
|
||||
});
|
||||
|
||||
const verifactuResult = this._verifactuMapper.mapToPersistence(source.verifactu, {
|
||||
errors,
|
||||
parent: source,
|
||||
|
||||
@ -13,13 +13,14 @@ import {
|
||||
maybeFromNullableOrEmptyString,
|
||||
maybeFromNullableResult,
|
||||
maybeToNullable,
|
||||
maybeToNullableString,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import {
|
||||
type IIssuedInvoiceCreateProps,
|
||||
type IssuedInvoice,
|
||||
type IIssuedInvoiceItemCreateProps,
|
||||
type IssuedInvoice,
|
||||
IssuedInvoiceItem,
|
||||
ItemAmount,
|
||||
ItemDescription,
|
||||
@ -56,7 +57,11 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
||||
attributes: Partial<IIssuedInvoiceCreateProps>;
|
||||
};
|
||||
|
||||
const itemId = extractOrPushError(UniqueID.create(raw.id), `items[${index}].id`, errors);
|
||||
const itemId = extractOrPushError(
|
||||
UniqueID.create(raw.item_id),
|
||||
`items[${index}].item_id`,
|
||||
errors
|
||||
);
|
||||
const description = extractOrPushError(
|
||||
maybeFromNullableResult(raw.description, (v) => ItemDescription.create(v)),
|
||||
`items[${index}].description`,
|
||||
@ -75,7 +80,10 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
||||
errors
|
||||
);
|
||||
const subtotalAmount = extractOrPushError(
|
||||
ItemAmount.create({ value: raw.subtotal_amount_value, currency_code: attributes.currencyCode?.code }),
|
||||
ItemAmount.create({
|
||||
value: raw.subtotal_amount_value,
|
||||
currency_code: attributes.currencyCode?.code,
|
||||
}),
|
||||
`items[${index}].subtotal_amount_value`,
|
||||
errors
|
||||
);
|
||||
@ -130,7 +138,10 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
||||
errors
|
||||
);
|
||||
const ivaAmount = extractOrPushError(
|
||||
ItemAmount.create({ value: raw.iva_amount_value, currency_code: attributes.currencyCode?.code }),
|
||||
ItemAmount.create({
|
||||
value: raw.iva_amount_value,
|
||||
currency_code: attributes.currencyCode?.code,
|
||||
}),
|
||||
`items[${index}].iva_amount_value`,
|
||||
errors
|
||||
);
|
||||
@ -141,13 +152,18 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
||||
errors
|
||||
);
|
||||
const recAmount = extractOrPushError(
|
||||
ItemAmount.create({ value: raw.rec_amount_value, currency_code: attributes.currencyCode?.code }),
|
||||
ItemAmount.create({
|
||||
value: raw.rec_amount_value,
|
||||
currency_code: attributes.currencyCode?.code,
|
||||
}),
|
||||
`items[${index}].rec_amount_value`,
|
||||
errors
|
||||
);
|
||||
const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code);
|
||||
const retentionPercentage = extractOrPushError(
|
||||
maybeFromNullableResult(raw.retention_percentage_value, (value) => TaxPercentage.create({ value })),
|
||||
maybeFromNullableResult(raw.retention_percentage_value, (value) =>
|
||||
TaxPercentage.create({ value })
|
||||
),
|
||||
`items[${index}].retention_percentage_value`,
|
||||
errors
|
||||
);
|
||||
@ -160,12 +176,18 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
||||
errors
|
||||
);
|
||||
const taxesAmount = extractOrPushError(
|
||||
ItemAmount.create({ value: raw.taxes_amount_value, currency_code: attributes.currencyCode?.code }),
|
||||
ItemAmount.create({
|
||||
value: raw.taxes_amount_value,
|
||||
currency_code: attributes.currencyCode?.code,
|
||||
}),
|
||||
`items[${index}].taxes_amount_value`,
|
||||
errors
|
||||
);
|
||||
const totalAmount = extractOrPushError(
|
||||
ItemAmount.create({ value: raw.total_amount_value, currency_code: attributes.currencyCode?.code }),
|
||||
ItemAmount.create({
|
||||
value: raw.total_amount_value,
|
||||
currency_code: attributes.currencyCode?.code,
|
||||
}),
|
||||
`items[${index}].total_amount_value`,
|
||||
errors
|
||||
);
|
||||
@ -258,18 +280,25 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
||||
};
|
||||
|
||||
return Result.ok({
|
||||
id: source.id.toPrimitive(),
|
||||
item_id: source.id.toPrimitive(),
|
||||
issued_invoice_id: parent.id.toPrimitive(),
|
||||
position: index,
|
||||
|
||||
description: maybeToNullable(source.description, (v) => v.toPrimitive()),
|
||||
|
||||
quantity_value: maybeToNullable(source.quantity, (v) => v.toPrimitive().value),
|
||||
quantity_scale:
|
||||
maybeToNullable(source.quantity, (v) => v.toPrimitive().scale) ?? ItemQuantity.DEFAULT_SCALE,
|
||||
maybeToNullable(source.quantity, (v) => v.toPrimitive().scale) ??
|
||||
ItemQuantity.DEFAULT_SCALE,
|
||||
|
||||
unit_amount_value: maybeToNullable(source.unitAmount, (v) => v.toPrimitive().value),
|
||||
unit_amount_scale:
|
||||
maybeToNullable(source.unitAmount, (v) => v.toPrimitive().scale) ?? ItemAmount.DEFAULT_SCALE,
|
||||
maybeToNullable(source.unitAmount, (v) => v.toPrimitive().scale) ??
|
||||
ItemAmount.DEFAULT_SCALE,
|
||||
|
||||
subtotal_amount_value: source.subtotalAmount.toPrimitive().value,
|
||||
subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale,
|
||||
|
||||
item_discount_percentage_value: maybeToNullable(
|
||||
source.itemDiscountPercentage,
|
||||
(v) => v.toPrimitive().value
|
||||
@ -277,36 +306,62 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
||||
item_discount_percentage_scale:
|
||||
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
||||
DiscountPercentage.DEFAULT_SCALE,
|
||||
|
||||
item_discount_amount_value: source.itemDiscountAmount.toPrimitive().value,
|
||||
item_discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale,
|
||||
|
||||
global_discount_percentage_value: source.globalDiscountPercentage.toPrimitive().value,
|
||||
global_discount_percentage_scale: source.globalDiscountPercentage.toPrimitive().scale,
|
||||
|
||||
global_discount_amount_value: source.globalDiscountAmount.toPrimitive().value,
|
||||
global_discount_amount_scale: source.globalDiscountAmount.toPrimitive().scale,
|
||||
|
||||
total_discount_amount_value: source.totalDiscountAmount.toPrimitive().value,
|
||||
total_discount_amount_scale: source.totalDiscountAmount.toPrimitive().scale,
|
||||
|
||||
taxable_amount_value: source.taxableAmount.toPrimitive().value,
|
||||
taxable_amount_scale: source.taxableAmount.toPrimitive().scale,
|
||||
iva_code: source.ivaCode ?? null,
|
||||
iva_percentage_value: source.ivaPercentage?.toPrimitive().value ?? null,
|
||||
iva_percentage_scale: source.ivaPercentage?.toPrimitive().scale ?? TaxPercentage.DEFAULT_SCALE,
|
||||
iva_amount_value: source.ivaAmount.toPrimitive().value,
|
||||
iva_amount_scale: source.ivaAmount.toPrimitive().scale,
|
||||
rec_code: source.recCode ?? null,
|
||||
rec_percentage_value: source.recPercentage?.toPrimitive().value ?? null,
|
||||
rec_percentage_scale: source.recPercentage?.toPrimitive().scale ?? TaxPercentage.DEFAULT_SCALE,
|
||||
rec_amount_value: source.recAmount.toPrimitive().value,
|
||||
rec_amount_scale: source.recAmount.toPrimitive().scale,
|
||||
retention_code: source.retentionCode ?? null,
|
||||
retention_percentage_value: source.retentionPercentage?.toPrimitive().value ?? null,
|
||||
|
||||
// IVA
|
||||
iva_code: maybeToNullableString(source.ivaCode),
|
||||
|
||||
iva_percentage_value: maybeToNullable(source.ivaPercentage, (v) => v.toPrimitive().value),
|
||||
iva_percentage_scale:
|
||||
maybeToNullable(source.ivaPercentage, (v) => v.toPrimitive().scale) ?? 2,
|
||||
|
||||
iva_amount_value: source.ivaAmount.value,
|
||||
iva_amount_scale: source.ivaAmount.scale,
|
||||
|
||||
// REC
|
||||
rec_code: maybeToNullableString(source.recCode),
|
||||
|
||||
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
||||
rec_percentage_scale:
|
||||
maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ?? 2,
|
||||
|
||||
rec_amount_value: source.recAmount.value,
|
||||
rec_amount_scale: source.recAmount.scale,
|
||||
|
||||
// RET
|
||||
retention_code: maybeToNullableString(source.retentionCode),
|
||||
|
||||
retention_percentage_value: maybeToNullable(
|
||||
source.retentionPercentage,
|
||||
(v) => v.toPrimitive().value
|
||||
),
|
||||
retention_percentage_scale:
|
||||
source.retentionPercentage?.toPrimitive().scale ?? TaxPercentage.DEFAULT_SCALE,
|
||||
retention_amount_value: source.retentionAmount.toPrimitive().value,
|
||||
retention_amount_scale: source.retentionAmount.toPrimitive().scale,
|
||||
maybeToNullable(source.retentionPercentage, (v) => v.toPrimitive().scale) ?? 2,
|
||||
|
||||
retention_amount_value: source.retentionAmount.value,
|
||||
retention_amount_scale: source.retentionAmount.scale,
|
||||
|
||||
//
|
||||
taxes_amount_value: source.taxesAmount.toPrimitive().value,
|
||||
taxes_amount_scale: source.taxesAmount.toPrimitive().scale,
|
||||
|
||||
//
|
||||
total_amount_value: source.totalAmount.toPrimitive().value,
|
||||
total_amount_scale: source.totalAmount.toPrimitive().scale,
|
||||
});
|
||||
} satisfies IssuedInvoiceItemCreationAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,15 +28,31 @@ export class SequelizeIssuedInvoiceV2RecipientDomainMapper {
|
||||
attributes: Partial<IIssuedInvoiceCreateProps>;
|
||||
};
|
||||
|
||||
const customerName = extractOrPushError(Name.create(source.customer_name!), "customer_name", errors);
|
||||
const customerTin = extractOrPushError(TINNumber.create(source.customer_tin!), "customer_tin", errors);
|
||||
const customerStreet = extractOrPushError(Street.create(source.customer_street!), "customer_street", errors);
|
||||
const customerName = extractOrPushError(
|
||||
Name.create(source.customer_name!),
|
||||
"customer_name",
|
||||
errors
|
||||
);
|
||||
const customerTin = extractOrPushError(
|
||||
TINNumber.create(source.customer_tin!),
|
||||
"customer_tin",
|
||||
errors
|
||||
);
|
||||
const customerStreet = extractOrPushError(
|
||||
Street.create(source.customer_street!),
|
||||
"customer_street",
|
||||
errors
|
||||
);
|
||||
const customerStreet2 = extractOrPushError(
|
||||
maybeFromNullableResult(source.customer_street2, (value) => Street.create(value)),
|
||||
"customer_street2",
|
||||
errors
|
||||
);
|
||||
const customerCity = extractOrPushError(City.create(source.customer_city!), "customer_city", errors);
|
||||
const customerCity = extractOrPushError(
|
||||
City.create(source.customer_city!),
|
||||
"customer_city",
|
||||
errors
|
||||
);
|
||||
const customerProvince = extractOrPushError(
|
||||
maybeFromNullableResult(source.customer_province, (value) => Province.create(value)),
|
||||
"customer_province",
|
||||
@ -75,7 +91,7 @@ export class SequelizeIssuedInvoiceV2RecipientDomainMapper {
|
||||
return Result.ok(createResult.data);
|
||||
}
|
||||
|
||||
mapToPersistence(source: IssuedInvoiceRecipient) {
|
||||
mapToPersistence(source: IssuedInvoiceRecipient, params?: MapperParamsType) {
|
||||
return {
|
||||
customer_tin: source.tin.toString(),
|
||||
customer_name: source.name.toPrimitive(),
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
export * from "./sequelize-issued-invoice-summary.mapper";
|
||||
export * from "./sequelize-issued-invoice-v2-summary.mapper";
|
||||
export * from "./sequelize-issued-invoice-v2-recipient-summary.mapper";
|
||||
//export * from "./sequelize-issued-invoice-v2-recipient-summary.mapper";
|
||||
|
||||
@ -21,8 +21,8 @@ import {
|
||||
} from "../../../../../../domain";
|
||||
import type { IssuedInvoiceModel } from "../../../../../common";
|
||||
|
||||
import { SequelizeVerifactuRecordSummaryMapper } from "./sequelize-verifactu-record-summary.mapper";
|
||||
import { SequelizeIssuedInvoiceV2RecipientSummaryMapper } from "./sequelize-issued-invoice-v2-recipient-summary.mapper";
|
||||
import { SequelizeVerifactuRecordSummaryMapper } from "./sequelize-verifactu-record-summary.mapper";
|
||||
|
||||
export class SequelizeIssuedInvoiceV2SummaryMapper extends SequelizeQueryMapper<
|
||||
IssuedInvoiceModel,
|
||||
@ -113,12 +113,17 @@ export class SequelizeIssuedInvoiceV2SummaryMapper extends SequelizeQueryMapper<
|
||||
"invoice_number",
|
||||
errors
|
||||
);
|
||||
const invoiceDate = extractOrPushError(UtcDate.createFromISO(raw.invoice_date), "invoice_date", errors);
|
||||
const invoiceDate = extractOrPushError(
|
||||
UtcDate.createFromISO(raw.invoice_date),
|
||||
"invoice_date",
|
||||
errors
|
||||
);
|
||||
const operationDate = extractOrPushError(
|
||||
maybeFromNullableResult(raw.operation_date, (value) => UtcDate.createFromISO(value)),
|
||||
"operation_date",
|
||||
errors
|
||||
);
|
||||
|
||||
const reference = extractOrPushError(
|
||||
maybeFromNullableResult(raw.reference, (value) => Result.ok(String(value))),
|
||||
"reference",
|
||||
|
||||
@ -5,10 +5,10 @@ import {
|
||||
requireCompanyContextGuard,
|
||||
} from "@erp/core/api";
|
||||
|
||||
import type { GetProformaByIdUseCase } from "../../../../application";
|
||||
import type { GetProformaByIdUseCase } from "../../../../application/index.ts";
|
||||
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts";
|
||||
|
||||
export class GetProformaController extends ExpressController {
|
||||
export class GetProformaByIdController extends ExpressController {
|
||||
public constructor(private readonly useCase: GetProformaByIdUseCase) {
|
||||
super();
|
||||
this.errorMapper = proformasApiErrorMapper;
|
||||
@ -1,7 +1,7 @@
|
||||
//export * from "./change-status-proforma.controller";
|
||||
//export * from "./create-proforma.controller";
|
||||
//export * from "./delete-proforma.controller";
|
||||
export * from "./get-proforma.controller";
|
||||
export * from "./get-proforma-by-id.controller";
|
||||
export * from "./issue-proforma.controller";
|
||||
export * from "./list-proformas.controller";
|
||||
export * from "./preview-proforma-report.controller";
|
||||
|
||||
@ -3,7 +3,7 @@ import { requireIdentityTenant } from "@erp/identity/api";
|
||||
import { type NextFunction, type Request, type Response, Router } from "express";
|
||||
|
||||
import {
|
||||
GetProformaController,
|
||||
GetProformaByIdController,
|
||||
IssueProformaController,
|
||||
ListProformasController,
|
||||
PreviewProformaReportController,
|
||||
@ -15,9 +15,9 @@ import {
|
||||
ChangeStatusProformaByIdParamsRequestSchema,
|
||||
ChangeStatusProformaByIdRequestSchema,
|
||||
CreateProformaRequestSchema,
|
||||
LegacyIssueProformaByIdParamsRequestSchema,
|
||||
GetProformaByIdRequestSchema,
|
||||
IssueProformaByIdParamsRequestSchema,
|
||||
LegacyIssueProformaByIdParamsRequestSchema,
|
||||
ListProformasRequestSchema,
|
||||
PreviewProformaReportByIdParamsRequestSchema,
|
||||
ReportProformaPdfByIdParamsRequestSchema,
|
||||
@ -28,8 +28,8 @@ import type { IIssuedInvoicePublicServices } from "../../../application";
|
||||
|
||||
import { ChangeStatusProformaController } from "./controllers/change-status-proforma.controller";
|
||||
import { CreateProformaController } from "./controllers/create-proforma.controller";
|
||||
import { IssueProformaByIdRequestMapper } from "./mappers";
|
||||
import { UpdateProformaController } from "./controllers/update-proforma.controller";
|
||||
import { IssueProformaByIdRequestMapper } from "./mappers";
|
||||
|
||||
export const proformasRouter = (params: StartParams) => {
|
||||
const { app, config, getService, getInternal } = params;
|
||||
@ -67,7 +67,7 @@ export const proformasRouter = (params: StartParams) => {
|
||||
validateRequest(GetProformaByIdRequestSchema, "params"),
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const useCase = deps.useCases.getProformaById();
|
||||
const controller = new GetProformaController(useCase);
|
||||
const controller = new GetProformaByIdController(useCase);
|
||||
return controller.execute(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
@ -218,11 +218,7 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
||||
errors.push({ path: "taxes", message: taxesResult.error.message });
|
||||
}
|
||||
|
||||
const recipient = this._recipientMapper.mapToPersistence(source.recipient, {
|
||||
errors,
|
||||
parent: source,
|
||||
...params,
|
||||
});
|
||||
const recipient = this._recipientMapper.mapToPersistence(source.recipient);
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(
|
||||
@ -233,8 +229,11 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
||||
const allAmounts = source.totals();
|
||||
|
||||
return Result.ok({
|
||||
// Identificación
|
||||
id: source.id.toPrimitive(),
|
||||
company_id: source.companyId.toPrimitive(),
|
||||
|
||||
// Flags / estado / serie / número
|
||||
status: source.status.toPrimitive(),
|
||||
proforma_reference: source.proformaReference.toPrimitive(),
|
||||
proforma_date: source.invoiceDate.toPrimitive(),
|
||||
@ -242,36 +241,49 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
||||
target_invoice_series_code: maybeToNullable(source.series, (v) => v.toPrimitive()),
|
||||
language_code: source.languageCode.toPrimitive(),
|
||||
currency_code: source.currencyCode.toPrimitive(),
|
||||
|
||||
reference: maybeToNullable(source.reference, (reference) => reference),
|
||||
description: maybeToNullable(source.description, (description) => description),
|
||||
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
|
||||
payment_method_id: maybeToNullable(source.paymentMethodId, (value) => value.toPrimitive()),
|
||||
payment_term_id: null,
|
||||
tax_regime_code: maybeToNullable(source.taxRegimeCode, (value) => value),
|
||||
|
||||
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||
|
||||
items_discount_amount_value: allAmounts.itemsDiscountAmount.value,
|
||||
items_discount_amount_scale: allAmounts.itemsDiscountAmount.scale,
|
||||
|
||||
global_discount_percentage_value: source.globalDiscountPercentage.toPrimitive().value,
|
||||
global_discount_percentage_scale: source.globalDiscountPercentage.toPrimitive().scale,
|
||||
|
||||
global_discount_amount_value: allAmounts.globalDiscountAmount.value,
|
||||
global_discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
||||
|
||||
total_discount_amount_value: allAmounts.totalDiscountAmount.value,
|
||||
total_discount_amount_scale: allAmounts.totalDiscountAmount.scale,
|
||||
|
||||
taxable_amount_value: allAmounts.taxableAmount.value,
|
||||
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
||||
|
||||
iva_amount_value: allAmounts.ivaAmount.value,
|
||||
iva_amount_scale: allAmounts.ivaAmount.scale,
|
||||
|
||||
rec_amount_value: allAmounts.recAmount.value,
|
||||
rec_amount_scale: allAmounts.recAmount.scale,
|
||||
|
||||
retention_amount_value: allAmounts.retentionAmount.value,
|
||||
retention_amount_scale: allAmounts.retentionAmount.scale,
|
||||
|
||||
taxes_amount_value: allAmounts.taxesAmount.value,
|
||||
taxes_amount_scale: allAmounts.taxesAmount.scale,
|
||||
|
||||
total_amount_value: allAmounts.totalAmount.value,
|
||||
total_amount_scale: allAmounts.totalAmount.scale,
|
||||
|
||||
customer_id: source.customerId.toPrimitive(),
|
||||
...recipient,
|
||||
|
||||
taxes: taxesResult.data,
|
||||
items: itemsResult.data,
|
||||
} satisfies ProformaCreationAttributes);
|
||||
|
||||
@ -11,7 +11,6 @@ import {
|
||||
type ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
maybeFromNullableResult,
|
||||
maybeToNullable,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
@ -28,44 +27,49 @@ export class SequelizeProformaV2RecipientDomainMapper {
|
||||
parent: Partial<IProformaCreateProps>;
|
||||
};
|
||||
|
||||
if (!source.customer_name) {
|
||||
return Result.ok(Maybe.none());
|
||||
}
|
||||
const _name = source.current_customer.name;
|
||||
const _tin = source.current_customer.tin;
|
||||
const _street = source.current_customer.street;
|
||||
const _street2 = source.current_customer.street2;
|
||||
const _city = source.current_customer.city;
|
||||
const _postal_code = source.current_customer.postal_code;
|
||||
const _province = source.current_customer.province;
|
||||
const _country = source.current_customer.country;
|
||||
|
||||
const customerName = extractOrPushError(Name.create(source.customer_name), "customer_name", errors);
|
||||
const customerName = extractOrPushError(Name.create(_name), "current_customer.name", errors);
|
||||
const customerTin = extractOrPushError(
|
||||
maybeFromNullableResult(source.customer_tin, (value) => TINNumber.create(value)),
|
||||
"customer_tin",
|
||||
maybeFromNullableResult(_tin, (value) => TINNumber.create(value)),
|
||||
"current_customer.tin",
|
||||
errors
|
||||
);
|
||||
const customerStreet = extractOrPushError(
|
||||
maybeFromNullableResult(source.customer_street, (value) => Street.create(value)),
|
||||
"customer_street",
|
||||
maybeFromNullableResult(_street, (value) => Street.create(value)),
|
||||
"current_customer.street",
|
||||
errors
|
||||
);
|
||||
const customerStreet2 = extractOrPushError(
|
||||
maybeFromNullableResult(source.customer_street2, (value) => Street.create(value)),
|
||||
"customer_street2",
|
||||
maybeFromNullableResult(_street2, (value) => Street.create(value)),
|
||||
"current_customer.street2",
|
||||
errors
|
||||
);
|
||||
const customerCity = extractOrPushError(
|
||||
maybeFromNullableResult(source.customer_city, (value) => City.create(value)),
|
||||
"customer_city",
|
||||
maybeFromNullableResult(_city, (value) => City.create(value)),
|
||||
"current_customer.city",
|
||||
errors
|
||||
);
|
||||
const customerProvince = extractOrPushError(
|
||||
maybeFromNullableResult(source.customer_province, (value) => Province.create(value)),
|
||||
"customer_province",
|
||||
maybeFromNullableResult(_province, (value) => Province.create(value)),
|
||||
"current_customer.province",
|
||||
errors
|
||||
);
|
||||
const customerPostalCode = extractOrPushError(
|
||||
maybeFromNullableResult(source.customer_postal_code, (value) => PostalCode.create(value)),
|
||||
"customer_postal_code",
|
||||
maybeFromNullableResult(_postal_code, (value) => PostalCode.create(value)),
|
||||
"current_customer.postal_code",
|
||||
errors
|
||||
);
|
||||
const customerCountry = extractOrPushError(
|
||||
maybeFromNullableResult(source.customer_country, (value) => Country.create(value)),
|
||||
"customer_country",
|
||||
maybeFromNullableResult(_country, (value) => Country.create(value)),
|
||||
"current_customer.country",
|
||||
errors
|
||||
);
|
||||
|
||||
@ -92,7 +96,8 @@ export class SequelizeProformaV2RecipientDomainMapper {
|
||||
}
|
||||
|
||||
mapToPersistence(source: Maybe<ProformaRecipient>) {
|
||||
if (source.isNone()) {
|
||||
return Result.ok();
|
||||
/*if (source.isNone()) {
|
||||
return {
|
||||
customer_tin: null,
|
||||
customer_name: null,
|
||||
@ -117,5 +122,6 @@ export class SequelizeProformaV2RecipientDomainMapper {
|
||||
customer_postal_code: maybeToNullable(recipient.postalCode, (value) => value.toPrimitive()),
|
||||
customer_country: maybeToNullable(recipient.country, (value) => value.toPrimitive()),
|
||||
};
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,6 +203,8 @@ export class SequelizeProformaRepositoryV2
|
||||
transaction: Transaction,
|
||||
options: FindOptions<InferAttributes<ProformaModel>> = {}
|
||||
): Promise<Result<Proforma, Error>> {
|
||||
const { CustomerModel } = this.database.models;
|
||||
|
||||
try {
|
||||
const normalizedOrder = Array.isArray(options.order)
|
||||
? options.order
|
||||
@ -225,6 +227,7 @@ export class SequelizeProformaRepositoryV2
|
||||
order: [...normalizedOrder, [{ model: ProformaItemModel, as: "items" }, "position", "ASC"]],
|
||||
include: [
|
||||
...normalizedInclude,
|
||||
{ model: CustomerModel, as: "current_customer", required: false },
|
||||
{ model: ProformaItemModel, as: "items", required: false },
|
||||
{ model: ProformaTaxModel, as: "taxes", required: false },
|
||||
{ model: IssuedInvoiceModel, as: "linked_invoice", required: false, attributes: ["id"] },
|
||||
|
||||
@ -21,7 +21,6 @@ import {
|
||||
export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
||||
id: z.uuid(),
|
||||
company_id: z.uuid(),
|
||||
is_proforma: z.boolean(),
|
||||
|
||||
invoice_number: z.string(),
|
||||
status: IssuedInvoiceStatusSchema,
|
||||
|
||||
@ -1,50 +1,9 @@
|
||||
import {
|
||||
CurrencyCodeSchema,
|
||||
IsoDateSchema,
|
||||
LanguageCodeSchema,
|
||||
MoneySchema,
|
||||
createPaginatedListSchema,
|
||||
} from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
import { createPaginatedListSchema } from "@erp/core";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import {
|
||||
IssuedInvoiceRecipientSummarySchema,
|
||||
IssuedInvoiceStatusSchema,
|
||||
VerifactuRecordSchema,
|
||||
} from "../../shared";
|
||||
import { IssuedInvoiceSummarySchema } from "../../shared";
|
||||
|
||||
export const ListIssuedInvoicesResponseSchema = createPaginatedListSchema(
|
||||
z.object({
|
||||
id: z.uuid(),
|
||||
company_id: z.uuid(),
|
||||
is_proforma: z.boolean(),
|
||||
|
||||
invoice_number: z.string(),
|
||||
status: IssuedInvoiceStatusSchema,
|
||||
series: z.string(),
|
||||
|
||||
invoice_date: IsoDateSchema,
|
||||
operation_date: IsoDateSchema.nullable(),
|
||||
|
||||
language_code: LanguageCodeSchema,
|
||||
currency_code: CurrencyCodeSchema,
|
||||
|
||||
reference: z.string().nullable(),
|
||||
description: z.string(),
|
||||
|
||||
customer_id: z.uuid(),
|
||||
recipient: IssuedInvoiceRecipientSummarySchema,
|
||||
|
||||
subtotal_amount: MoneySchema,
|
||||
total_discount_amount: MoneySchema,
|
||||
taxable_amount: MoneySchema,
|
||||
taxes_amount: MoneySchema,
|
||||
total_amount: MoneySchema,
|
||||
|
||||
verifactu: VerifactuRecordSchema,
|
||||
|
||||
linked_proforma_id: z.uuid().nullable(),
|
||||
})
|
||||
IssuedInvoiceSummarySchema
|
||||
);
|
||||
|
||||
export type ListIssuedInvoicesResponseDTO = z.infer<typeof ListIssuedInvoicesResponseSchema>;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./issued-invoice-item-detail.dto";
|
||||
export * from "./issued-invoice-recipient-summary.dto";
|
||||
export * from "./issued-invoice-status.dto";
|
||||
export * from "./issued-invoice-summary.dto";
|
||||
export * from "./verifactu-record.dto";
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
import { CurrencyCodeSchema, IsoDateSchema, LanguageCodeSchema, MoneySchema } from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { IssuedInvoiceRecipientSummarySchema } from "./issued-invoice-recipient-summary.dto";
|
||||
import { IssuedInvoiceStatusSchema } from "./issued-invoice-status.dto";
|
||||
import { VerifactuRecordSchema } from "./verifactu-record.dto";
|
||||
|
||||
export const IssuedInvoiceSummarySchema = z.object({
|
||||
id: z.uuid(),
|
||||
company_id: z.uuid(),
|
||||
|
||||
invoice_number: z.string(),
|
||||
status: IssuedInvoiceStatusSchema,
|
||||
series: z.string(),
|
||||
|
||||
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: IssuedInvoiceRecipientSummarySchema,
|
||||
|
||||
subtotal_amount: MoneySchema,
|
||||
total_discount_amount: MoneySchema,
|
||||
taxable_amount: MoneySchema,
|
||||
taxes_amount: MoneySchema,
|
||||
total_amount: MoneySchema,
|
||||
|
||||
verifactu: VerifactuRecordSchema,
|
||||
|
||||
linked_proforma_id: z.uuid().nullable(),
|
||||
});
|
||||
|
||||
export type IssuedInvoiceSummaryDTO = z.infer<typeof IssuedInvoiceSummarySchema>;
|
||||
@ -1,3 +1,4 @@
|
||||
import { getUserErrorMessage } from "@erp/core/client";
|
||||
import { useDebounce } from "@erp/core/hooks";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
@ -37,13 +38,15 @@ export const useIssuedInvoiceListController = () => {
|
||||
filters:
|
||||
verifactuStatusFilter === "all"
|
||||
? []
|
||||
: [{ field: "verifactu.status", operator: "eq", value: verifactuStatusFilter }],
|
||||
: [{ field: "verifactu.status", operator: "EQUALS", value: verifactuStatusFilter }],
|
||||
}),
|
||||
[debouncedSearch, pageIndex, pageSize, verifactuStatusFilter]
|
||||
);
|
||||
|
||||
const query = useIssuedInvoiceListQuery({ criteria });
|
||||
|
||||
const errorMessage = query.error ? getUserErrorMessage(query.error) : null;
|
||||
|
||||
const setStatusFilterValue = (value: string) => {
|
||||
const nextValue = (value || "all") as VerifactuRecordListStatusFilter;
|
||||
|
||||
@ -88,8 +91,8 @@ export const useIssuedInvoiceListController = () => {
|
||||
|
||||
isError: query.isError,
|
||||
error: query.error,
|
||||
|
||||
refetch: query.refetch,
|
||||
errorMessage,
|
||||
retry: query.refetch,
|
||||
|
||||
pageIndex,
|
||||
pageSize,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ErrorAlert, PageHeader, SimpleSearchInput } from "@erp/core/components";
|
||||
import { AppContent, BackHistoryButton, LogoVerifactu } from "@repo/rdx-ui/components";
|
||||
import { ErrorState, PageHeader, SimpleSearchInput } from "@erp/core/components";
|
||||
import { LogoVerifactu } from "@repo/rdx-ui/components";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
@ -44,8 +44,16 @@ export const ListIssuedInvoicesPage = () => {
|
||||
widthClass: "w-[500px]",
|
||||
});*/
|
||||
|
||||
if (listCtrl.isError) {
|
||||
if (listCtrl.isError && listCtrl.errorMessage) {
|
||||
return (
|
||||
<ErrorState
|
||||
description={listCtrl.errorMessage.description}
|
||||
onRetry={listCtrl.retry}
|
||||
reference={listCtrl.errorMessage.reference}
|
||||
title={listCtrl.errorMessage.title}
|
||||
/>
|
||||
);
|
||||
/*return (
|
||||
<AppContent>
|
||||
<ErrorAlert
|
||||
message={(listCtrl.error as Error)?.message || "Error al cargar el listado"}
|
||||
@ -53,7 +61,7 @@ export const ListIssuedInvoicesPage = () => {
|
||||
/>
|
||||
<BackHistoryButton />
|
||||
</AppContent>
|
||||
);
|
||||
);*/
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export * from "./list";
|
||||
@ -1,3 +0,0 @@
|
||||
export * from "../../../list/ui/blocks/issued-invoices-grid/use-issued-invoices-grid-columns";
|
||||
|
||||
export * from "./use-issued-invoices-list";
|
||||
@ -1,52 +0,0 @@
|
||||
// src/modules/issued-invoices/hooks/use-proformas-list.ts
|
||||
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import { useDebounce } from "@erp/core/hooks";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { useIssuedInvoicesQuery } from "../../../hooks";
|
||||
|
||||
export const useIssuedInvoicesList = () => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [search, setSearch] = useState("");
|
||||
const [status, setStatus] = useState("all");
|
||||
|
||||
const debouncedQ = useDebounce(search, 300);
|
||||
|
||||
const criteria = useMemo<CriteriaDTO>(() => {
|
||||
const baseFilters =
|
||||
status === "all" ? [] : [{ field: "status", operator: "CONTAINS", value: status }];
|
||||
|
||||
return {
|
||||
q: debouncedQ || "",
|
||||
pageSize,
|
||||
pageNumber: pageIndex,
|
||||
order: "desc",
|
||||
orderBy: "invoice_date",
|
||||
filters: baseFilters,
|
||||
};
|
||||
}, [pageSize, pageIndex, debouncedQ, status]);
|
||||
|
||||
const query = useIssuedInvoicesQuery({ criteria });
|
||||
const data = useMemo(
|
||||
() => (query.data ? IssuedInvoiceSummaryDtoAdapter.fromDto(query.data) : undefined),
|
||||
[query.data]
|
||||
);
|
||||
|
||||
const setSearchValue = (value: string) => setSearch(value.trim().replace(/\s+/g, " "));
|
||||
|
||||
const setStatusFilter = (newStatus: string) => setStatus(newStatus);
|
||||
|
||||
return {
|
||||
...query,
|
||||
data,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
search,
|
||||
setPageIndex,
|
||||
setPageSize,
|
||||
setSearchValue,
|
||||
setStatusFilter,
|
||||
};
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export * from "./issued-invoice-list-page";
|
||||
@ -1 +0,0 @@
|
||||
export * from "./issued-invoices-grid";
|
||||
@ -1,108 +0,0 @@
|
||||
import { SimpleSearchInput } from "@erp/core/components";
|
||||
import { DataTable, SkeletonDataTable } from "@repo/rdx-ui/components";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@repo/shadcn-ui/components";
|
||||
import { FilterIcon } from "lucide-react";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
import type { IssuedInvoiceSummaryPageData } from "../../../schema/issued-invoice-summary.web.schema";
|
||||
import { useIssuedInvoicesGridColumns } from "../hooks";
|
||||
|
||||
interface IssuedInvoicesGridProps {
|
||||
data: IssuedInvoiceSummaryPageData;
|
||||
loading?: boolean;
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
searchValue: string;
|
||||
onSearchChange: (v: string) => void;
|
||||
onPageChange: (p: number) => void;
|
||||
onPageSizeChange: (s: number) => void;
|
||||
onRowClick?: (id: string) => void;
|
||||
onExportClick?: () => void;
|
||||
}
|
||||
|
||||
export const IssuedInvoicesGrid = ({
|
||||
data,
|
||||
loading,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
searchValue,
|
||||
onSearchChange,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
onRowClick,
|
||||
onExportClick,
|
||||
}: IssuedInvoicesGridProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { items, total_items } = data;
|
||||
|
||||
const columns = useIssuedInvoicesGridColumns({
|
||||
onDownloadPdf: (invoice) => null, //downloadInvoicePdf(inv.id),
|
||||
onSendEmail: (invoice) => null, //sendInvoiceEmail(inv.id),
|
||||
});
|
||||
|
||||
if (loading)
|
||||
return (
|
||||
<SkeletonDataTable
|
||||
columns={columns.length}
|
||||
footerProps={{ pageIndex, pageSize, totalItems: total_items ?? 0 }}
|
||||
rows={Math.max(6, pageSize)}
|
||||
showFooter
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<SimpleSearchInput loading={loading} onSearchChange={onSearchChange} />
|
||||
<Select defaultValue="all">
|
||||
<SelectTrigger className="w-full sm:w-48">
|
||||
<FilterIcon aria-hidden className="mr-2 size-4" />
|
||||
<SelectValue placeholder={t("filters.status")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">{t("catalog.issued_invoices.status.all")}</SelectItem>
|
||||
<SelectItem value="pendiente">
|
||||
{t("catalog.issued_invoices.status.pendiente")}
|
||||
</SelectItem>
|
||||
<SelectItem value="aceptado_con_error">
|
||||
{t("catalog.issued_invoices.status.aceptado_con_error")}
|
||||
</SelectItem>
|
||||
<SelectItem value="incorrecto">
|
||||
{t("catalog.issued_invoices.status.incorrecto")}
|
||||
</SelectItem>
|
||||
<SelectItem value="duplicado">
|
||||
{t("catalog.issued_invoices.status.duplicado")}
|
||||
</SelectItem>
|
||||
<SelectItem value="anulado">{t("catalog.issued_invoices.status.anulado")}</SelectItem>
|
||||
<SelectItem value="factura_inexistente">
|
||||
{t("catalog.issued_invoices.status.factura_inexistente")}
|
||||
</SelectItem>
|
||||
<SelectItem value="rechazado">
|
||||
{t("catalog.issued_invoices.status.rechazado")}
|
||||
</SelectItem>
|
||||
<SelectItem value="error">{t("catalog.issued_invoices.status.error")}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={items}
|
||||
enablePagination
|
||||
manualPagination
|
||||
onPageChange={onPageChange}
|
||||
onPageSizeChange={onPageSizeChange}
|
||||
onRowClick={(row, _index) => onRowClick?.(row.id)}
|
||||
pageIndex={pageIndex}
|
||||
pageSize={pageSize}
|
||||
totalItems={total_items}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,4 +1,5 @@
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import { isApiClientError } from "@erp/core/client";
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, useQuery } from "@tanstack/react-query";
|
||||
|
||||
@ -27,5 +28,21 @@ export const useIssuedInvoiceListQuery = (options?: IssuedInvoicesListQueryOptio
|
||||
},
|
||||
enabled,
|
||||
placeholderData: (previousData) => previousData, // Mantiene la página anterior durante refetch por cambio de criteria
|
||||
retry: (failureCount, error) => {
|
||||
if (!isApiClientError(error)) {
|
||||
return failureCount < 1;
|
||||
}
|
||||
|
||||
if (
|
||||
error.kind === "validation" ||
|
||||
error.kind === "forbidden" ||
|
||||
error.kind === "not-found" ||
|
||||
error.kind === "conflict"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return failureCount < 2;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -38,8 +38,6 @@ export const buildUpdateProformaByIdParams = (
|
||||
throw new Error("proformaId is required");
|
||||
}
|
||||
|
||||
console.log(patch);
|
||||
|
||||
const data: UpdateProformaByIdParams["data"] = {};
|
||||
|
||||
if (ObjectHelper.hasOwn(patch, "series")) {
|
||||
|
||||
@ -132,7 +132,6 @@ export class UpdateCustomerInputMapper implements IUpdateCustomerInputMapper {
|
||||
});
|
||||
|
||||
toPatchField(dto.tax_regime_code).ifSetOrNull((taxRegimeCode) => {
|
||||
console.log("Hay taxRegimeCode", taxRegimeCode);
|
||||
customerPatchProps.taxRegimeCode = extractOrPushError(
|
||||
maybeFromNullableResult(taxRegimeCode, (value) => Result.ok(String(value))),
|
||||
"tax_regime_code",
|
||||
@ -162,9 +161,6 @@ export class UpdateCustomerInputMapper implements IUpdateCustomerInputMapper {
|
||||
|
||||
this.throwIfValidationErrors(errors);
|
||||
|
||||
console.log("dto => ", dto);
|
||||
console.log("customerPatchProps => ", customerPatchProps);
|
||||
|
||||
return Result.ok(customerPatchProps);
|
||||
} catch (err: unknown) {
|
||||
console.error(err);
|
||||
|
||||
@ -68,8 +68,6 @@ export class CustomerRepository
|
||||
|
||||
const { id, ...updatePayload } = dtoResult.data;
|
||||
|
||||
console.log(dtoResult.data);
|
||||
|
||||
const [affected] = await CustomerModel.update(updatePayload, {
|
||||
where: { id /*, version */ },
|
||||
transaction,
|
||||
|
||||
@ -99,9 +99,7 @@ export const useCreateCustomerController = (options?: UseCustomerCreateControlle
|
||||
}
|
||||
options?.onCreated?.(created);
|
||||
} catch (error: unknown) {
|
||||
console.log(error);
|
||||
const normalizedError = normalizeSubmitError(error);
|
||||
console.debug(normalizedError);
|
||||
|
||||
// No revierto el form para que no se pierdan los cambios que
|
||||
// ha hecho el usuario y no han sido guardados.
|
||||
|
||||
@ -617,6 +617,9 @@ importers:
|
||||
'@erp/catalogs':
|
||||
specifier: workspace:*
|
||||
version: link:../catalogs
|
||||
'@erp/companies':
|
||||
specifier: workspace:*
|
||||
version: link:../companies
|
||||
'@erp/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user