.
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,
|
"noUncheckedSideEffectImports": true,
|
||||||
"allowUnreachableCode": 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"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,6 @@ export class CreatePaymentMethodUseCase {
|
|||||||
|
|
||||||
public execute(params: CreatePaymentMethodUseCaseInput) {
|
public execute(params: CreatePaymentMethodUseCaseInput) {
|
||||||
const { dto, companyId } = params;
|
const { dto, companyId } = params;
|
||||||
console.log("Executing CreatePaymentMethodUseCase with params:", params);
|
|
||||||
|
|
||||||
const mappedPropsResult = this.deps.dtoMapper.map(dto, { companyId });
|
const mappedPropsResult = this.deps.dtoMapper.map(dto, { companyId });
|
||||||
if (mappedPropsResult.isFailure) {
|
if (mappedPropsResult.isFailure) {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import type {
|
|||||||
|
|
||||||
export class TaxDefinitionPublicModelMapper {
|
export class TaxDefinitionPublicModelMapper {
|
||||||
public toPublicModel(taxDefinition: TaxDefinition): TaxDefinitionPublicModel {
|
public toPublicModel(taxDefinition: TaxDefinition): TaxDefinitionPublicModel {
|
||||||
console.log(taxDefinition.description)
|
|
||||||
return {
|
return {
|
||||||
id: taxDefinition.id,
|
id: taxDefinition.id,
|
||||||
companyId: Maybe.some(taxDefinition.companyId),
|
companyId: Maybe.some(taxDefinition.companyId),
|
||||||
|
|||||||
@ -80,8 +80,6 @@ export class SequelizeTaxDefinitionDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
const allowedSurchargeCodes = extractOrPushError(
|
const allowedSurchargeCodes = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.allowed_surcharge_codes, (v) => {
|
maybeFromNullableResult(raw.allowed_surcharge_codes, (v) => {
|
||||||
console.log(v);
|
|
||||||
|
|
||||||
const values = v.split(";");
|
const values = v.split(";");
|
||||||
if (!Array.isArray(values))
|
if (!Array.isArray(values))
|
||||||
return Result.fail(new Error("Invalid allowed_surcharge_codes"));
|
return Result.fail(new Error("Invalid allowed_surcharge_codes"));
|
||||||
|
|||||||
@ -59,6 +59,7 @@ export abstract class ExpressController {
|
|||||||
|
|
||||||
await this.executeImpl();
|
await this.executeImpl();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
console.error("Error in ExpressController.execute:", error);
|
||||||
this.handleError(error as 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-alert";
|
||||||
|
export * from "./error-state";
|
||||||
export * from "./form";
|
export * from "./form";
|
||||||
export * from "./not-found-card";
|
export * from "./not-found-card";
|
||||||
export * from "./page-header";
|
export * from "./page-header";
|
||||||
|
|||||||
@ -3,13 +3,23 @@ import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
|
|||||||
import type { ICustomParams, IDataSource } from "../datasource.interface";
|
import type { ICustomParams, IDataSource } from "../datasource.interface";
|
||||||
|
|
||||||
import { defaultAxiosRequestConfig } from "./create-axios-instance";
|
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 => {
|
export const createAxiosDataSource = (client: AxiosInstance): IDataSource => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error("[Axios] Se esperaba una instancia de Axios.");
|
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.");
|
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(),
|
getBaseUrl: () => client.getUri(),
|
||||||
|
|
||||||
getList: async <R>(resource: string, params?: Record<string, unknown>): Promise<R> => {
|
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;
|
signal?: AbortSignal;
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await client.get<R>(resource, {
|
return executeRequest(() =>
|
||||||
|
client.get<R>(resource, {
|
||||||
signal,
|
signal,
|
||||||
params: rest,
|
params: queryParams,
|
||||||
});
|
})
|
||||||
|
);
|
||||||
return res.data;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getOne: async <R>(
|
getOne: async <R>(
|
||||||
resource: string,
|
resource: string,
|
||||||
id: string | number,
|
id: string | number,
|
||||||
params?: Record<string, unknown>
|
params?: Record<string, unknown>
|
||||||
): Promise<R> => {
|
): Promise<R> =>
|
||||||
const res = await client.get<R>(`${resource}/${id}`, params);
|
executeRequest(() => client.get<R>(`${resource}/${id}`, params as AxiosRequestConfig)),
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
getMany: async <R>(resource: string, ids: Array<string | number>): Promise<R> => {
|
getMany: async <R>(resource: string, ids: Array<string | number>): Promise<R> =>
|
||||||
const res = await client.get<R>(resource, {
|
executeRequest(() =>
|
||||||
|
client.get<R>(resource, {
|
||||||
params: { ids },
|
params: { ids },
|
||||||
});
|
})
|
||||||
|
),
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
createOne: async <TData, R>(
|
createOne: async <TData, R>(
|
||||||
resource: string,
|
resource: string,
|
||||||
data: TData,
|
data: TData,
|
||||||
params?: Record<string, unknown>
|
params?: Record<string, unknown>
|
||||||
): Promise<R> => {
|
): Promise<R> =>
|
||||||
const res = await client.post<R, AxiosResponse<R>, TData>(
|
executeRequest(() =>
|
||||||
resource,
|
client.post<R, AxiosResponse<R>, TData>(resource, data, params as AxiosRequestConfig<TData>)
|
||||||
data,
|
),
|
||||||
params as AxiosRequestConfig<TData>
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateOne: async <TData, R>(
|
updateOne: async <TData, R>(
|
||||||
resource: string,
|
resource: string,
|
||||||
id: string | number,
|
id: string | number,
|
||||||
data: TData,
|
data: TData,
|
||||||
params?: Record<string, unknown>
|
params?: Record<string, unknown>
|
||||||
): Promise<R> => {
|
): Promise<R> =>
|
||||||
const url = `${resource}/${id}`;
|
executeRequest(() =>
|
||||||
|
client.put<R, AxiosResponse<R>, TData>(
|
||||||
const res = await client.put<R, AxiosResponse<R>, TData>(
|
`${resource}/${id}`,
|
||||||
url,
|
|
||||||
data,
|
data,
|
||||||
params as AxiosRequestConfig<TData>
|
params as AxiosRequestConfig<TData>
|
||||||
);
|
)
|
||||||
|
),
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteOne: async <R = void>(
|
deleteOne: async <R = void>(
|
||||||
resource: string,
|
resource: string,
|
||||||
id: string | number,
|
id: string | number,
|
||||||
params?: Record<string, unknown>
|
params?: Record<string, unknown>
|
||||||
): Promise<R> => {
|
): Promise<R> =>
|
||||||
const res = await client.delete<R>(`${resource}/${id}`, params);
|
executeRequest(() => client.delete<R>(`${resource}/${id}`, params as AxiosRequestConfig)),
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
custom: async <TData, R>(customParams: ICustomParams<TData>): Promise<R> => {
|
custom: async <TData, R>(customParams: ICustomParams<TData>): Promise<R> => {
|
||||||
const { url, path, method, responseType, headers, signal, data, ...payload } = customParams;
|
const { url, path, method, responseType, headers, signal, data, ...payload } = customParams;
|
||||||
|
|
||||||
const requestUrl = path ? `${client.getUri()}/${path}` : url;
|
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> = {
|
const config: AxiosRequestConfig<TData> = {
|
||||||
url: requestUrl,
|
url: requestUrl,
|
||||||
method,
|
method,
|
||||||
responseType,
|
responseType,
|
||||||
signal,
|
signal,
|
||||||
...payload,
|
|
||||||
...defaultAxiosRequestConfig,
|
...defaultAxiosRequestConfig,
|
||||||
|
...payload,
|
||||||
headers,
|
headers,
|
||||||
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
let customResponse: AxiosResponse<R, TData>;
|
return executeRequest(() => client.request<R, AxiosResponse<R>, TData>(config));
|
||||||
|
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 "./axios";
|
||||||
export * from "./build-text-filters";
|
export * from "./build-text-filters";
|
||||||
export * from "./datasource.interface";
|
export * from "./datasource.interface";
|
||||||
|
export * from "./errors";
|
||||||
|
|||||||
@ -4,9 +4,9 @@ import type { Maybe } from "@repo/rdx-utils";
|
|||||||
import type {
|
import type {
|
||||||
InvoiceAmount,
|
InvoiceAmount,
|
||||||
InvoiceNumber,
|
InvoiceNumber,
|
||||||
InvoiceRecipient,
|
|
||||||
InvoiceSerie,
|
InvoiceSerie,
|
||||||
InvoiceStatus,
|
InvoiceStatus,
|
||||||
|
IssuedInvoiceRecipient,
|
||||||
VerifactuRecord,
|
VerifactuRecord,
|
||||||
} from "../../../domain";
|
} from "../../../domain";
|
||||||
|
|
||||||
@ -14,7 +14,6 @@ export type IssuedInvoiceSummary = {
|
|||||||
id: UniqueID;
|
id: UniqueID;
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
|
|
||||||
isProforma: boolean;
|
|
||||||
invoiceNumber: InvoiceNumber;
|
invoiceNumber: InvoiceNumber;
|
||||||
status: InvoiceStatus;
|
status: InvoiceStatus;
|
||||||
series: Maybe<InvoiceSerie>;
|
series: Maybe<InvoiceSerie>;
|
||||||
@ -26,7 +25,7 @@ export type IssuedInvoiceSummary = {
|
|||||||
description: Maybe<string>;
|
description: Maybe<string>;
|
||||||
|
|
||||||
customerId: UniqueID;
|
customerId: UniqueID;
|
||||||
recipient: InvoiceRecipient;
|
recipient: IssuedInvoiceRecipient;
|
||||||
|
|
||||||
languageCode: LanguageCode;
|
languageCode: LanguageCode;
|
||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
|
|||||||
@ -31,16 +31,15 @@ export class IssuedInvoiceSummarySnapshotBuilder implements IIssuedInvoiceSummar
|
|||||||
return {
|
return {
|
||||||
id: invoice.id.toString(),
|
id: invoice.id.toString(),
|
||||||
company_id: invoice.companyId.toString(),
|
company_id: invoice.companyId.toString(),
|
||||||
is_proforma: invoice.isProforma,
|
|
||||||
|
|
||||||
invoice_number: invoice.invoiceNumber.toString(),
|
invoice_number: invoice.invoiceNumber.toString(),
|
||||||
status: invoice.status.toPrimitive(),
|
status: invoice.status.toPrimitive(),
|
||||||
series: maybeToEmptyString(invoice.series, (v) => v.toString()),
|
series: maybeToEmptyString(invoice.series, (v) => v.toString()),
|
||||||
|
|
||||||
invoice_date: invoice.invoiceDate.toDateString(),
|
invoice_date: invoice.invoiceDate.toDateString(),
|
||||||
operation_date: maybeToEmptyString(invoice.operationDate, (v) => v.toDateString()),
|
operation_date: maybeToNullable(invoice.operationDate, (v) => v.toDateString()),
|
||||||
reference: maybeToEmptyString(invoice.reference, (v) => v.toString()),
|
reference: maybeToNullable(invoice.reference, (v) => v.toString()),
|
||||||
description: maybeToEmptyString(invoice.description, (v) => v.toString()),
|
description: maybeToNullable(invoice.description, (v) => v.toString()),
|
||||||
|
|
||||||
customer_id: invoice.customerId.toString(),
|
customer_id: invoice.customerId.toString(),
|
||||||
|
|
||||||
|
|||||||
@ -4,20 +4,19 @@
|
|||||||
export interface IIssuedInvoiceSummarySnapshot {
|
export interface IIssuedInvoiceSummarySnapshot {
|
||||||
id: string;
|
id: string;
|
||||||
company_id: string;
|
company_id: string;
|
||||||
is_proforma: boolean;
|
|
||||||
|
|
||||||
invoice_number: string;
|
invoice_number: string;
|
||||||
status: string;
|
status: string;
|
||||||
series: string;
|
series: string;
|
||||||
|
|
||||||
invoice_date: string;
|
invoice_date: string;
|
||||||
operation_date: string;
|
operation_date: string | null;
|
||||||
|
|
||||||
language_code: string;
|
language_code: string;
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
|
|
||||||
reference: string | null;
|
reference: string | null;
|
||||||
description: string;
|
description: string | null;
|
||||||
|
|
||||||
customer_id: string;
|
customer_id: string;
|
||||||
recipient: {
|
recipient: {
|
||||||
|
|||||||
@ -15,12 +15,12 @@ import { Maybe, NumberHelper, Result } from "@repo/rdx-utils";
|
|||||||
|
|
||||||
import type { CreateProformaRequestDTO } from "../../../../common";
|
import type { CreateProformaRequestDTO } from "../../../../common";
|
||||||
import {
|
import {
|
||||||
type InvoiceRecipient,
|
|
||||||
InvoiceSerie,
|
InvoiceSerie,
|
||||||
InvoiceStatus,
|
InvoiceStatus,
|
||||||
ItemAmount,
|
ItemAmount,
|
||||||
ItemDescription,
|
ItemDescription,
|
||||||
ItemQuantity,
|
ItemQuantity,
|
||||||
|
type ProformaRecipient,
|
||||||
} from "../../../domain";
|
} from "../../../domain";
|
||||||
import type {
|
import type {
|
||||||
ProformaCreateInputProps,
|
ProformaCreateInputProps,
|
||||||
@ -64,8 +64,8 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const invoiceDate = extractOrPushError(
|
const proformaDate = extractOrPushError(
|
||||||
UtcDate.createFromISO(dto.invoice_date),
|
UtcDate.createFromISO(dto.proforma_date),
|
||||||
"invoice_date",
|
"invoice_date",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -142,11 +142,11 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
|||||||
//invoiceNumber: invoiceNumber!,
|
//invoiceNumber: invoiceNumber!,
|
||||||
series: series!,
|
series: series!,
|
||||||
|
|
||||||
proformaDate: invoiceDate!,
|
proformaDate: proformaDate!,
|
||||||
operationDate: operationDate!,
|
operationDate: operationDate!,
|
||||||
|
|
||||||
customerId: customerId!,
|
customerId: customerId!,
|
||||||
recipient: Maybe.none<InvoiceRecipient>(),
|
recipient: Maybe.none<ProformaRecipient>(),
|
||||||
|
|
||||||
reference: reference!,
|
reference: reference!,
|
||||||
description: description!,
|
description: description!,
|
||||||
@ -169,6 +169,7 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper {
|
|||||||
props,
|
props,
|
||||||
});
|
});
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
console.error(err);
|
||||||
return Result.fail(new DomainError("Proforma props mapping failed", { cause: 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.
|
* - 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[];
|
items: ProformaItemCreateInputProps[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -77,8 +77,6 @@ export class ProformaUpdater implements IProformaUpdater {
|
|||||||
}): Promise<Result<ProformaPatchProps, Error>> {
|
}): Promise<Result<ProformaPatchProps, Error>> {
|
||||||
const { patch, companyId, currentInvoiceDate } = params;
|
const { patch, companyId, currentInvoiceDate } = params;
|
||||||
|
|
||||||
console.log(patch);
|
|
||||||
|
|
||||||
if (patch.taxRegimeCode !== undefined) {
|
if (patch.taxRegimeCode !== undefined) {
|
||||||
const taxRegimeResult = await this.deps.taxResolver.ensureTaxRegimeByCode({
|
const taxRegimeResult = await this.deps.taxResolver.ensureTaxRegimeByCode({
|
||||||
companyId,
|
companyId,
|
||||||
|
|||||||
@ -10,7 +10,6 @@ export interface IProformaSummarySnapshotBuilder
|
|||||||
export class ProformaSummarySnapshotBuilder implements IProformaSummarySnapshotBuilder {
|
export class ProformaSummarySnapshotBuilder implements IProformaSummarySnapshotBuilder {
|
||||||
toOutput(proforma: ProformaSummary): ProformaSummaryDTO {
|
toOutput(proforma: ProformaSummary): ProformaSummaryDTO {
|
||||||
const recipient = proforma.recipient.toObjectString();
|
const recipient = proforma.recipient.toObjectString();
|
||||||
console.log("Proforma => ", proforma);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: proforma.id.toString(),
|
id: proforma.id.toString(),
|
||||||
|
|||||||
@ -19,48 +19,85 @@ export class IssuedInvoiceItemModel extends Model<
|
|||||||
InferAttributes<IssuedInvoiceItemModel>,
|
InferAttributes<IssuedInvoiceItemModel>,
|
||||||
InferCreationAttributes<IssuedInvoiceItemModel, { omit: "issued_invoice" }>
|
InferCreationAttributes<IssuedInvoiceItemModel, { omit: "issued_invoice" }>
|
||||||
> {
|
> {
|
||||||
declare id: string;
|
declare item_id: string;
|
||||||
declare issued_invoice_id: string;
|
declare issued_invoice_id: string;
|
||||||
|
|
||||||
declare position: number;
|
declare position: number;
|
||||||
|
|
||||||
declare description: CreationOptional<string | null>;
|
declare description: CreationOptional<string | null>;
|
||||||
|
|
||||||
declare quantity_value: CreationOptional<number | null>;
|
declare quantity_value: CreationOptional<number | null>;
|
||||||
declare quantity_scale: number;
|
declare quantity_scale: number;
|
||||||
|
|
||||||
declare unit_amount_value: CreationOptional<number | null>;
|
declare unit_amount_value: CreationOptional<number | null>;
|
||||||
declare unit_amount_scale: number;
|
declare unit_amount_scale: number;
|
||||||
|
|
||||||
|
// Subtotal (cantidad * importe unitario)
|
||||||
declare subtotal_amount_value: number;
|
declare subtotal_amount_value: number;
|
||||||
declare subtotal_amount_scale: number;
|
declare subtotal_amount_scale: number;
|
||||||
|
|
||||||
|
// Discount percentage
|
||||||
declare item_discount_percentage_value: CreationOptional<number | null>;
|
declare item_discount_percentage_value: CreationOptional<number | null>;
|
||||||
declare item_discount_percentage_scale: number;
|
declare item_discount_percentage_scale: number;
|
||||||
|
|
||||||
|
// Discount amount
|
||||||
declare item_discount_amount_value: number;
|
declare item_discount_amount_value: number;
|
||||||
declare item_discount_amount_scale: 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_value: number;
|
||||||
declare global_discount_percentage_scale: 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_value: number;
|
||||||
declare global_discount_amount_scale: 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_value: number;
|
||||||
declare total_discount_amount_scale: number;
|
declare total_discount_amount_scale: number;
|
||||||
|
|
||||||
|
// Taxable amount (base imponible tras los dos descuentos)
|
||||||
declare taxable_amount_value: number;
|
declare taxable_amount_value: number;
|
||||||
declare taxable_amount_scale: number;
|
declare taxable_amount_scale: number;
|
||||||
|
|
||||||
|
// IVA percentage
|
||||||
declare iva_code: CreationOptional<string | null>;
|
declare iva_code: CreationOptional<string | null>;
|
||||||
|
|
||||||
declare iva_percentage_value: CreationOptional<number | null>;
|
declare iva_percentage_value: CreationOptional<number | null>;
|
||||||
declare iva_percentage_scale: number;
|
declare iva_percentage_scale: number;
|
||||||
|
|
||||||
|
// IVA amount
|
||||||
declare iva_amount_value: number;
|
declare iva_amount_value: number;
|
||||||
declare iva_amount_scale: number;
|
declare iva_amount_scale: number;
|
||||||
|
|
||||||
|
// Recargo de equivalencia percentage
|
||||||
declare rec_code: CreationOptional<string | null>;
|
declare rec_code: CreationOptional<string | null>;
|
||||||
|
|
||||||
declare rec_percentage_value: CreationOptional<number | null>;
|
declare rec_percentage_value: CreationOptional<number | null>;
|
||||||
declare rec_percentage_scale: number;
|
declare rec_percentage_scale: number;
|
||||||
|
|
||||||
|
// Recargo de equivalencia amount
|
||||||
declare rec_amount_value: number;
|
declare rec_amount_value: number;
|
||||||
declare rec_amount_scale: number;
|
declare rec_amount_scale: number;
|
||||||
|
|
||||||
|
// Retention percentage
|
||||||
declare retention_code: CreationOptional<string | null>;
|
declare retention_code: CreationOptional<string | null>;
|
||||||
|
|
||||||
declare retention_percentage_value: CreationOptional<number | null>;
|
declare retention_percentage_value: CreationOptional<number | null>;
|
||||||
declare retention_percentage_scale: number;
|
declare retention_percentage_scale: number;
|
||||||
|
|
||||||
declare retention_amount_value: number;
|
declare retention_amount_value: number;
|
||||||
declare retention_amount_scale: number;
|
declare retention_amount_scale: number;
|
||||||
|
|
||||||
|
// Total taxes amount / taxes total
|
||||||
declare taxes_amount_value: number;
|
declare taxes_amount_value: number;
|
||||||
declare taxes_amount_scale: number;
|
declare taxes_amount_scale: number;
|
||||||
|
|
||||||
|
// Total
|
||||||
declare total_amount_value: number;
|
declare total_amount_value: number;
|
||||||
declare total_amount_scale: number;
|
declare total_amount_scale: number;
|
||||||
|
|
||||||
|
// Relaciones
|
||||||
declare issued_invoice: NonAttribute<IssuedInvoiceModel>;
|
declare issued_invoice: NonAttribute<IssuedInvoiceModel>;
|
||||||
|
|
||||||
static associate(database: Sequelize) {
|
static associate(database: Sequelize) {
|
||||||
@ -88,10 +125,9 @@ export class IssuedInvoiceItemModel extends Model<
|
|||||||
export default (database: Sequelize) => {
|
export default (database: Sequelize) => {
|
||||||
IssuedInvoiceItemModel.init(
|
IssuedInvoiceItemModel.init(
|
||||||
{
|
{
|
||||||
id: {
|
item_id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
field: "item_id",
|
|
||||||
charset: "utf8mb4",
|
charset: "utf8mb4",
|
||||||
collate: "utf8mb4_bin",
|
collate: "utf8mb4_bin",
|
||||||
} as any,
|
} as any,
|
||||||
@ -112,96 +148,117 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
quantity_value: {
|
quantity_value: {
|
||||||
type: new DataTypes.BIGINT(),
|
type: new DataTypes.BIGINT(),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
quantity_scale: {
|
quantity_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 2,
|
defaultValue: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
unit_amount_value: {
|
unit_amount_value: {
|
||||||
type: new DataTypes.BIGINT(),
|
type: new DataTypes.BIGINT(),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
unit_amount_scale: {
|
unit_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
subtotal_amount_value: {
|
subtotal_amount_value: {
|
||||||
type: new DataTypes.BIGINT(),
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
subtotal_amount_scale: {
|
subtotal_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
item_discount_percentage_value: {
|
item_discount_percentage_value: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
item_discount_percentage_scale: {
|
item_discount_percentage_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 2,
|
defaultValue: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
item_discount_amount_value: {
|
item_discount_amount_value: {
|
||||||
type: new DataTypes.BIGINT(),
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
item_discount_amount_scale: {
|
item_discount_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
global_discount_percentage_value: {
|
global_discount_percentage_value: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
global_discount_percentage_scale: {
|
global_discount_percentage_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 2,
|
defaultValue: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
global_discount_amount_value: {
|
global_discount_amount_value: {
|
||||||
type: new DataTypes.BIGINT(),
|
type: new DataTypes.BIGINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
global_discount_amount_scale: {
|
global_discount_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
total_discount_amount_value: {
|
total_discount_amount_value: {
|
||||||
type: new DataTypes.BIGINT(),
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
total_discount_amount_scale: {
|
total_discount_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
taxable_amount_value: {
|
taxable_amount_value: {
|
||||||
type: new DataTypes.BIGINT(),
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
taxable_amount_scale: {
|
taxable_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// IVA %
|
||||||
|
|
||||||
iva_code: {
|
iva_code: {
|
||||||
type: DataTypes.STRING(40),
|
type: DataTypes.STRING(40),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -217,6 +274,7 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 2,
|
defaultValue: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
iva_amount_value: {
|
iva_amount_value: {
|
||||||
type: DataTypes.BIGINT,
|
type: DataTypes.BIGINT,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
@ -227,11 +285,14 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// REC %
|
||||||
rec_code: {
|
rec_code: {
|
||||||
type: DataTypes.STRING(40),
|
type: DataTypes.STRING(40),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
rec_percentage_value: {
|
rec_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -242,6 +303,7 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 2,
|
defaultValue: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
rec_amount_value: {
|
rec_amount_value: {
|
||||||
type: DataTypes.BIGINT,
|
type: DataTypes.BIGINT,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
@ -252,11 +314,14 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Retención %
|
||||||
retention_code: {
|
retention_code: {
|
||||||
type: DataTypes.STRING(40),
|
type: DataTypes.STRING(40),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
retention_percentage_value: {
|
retention_percentage_value: {
|
||||||
type: DataTypes.SMALLINT,
|
type: DataTypes.SMALLINT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -267,6 +332,7 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 2,
|
defaultValue: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
retention_amount_value: {
|
retention_amount_value: {
|
||||||
type: DataTypes.BIGINT,
|
type: DataTypes.BIGINT,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
@ -277,21 +343,25 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
taxes_amount_value: {
|
taxes_amount_value: {
|
||||||
type: new DataTypes.BIGINT(),
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
taxes_amount_scale: {
|
taxes_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
total_amount_value: {
|
total_amount_value: {
|
||||||
type: new DataTypes.BIGINT(),
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
total_amount_scale: {
|
total_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
@ -302,11 +372,15 @@ export default (database: Sequelize) => {
|
|||||||
sequelize: database,
|
sequelize: database,
|
||||||
modelName: "IssuedInvoiceItemModel",
|
modelName: "IssuedInvoiceItemModel",
|
||||||
tableName: "issued_invoice_items",
|
tableName: "issued_invoice_items",
|
||||||
|
|
||||||
charset: "utf8mb4",
|
charset: "utf8mb4",
|
||||||
collate: "utf8mb4_unicode_ci",
|
collate: "utf8mb4_unicode_ci",
|
||||||
underscored: true,
|
underscored: true,
|
||||||
|
|
||||||
indexes: [{ fields: ["issued_invoice_id"] }, { fields: ["issued_invoice_id", "position"] }],
|
indexes: [{ fields: ["issued_invoice_id"] }, { fields: ["issued_invoice_id", "position"] }],
|
||||||
whereMergeStrategy: "and",
|
|
||||||
|
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||||
|
|
||||||
defaultScope: {
|
defaultScope: {
|
||||||
order: [["position", "ASC"]],
|
order: [["position", "ASC"]],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -51,43 +51,68 @@ export class IssuedInvoiceModel extends Model<
|
|||||||
|
|
||||||
declare reference: CreationOptional<string | null>;
|
declare reference: CreationOptional<string | null>;
|
||||||
declare description: string;
|
declare description: string;
|
||||||
|
|
||||||
declare notes: CreationOptional<string | null>;
|
declare notes: CreationOptional<string | null>;
|
||||||
|
|
||||||
declare language_code: CreationOptional<string>;
|
declare language_code: CreationOptional<string>;
|
||||||
declare currency_code: CreationOptional<string>;
|
declare currency_code: CreationOptional<string>;
|
||||||
|
|
||||||
|
// Método de pago
|
||||||
declare payment_method_id: CreationOptional<string | null>;
|
declare payment_method_id: CreationOptional<string | null>;
|
||||||
declare payment_method_name: CreationOptional<string | null>;
|
declare payment_method_name: CreationOptional<string | null>;
|
||||||
declare payment_method_description: CreationOptional<string | null>;
|
declare payment_method_description: CreationOptional<string | null>;
|
||||||
|
|
||||||
|
// Plazo de pago
|
||||||
declare payment_term_id: CreationOptional<string | null>;
|
declare payment_term_id: CreationOptional<string | null>;
|
||||||
declare payment_term_name: CreationOptional<string | null>;
|
declare payment_term_name: CreationOptional<string | null>;
|
||||||
declare payment_term_description: CreationOptional<string | null>;
|
declare payment_term_description: CreationOptional<string | null>;
|
||||||
declare payment_term_snapshot_json: CreationOptional<string | null>;
|
declare payment_term_snapshot_json: CreationOptional<string | null>;
|
||||||
|
|
||||||
|
// Régimen fiscal
|
||||||
declare tax_regime_code: string;
|
declare tax_regime_code: string;
|
||||||
declare tax_regime_description: string;
|
declare tax_regime_description: string;
|
||||||
|
|
||||||
|
// Subtotal
|
||||||
declare subtotal_amount_value: number;
|
declare subtotal_amount_value: number;
|
||||||
declare subtotal_amount_scale: 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_value: number;
|
||||||
declare items_discount_amount_scale: number;
|
declare items_discount_amount_scale: number;
|
||||||
|
|
||||||
|
// Global/header discount percentage
|
||||||
declare global_discount_percentage_value: number;
|
declare global_discount_percentage_value: number;
|
||||||
declare global_discount_percentage_scale: number;
|
declare global_discount_percentage_scale: number;
|
||||||
|
|
||||||
|
// Global/header discount amount
|
||||||
declare global_discount_amount_value: number;
|
declare global_discount_amount_value: number;
|
||||||
declare global_discount_amount_scale: number;
|
declare global_discount_amount_scale: number;
|
||||||
|
|
||||||
|
// Total discount amount (subtotal - descuentos)
|
||||||
declare total_discount_amount_value: number;
|
declare total_discount_amount_value: number;
|
||||||
declare total_discount_amount_scale: number;
|
declare total_discount_amount_scale: number;
|
||||||
|
|
||||||
|
// Taxable amount (base imponible)
|
||||||
declare taxable_amount_value: number;
|
declare taxable_amount_value: number;
|
||||||
declare taxable_amount_scale: 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 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 rec_amount_scale: number;
|
||||||
declare retention_amount_value: CreationOptional<number | null>;
|
|
||||||
|
// Retention amount
|
||||||
|
declare retention_amount_value: number;
|
||||||
declare retention_amount_scale: number;
|
declare retention_amount_scale: number;
|
||||||
|
|
||||||
|
// Total taxes amount / taxes total
|
||||||
declare taxes_amount_value: number;
|
declare taxes_amount_value: number;
|
||||||
declare taxes_amount_scale: number;
|
declare taxes_amount_scale: number;
|
||||||
|
|
||||||
|
// Total
|
||||||
declare total_amount_value: number;
|
declare total_amount_value: number;
|
||||||
declare total_amount_scale: number;
|
declare total_amount_scale: number;
|
||||||
|
|
||||||
|
|||||||
@ -76,18 +76,18 @@ export class ProformaModel extends Model<
|
|||||||
declare total_amount_scale: number;
|
declare total_amount_scale: number;
|
||||||
|
|
||||||
declare customer_id: string;
|
declare customer_id: string;
|
||||||
declare customer_tin: CreationOptional<string | null>;
|
/*declare customer_tin: CreationOptional<string | null>;
|
||||||
declare customer_name: CreationOptional<string | null>;
|
declare customer_name: CreationOptional<string | null>;
|
||||||
declare customer_street: CreationOptional<string | null>;
|
declare customer_street: CreationOptional<string | null>;
|
||||||
declare customer_street2: CreationOptional<string | null>;
|
declare customer_street2: CreationOptional<string | null>;
|
||||||
declare customer_city: CreationOptional<string | null>;
|
declare customer_city: CreationOptional<string | null>;
|
||||||
declare customer_province: CreationOptional<string | null>;
|
declare customer_province: CreationOptional<string | null>;
|
||||||
declare customer_postal_code: 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 items: NonAttribute<ProformaItemModel[]>;
|
||||||
declare taxes: NonAttribute<ProformaTaxModel[]>;
|
declare taxes: NonAttribute<ProformaTaxModel[]>;
|
||||||
declare current_customer?: NonAttribute<CustomerModel | null>;
|
declare current_customer: NonAttribute<CustomerModel>;
|
||||||
declare linked_invoice?: NonAttribute<IssuedInvoiceModel | null>;
|
declare linked_invoice?: NonAttribute<IssuedInvoiceModel | null>;
|
||||||
|
|
||||||
static associate(database: Sequelize) {
|
static associate(database: Sequelize) {
|
||||||
@ -352,7 +352,7 @@ export default (database: Sequelize) => {
|
|||||||
collate: "utf8mb4_bin",
|
collate: "utf8mb4_bin",
|
||||||
} as any,
|
} as any,
|
||||||
|
|
||||||
customer_tin: {
|
/*customer_tin: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
@ -391,7 +391,7 @@ export default (database: Sequelize) => {
|
|||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},*/
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequelize: database,
|
sequelize: database,
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export const issuedInvoicesRouter = (params: StartParams) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:invoice_id/report",
|
"/:invoice_id/report/pdf",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(ReportIssueInvoiceByIdQueryRequestSchema, "query"),
|
validateRequest(ReportIssueInvoiceByIdQueryRequestSchema, "query"),
|
||||||
validateRequest(ReportIssueInvoiceByIdParamsRequestSchema, "params"),
|
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-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-recipient-domain.mapper";
|
||||||
export * from "./sequelize-issued-invoice-v2-taxes-domain.mapper";
|
export * from "./sequelize-issued-invoice-v2-taxes-domain.mapper";
|
||||||
export * from "./sequelize-issued-invoice-v2-verifactu-domain.mapper";
|
export * from "./sequelize-issued-invoice-v2-verifactu-domain.mapper";
|
||||||
|
*/
|
||||||
|
|||||||
@ -98,7 +98,11 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
"reference",
|
"reference",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const description = extractOrPushError(Result.ok(String(raw.description ?? "")), "description", errors);
|
const description = extractOrPushError(
|
||||||
|
Result.ok(String(raw.description ?? "")),
|
||||||
|
"description",
|
||||||
|
errors
|
||||||
|
);
|
||||||
const notes = extractOrPushError(
|
const notes = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.notes, (value) => TextValue.create(value)),
|
maybeFromNullableResult(raw.notes, (value) => TextValue.create(value)),
|
||||||
"notes",
|
"notes",
|
||||||
@ -124,18 +128,18 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const taxRegime = extractOrPushError(
|
const taxRegime = extractOrPushError(
|
||||||
!isNullishOrEmpty(raw.tax_regime_code)
|
isNullishOrEmpty(raw.tax_regime_code)
|
||||||
? InvoiceTaxRegime.create({
|
? Result.fail(
|
||||||
code: String(raw.tax_regime_code),
|
|
||||||
description: String(raw.tax_regime_description ?? raw.tax_regime_code),
|
|
||||||
})
|
|
||||||
: Result.fail(
|
|
||||||
new DomainValidationError(
|
new DomainValidationError(
|
||||||
"MISSING_TAX_REGIME",
|
"MISSING_TAX_REGIME",
|
||||||
"tax_regime",
|
"tax_regime",
|
||||||
"Issued invoice requires persisted 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",
|
"tax_regime",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -167,7 +171,10 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const totalDiscountAmount = extractOrPushError(
|
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",
|
"total_discount_amount_value",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -187,7 +194,10 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const retentionAmount = extractOrPushError(
|
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",
|
"retention_amount_value",
|
||||||
errors
|
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 {
|
try {
|
||||||
|
console.debug(raw);
|
||||||
|
|
||||||
const errors: ValidationErrorDetail[] = [];
|
const errors: ValidationErrorDetail[] = [];
|
||||||
const attributes = this._mapAttributesToDomain(raw, { errors, ...params });
|
const attributes = this._mapAttributesToDomain(raw, { errors, ...params });
|
||||||
const recipientResult = this._recipientMapper.mapToDomain(raw, {
|
const recipientResult = this._recipientMapper.mapToDomain(raw, {
|
||||||
@ -319,23 +334,27 @@ export class SequelizeIssuedInvoiceV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<IssuedInvoiceCreationAttributes, Error> {
|
): Result<IssuedInvoiceCreationAttributes, Error> {
|
||||||
const errors: ValidationErrorDetail[] = [];
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
const itemsResult = this._itemsMapper.mapToPersistenceArray(source.items, {
|
const itemsResult = this._itemsMapper.mapToPersistenceArray(source.items, {
|
||||||
errors,
|
errors,
|
||||||
parent: source,
|
parent: source,
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (itemsResult.isFailure) errors.push({ path: "items", message: itemsResult.error.message });
|
if (itemsResult.isFailure) errors.push({ path: "items", message: itemsResult.error.message });
|
||||||
const taxesResult = this._taxesMapper.mapToPersistenceArray(source.taxes, {
|
const taxesResult = this._taxesMapper.mapToPersistenceArray(source.taxes, {
|
||||||
errors,
|
errors,
|
||||||
parent: source,
|
parent: source,
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (taxesResult.isFailure) errors.push({ path: "taxes", message: taxesResult.error.message });
|
if (taxesResult.isFailure) errors.push({ path: "taxes", message: taxesResult.error.message });
|
||||||
const recipient = this._recipientMapper.mapToPersistence(source.recipient, {
|
const recipient = this._recipientMapper.mapToPersistence(source.recipient, {
|
||||||
errors,
|
errors,
|
||||||
parent: source,
|
parent: source,
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
const verifactuResult = this._verifactuMapper.mapToPersistence(source.verifactu, {
|
const verifactuResult = this._verifactuMapper.mapToPersistence(source.verifactu, {
|
||||||
errors,
|
errors,
|
||||||
parent: source,
|
parent: source,
|
||||||
|
|||||||
@ -13,13 +13,14 @@ import {
|
|||||||
maybeFromNullableOrEmptyString,
|
maybeFromNullableOrEmptyString,
|
||||||
maybeFromNullableResult,
|
maybeFromNullableResult,
|
||||||
maybeToNullable,
|
maybeToNullable,
|
||||||
|
maybeToNullableString,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type IIssuedInvoiceCreateProps,
|
type IIssuedInvoiceCreateProps,
|
||||||
type IssuedInvoice,
|
|
||||||
type IIssuedInvoiceItemCreateProps,
|
type IIssuedInvoiceItemCreateProps,
|
||||||
|
type IssuedInvoice,
|
||||||
IssuedInvoiceItem,
|
IssuedInvoiceItem,
|
||||||
ItemAmount,
|
ItemAmount,
|
||||||
ItemDescription,
|
ItemDescription,
|
||||||
@ -56,7 +57,11 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
attributes: Partial<IIssuedInvoiceCreateProps>;
|
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(
|
const description = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.description, (v) => ItemDescription.create(v)),
|
maybeFromNullableResult(raw.description, (v) => ItemDescription.create(v)),
|
||||||
`items[${index}].description`,
|
`items[${index}].description`,
|
||||||
@ -75,7 +80,10 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const subtotalAmount = extractOrPushError(
|
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`,
|
`items[${index}].subtotal_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -130,7 +138,10 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const ivaAmount = extractOrPushError(
|
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`,
|
`items[${index}].iva_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -141,13 +152,18 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const recAmount = extractOrPushError(
|
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`,
|
`items[${index}].rec_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code);
|
const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code);
|
||||||
const retentionPercentage = extractOrPushError(
|
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`,
|
`items[${index}].retention_percentage_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -160,12 +176,18 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const taxesAmount = extractOrPushError(
|
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`,
|
`items[${index}].taxes_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const totalAmount = extractOrPushError(
|
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`,
|
`items[${index}].total_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -258,18 +280,25 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
};
|
};
|
||||||
|
|
||||||
return Result.ok({
|
return Result.ok({
|
||||||
id: source.id.toPrimitive(),
|
item_id: source.id.toPrimitive(),
|
||||||
issued_invoice_id: parent.id.toPrimitive(),
|
issued_invoice_id: parent.id.toPrimitive(),
|
||||||
position: index,
|
position: index,
|
||||||
|
|
||||||
description: maybeToNullable(source.description, (v) => v.toPrimitive()),
|
description: maybeToNullable(source.description, (v) => v.toPrimitive()),
|
||||||
|
|
||||||
quantity_value: maybeToNullable(source.quantity, (v) => v.toPrimitive().value),
|
quantity_value: maybeToNullable(source.quantity, (v) => v.toPrimitive().value),
|
||||||
quantity_scale:
|
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_value: maybeToNullable(source.unitAmount, (v) => v.toPrimitive().value),
|
||||||
unit_amount_scale:
|
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_value: source.subtotalAmount.toPrimitive().value,
|
||||||
subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale,
|
subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale,
|
||||||
|
|
||||||
item_discount_percentage_value: maybeToNullable(
|
item_discount_percentage_value: maybeToNullable(
|
||||||
source.itemDiscountPercentage,
|
source.itemDiscountPercentage,
|
||||||
(v) => v.toPrimitive().value
|
(v) => v.toPrimitive().value
|
||||||
@ -277,36 +306,62 @@ export class SequelizeIssuedInvoiceV2ItemDomainMapper extends SequelizeDomainMap
|
|||||||
item_discount_percentage_scale:
|
item_discount_percentage_scale:
|
||||||
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
DiscountPercentage.DEFAULT_SCALE,
|
DiscountPercentage.DEFAULT_SCALE,
|
||||||
|
|
||||||
item_discount_amount_value: source.itemDiscountAmount.toPrimitive().value,
|
item_discount_amount_value: source.itemDiscountAmount.toPrimitive().value,
|
||||||
item_discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale,
|
item_discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale,
|
||||||
|
|
||||||
global_discount_percentage_value: source.globalDiscountPercentage.toPrimitive().value,
|
global_discount_percentage_value: source.globalDiscountPercentage.toPrimitive().value,
|
||||||
global_discount_percentage_scale: source.globalDiscountPercentage.toPrimitive().scale,
|
global_discount_percentage_scale: source.globalDiscountPercentage.toPrimitive().scale,
|
||||||
|
|
||||||
global_discount_amount_value: source.globalDiscountAmount.toPrimitive().value,
|
global_discount_amount_value: source.globalDiscountAmount.toPrimitive().value,
|
||||||
global_discount_amount_scale: source.globalDiscountAmount.toPrimitive().scale,
|
global_discount_amount_scale: source.globalDiscountAmount.toPrimitive().scale,
|
||||||
|
|
||||||
total_discount_amount_value: source.totalDiscountAmount.toPrimitive().value,
|
total_discount_amount_value: source.totalDiscountAmount.toPrimitive().value,
|
||||||
total_discount_amount_scale: source.totalDiscountAmount.toPrimitive().scale,
|
total_discount_amount_scale: source.totalDiscountAmount.toPrimitive().scale,
|
||||||
|
|
||||||
taxable_amount_value: source.taxableAmount.toPrimitive().value,
|
taxable_amount_value: source.taxableAmount.toPrimitive().value,
|
||||||
taxable_amount_scale: source.taxableAmount.toPrimitive().scale,
|
taxable_amount_scale: source.taxableAmount.toPrimitive().scale,
|
||||||
iva_code: source.ivaCode ?? null,
|
|
||||||
iva_percentage_value: source.ivaPercentage?.toPrimitive().value ?? null,
|
// IVA
|
||||||
iva_percentage_scale: source.ivaPercentage?.toPrimitive().scale ?? TaxPercentage.DEFAULT_SCALE,
|
iva_code: maybeToNullableString(source.ivaCode),
|
||||||
iva_amount_value: source.ivaAmount.toPrimitive().value,
|
|
||||||
iva_amount_scale: source.ivaAmount.toPrimitive().scale,
|
iva_percentage_value: maybeToNullable(source.ivaPercentage, (v) => v.toPrimitive().value),
|
||||||
rec_code: source.recCode ?? null,
|
iva_percentage_scale:
|
||||||
rec_percentage_value: source.recPercentage?.toPrimitive().value ?? null,
|
maybeToNullable(source.ivaPercentage, (v) => v.toPrimitive().scale) ?? 2,
|
||||||
rec_percentage_scale: source.recPercentage?.toPrimitive().scale ?? TaxPercentage.DEFAULT_SCALE,
|
|
||||||
rec_amount_value: source.recAmount.toPrimitive().value,
|
iva_amount_value: source.ivaAmount.value,
|
||||||
rec_amount_scale: source.recAmount.toPrimitive().scale,
|
iva_amount_scale: source.ivaAmount.scale,
|
||||||
retention_code: source.retentionCode ?? null,
|
|
||||||
retention_percentage_value: source.retentionPercentage?.toPrimitive().value ?? null,
|
// 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:
|
retention_percentage_scale:
|
||||||
source.retentionPercentage?.toPrimitive().scale ?? TaxPercentage.DEFAULT_SCALE,
|
maybeToNullable(source.retentionPercentage, (v) => v.toPrimitive().scale) ?? 2,
|
||||||
retention_amount_value: source.retentionAmount.toPrimitive().value,
|
|
||||||
retention_amount_scale: source.retentionAmount.toPrimitive().scale,
|
retention_amount_value: source.retentionAmount.value,
|
||||||
|
retention_amount_scale: source.retentionAmount.scale,
|
||||||
|
|
||||||
|
//
|
||||||
taxes_amount_value: source.taxesAmount.toPrimitive().value,
|
taxes_amount_value: source.taxesAmount.toPrimitive().value,
|
||||||
taxes_amount_scale: source.taxesAmount.toPrimitive().scale,
|
taxes_amount_scale: source.taxesAmount.toPrimitive().scale,
|
||||||
|
|
||||||
|
//
|
||||||
total_amount_value: source.totalAmount.toPrimitive().value,
|
total_amount_value: source.totalAmount.toPrimitive().value,
|
||||||
total_amount_scale: source.totalAmount.toPrimitive().scale,
|
total_amount_scale: source.totalAmount.toPrimitive().scale,
|
||||||
});
|
} satisfies IssuedInvoiceItemCreationAttributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,15 +28,31 @@ export class SequelizeIssuedInvoiceV2RecipientDomainMapper {
|
|||||||
attributes: Partial<IIssuedInvoiceCreateProps>;
|
attributes: Partial<IIssuedInvoiceCreateProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const customerName = extractOrPushError(Name.create(source.customer_name!), "customer_name", errors);
|
const customerName = extractOrPushError(
|
||||||
const customerTin = extractOrPushError(TINNumber.create(source.customer_tin!), "customer_tin", errors);
|
Name.create(source.customer_name!),
|
||||||
const customerStreet = extractOrPushError(Street.create(source.customer_street!), "customer_street", errors);
|
"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(
|
const customerStreet2 = extractOrPushError(
|
||||||
maybeFromNullableResult(source.customer_street2, (value) => Street.create(value)),
|
maybeFromNullableResult(source.customer_street2, (value) => Street.create(value)),
|
||||||
"customer_street2",
|
"customer_street2",
|
||||||
errors
|
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(
|
const customerProvince = extractOrPushError(
|
||||||
maybeFromNullableResult(source.customer_province, (value) => Province.create(value)),
|
maybeFromNullableResult(source.customer_province, (value) => Province.create(value)),
|
||||||
"customer_province",
|
"customer_province",
|
||||||
@ -75,7 +91,7 @@ export class SequelizeIssuedInvoiceV2RecipientDomainMapper {
|
|||||||
return Result.ok(createResult.data);
|
return Result.ok(createResult.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapToPersistence(source: IssuedInvoiceRecipient) {
|
mapToPersistence(source: IssuedInvoiceRecipient, params?: MapperParamsType) {
|
||||||
return {
|
return {
|
||||||
customer_tin: source.tin.toString(),
|
customer_tin: source.tin.toString(),
|
||||||
customer_name: source.name.toPrimitive(),
|
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-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";
|
} from "../../../../../../domain";
|
||||||
import type { IssuedInvoiceModel } from "../../../../../common";
|
import type { IssuedInvoiceModel } from "../../../../../common";
|
||||||
|
|
||||||
import { SequelizeVerifactuRecordSummaryMapper } from "./sequelize-verifactu-record-summary.mapper";
|
|
||||||
import { SequelizeIssuedInvoiceV2RecipientSummaryMapper } from "./sequelize-issued-invoice-v2-recipient-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<
|
export class SequelizeIssuedInvoiceV2SummaryMapper extends SequelizeQueryMapper<
|
||||||
IssuedInvoiceModel,
|
IssuedInvoiceModel,
|
||||||
@ -113,12 +113,17 @@ export class SequelizeIssuedInvoiceV2SummaryMapper extends SequelizeQueryMapper<
|
|||||||
"invoice_number",
|
"invoice_number",
|
||||||
errors
|
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(
|
const operationDate = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.operation_date, (value) => UtcDate.createFromISO(value)),
|
maybeFromNullableResult(raw.operation_date, (value) => UtcDate.createFromISO(value)),
|
||||||
"operation_date",
|
"operation_date",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const reference = extractOrPushError(
|
const reference = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.reference, (value) => Result.ok(String(value))),
|
maybeFromNullableResult(raw.reference, (value) => Result.ok(String(value))),
|
||||||
"reference",
|
"reference",
|
||||||
|
|||||||
@ -5,10 +5,10 @@ import {
|
|||||||
requireCompanyContextGuard,
|
requireCompanyContextGuard,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { GetProformaByIdUseCase } from "../../../../application";
|
import type { GetProformaByIdUseCase } from "../../../../application/index.ts";
|
||||||
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.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) {
|
public constructor(private readonly useCase: GetProformaByIdUseCase) {
|
||||||
super();
|
super();
|
||||||
this.errorMapper = proformasApiErrorMapper;
|
this.errorMapper = proformasApiErrorMapper;
|
||||||
@ -1,7 +1,7 @@
|
|||||||
//export * from "./change-status-proforma.controller";
|
//export * from "./change-status-proforma.controller";
|
||||||
//export * from "./create-proforma.controller";
|
//export * from "./create-proforma.controller";
|
||||||
//export * from "./delete-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 "./issue-proforma.controller";
|
||||||
export * from "./list-proformas.controller";
|
export * from "./list-proformas.controller";
|
||||||
export * from "./preview-proforma-report.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 { type NextFunction, type Request, type Response, Router } from "express";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GetProformaController,
|
GetProformaByIdController,
|
||||||
IssueProformaController,
|
IssueProformaController,
|
||||||
ListProformasController,
|
ListProformasController,
|
||||||
PreviewProformaReportController,
|
PreviewProformaReportController,
|
||||||
@ -15,9 +15,9 @@ import {
|
|||||||
ChangeStatusProformaByIdParamsRequestSchema,
|
ChangeStatusProformaByIdParamsRequestSchema,
|
||||||
ChangeStatusProformaByIdRequestSchema,
|
ChangeStatusProformaByIdRequestSchema,
|
||||||
CreateProformaRequestSchema,
|
CreateProformaRequestSchema,
|
||||||
LegacyIssueProformaByIdParamsRequestSchema,
|
|
||||||
GetProformaByIdRequestSchema,
|
GetProformaByIdRequestSchema,
|
||||||
IssueProformaByIdParamsRequestSchema,
|
IssueProformaByIdParamsRequestSchema,
|
||||||
|
LegacyIssueProformaByIdParamsRequestSchema,
|
||||||
ListProformasRequestSchema,
|
ListProformasRequestSchema,
|
||||||
PreviewProformaReportByIdParamsRequestSchema,
|
PreviewProformaReportByIdParamsRequestSchema,
|
||||||
ReportProformaPdfByIdParamsRequestSchema,
|
ReportProformaPdfByIdParamsRequestSchema,
|
||||||
@ -28,8 +28,8 @@ import type { IIssuedInvoicePublicServices } from "../../../application";
|
|||||||
|
|
||||||
import { ChangeStatusProformaController } from "./controllers/change-status-proforma.controller";
|
import { ChangeStatusProformaController } from "./controllers/change-status-proforma.controller";
|
||||||
import { CreateProformaController } from "./controllers/create-proforma.controller";
|
import { CreateProformaController } from "./controllers/create-proforma.controller";
|
||||||
import { IssueProformaByIdRequestMapper } from "./mappers";
|
|
||||||
import { UpdateProformaController } from "./controllers/update-proforma.controller";
|
import { UpdateProformaController } from "./controllers/update-proforma.controller";
|
||||||
|
import { IssueProformaByIdRequestMapper } from "./mappers";
|
||||||
|
|
||||||
export const proformasRouter = (params: StartParams) => {
|
export const proformasRouter = (params: StartParams) => {
|
||||||
const { app, config, getService, getInternal } = params;
|
const { app, config, getService, getInternal } = params;
|
||||||
@ -67,7 +67,7 @@ export const proformasRouter = (params: StartParams) => {
|
|||||||
validateRequest(GetProformaByIdRequestSchema, "params"),
|
validateRequest(GetProformaByIdRequestSchema, "params"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.getProformaById();
|
const useCase = deps.useCases.getProformaById();
|
||||||
const controller = new GetProformaController(useCase);
|
const controller = new GetProformaByIdController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -218,11 +218,7 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
errors.push({ path: "taxes", message: taxesResult.error.message });
|
errors.push({ path: "taxes", message: taxesResult.error.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipient = this._recipientMapper.mapToPersistence(source.recipient, {
|
const recipient = this._recipientMapper.mapToPersistence(source.recipient);
|
||||||
errors,
|
|
||||||
parent: source,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
@ -233,8 +229,11 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
const allAmounts = source.totals();
|
const allAmounts = source.totals();
|
||||||
|
|
||||||
return Result.ok({
|
return Result.ok({
|
||||||
|
// Identificación
|
||||||
id: source.id.toPrimitive(),
|
id: source.id.toPrimitive(),
|
||||||
company_id: source.companyId.toPrimitive(),
|
company_id: source.companyId.toPrimitive(),
|
||||||
|
|
||||||
|
// Flags / estado / serie / número
|
||||||
status: source.status.toPrimitive(),
|
status: source.status.toPrimitive(),
|
||||||
proforma_reference: source.proformaReference.toPrimitive(),
|
proforma_reference: source.proformaReference.toPrimitive(),
|
||||||
proforma_date: source.invoiceDate.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()),
|
target_invoice_series_code: maybeToNullable(source.series, (v) => v.toPrimitive()),
|
||||||
language_code: source.languageCode.toPrimitive(),
|
language_code: source.languageCode.toPrimitive(),
|
||||||
currency_code: source.currencyCode.toPrimitive(),
|
currency_code: source.currencyCode.toPrimitive(),
|
||||||
|
|
||||||
reference: maybeToNullable(source.reference, (reference) => reference),
|
reference: maybeToNullable(source.reference, (reference) => reference),
|
||||||
description: maybeToNullable(source.description, (description) => description),
|
description: maybeToNullable(source.description, (description) => description),
|
||||||
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
|
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
|
||||||
payment_method_id: maybeToNullable(source.paymentMethodId, (value) => value.toPrimitive()),
|
payment_method_id: maybeToNullable(source.paymentMethodId, (value) => value.toPrimitive()),
|
||||||
payment_term_id: null,
|
payment_term_id: null,
|
||||||
tax_regime_code: maybeToNullable(source.taxRegimeCode, (value) => value),
|
tax_regime_code: maybeToNullable(source.taxRegimeCode, (value) => value),
|
||||||
|
|
||||||
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||||
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||||
|
|
||||||
items_discount_amount_value: allAmounts.itemsDiscountAmount.value,
|
items_discount_amount_value: allAmounts.itemsDiscountAmount.value,
|
||||||
items_discount_amount_scale: allAmounts.itemsDiscountAmount.scale,
|
items_discount_amount_scale: allAmounts.itemsDiscountAmount.scale,
|
||||||
|
|
||||||
global_discount_percentage_value: source.globalDiscountPercentage.toPrimitive().value,
|
global_discount_percentage_value: source.globalDiscountPercentage.toPrimitive().value,
|
||||||
global_discount_percentage_scale: source.globalDiscountPercentage.toPrimitive().scale,
|
global_discount_percentage_scale: source.globalDiscountPercentage.toPrimitive().scale,
|
||||||
|
|
||||||
global_discount_amount_value: allAmounts.globalDiscountAmount.value,
|
global_discount_amount_value: allAmounts.globalDiscountAmount.value,
|
||||||
global_discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
global_discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
||||||
|
|
||||||
total_discount_amount_value: allAmounts.totalDiscountAmount.value,
|
total_discount_amount_value: allAmounts.totalDiscountAmount.value,
|
||||||
total_discount_amount_scale: allAmounts.totalDiscountAmount.scale,
|
total_discount_amount_scale: allAmounts.totalDiscountAmount.scale,
|
||||||
|
|
||||||
taxable_amount_value: allAmounts.taxableAmount.value,
|
taxable_amount_value: allAmounts.taxableAmount.value,
|
||||||
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
||||||
|
|
||||||
iva_amount_value: allAmounts.ivaAmount.value,
|
iva_amount_value: allAmounts.ivaAmount.value,
|
||||||
iva_amount_scale: allAmounts.ivaAmount.scale,
|
iva_amount_scale: allAmounts.ivaAmount.scale,
|
||||||
|
|
||||||
rec_amount_value: allAmounts.recAmount.value,
|
rec_amount_value: allAmounts.recAmount.value,
|
||||||
rec_amount_scale: allAmounts.recAmount.scale,
|
rec_amount_scale: allAmounts.recAmount.scale,
|
||||||
|
|
||||||
retention_amount_value: allAmounts.retentionAmount.value,
|
retention_amount_value: allAmounts.retentionAmount.value,
|
||||||
retention_amount_scale: allAmounts.retentionAmount.scale,
|
retention_amount_scale: allAmounts.retentionAmount.scale,
|
||||||
|
|
||||||
taxes_amount_value: allAmounts.taxesAmount.value,
|
taxes_amount_value: allAmounts.taxesAmount.value,
|
||||||
taxes_amount_scale: allAmounts.taxesAmount.scale,
|
taxes_amount_scale: allAmounts.taxesAmount.scale,
|
||||||
|
|
||||||
total_amount_value: allAmounts.totalAmount.value,
|
total_amount_value: allAmounts.totalAmount.value,
|
||||||
total_amount_scale: allAmounts.totalAmount.scale,
|
total_amount_scale: allAmounts.totalAmount.scale,
|
||||||
|
|
||||||
customer_id: source.customerId.toPrimitive(),
|
customer_id: source.customerId.toPrimitive(),
|
||||||
...recipient,
|
|
||||||
taxes: taxesResult.data,
|
taxes: taxesResult.data,
|
||||||
items: itemsResult.data,
|
items: itemsResult.data,
|
||||||
} satisfies ProformaCreationAttributes);
|
} satisfies ProformaCreationAttributes);
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import {
|
|||||||
type ValidationErrorDetail,
|
type ValidationErrorDetail,
|
||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
maybeFromNullableResult,
|
maybeFromNullableResult,
|
||||||
maybeToNullable,
|
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
@ -28,44 +27,49 @@ export class SequelizeProformaV2RecipientDomainMapper {
|
|||||||
parent: Partial<IProformaCreateProps>;
|
parent: Partial<IProformaCreateProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!source.customer_name) {
|
const _name = source.current_customer.name;
|
||||||
return Result.ok(Maybe.none());
|
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(
|
const customerTin = extractOrPushError(
|
||||||
maybeFromNullableResult(source.customer_tin, (value) => TINNumber.create(value)),
|
maybeFromNullableResult(_tin, (value) => TINNumber.create(value)),
|
||||||
"customer_tin",
|
"current_customer.tin",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const customerStreet = extractOrPushError(
|
const customerStreet = extractOrPushError(
|
||||||
maybeFromNullableResult(source.customer_street, (value) => Street.create(value)),
|
maybeFromNullableResult(_street, (value) => Street.create(value)),
|
||||||
"customer_street",
|
"current_customer.street",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const customerStreet2 = extractOrPushError(
|
const customerStreet2 = extractOrPushError(
|
||||||
maybeFromNullableResult(source.customer_street2, (value) => Street.create(value)),
|
maybeFromNullableResult(_street2, (value) => Street.create(value)),
|
||||||
"customer_street2",
|
"current_customer.street2",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const customerCity = extractOrPushError(
|
const customerCity = extractOrPushError(
|
||||||
maybeFromNullableResult(source.customer_city, (value) => City.create(value)),
|
maybeFromNullableResult(_city, (value) => City.create(value)),
|
||||||
"customer_city",
|
"current_customer.city",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const customerProvince = extractOrPushError(
|
const customerProvince = extractOrPushError(
|
||||||
maybeFromNullableResult(source.customer_province, (value) => Province.create(value)),
|
maybeFromNullableResult(_province, (value) => Province.create(value)),
|
||||||
"customer_province",
|
"current_customer.province",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const customerPostalCode = extractOrPushError(
|
const customerPostalCode = extractOrPushError(
|
||||||
maybeFromNullableResult(source.customer_postal_code, (value) => PostalCode.create(value)),
|
maybeFromNullableResult(_postal_code, (value) => PostalCode.create(value)),
|
||||||
"customer_postal_code",
|
"current_customer.postal_code",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const customerCountry = extractOrPushError(
|
const customerCountry = extractOrPushError(
|
||||||
maybeFromNullableResult(source.customer_country, (value) => Country.create(value)),
|
maybeFromNullableResult(_country, (value) => Country.create(value)),
|
||||||
"customer_country",
|
"current_customer.country",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -92,7 +96,8 @@ export class SequelizeProformaV2RecipientDomainMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mapToPersistence(source: Maybe<ProformaRecipient>) {
|
mapToPersistence(source: Maybe<ProformaRecipient>) {
|
||||||
if (source.isNone()) {
|
return Result.ok();
|
||||||
|
/*if (source.isNone()) {
|
||||||
return {
|
return {
|
||||||
customer_tin: null,
|
customer_tin: null,
|
||||||
customer_name: null,
|
customer_name: null,
|
||||||
@ -117,5 +122,6 @@ export class SequelizeProformaV2RecipientDomainMapper {
|
|||||||
customer_postal_code: maybeToNullable(recipient.postalCode, (value) => value.toPrimitive()),
|
customer_postal_code: maybeToNullable(recipient.postalCode, (value) => value.toPrimitive()),
|
||||||
customer_country: maybeToNullable(recipient.country, (value) => value.toPrimitive()),
|
customer_country: maybeToNullable(recipient.country, (value) => value.toPrimitive()),
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -203,6 +203,8 @@ export class SequelizeProformaRepositoryV2
|
|||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
options: FindOptions<InferAttributes<ProformaModel>> = {}
|
options: FindOptions<InferAttributes<ProformaModel>> = {}
|
||||||
): Promise<Result<Proforma, Error>> {
|
): Promise<Result<Proforma, Error>> {
|
||||||
|
const { CustomerModel } = this.database.models;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const normalizedOrder = Array.isArray(options.order)
|
const normalizedOrder = Array.isArray(options.order)
|
||||||
? options.order
|
? options.order
|
||||||
@ -225,6 +227,7 @@ export class SequelizeProformaRepositoryV2
|
|||||||
order: [...normalizedOrder, [{ model: ProformaItemModel, as: "items" }, "position", "ASC"]],
|
order: [...normalizedOrder, [{ model: ProformaItemModel, as: "items" }, "position", "ASC"]],
|
||||||
include: [
|
include: [
|
||||||
...normalizedInclude,
|
...normalizedInclude,
|
||||||
|
{ model: CustomerModel, as: "current_customer", required: false },
|
||||||
{ model: ProformaItemModel, as: "items", required: false },
|
{ model: ProformaItemModel, as: "items", required: false },
|
||||||
{ model: ProformaTaxModel, as: "taxes", required: false },
|
{ model: ProformaTaxModel, as: "taxes", required: false },
|
||||||
{ model: IssuedInvoiceModel, as: "linked_invoice", required: false, attributes: ["id"] },
|
{ model: IssuedInvoiceModel, as: "linked_invoice", required: false, attributes: ["id"] },
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import {
|
|||||||
export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
company_id: z.uuid(),
|
company_id: z.uuid(),
|
||||||
is_proforma: z.boolean(),
|
|
||||||
|
|
||||||
invoice_number: z.string(),
|
invoice_number: z.string(),
|
||||||
status: IssuedInvoiceStatusSchema,
|
status: IssuedInvoiceStatusSchema,
|
||||||
|
|||||||
@ -1,50 +1,9 @@
|
|||||||
import {
|
import { createPaginatedListSchema } from "@erp/core";
|
||||||
CurrencyCodeSchema,
|
import type { z } from "zod/v4";
|
||||||
IsoDateSchema,
|
|
||||||
LanguageCodeSchema,
|
|
||||||
MoneySchema,
|
|
||||||
createPaginatedListSchema,
|
|
||||||
} from "@erp/core";
|
|
||||||
import { z } from "zod/v4";
|
|
||||||
|
|
||||||
import {
|
import { IssuedInvoiceSummarySchema } from "../../shared";
|
||||||
IssuedInvoiceRecipientSummarySchema,
|
|
||||||
IssuedInvoiceStatusSchema,
|
|
||||||
VerifactuRecordSchema,
|
|
||||||
} from "../../shared";
|
|
||||||
|
|
||||||
export const ListIssuedInvoicesResponseSchema = createPaginatedListSchema(
|
export const ListIssuedInvoicesResponseSchema = createPaginatedListSchema(
|
||||||
z.object({
|
IssuedInvoiceSummarySchema
|
||||||
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(),
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ListIssuedInvoicesResponseDTO = z.infer<typeof ListIssuedInvoicesResponseSchema>;
|
export type ListIssuedInvoicesResponseDTO = z.infer<typeof ListIssuedInvoicesResponseSchema>;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./issued-invoice-item-detail.dto";
|
export * from "./issued-invoice-item-detail.dto";
|
||||||
export * from "./issued-invoice-recipient-summary.dto";
|
export * from "./issued-invoice-recipient-summary.dto";
|
||||||
export * from "./issued-invoice-status.dto";
|
export * from "./issued-invoice-status.dto";
|
||||||
|
export * from "./issued-invoice-summary.dto";
|
||||||
export * from "./verifactu-record.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 { useDebounce } from "@erp/core/hooks";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
|
||||||
@ -37,13 +38,15 @@ export const useIssuedInvoiceListController = () => {
|
|||||||
filters:
|
filters:
|
||||||
verifactuStatusFilter === "all"
|
verifactuStatusFilter === "all"
|
||||||
? []
|
? []
|
||||||
: [{ field: "verifactu.status", operator: "eq", value: verifactuStatusFilter }],
|
: [{ field: "verifactu.status", operator: "EQUALS", value: verifactuStatusFilter }],
|
||||||
}),
|
}),
|
||||||
[debouncedSearch, pageIndex, pageSize, verifactuStatusFilter]
|
[debouncedSearch, pageIndex, pageSize, verifactuStatusFilter]
|
||||||
);
|
);
|
||||||
|
|
||||||
const query = useIssuedInvoiceListQuery({ criteria });
|
const query = useIssuedInvoiceListQuery({ criteria });
|
||||||
|
|
||||||
|
const errorMessage = query.error ? getUserErrorMessage(query.error) : null;
|
||||||
|
|
||||||
const setStatusFilterValue = (value: string) => {
|
const setStatusFilterValue = (value: string) => {
|
||||||
const nextValue = (value || "all") as VerifactuRecordListStatusFilter;
|
const nextValue = (value || "all") as VerifactuRecordListStatusFilter;
|
||||||
|
|
||||||
@ -88,8 +91,8 @@ export const useIssuedInvoiceListController = () => {
|
|||||||
|
|
||||||
isError: query.isError,
|
isError: query.isError,
|
||||||
error: query.error,
|
error: query.error,
|
||||||
|
errorMessage,
|
||||||
refetch: query.refetch,
|
retry: query.refetch,
|
||||||
|
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ErrorAlert, PageHeader, SimpleSearchInput } from "@erp/core/components";
|
import { ErrorState, PageHeader, SimpleSearchInput } from "@erp/core/components";
|
||||||
import { AppContent, BackHistoryButton, LogoVerifactu } from "@repo/rdx-ui/components";
|
import { LogoVerifactu } from "@repo/rdx-ui/components";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
AlertDescription,
|
AlertDescription,
|
||||||
@ -44,8 +44,16 @@ export const ListIssuedInvoicesPage = () => {
|
|||||||
widthClass: "w-[500px]",
|
widthClass: "w-[500px]",
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
if (listCtrl.isError) {
|
if (listCtrl.isError && listCtrl.errorMessage) {
|
||||||
return (
|
return (
|
||||||
|
<ErrorState
|
||||||
|
description={listCtrl.errorMessage.description}
|
||||||
|
onRetry={listCtrl.retry}
|
||||||
|
reference={listCtrl.errorMessage.reference}
|
||||||
|
title={listCtrl.errorMessage.title}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
/*return (
|
||||||
<AppContent>
|
<AppContent>
|
||||||
<ErrorAlert
|
<ErrorAlert
|
||||||
message={(listCtrl.error as Error)?.message || "Error al cargar el listado"}
|
message={(listCtrl.error as Error)?.message || "Error al cargar el listado"}
|
||||||
@ -53,7 +61,7 @@ export const ListIssuedInvoicesPage = () => {
|
|||||||
/>
|
/>
|
||||||
<BackHistoryButton />
|
<BackHistoryButton />
|
||||||
</AppContent>
|
</AppContent>
|
||||||
);
|
);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 type { CriteriaDTO } from "@erp/core";
|
||||||
|
import { isApiClientError } from "@erp/core/client";
|
||||||
import { useDataSource } from "@erp/core/hooks";
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
import { type DefaultError, useQuery } from "@tanstack/react-query";
|
import { type DefaultError, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
@ -27,5 +28,21 @@ export const useIssuedInvoiceListQuery = (options?: IssuedInvoicesListQueryOptio
|
|||||||
},
|
},
|
||||||
enabled,
|
enabled,
|
||||||
placeholderData: (previousData) => previousData, // Mantiene la página anterior durante refetch por cambio de criteria
|
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");
|
throw new Error("proformaId is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(patch);
|
|
||||||
|
|
||||||
const data: UpdateProformaByIdParams["data"] = {};
|
const data: UpdateProformaByIdParams["data"] = {};
|
||||||
|
|
||||||
if (ObjectHelper.hasOwn(patch, "series")) {
|
if (ObjectHelper.hasOwn(patch, "series")) {
|
||||||
|
|||||||
@ -132,7 +132,6 @@ export class UpdateCustomerInputMapper implements IUpdateCustomerInputMapper {
|
|||||||
});
|
});
|
||||||
|
|
||||||
toPatchField(dto.tax_regime_code).ifSetOrNull((taxRegimeCode) => {
|
toPatchField(dto.tax_regime_code).ifSetOrNull((taxRegimeCode) => {
|
||||||
console.log("Hay taxRegimeCode", taxRegimeCode);
|
|
||||||
customerPatchProps.taxRegimeCode = extractOrPushError(
|
customerPatchProps.taxRegimeCode = extractOrPushError(
|
||||||
maybeFromNullableResult(taxRegimeCode, (value) => Result.ok(String(value))),
|
maybeFromNullableResult(taxRegimeCode, (value) => Result.ok(String(value))),
|
||||||
"tax_regime_code",
|
"tax_regime_code",
|
||||||
@ -162,9 +161,6 @@ export class UpdateCustomerInputMapper implements IUpdateCustomerInputMapper {
|
|||||||
|
|
||||||
this.throwIfValidationErrors(errors);
|
this.throwIfValidationErrors(errors);
|
||||||
|
|
||||||
console.log("dto => ", dto);
|
|
||||||
console.log("customerPatchProps => ", customerPatchProps);
|
|
||||||
|
|
||||||
return Result.ok(customerPatchProps);
|
return Result.ok(customerPatchProps);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
@ -68,8 +68,6 @@ export class CustomerRepository
|
|||||||
|
|
||||||
const { id, ...updatePayload } = dtoResult.data;
|
const { id, ...updatePayload } = dtoResult.data;
|
||||||
|
|
||||||
console.log(dtoResult.data);
|
|
||||||
|
|
||||||
const [affected] = await CustomerModel.update(updatePayload, {
|
const [affected] = await CustomerModel.update(updatePayload, {
|
||||||
where: { id /*, version */ },
|
where: { id /*, version */ },
|
||||||
transaction,
|
transaction,
|
||||||
|
|||||||
@ -99,9 +99,7 @@ export const useCreateCustomerController = (options?: UseCustomerCreateControlle
|
|||||||
}
|
}
|
||||||
options?.onCreated?.(created);
|
options?.onCreated?.(created);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.log(error);
|
|
||||||
const normalizedError = normalizeSubmitError(error);
|
const normalizedError = normalizeSubmitError(error);
|
||||||
console.debug(normalizedError);
|
|
||||||
|
|
||||||
// No revierto el form para que no se pierdan los cambios que
|
// No revierto el form para que no se pierdan los cambios que
|
||||||
// ha hecho el usuario y no han sido guardados.
|
// ha hecho el usuario y no han sido guardados.
|
||||||
|
|||||||
@ -617,6 +617,9 @@ importers:
|
|||||||
'@erp/catalogs':
|
'@erp/catalogs':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../catalogs
|
version: link:../catalogs
|
||||||
|
'@erp/companies':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../companies
|
||||||
'@erp/core':
|
'@erp/core':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core
|
version: link:../core
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user