Formas de pago y vencimientos
This commit is contained in:
parent
475ab9fec3
commit
66783e1383
@ -7,7 +7,7 @@
|
||||
"start": "NODE_ENV=production node --env-file=.env.production dist/index.js",
|
||||
"dev": "node --import=tsx --watch src/index.ts",
|
||||
"clean": "rimraf .turbo node_modules dist",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"check": "biome check .",
|
||||
"lint": "biome lint .",
|
||||
"format": "biome format --write"
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"version": "0.6.7",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"dev": "vite --host --clearScreen false",
|
||||
"build": "tsc && vite build",
|
||||
"build:rodax": "tsc && vite build --mode rodax",
|
||||
@ -24,13 +25,13 @@
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"typescript": "~6.0.2",
|
||||
"vite": "^8.0.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@erp/auth": "workspace:*",
|
||||
"@erp/core": "workspace:*",
|
||||
"@erp/catalogs": "workspace:*",
|
||||
"@erp/customer-invoices": "workspace:*",
|
||||
"@erp/customers": "workspace:*",
|
||||
"@fontsource-variable/geist": "^5.2.8",
|
||||
@ -41,6 +42,7 @@
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@tanstack/react-query": "^5.98.0",
|
||||
"axios": "^1.15.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dinero.js": "1.9.1",
|
||||
"lucide-react": "^1.8.0",
|
||||
"react": "^19.2.5",
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { AuthModuleManifiest } from "@erp/auth/client";
|
||||
import CoreModuleManifiest, { type IModuleClient } from "@erp/core/client";
|
||||
import { CustomerInvoicesModuleManifiest } from "@erp/customer-invoices/client";
|
||||
import { CustomersModuleManifiest } from "@erp/customers/client";
|
||||
import { AuthModuleManifest } from "@erp/auth/client";
|
||||
import { CatalogsModuleManifest } from "@erp/catalogs/client";
|
||||
import CoreModuleManifest, { type IModuleClient } from "@erp/core/client";
|
||||
import { CustomerInvoicesModuleManifest } from "@erp/customer-invoices/client";
|
||||
import { CustomersModuleManifest } from "@erp/customers/client";
|
||||
|
||||
export const modules: IModuleClient[] = [
|
||||
AuthModuleManifiest,
|
||||
CoreModuleManifiest,
|
||||
CustomersModuleManifiest,
|
||||
CustomerInvoicesModuleManifiest,
|
||||
AuthModuleManifest,
|
||||
CoreModuleManifest,
|
||||
CatalogsModuleManifest,
|
||||
CustomersModuleManifest,
|
||||
CustomerInvoicesModuleManifest,
|
||||
];
|
||||
|
||||
10
biome.json
10
biome.json
@ -10,11 +10,6 @@
|
||||
"ignoreUnknown": true,
|
||||
"includes": [
|
||||
"**",
|
||||
"!!**/supplier-invoices",
|
||||
"!!**/suppliers",
|
||||
"!!**/auth",
|
||||
"!!**/rdx-criteria",
|
||||
"!!**/shadcn-ui",
|
||||
"!!**/node_modules",
|
||||
"!!**/.next",
|
||||
"!!**/dist",
|
||||
@ -50,10 +45,7 @@
|
||||
"noInferrableTypes": "error",
|
||||
"noNamespace": "error",
|
||||
"noNegationElse": "warn",
|
||||
"noNonNullAssertion": {
|
||||
"level": "info",
|
||||
"fix": "none"
|
||||
},
|
||||
"noNonNullAssertion": "info",
|
||||
"noParameterAssign": "error",
|
||||
"noUnusedTemplateLiteral": "error",
|
||||
"noUselessElse": "warn",
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { NextFunction, Response } from "express";
|
||||
|
||||
import { logger } from "@erp/core/api";
|
||||
import type { NextFunction, Response } from "express";
|
||||
import passport from "passport";
|
||||
import { ExtractJwt, Strategy as JwtStrategy } from "passport-jwt";
|
||||
import { TabContext } from "../../../../../../apps/server/archive/contexts/auth/domain";
|
||||
import {
|
||||
|
||||
import type { TabContext } from "../../../../../../apps/server/archive/contexts/auth/domain";
|
||||
import type {
|
||||
IAuthService,
|
||||
ITabContextService,
|
||||
} from "../../../../../../apps/server/archive/contexts/auth/domain/services";
|
||||
import { TabContextRequest } from "../../../../../../apps/server/archive/contexts/auth/infraestructure/express/types";
|
||||
import type { TabContextRequest } from "../../../../../../apps/server/archive/contexts/auth/infraestructure/express/types";
|
||||
|
||||
const SECRET_KEY = process.env.JWT_SECRET || "supersecretkey";
|
||||
|
||||
@ -48,7 +49,7 @@ export class PassportAuthProvider {
|
||||
const checkUserId = user.id.equals(userIdVO.data);
|
||||
const checkRoles = true; //user.hasRoles(roles);
|
||||
|
||||
if (!checkUserId || !checkRoles) {
|
||||
if (!(checkUserId && checkRoles)) {
|
||||
return Result.fail(new Error("Invalid token data"));
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import { AuthRoutes } from "./auth-routes";
|
||||
const MODULE_NAME = "auth";
|
||||
const MODULE_VERSION = "1.0.0";
|
||||
|
||||
export const AuthModuleManifiest: IModuleClient = {
|
||||
export const AuthModuleManifest: IModuleClient = {
|
||||
name: MODULE_NAME,
|
||||
version: MODULE_VERSION,
|
||||
dependencies: ["core"],
|
||||
@ -22,4 +22,4 @@ export const AuthModuleManifiest: IModuleClient = {
|
||||
},
|
||||
};
|
||||
|
||||
export default AuthModuleManifiest;
|
||||
export default AuthModuleManifest;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@erp/catalogs",
|
||||
"description": "Catalogs module",
|
||||
"version": "0.1.0",
|
||||
"version": "0.6.7",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
@ -12,21 +12,45 @@
|
||||
"clean": "rimraf .turbo node_modules dist"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/api/index.ts",
|
||||
"./api": "./src/api/index.ts"
|
||||
".": "./src/common/index.ts",
|
||||
"./common": "./src/common/index.ts",
|
||||
"./api": "./src/api/index.ts",
|
||||
"./client": "./src/web/manifest.ts",
|
||||
"./client/payment-methods": "./src/web/payment-methods/index.ts",
|
||||
"./client/payment-terms": "./src/web/payment-terms/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@erp/core": "workspace:*",
|
||||
"@erp/auth": "workspace:*",
|
||||
"@repo/rdx-ddd": "workspace:*",
|
||||
"@repo/rdx-utils": "workspace:*",
|
||||
"@repo/rdx-criteria": "workspace:*",
|
||||
"express": "^4.22.1",
|
||||
"sequelize": "^6.37.8",
|
||||
"zod": "^4.3.6"
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@erp/auth": "workspace:*",
|
||||
"@erp/core": "workspace:*",
|
||||
"@repo/i18next": "workspace:*",
|
||||
"@repo/rdx-criteria": "workspace:*",
|
||||
"@repo/rdx-ddd": "workspace:*",
|
||||
"@repo/rdx-logger": "workspace:*",
|
||||
"@repo/rdx-ui": "workspace:*",
|
||||
"@repo/rdx-utils": "workspace:*",
|
||||
"@repo/shadcn-ui": "workspace:*",
|
||||
"@tanstack/react-query": "^5.98.0",
|
||||
"express": "^4.22.1",
|
||||
"lucide-react": "^1.8.0",
|
||||
"react-hook-form": "^7.72.1",
|
||||
"react-i18next": "^17.0.2",
|
||||
"react-router-dom": "^7.14.0",
|
||||
"sequelize": "^6.37.8",
|
||||
"use-debounce": "^10.1.1",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
export * from "./errors";
|
||||
export * from "./payment-method.aggregate";
|
||||
export * from "./payment-method-name";
|
||||
export * from "./payment-method-type";
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import { InvalidPaymentMethodTypeError } from "./errors";
|
||||
|
||||
export const PAYMENT_METHOD_TYPES = [
|
||||
"cash",
|
||||
"bank_transfer",
|
||||
"card",
|
||||
"direct_debit",
|
||||
"other",
|
||||
] as const;
|
||||
|
||||
export type PaymentMethodTypeValue = (typeof PAYMENT_METHOD_TYPES)[number];
|
||||
|
||||
export class PaymentMethodType {
|
||||
private constructor(private readonly value: PaymentMethodTypeValue) {}
|
||||
|
||||
public static create(type: string): Result<PaymentMethodType, Error> {
|
||||
const normalized = String(type).trim() as PaymentMethodTypeValue;
|
||||
|
||||
if (!PAYMENT_METHOD_TYPES.includes(normalized)) {
|
||||
return Result.fail(
|
||||
new InvalidPaymentMethodTypeError(
|
||||
`Payment method type must be one of: ${PAYMENT_METHOD_TYPES.join(", ")}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(new PaymentMethodType(normalized));
|
||||
}
|
||||
|
||||
public static fromPersistence(type: PaymentMethodTypeValue): PaymentMethodType {
|
||||
return new PaymentMethodType(type);
|
||||
}
|
||||
|
||||
public toString(): PaymentMethodTypeValue {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public toPrimitive(): PaymentMethodTypeValue {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
@ -214,15 +214,4 @@ export class PaymentTerm extends AggregateRoot<PaymentTermInternalProps> {
|
||||
this.props.isActive = true;
|
||||
return Result.ok(true);
|
||||
}
|
||||
|
||||
private static sameDescription(current: Maybe<TextValue>, next: Maybe<TextValue>): boolean {
|
||||
return current.match(
|
||||
(currentValue: TextValue) =>
|
||||
next.match(
|
||||
(nextValue: TextValue) => currentValue.toPrimitive() === nextValue.toPrimitive(),
|
||||
() => false
|
||||
),
|
||||
() => next.isNone()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { PaymentMethodSummary } from "../../../../../application/payment-methods/models";
|
||||
import type { PaymentMethodSummary } from "../../../../../application";
|
||||
import type { PaymentMethodModel } from "../models";
|
||||
|
||||
export class SequelizePaymentMethodSummaryMapper extends SequelizeQueryMapper<
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { z } from "zod/v4";
|
||||
import { createPaginatedListSchema } from "@erp/core";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { PaymentMethodSummarySchema } from "../shared";
|
||||
|
||||
export const ListPaymentMethodsResponseSchema = z.array(PaymentMethodSummarySchema);
|
||||
export const ListPaymentMethodsResponseSchema = createPaginatedListSchema(
|
||||
PaymentMethodSummarySchema
|
||||
);
|
||||
export type ListPaymentMethodsResponseDTO = z.infer<typeof ListPaymentMethodsResponseSchema>;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { z } from "zod/v4";
|
||||
import { createPaginatedListSchema } from "@erp/core";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { PaymentTermSummarySchema } from "../shared/payment-term-summary.dto";
|
||||
|
||||
export const ListPaymentTermsResponseSchema = z.array(PaymentTermSummarySchema);
|
||||
export const ListPaymentTermsResponseSchema = createPaginatedListSchema(PaymentTermSummarySchema);
|
||||
export type ListPaymentTermsResponseDTO = z.infer<typeof ListPaymentTermsResponseSchema>;
|
||||
|
||||
6
modules/catalogs/src/web/index.ts
Normal file
6
modules/catalogs/src/web/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export {
|
||||
CatalogsModuleManifest,
|
||||
default,
|
||||
} from "./manifest";
|
||||
export * from "./payment-methods";
|
||||
export * from "./payment-terms";
|
||||
18
modules/catalogs/src/web/manifest.ts
Normal file
18
modules/catalogs/src/web/manifest.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { IModuleClient, ModuleClientParams } from "@erp/core/client";
|
||||
|
||||
export const MODULE_NAME = "Catalogs";
|
||||
const MODULE_VERSION = "1.0.0";
|
||||
|
||||
export const CatalogsModuleManifest: IModuleClient = {
|
||||
name: MODULE_NAME,
|
||||
version: MODULE_VERSION,
|
||||
dependencies: ["auth", "Core"],
|
||||
protected: true,
|
||||
layout: "app",
|
||||
|
||||
routes: (params: ModuleClientParams) => {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
export default CatalogsModuleManifest;
|
||||
2
modules/catalogs/src/web/payment-methods/index.ts
Normal file
2
modules/catalogs/src/web/payment-methods/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./shared";
|
||||
export * from "./utils";
|
||||
@ -0,0 +1 @@
|
||||
export * from './list-payment-methods.adapter';
|
||||
@ -0,0 +1,53 @@
|
||||
import type { ListPaymentMethodsResponseDTO } from "../../../../common";
|
||||
import type { ListPaymentMethodsResult } from "../api";
|
||||
import type { PaymentMethodList, PaymentMethodListRow } from "../entities";
|
||||
|
||||
/**
|
||||
* Adaptador para transformar los datos de la API de ListPaymentMethodsResult
|
||||
* a la entidad PaymentMethodList utilizada en la aplicación.
|
||||
* Reglas de adaptación:
|
||||
* - page, per_page, total_pages, total_items se asignan directamente.
|
||||
* - items se transforma utilizando PaymentMethodListRowAdapter para cada elemento.
|
||||
*
|
||||
* @param pageDto - lista de proformas desde la API.
|
||||
* @param context - Contexto adicional opcional para la adaptación.
|
||||
* @returns {PaymentMethodList} Objeto adaptado a PaymentMehodList.
|
||||
*/
|
||||
|
||||
export const ListPaymentMethodsAdapter = {
|
||||
fromDto(dto: ListPaymentMethodsResult, context?: unknown): PaymentMethodList {
|
||||
return {
|
||||
page: dto.page,
|
||||
perPage: dto.per_page,
|
||||
totalPages: dto.total_pages,
|
||||
totalItems: dto.total_items,
|
||||
items: dto.items.map((rowDto) => PaymentMehodListRowAdapter.fromDto(rowDto, context)),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Adaptador para transformar los items de la API de ListPaymentMehodsResult a la entidad PaymentMehodListRow.
|
||||
* Reglas de adaptación:
|
||||
* - id, company_id se asignan directamente.
|
||||
*
|
||||
* @param rowDto - item de proforma desde la API.
|
||||
* @param context - Contexto adicional opcional para la adaptación.
|
||||
* @returns {PaymentMethodListRow} Objeto adaptado a PaymentMehodListRow.
|
||||
*/
|
||||
|
||||
type ListPaymentMethodsItemOutput = ListPaymentMethodsResponseDTO["items"][number];
|
||||
|
||||
const PaymentMehodListRowAdapter = {
|
||||
fromDto(dto: ListPaymentMethodsItemOutput, context?: unknown): PaymentMethodListRow {
|
||||
return {
|
||||
id: dto.id,
|
||||
companyId: dto.company_id,
|
||||
|
||||
name: dto.name,
|
||||
|
||||
isSystem: dto.is_system,
|
||||
isActive: dto.is_active,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./list-payment-methods-by-criteria.api";
|
||||
@ -0,0 +1,38 @@
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
import type { ListPaymentMethodsResponseDTO } from "../../../../common";
|
||||
|
||||
/**
|
||||
* Recupera una lista de métodos de pago del sistema utilizando la
|
||||
* fuente de datos proporcionada y los criterios de búsqueda especificados.
|
||||
*
|
||||
* @param dataSource - La fuente de datos para interactuar con la API.
|
||||
* @param params - Los parámetros necesarios para listar los métodos de pago, incluyendo los criterios de búsqueda.
|
||||
* @returns Una promesa que resuelve con una lista de métodos de pago que cumplen con los criterios especificados.
|
||||
* @throws Error si la recuperación de la lista de métodos de pago falla.
|
||||
*/
|
||||
|
||||
export type ListPaymentMethodsByCriteriaParams = {
|
||||
criteria?: CriteriaDTO;
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
|
||||
export type ListPaymentMethodsResult = ListPaymentMethodsResponseDTO;
|
||||
|
||||
export function getListPaymentMethodsByCriteria(
|
||||
dataSource: IDataSource,
|
||||
params: ListPaymentMethodsByCriteriaParams
|
||||
): Promise<ListPaymentMethodsResult> {
|
||||
const { criteria, signal } = params || {
|
||||
criteria: {
|
||||
page: 1,
|
||||
per_page: 9999,
|
||||
},
|
||||
signal: undefined,
|
||||
};
|
||||
return dataSource.getList<ListPaymentMethodsResponseDTO>("catalogs/payment-methods", {
|
||||
signal,
|
||||
...criteria,
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
export * from "./payment-method.entity";
|
||||
export * from "./payment-method-list.entity";
|
||||
export * from "./payment-method-list-row.entity";
|
||||
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Interface que representa una fila de la lista de
|
||||
* formas de pago en el sistema, adaptada desde la respuesta de la API.
|
||||
* Contiene los campos justos para mostrar
|
||||
* la información básica de cada forma de pago en la lista.
|
||||
*/
|
||||
|
||||
export interface PaymentMethodListRow {
|
||||
id: string;
|
||||
companyId: string;
|
||||
|
||||
name: string;
|
||||
|
||||
isSystem: boolean;
|
||||
isActive: boolean;
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import type { PaymentMethodListRow } from "./payment-method-list-row.entity";
|
||||
|
||||
/**
|
||||
* Interface que representa la respuesta paginada de una lista de formas de pago,
|
||||
* adaptada desde la respuesta de la API.
|
||||
*/
|
||||
|
||||
export interface PaymentMethodList {
|
||||
items: PaymentMethodListRow[];
|
||||
totalPages: number;
|
||||
totalItems: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
export interface PaymentMethod {
|
||||
id: string;
|
||||
companyId: string;
|
||||
|
||||
name: string;
|
||||
description: string | null;
|
||||
|
||||
isSystem: boolean;
|
||||
isActive: boolean;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./use-payment-method-list-query";
|
||||
@ -0,0 +1,44 @@
|
||||
import type { QueryKey } from "@tanstack/react-query";
|
||||
|
||||
import type { ListPaymentMethodsRequestDTO } from "../../../../common";
|
||||
|
||||
/**
|
||||
* Prefijo base para listados
|
||||
*/
|
||||
export const LIST_PAYMENT_METHODS_QUERY_KEY_PREFIX = ["payment-methods"] as const;
|
||||
|
||||
/**
|
||||
* Query key para listado de payment methods
|
||||
*/
|
||||
export const LIST_PAYMENT_METHODS_QUERY_KEY = (criteria?: ListPaymentMethodsRequestDTO): QueryKey =>
|
||||
[
|
||||
...LIST_PAYMENT_METHODS_QUERY_KEY_PREFIX,
|
||||
{
|
||||
pageNumber: criteria?.pageNumber ?? 1,
|
||||
pageSize: criteria?.pageSize ?? 5,
|
||||
q: criteria?.q ?? "",
|
||||
filters: criteria?.filters ?? [],
|
||||
orderBy: criteria?.orderBy ?? "",
|
||||
order: criteria?.order ?? "",
|
||||
},
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Query key para detalle de payment method
|
||||
*/
|
||||
export const PAYMENT_METHODS_DETAIL_QUERY_KEY_PREFIX = ["payment-methods:detail"] as const;
|
||||
export const PAYMENT_METHOD_QUERY_KEY = (paymentMethodId?: string): QueryKey => [
|
||||
...PAYMENT_METHODS_DETAIL_QUERY_KEY_PREFIX,
|
||||
{ paymentMethodId },
|
||||
];
|
||||
|
||||
/**
|
||||
* Keys para mutaciones
|
||||
*/
|
||||
export const CREATE_PAYMENT_METHOD_MUTATION_KEY = ["payment-methods:create"] as const;
|
||||
export const UPDATE_PAYMENT_METHOD_MUTATION_KEY = ["payment-methods:update"] as const;
|
||||
export const DELETE_PAYMENT_METHOD_MUTATION_KEY = ["payment-methods:delete"] as const;
|
||||
|
||||
/**
|
||||
* Operaciones de dominio
|
||||
*/
|
||||
@ -0,0 +1,32 @@
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ListPaymentMethodsAdapter } from "../adapters";
|
||||
import { getListPaymentMethodsByCriteria } from "../api";
|
||||
import type { PaymentMethodList } from "../entities";
|
||||
|
||||
import { LIST_PAYMENT_METHODS_QUERY_KEY } from "./keys";
|
||||
|
||||
export interface PaymentMethodsListQueryOptions {
|
||||
enabled?: boolean;
|
||||
criteria?: Partial<CriteriaDTO>;
|
||||
}
|
||||
|
||||
export const usePaymentMethodsListQuery = (
|
||||
options?: PaymentMethodsListQueryOptions
|
||||
): UseQueryResult<PaymentMethodList, DefaultError> => {
|
||||
const dataSource = useDataSource();
|
||||
const enabled = options?.enabled ?? true;
|
||||
const criteria = options?.criteria ?? {};
|
||||
|
||||
return useQuery<PaymentMethodList, DefaultError>({
|
||||
queryKey: LIST_PAYMENT_METHODS_QUERY_KEY(criteria),
|
||||
queryFn: async ({ signal }) => {
|
||||
const dto = await getListPaymentMethodsByCriteria(dataSource, { signal, criteria });
|
||||
return ListPaymentMethodsAdapter.fromDto(dto);
|
||||
},
|
||||
enabled,
|
||||
placeholderData: (previousData) => previousData, // Mantiene la página anterior durante refetch por cambio de criteria
|
||||
});
|
||||
};
|
||||
2
modules/catalogs/src/web/payment-methods/shared/index.ts
Normal file
2
modules/catalogs/src/web/payment-methods/shared/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./entities";
|
||||
export * from "./hooks";
|
||||
1
modules/catalogs/src/web/payment-methods/utils/index.ts
Normal file
1
modules/catalogs/src/web/payment-methods/utils/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './payment-method-options.utils';
|
||||
@ -0,0 +1,12 @@
|
||||
import type { SelectFieldItem } from "@repo/rdx-ui/components";
|
||||
|
||||
import type { PaymentMethodListRow } from "../shared";
|
||||
|
||||
export const getPaymentMethodOptions = (
|
||||
paymentMethods: PaymentMethodListRow[]
|
||||
): SelectFieldItem[] => {
|
||||
return paymentMethods.map((paymentMethod) => ({
|
||||
value: paymentMethod.id,
|
||||
label: paymentMethod.name,
|
||||
}));
|
||||
};
|
||||
2
modules/catalogs/src/web/payment-terms/index.ts
Normal file
2
modules/catalogs/src/web/payment-terms/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./shared";
|
||||
export * from "./utils";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./list-payment-terms.adapter";
|
||||
@ -0,0 +1,68 @@
|
||||
import type { ListPaymentTermsResponseDTO } from "../../../../common";
|
||||
import type { ListPaymentTermsResult } from "../api";
|
||||
import type { PaymentTermList, PaymentTermListRow } from "../entities";
|
||||
|
||||
/**
|
||||
* Adaptador para transformar los datos de la API de ListPaymentTermsResult
|
||||
* a la entidad PaymentTermList utilizada en la aplicación.
|
||||
* Reglas de adaptación:
|
||||
* - page, per_page, total_pages, total_items se asignan directamente.
|
||||
* - items se transforma utilizando PaymentTermListRowAdapter para cada elemento.
|
||||
*
|
||||
* @param pageDto - lista de proformas desde la API.
|
||||
* @param context - Contexto adicional opcional para la adaptación.
|
||||
* @returns {PaymentTermList} Objeto adaptado a PaymentMehodList.
|
||||
*/
|
||||
|
||||
export const ListPaymentTermsAdapter = {
|
||||
fromDto(dto: ListPaymentTermsResult, _context?: unknown): PaymentTermList {
|
||||
return {
|
||||
page: dto.page,
|
||||
perPage: dto.per_page,
|
||||
totalPages: dto.total_pages,
|
||||
totalItems: dto.total_items,
|
||||
items: dto.items.map((rowDto) => PaymentTermListRowAdapter.fromDto(rowDto, _context)),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Adaptador para transformar los items de la API de ListPaymentTermsResult a la entidad PaymentTermListRow.
|
||||
* Reglas de adaptación:
|
||||
* - id, company_id, se asignan directamente.
|
||||
*
|
||||
* @param rowDto - item de proforma desde la API.
|
||||
* @param context - Contexto adicional opcional para la adaptación.
|
||||
* @returns {PaymentTermListRow} Objeto adaptado a PaymentMehodListRow.
|
||||
*/
|
||||
|
||||
type ListPaymentTermsItemOutput = ListPaymentTermsResponseDTO["items"][number];
|
||||
|
||||
const PaymentTermListRowAdapter = {
|
||||
fromDto(dto: ListPaymentTermsItemOutput, _context?: unknown): PaymentTermListRow {
|
||||
return {
|
||||
id: dto.id,
|
||||
companyId: dto.company_id,
|
||||
name: dto.name,
|
||||
isSystem: dto.is_system,
|
||||
isActive: dto.is_active,
|
||||
//dueRules: dto.due_rules.map(PaymentTermDueRuleAdapter.fromDto),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/** */
|
||||
/*
|
||||
const PaymentTermDueRuleAdapter = {
|
||||
fromDto(dto: PaymentTermDueRuleDTO): PaymentTermDueRule {
|
||||
return {
|
||||
dueDays: Number(dto.due_days),
|
||||
percentage: percentageDtoToNumber(dto.percentage),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const percentageDtoToNumber = (dto: { value: string; scale: string }): number => {
|
||||
return Number(dto.value) / 10 ** Number(dto.scale);
|
||||
};
|
||||
*/
|
||||
@ -0,0 +1 @@
|
||||
export * from "./list-payment-terms-by-criteria.api";
|
||||
@ -0,0 +1,38 @@
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
import type { ListPaymentTermsResponseDTO } from "../../../../common";
|
||||
|
||||
/**
|
||||
* Recupera una lista de vencimientos del sistema utilizando la
|
||||
* fuente de datos proporcionada y los criterios de búsqueda especificados.
|
||||
*
|
||||
* @param dataSource - La fuente de datos para interactuar con la API.
|
||||
* @param params - Los parámetros necesarios para listar los vencimientos, incluyendo los criterios de búsqueda.
|
||||
* @returns Una promesa que resuelve con una lista de vencimientos que cumplen con los criterios especificados.
|
||||
* @throws Error si la recuperación de la lista de vencimientos falla.
|
||||
*/
|
||||
|
||||
export type ListPaymentTermsByCriteriaParams = {
|
||||
criteria?: CriteriaDTO;
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
|
||||
export type ListPaymentTermsResult = ListPaymentTermsResponseDTO;
|
||||
|
||||
export function getListPaymentTermsByCriteria(
|
||||
dataSource: IDataSource,
|
||||
params: ListPaymentTermsByCriteriaParams
|
||||
): Promise<ListPaymentTermsResult> {
|
||||
const { criteria, signal } = params || {
|
||||
criteria: {
|
||||
page: 1,
|
||||
per_page: 9999,
|
||||
},
|
||||
signal: undefined,
|
||||
};
|
||||
return dataSource.getList<ListPaymentTermsResponseDTO>("catalogs/payment-terms", {
|
||||
signal,
|
||||
...criteria,
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
export * from "./payment-term.entity";
|
||||
export * from "./payment-term-list.entity";
|
||||
export * from "./payment-term-list-row.entity";
|
||||
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Interface que representa una fila de la lista de
|
||||
* vencimientos en el sistema, adaptada desde la respuesta de la API.
|
||||
* Contiene los campos justos para mostrar
|
||||
* la información básica de cada vencimiento en la lista.
|
||||
*/
|
||||
|
||||
export interface PaymentTermListRow {
|
||||
id: string;
|
||||
companyId: string;
|
||||
|
||||
name: string;
|
||||
|
||||
isSystem: boolean;
|
||||
isActive: boolean;
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import type { PaymentTermListRow } from "./payment-term-list-row.entity";
|
||||
|
||||
/**
|
||||
* Interface que representa la respuesta paginada de una lista de vencimientos,
|
||||
* adaptada desde la respuesta de la API.
|
||||
*/
|
||||
|
||||
export interface PaymentTermList {
|
||||
items: PaymentTermListRow[];
|
||||
totalPages: number;
|
||||
totalItems: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
export interface PaymentTerm {
|
||||
id: string;
|
||||
companyId: string;
|
||||
|
||||
name: string;
|
||||
description: string | null;
|
||||
|
||||
isSystem: boolean;
|
||||
isActive: boolean;
|
||||
dueRules: PaymentTermDueRule[];
|
||||
}
|
||||
|
||||
export interface PaymentTermDueRule {
|
||||
dueDays: number;
|
||||
percentage: number;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./use-payment-term-list-query";
|
||||
44
modules/catalogs/src/web/payment-terms/shared/hooks/keys.ts
Normal file
44
modules/catalogs/src/web/payment-terms/shared/hooks/keys.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { QueryKey } from "@tanstack/react-query";
|
||||
|
||||
import type { ListPaymentTermsRequestDTO } from "../../../../common";
|
||||
|
||||
/**
|
||||
* Prefijo base para listados
|
||||
*/
|
||||
export const LIST_PAYMENT_TERMS_QUERY_KEY_PREFIX = ["payment-terms"] as const;
|
||||
|
||||
/**
|
||||
* Query key para listado de payment terms
|
||||
*/
|
||||
export const LIST_PAYMENT_TERMS_QUERY_KEY = (criteria?: ListPaymentTermsRequestDTO): QueryKey =>
|
||||
[
|
||||
...LIST_PAYMENT_TERMS_QUERY_KEY_PREFIX,
|
||||
{
|
||||
pageNumber: criteria?.pageNumber ?? 1,
|
||||
pageSize: criteria?.pageSize ?? 5,
|
||||
q: criteria?.q ?? "",
|
||||
filters: criteria?.filters ?? [],
|
||||
orderBy: criteria?.orderBy ?? "",
|
||||
order: criteria?.order ?? "",
|
||||
},
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Query key para detalle de payment term
|
||||
*/
|
||||
export const PAYMENT_TERMS_DETAIL_QUERY_KEY_PREFIX = ["payment-terms:detail"] as const;
|
||||
export const PAYMENT_TERM_QUERY_KEY = (paymentTermId?: string): QueryKey => [
|
||||
...PAYMENT_TERMS_DETAIL_QUERY_KEY_PREFIX,
|
||||
{ paymentTermId },
|
||||
];
|
||||
|
||||
/**
|
||||
* Keys para mutaciones
|
||||
*/
|
||||
export const CREATE_PAYMENT_TERM_MUTATION_KEY = ["payment-terms:create"] as const;
|
||||
export const UPDATE_PAYMENT_TERM_MUTATION_KEY = ["payment-terms:update"] as const;
|
||||
export const DELETE_PAYMENT_TERM_MUTATION_KEY = ["payment-terms:delete"] as const;
|
||||
|
||||
/**
|
||||
* Operaciones de dominio
|
||||
*/
|
||||
@ -0,0 +1,32 @@
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ListPaymentTermsAdapter } from "../adapters";
|
||||
import { getListPaymentTermsByCriteria } from "../api";
|
||||
import type { PaymentTermList } from "../entities";
|
||||
|
||||
import { LIST_PAYMENT_TERMS_QUERY_KEY } from "./keys";
|
||||
|
||||
export interface PaymentTermsListQueryOptions {
|
||||
enabled?: boolean;
|
||||
criteria?: Partial<CriteriaDTO>;
|
||||
}
|
||||
|
||||
export const usePaymentTermsListQuery = (
|
||||
options?: PaymentTermsListQueryOptions
|
||||
): UseQueryResult<PaymentTermList, DefaultError> => {
|
||||
const dataSource = useDataSource();
|
||||
const enabled = options?.enabled ?? true;
|
||||
const criteria = options?.criteria ?? {};
|
||||
|
||||
return useQuery<PaymentTermList, DefaultError>({
|
||||
queryKey: LIST_PAYMENT_TERMS_QUERY_KEY(criteria),
|
||||
queryFn: async ({ signal }) => {
|
||||
const dto = await getListPaymentTermsByCriteria(dataSource, { signal, criteria });
|
||||
return ListPaymentTermsAdapter.fromDto(dto);
|
||||
},
|
||||
enabled,
|
||||
placeholderData: (previousData) => previousData, // Mantiene la página anterior durante refetch por cambio de criteria
|
||||
});
|
||||
};
|
||||
2
modules/catalogs/src/web/payment-terms/shared/index.ts
Normal file
2
modules/catalogs/src/web/payment-terms/shared/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./entities";
|
||||
export * from "./hooks";
|
||||
1
modules/catalogs/src/web/payment-terms/utils/index.ts
Normal file
1
modules/catalogs/src/web/payment-terms/utils/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './payment-term-options.utils';
|
||||
@ -0,0 +1,10 @@
|
||||
import type { SelectFieldItem } from "@repo/rdx-ui/components";
|
||||
|
||||
import type { PaymentTermListRow } from "../shared/entities";
|
||||
|
||||
export const getPaymentTermOptions = (paymentTerms: PaymentTermListRow[]): SelectFieldItem[] => {
|
||||
return paymentTerms.map((paymentTerm) => ({
|
||||
value: paymentTerm.id,
|
||||
label: paymentTerm.name,
|
||||
}));
|
||||
};
|
||||
@ -10,11 +10,16 @@
|
||||
"lib": ["ES2022"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
"@types/react": "^19.2.14",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -3,7 +3,7 @@ import type { IModuleClient, ModuleClientParams } from "./lib";
|
||||
export const MODULE_NAME = "Core";
|
||||
const MODULE_VERSION = "1.0.0";
|
||||
|
||||
export const CoreModuleManifiest: IModuleClient = {
|
||||
export const CoreModuleManifest: IModuleClient = {
|
||||
name: MODULE_NAME,
|
||||
version: MODULE_VERSION,
|
||||
dependencies: [],
|
||||
@ -15,6 +15,6 @@ export const CoreModuleManifiest: IModuleClient = {
|
||||
},
|
||||
};
|
||||
|
||||
export default CoreModuleManifiest;
|
||||
export default CoreModuleManifest;
|
||||
|
||||
export * from "./lib";
|
||||
|
||||
@ -29,8 +29,10 @@
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"date-fns": "^4.1.0",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -39,6 +41,7 @@
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@erp/auth": "workspace:*",
|
||||
"@erp/core": "workspace:*",
|
||||
"@erp/catalogs": "workspace:*",
|
||||
"@erp/customers": "workspace:*",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@lglab/react-qr-code": "^1.4.10",
|
||||
@ -52,7 +55,6 @@
|
||||
"@tanstack/react-query": "^5.98.0",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"dinero.js": "1.9.1",
|
||||
"express": "^4.22.1",
|
||||
"libphonenumber-js": "^1.12.41",
|
||||
|
||||
@ -46,6 +46,7 @@ export const CreateProformaRequestSchema = z.object({
|
||||
global_discount_percentage: PercentageSchema,
|
||||
|
||||
payment_method_id: z.uuid().nullable().optional(),
|
||||
payment_term_id: z.uuid().nullable().optional(),
|
||||
|
||||
items: z.array(CreateProformaItemRequestSchema),
|
||||
});
|
||||
|
||||
@ -52,6 +52,7 @@ export const UpdateProformaByIdRequestSchema = z.object({
|
||||
global_discount_percentage: PercentageSchema.optional(),
|
||||
|
||||
payment_method_id: z.uuid().nullable().optional(),
|
||||
payment_term_id: z.uuid().nullable().optional(),
|
||||
|
||||
// retención como código??? retencion_15
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
} from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { PaymentMethodRefSchema, TaxesBreakdownSchema } from "../../shared";
|
||||
import { PaymentMethodRefSchema, PaymentTermRefSchema, TaxesBreakdownSchema } from "../../shared";
|
||||
import {
|
||||
ProformaItemDetailSchema,
|
||||
ProformaRecipientSummarySchema,
|
||||
@ -41,6 +41,7 @@ export const GetProformaByIdResponseSchema = z.object({
|
||||
taxes: z.array(TaxesBreakdownSchema),
|
||||
|
||||
payment_method: PaymentMethodRefSchema.nullable(),
|
||||
payment_term: PaymentTermRefSchema.nullable(),
|
||||
|
||||
subtotal_amount: MoneySchema,
|
||||
items_discount_amount: MoneySchema,
|
||||
|
||||
@ -4,5 +4,4 @@ import type { z } from "zod/v4";
|
||||
import { ProformaSummarySchema } from "../../shared/proforma";
|
||||
|
||||
export const ListProformasResponseSchema = createPaginatedListSchema(ProformaSummarySchema);
|
||||
|
||||
export type ListProformasResponseDTO = z.infer<typeof ListProformasResponseSchema>;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
export * from "./issued-invoices";
|
||||
export * from "./item-position.dto";
|
||||
export * from "./payment-method-ref.dto";
|
||||
export * from "./payment-term-ref.dto";
|
||||
export * from "./proforma";
|
||||
export * from "./tax-combination-code.dto";
|
||||
export * from "./taxes-breakdown.dto";
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const PaymentTermRefSchema = z.object({
|
||||
id: z.uuid(),
|
||||
description: z.string(),
|
||||
});
|
||||
|
||||
export type PaymentTermRefDTO = z.infer<typeof PaymentTermRefSchema>;
|
||||
@ -5,10 +5,10 @@ import { CustomerInvoiceRoutes } from "./customer-invoice-routes";
|
||||
export const MODULE_NAME = "CustomerInvoices";
|
||||
const MODULE_VERSION = "1.0.0";
|
||||
|
||||
export const CustomerInvoicesModuleManifiest: IModuleClient = {
|
||||
export const CustomerInvoicesModuleManifest: IModuleClient = {
|
||||
name: MODULE_NAME,
|
||||
version: MODULE_VERSION,
|
||||
dependencies: ["auth", "Core", "Customers"],
|
||||
dependencies: ["auth", "Core", "Catalogs", "Customers"],
|
||||
protected: true,
|
||||
layout: "app",
|
||||
|
||||
@ -17,4 +17,4 @@ export const CustomerInvoicesModuleManifiest: IModuleClient = {
|
||||
},
|
||||
};
|
||||
|
||||
export default CustomerInvoicesModuleManifiest;
|
||||
export default CustomerInvoicesModuleManifest;
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
export interface CalculateProformaDueDatesParams {
|
||||
issueDate: string | null;
|
||||
total: number;
|
||||
dueRules: PaymentTermDueRule[];
|
||||
}
|
||||
|
||||
export interface ProformaDueDatePreview {
|
||||
dueDate: string | null;
|
||||
dueDays: number;
|
||||
percentage: number;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export const calculateProformaDueDates = ({
|
||||
issueDate,
|
||||
total,
|
||||
dueRules,
|
||||
}: CalculateProformaDueDatesParams): ProformaDueDatePreview[] => {
|
||||
return dueRules.map((rule) => {
|
||||
return {
|
||||
dueDays: rule.dueDays,
|
||||
percentage: rule.percentage,
|
||||
dueDate: issueDate ? addDaysToDateOnly(issueDate, rule.dueDays) : null,
|
||||
amount: roundMoney(total * (rule.percentage / 100)),
|
||||
};
|
||||
});
|
||||
};
|
||||
@ -1 +1,2 @@
|
||||
export * from "./calculate-proforma-due-dates";
|
||||
export * from "./proforma-fiscal-options.utils";
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export * from "./use-update-proforma-controller";
|
||||
export * from "./use-update-proforma-items-controller";
|
||||
export * from "./use-update-proforma-page-controller";
|
||||
export * from "./use-update-proforma-payment-controller";
|
||||
export * from "./use-update-proforma-tax-controller";
|
||||
export * from "./use-update-proforma-totals-controller";
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
} from "../utils";
|
||||
|
||||
import { useUpdateProformaItemsController } from "./use-update-proforma-items-controller";
|
||||
import { useUpdateProformaPaymentController } from "./use-update-proforma-payment-controller";
|
||||
import { useUpdateProformaTaxController } from "./use-update-proforma-tax-controller";
|
||||
import { useUpdateProformaTotalsController } from "./use-update-proforma-totals-controller";
|
||||
|
||||
@ -251,6 +252,8 @@ export const useUpdateProformaController = (
|
||||
form,
|
||||
});
|
||||
|
||||
const paymentCtrl = useUpdateProformaPaymentController();
|
||||
|
||||
return {
|
||||
// form
|
||||
formId,
|
||||
@ -259,6 +262,7 @@ export const useUpdateProformaController = (
|
||||
itemsCtrl,
|
||||
taxCtrl,
|
||||
totalsCtrl,
|
||||
paymentCtrl,
|
||||
|
||||
//
|
||||
currencyCode,
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
import {
|
||||
getPaymentMethodOptions,
|
||||
usePaymentMethodsListQuery,
|
||||
} from "@erp/catalogs/client/payment-methods";
|
||||
import {
|
||||
getPaymentTermOptions,
|
||||
usePaymentTermsListQuery,
|
||||
} from "@erp/catalogs/client/payment-terms";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const useUpdateProformaPaymentController = () => {
|
||||
const paymentMethodsQuery = usePaymentMethodsListQuery();
|
||||
|
||||
const paymentTermsQuery = usePaymentTermsListQuery();
|
||||
|
||||
const paymentMethodOptions = useMemo(() => {
|
||||
return getPaymentMethodOptions(paymentMethodsQuery.data?.items ?? []);
|
||||
}, [paymentMethodsQuery.data?.items]);
|
||||
|
||||
const paymentTermOptions = useMemo(() => {
|
||||
return getPaymentTermOptions(paymentTermsQuery.data?.items ?? []);
|
||||
}, [paymentTermsQuery.data?.items]);
|
||||
|
||||
return {
|
||||
paymentMethodOptions,
|
||||
paymentTermOptions,
|
||||
|
||||
isLoading: paymentMethodsQuery.isLoading || paymentTermsQuery.isLoading,
|
||||
|
||||
isFetching: paymentMethodsQuery.isFetching || paymentTermsQuery.isFetching,
|
||||
|
||||
isError: paymentMethodsQuery.isError || paymentTermsQuery.isError,
|
||||
|
||||
error: paymentMethodsQuery.error ?? paymentTermsQuery.error,
|
||||
};
|
||||
};
|
||||
|
||||
export type UseUpdateProformaPaymentControllerResult = ReturnType<
|
||||
typeof useUpdateProformaPaymentController
|
||||
>;
|
||||
@ -47,6 +47,7 @@ export interface ProformaUpdateForm {
|
||||
retentionPercentage: number | null;
|
||||
|
||||
paymentMethodId: string | null;
|
||||
paymentTermId: string | null;
|
||||
|
||||
items: ProformaItemUpdateForm[];
|
||||
}
|
||||
|
||||
@ -48,6 +48,7 @@ export const ProformaUpdateFormSchema = z
|
||||
retentionPercentage: z.number().nullable(),
|
||||
|
||||
paymentMethodId: z.string().nullable(),
|
||||
paymentTermId: z.string().nullable(),
|
||||
|
||||
items: z.array(ProformaItemUpdateFormSchema).min(1),
|
||||
})
|
||||
|
||||
@ -38,6 +38,7 @@ export type ProformaUpdatePatch = {
|
||||
retentionPercentage?: number | null;
|
||||
|
||||
paymentMethodId?: string | null;
|
||||
paymentTermId?: string | null;
|
||||
|
||||
languageCode?: string;
|
||||
currencyCode?: string;
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "./proforma-info-alert";
|
||||
export * from "./proforma-update-skeleton";
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
// packages/rdx-ui/src/components/feedback/info-alert.tsx
|
||||
import { Alert, AlertDescription, AlertTitle } from "@repo/shadcn-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import type * as React from "react";
|
||||
|
||||
type ProformaInfoAlertProps = {
|
||||
title: string;
|
||||
description?: string;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
iconClassName?: string;
|
||||
titleClassName?: string;
|
||||
descriptionClassName?: string;
|
||||
};
|
||||
|
||||
export const ProformaInfoAlert = ({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
className,
|
||||
iconClassName,
|
||||
titleClassName,
|
||||
descriptionClassName,
|
||||
}: ProformaInfoAlertProps) => {
|
||||
return (
|
||||
<Alert
|
||||
className={cn(
|
||||
"relative flex items-start gap-3 rounded-md border border-primary-200 bg-primary-100 px-4 py-3 text-primary-950 shadow-none",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className={cn("mt-0.5 flex shrink-0 items-center justify-center", iconClassName)}>
|
||||
<InfoIcon className="size-10 text-primary-600" />
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<AlertTitle
|
||||
className={cn(
|
||||
"mb-0.5 text-base font-semibold leading-none text-primary-800",
|
||||
titleClassName
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</AlertTitle>
|
||||
|
||||
{(description || children) && (
|
||||
<AlertDescription
|
||||
className={cn("text-sm leading-5 text-foreground", descriptionClassName)}
|
||||
>
|
||||
{description ?? children}
|
||||
</AlertDescription>
|
||||
)}
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
@ -1,2 +1,10 @@
|
||||
export * from "./proforma-payment-editor";
|
||||
export * from "./proforma-settings-editor";
|
||||
export * from "./proforma-taxes-card";
|
||||
export * from "./proforma-update-editor-form";
|
||||
export * from "./proforma-update-header-editor";
|
||||
export * from "./proforma-update-item-row-editor";
|
||||
export * from "./proforma-update-items-editor";
|
||||
export * from "./proforma-update-items-totals";
|
||||
export * from "./proforma-update-recipient-editor";
|
||||
export * from "./proforma-update-tax-editor";
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
import {
|
||||
FormSectionCard,
|
||||
FormSectionGrid,
|
||||
SelectField,
|
||||
type SelectFieldItem,
|
||||
} from "@repo/rdx-ui/components";
|
||||
import { CreditCardIcon } from "lucide-react";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
|
||||
interface ProformaUpdatePaymentEditorProps {
|
||||
paymentMethodOptions: SelectFieldItem[];
|
||||
paymentTermOptions: SelectFieldItem[];
|
||||
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ProformaUpdatePaymentEditor = ({
|
||||
paymentMethodOptions,
|
||||
paymentTermOptions,
|
||||
disabled = false,
|
||||
readOnly = false,
|
||||
className,
|
||||
}: ProformaUpdatePaymentEditorProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<FormSectionCard
|
||||
className={className}
|
||||
description={t("form_groups.proformas.payment.description", "Forma de pago")}
|
||||
disabled={disabled}
|
||||
icon={<CreditCardIcon className="size-5" />}
|
||||
title={t("form_groups.proformas.payment.title", "Condiciones de pago")}
|
||||
>
|
||||
<FormSectionGrid className="w-full">
|
||||
<SelectField
|
||||
className="col-span-full"
|
||||
disabled={disabled}
|
||||
inputClassName="bg-background"
|
||||
items={paymentMethodOptions}
|
||||
label={t("form_fields.proformas.payment_method.label", "Forma de pago")}
|
||||
name="paymentMethodId"
|
||||
placeholder={t(
|
||||
"form_fields.proformas.payment_method.placeholder",
|
||||
"Selecciona una forma de pago"
|
||||
)}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
|
||||
<SelectField
|
||||
className="col-span-full"
|
||||
disabled={disabled}
|
||||
inputClassName="bg-background"
|
||||
items={paymentTermOptions}
|
||||
label={t("form_fields.proformas.payment_term.label", "Vencimiento")}
|
||||
name="paymentTermId"
|
||||
placeholder={t(
|
||||
"form_fields.proformas.payment_term.placeholder",
|
||||
"Selecciona un vencimiento"
|
||||
)}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</FormSectionGrid>
|
||||
</FormSectionCard>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,51 @@
|
||||
import { FormSectionCard, FormSectionGrid, SelectField } from "@repo/rdx-ui/components";
|
||||
import { Settings2Icon } from "lucide-react";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
|
||||
interface ProformaUpdateSettingsEditorProps {
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ProformaUpdateSettingsEditor = ({
|
||||
disabled = false,
|
||||
readOnly = false,
|
||||
className,
|
||||
}: ProformaUpdateSettingsEditorProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<FormSectionCard
|
||||
className={className}
|
||||
description={t("form_groups.proformas.settings.description", "Configuración de la proforma")}
|
||||
disabled={disabled}
|
||||
icon={<Settings2Icon className="size-5" />}
|
||||
title={t("form_groups.proformas.settings.title", "Configuración")}
|
||||
>
|
||||
<FormSectionGrid className="w-full">
|
||||
<SelectField
|
||||
className="col-span-full"
|
||||
disabled={disabled}
|
||||
inputClassName="bg-background"
|
||||
label={t("form_fields.proformas.status.label")}
|
||||
name="status"
|
||||
placeholder={t("form_fields.proformas.status.placeholder")}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
|
||||
<SelectField
|
||||
className="col-span-full"
|
||||
disabled={disabled}
|
||||
inputClassName="bg-background"
|
||||
label={t("form_fields.proformas.currency_code.label")}
|
||||
name="currencyCode"
|
||||
placeholder={t("form_fields.proformas.currency_code.placeholder")}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</FormSectionGrid>
|
||||
</FormSectionCard>
|
||||
);
|
||||
};
|
||||
@ -5,15 +5,21 @@ import { PercentageField } from "@repo/rdx-ui/components";
|
||||
import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers";
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
|
||||
import { ProformaUpdateRecipientEditor } from ".";
|
||||
import {
|
||||
ProformaUpdatePaymentEditor,
|
||||
ProformaUpdateRecipientEditor,
|
||||
ProformaUpdateSettingsEditor,
|
||||
} from ".";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
import type {
|
||||
UseUpdateProformaItemsControllerResult,
|
||||
UseUpdateProformaPaymentControllerResult,
|
||||
UseUpdateProformaTaxControllerResult,
|
||||
UseUpdateProformaTotalsControllerResult,
|
||||
} from "../../controllers";
|
||||
import { ProformaTotalsSummary } from "../blocks";
|
||||
import { ProformaInfoAlert } from "../components";
|
||||
|
||||
import { ProformaUpdateHeaderEditor } from "./proforma-update-header-editor";
|
||||
import { ProformaUpdateItemsEditor } from "./proforma-update-items-editor";
|
||||
@ -32,6 +38,7 @@ type ProformaUpdateEditorProps = {
|
||||
itemsCtrl: UseUpdateProformaItemsControllerResult;
|
||||
taxCtrl: UseUpdateProformaTaxControllerResult;
|
||||
totalsCtrl: UseUpdateProformaTotalsControllerResult;
|
||||
paymentCtrl: UseUpdateProformaPaymentControllerResult;
|
||||
|
||||
currencyCode?: string;
|
||||
languageCode?: string;
|
||||
@ -48,6 +55,7 @@ export const ProformaUpdateEditorForm = ({
|
||||
itemsCtrl,
|
||||
taxCtrl,
|
||||
totalsCtrl,
|
||||
paymentCtrl,
|
||||
currencyCode,
|
||||
languageCode,
|
||||
}: ProformaUpdateEditorProps) => {
|
||||
@ -55,43 +63,64 @@ export const ProformaUpdateEditorForm = ({
|
||||
|
||||
return (
|
||||
<form
|
||||
className="space-y-6"
|
||||
className="space-y-6 space-x-6 2xl:space-y-12"
|
||||
id={formId}
|
||||
noValidate
|
||||
onKeyDown={preventEnterKeySubmitForm}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-12">
|
||||
<ProformaUpdateHeaderEditor className="xl:col-span-9" disabled={isSubmitting} />
|
||||
<ProformaInfoAlert
|
||||
description="Esta proforma no tiene validez fiscal. Puedes convertirla en factura cuando sea aceptada por el cliente."
|
||||
title="Información importante"
|
||||
/>
|
||||
<div className="flex flex-row">
|
||||
<div className="basis-3/4 space-x-4 space-y-4 2xl:space-y-6 2xl:space-x-6">
|
||||
<div className="grid grid-cols-1 2xl:grid-cols-12 gap-4">
|
||||
<ProformaUpdateHeaderEditor className="2xl:col-span-8" disabled={isSubmitting} />
|
||||
|
||||
<ProformaUpdateRecipientEditor
|
||||
className="xl:col-span-3"
|
||||
disabled={isSubmitting}
|
||||
onChangeCustomerClick={onChangeCustomerClick}
|
||||
onCreateCustomerClick={onCreateCustomerClick}
|
||||
selectedCustomer={selectedCustomer}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ProformaUpdateItemsEditor disabled={isSubmitting} itemsCtrl={itemsCtrl} taxCtrl={taxCtrl} />
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-12">
|
||||
<ProformaUpdateTaxEditor className="md:col-span-4" taxCtrl={taxCtrl} />
|
||||
<ProformaTotalsSummary
|
||||
className="md:col-span-8"
|
||||
currency={currencyCode}
|
||||
globalDiscountField={
|
||||
<PercentageField
|
||||
<ProformaUpdateRecipientEditor
|
||||
className="xl:col-span-4"
|
||||
disabled={isSubmitting}
|
||||
inputClassName="bg-background"
|
||||
label={t("proformas.update.totals.globalDiscountPercentage", "Descuento global")}
|
||||
name="globalDiscountPercentage"
|
||||
onChangeCustomerClick={onChangeCustomerClick}
|
||||
onCreateCustomerClick={onCreateCustomerClick}
|
||||
selectedCustomer={selectedCustomer}
|
||||
/>
|
||||
}
|
||||
showRec={taxCtrl.hasRecPercentage}
|
||||
showRetention={taxCtrl.hasRetentionPercentage}
|
||||
totals={totalsCtrl.totals}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ProformaUpdateItemsEditor
|
||||
disabled={isSubmitting}
|
||||
itemsCtrl={itemsCtrl}
|
||||
taxCtrl={taxCtrl}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-12">
|
||||
<ProformaTotalsSummary
|
||||
className="md:col-span-8"
|
||||
currency={currencyCode}
|
||||
globalDiscountField={
|
||||
<PercentageField
|
||||
disabled={isSubmitting}
|
||||
inputClassName="bg-background"
|
||||
label={t("proformas.update.totals.globalDiscountPercentage", "Descuento global")}
|
||||
name="globalDiscountPercentage"
|
||||
/>
|
||||
}
|
||||
showRec={taxCtrl.hasRecPercentage}
|
||||
showRetention={taxCtrl.hasRetentionPercentage}
|
||||
totals={totalsCtrl.totals}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="basis-1/4 space-x-4 space-y-4 2xl:space-y-6 2xl:space-x-6">
|
||||
<ProformaUpdateSettingsEditor className="w-full bg-secondary ring-0" />
|
||||
<ProformaUpdateTaxEditor className="w-full bg-secondary ring-0" taxCtrl={taxCtrl} />
|
||||
<ProformaUpdatePaymentEditor
|
||||
className="w-full bg-secondary ring-0"
|
||||
disabled={isSubmitting || paymentCtrl.isLoading}
|
||||
paymentMethodOptions={paymentCtrl.paymentMethodOptions}
|
||||
paymentTermOptions={paymentCtrl.paymentTermOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col-reverse gap-3 border-t pt-4 sm:flex-row sm:justify-end">
|
||||
|
||||
@ -58,15 +58,6 @@ export const ProformaUpdateHeaderEditor = ({
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectField
|
||||
className="md:col-span-3"
|
||||
disabled={disabled}
|
||||
label={t("form_fields.proformas.currency_code.label")}
|
||||
name="currencyCode"
|
||||
placeholder={t("form_fields.proformas.currency_code.placeholder")}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
|
||||
<DatePickerField
|
||||
className="md:col-span-3 md:col-start-1"
|
||||
disabled={disabled}
|
||||
|
||||
@ -5,21 +5,6 @@ import {
|
||||
SwitchField,
|
||||
} from "@repo/rdx-ui/components";
|
||||
import { PercentageHelper } from "@repo/rdx-utils";
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
Switch,
|
||||
} from "@repo/shadcn-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import { ReceiptTextIcon } from "lucide-react";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
@ -59,199 +44,159 @@ export const ProformaUpdateTaxEditor = ({
|
||||
icon={<ReceiptTextIcon className="size-5" />}
|
||||
title={t("form_groups.proformas.taxes.title", "Impuestos")}
|
||||
>
|
||||
<FieldSet>
|
||||
<FieldLegend className="text-primary">1. Régimen fiscal</FieldLegend>
|
||||
<FieldDescription>
|
||||
Selecciona el régimen fiscal que aplica a esta proforma.
|
||||
</FieldDescription>
|
||||
<FormSectionGrid>
|
||||
<Field className="md:col-span-12 md:col-start-1" orientation="horizontal">
|
||||
<SelectField
|
||||
className="md:col-span-4 md:col-start-1"
|
||||
disabled={disabled}
|
||||
items={[
|
||||
{ value: "01", label: "01: Operación de régimen general." },
|
||||
{ value: "02", label: "02: Exportación." },
|
||||
{
|
||||
value: "03",
|
||||
label:
|
||||
"03: Operaciones a las que se aplique el régimen especial de bienes usados, objetos de arte, antigüedades y objetos de colección.",
|
||||
},
|
||||
{ value: "04", label: "04: Régimen especial del oro de inversión." },
|
||||
{ value: "05", label: "05: Régimen especial de las agencias de viajes." },
|
||||
{
|
||||
value: "06",
|
||||
label: "06: Régimen especial grupo de entidades en IVA o IGIC (Nivel Avanzado)",
|
||||
},
|
||||
{ value: "07", label: "07: Régimen especial del criterio de caja." },
|
||||
{ value: "08", label: "08: Operaciones sujetas al IPSI/IVA o IGIC." },
|
||||
{
|
||||
value: "09",
|
||||
label:
|
||||
"09: Facturación de las prestaciones de servicios de agencias de viaje que actúan como mediadoras en nombre y por cuenta ajena (D.A.4ª RD1619/2012)",
|
||||
},
|
||||
{
|
||||
value: "10",
|
||||
label:
|
||||
"10: Cobros por cuenta de terceros de honorarios profesionales o de derechos derivados de la propiedad industrial, de autor u otros por cuenta de sus socios, asociados o colegiados efectuados por sociedades, asociaciones, colegios profesionales u otras entidades que realicen estas funciones de cobro.",
|
||||
},
|
||||
{ value: "11", label: "11: Operaciones de arrendamiento de local de negocio." },
|
||||
{
|
||||
value: "14",
|
||||
label:
|
||||
"14: Factura con IVA o IGIC pendiente de devengo en certificaciones de obra cuyo destinatario sea una Administración Pública.",
|
||||
},
|
||||
{
|
||||
value: "15",
|
||||
label:
|
||||
"15: Factura con IVA o IGIC pendiente de devengo en operaciones de tracto sucesivo.",
|
||||
},
|
||||
{
|
||||
value: "17",
|
||||
label:
|
||||
"17: Operación acogida a alguno de los regímenes previstos en el Capítulo XI del Título IX (OSS e IOSS) o régimen especial de comerciante minorista",
|
||||
},
|
||||
{
|
||||
value: "18",
|
||||
label:
|
||||
"18: Recargo de equivalencia o régimen especial del pequeño empresario o profesional.",
|
||||
},
|
||||
{
|
||||
value: "19",
|
||||
label:
|
||||
"19: Operaciones de actividades incluidas en el Régimen Especial de Agricultura, Ganadería y Pesca (REAGYP) u operaciones interiores exentas por aplicación artículo 25 Ley 19/1994",
|
||||
},
|
||||
{ value: "20", label: "20: Régimen simplificado" },
|
||||
]}
|
||||
label={t("form_fields.proformas.tax_regime_code.label", "Régimen fiscal")}
|
||||
name="taxRegimeCode"
|
||||
placeholder={t(
|
||||
"form_fields.proformas.tax_regime_code.placeholder",
|
||||
"Selecciona el régimen fiscal para esta proforma"
|
||||
<FormSectionGrid>
|
||||
<SelectField
|
||||
className="col-span-full"
|
||||
disabled={disabled}
|
||||
inputClassName="bg-background"
|
||||
items={[
|
||||
{ value: "01", label: "01: Operación de régimen general." },
|
||||
{ value: "02", label: "02: Exportación." },
|
||||
{
|
||||
value: "03",
|
||||
label:
|
||||
"03: Operaciones a las que se aplique el régimen especial de bienes usados, objetos de arte, antigüedades y objetos de colección.",
|
||||
},
|
||||
{ value: "04", label: "04: Régimen especial del oro de inversión." },
|
||||
{ value: "05", label: "05: Régimen especial de las agencias de viajes." },
|
||||
{
|
||||
value: "06",
|
||||
label: "06: Régimen especial grupo de entidades en IVA o IGIC (Nivel Avanzado)",
|
||||
},
|
||||
{ value: "07", label: "07: Régimen especial del criterio de caja." },
|
||||
{ value: "08", label: "08: Operaciones sujetas al IPSI/IVA o IGIC." },
|
||||
{
|
||||
value: "09",
|
||||
label:
|
||||
"09: Facturación de las prestaciones de servicios de agencias de viaje que actúan como mediadoras en nombre y por cuenta ajena (D.A.4ª RD1619/2012)",
|
||||
},
|
||||
{
|
||||
value: "10",
|
||||
label:
|
||||
"10: Cobros por cuenta de terceros de honorarios profesionales o de derechos derivados de la propiedad industrial, de autor u otros por cuenta de sus socios, asociados o colegiados efectuados por sociedades, asociaciones, colegios profesionales u otras entidades que realicen estas funciones de cobro.",
|
||||
},
|
||||
{ value: "11", label: "11: Operaciones de arrendamiento de local de negocio." },
|
||||
{
|
||||
value: "14",
|
||||
label:
|
||||
"14: Factura con IVA o IGIC pendiente de devengo en certificaciones de obra cuyo destinatario sea una Administración Pública.",
|
||||
},
|
||||
{
|
||||
value: "15",
|
||||
label:
|
||||
"15: Factura con IVA o IGIC pendiente de devengo en operaciones de tracto sucesivo.",
|
||||
},
|
||||
{
|
||||
value: "17",
|
||||
label:
|
||||
"17: Operación acogida a alguno de los regímenes previstos en el Capítulo XI del Título IX (OSS e IOSS) o régimen especial de comerciante minorista",
|
||||
},
|
||||
{
|
||||
value: "18",
|
||||
label:
|
||||
"18: Recargo de equivalencia o régimen especial del pequeño empresario o profesional.",
|
||||
},
|
||||
{
|
||||
value: "19",
|
||||
label:
|
||||
"19: Operaciones de actividades incluidas en el Régimen Especial de Agricultura, Ganadería y Pesca (REAGYP) u operaciones interiores exentas por aplicación artículo 25 Ley 19/1994",
|
||||
},
|
||||
{ value: "20", label: "20: Régimen simplificado" },
|
||||
]}
|
||||
label={t("form_fields.proformas.tax_regime_code.label", "Régimen fiscal")}
|
||||
name="taxRegimeCode"
|
||||
placeholder={t(
|
||||
"form_fields.proformas.tax_regime_code.placeholder",
|
||||
"Selecciona el régimen fiscal para esta proforma"
|
||||
)}
|
||||
readOnly={readOnly || taxCtrl.usesPerLineTax}
|
||||
/>
|
||||
|
||||
<SelectField
|
||||
className="md:col-span-4 md:col-start-1"
|
||||
deserialize={(value) => (value === null || value === "" ? null : Number(value))}
|
||||
disabled={disabled}
|
||||
inputClassName="bg-background"
|
||||
items={getProformaTaxOptions()}
|
||||
label={t("form_fields.proformas.default_tax_percentage.label", "IVA por defecto")}
|
||||
name="taxPercentage"
|
||||
onChange={(value) => {
|
||||
const parsed = parseProformaTaxPercentage(value as number | null);
|
||||
if (parsed !== null) {
|
||||
taxCtrl.updateTaxPercentage(parsed);
|
||||
}
|
||||
}}
|
||||
placeholder={t(
|
||||
"form_fields.proformas.default_tax_percentage.placeholder",
|
||||
"Selecciona IVA"
|
||||
)}
|
||||
readOnly={readOnly || taxCtrl.usesPerLineTax}
|
||||
serialize={(value) => (typeof value === "number" ? String(value) : "")}
|
||||
/>
|
||||
|
||||
<SwitchField
|
||||
checked={taxCtrl.usesSingleTax}
|
||||
className="md:col-span-12 md:col-start-1 not-disabled:cursor-pointer"
|
||||
disabled={disabled || readOnly}
|
||||
label="Mismo IVA en todas las líneas de la proforma"
|
||||
name=""
|
||||
onCheckedChange={(checked) =>
|
||||
checked ? taxCtrl.disablePerLineTaxes() : taxCtrl.enablePerLineTaxes()
|
||||
}
|
||||
/>
|
||||
|
||||
<SwitchField
|
||||
className="md:col-span-12 md:col-start-1"
|
||||
disabled={disabled}
|
||||
label={
|
||||
<>
|
||||
{t(
|
||||
"form_fields.proformas.has_equivalence_surcharge.label",
|
||||
"Recargo de equivalencia"
|
||||
)}
|
||||
readOnly={readOnly || taxCtrl.usesPerLineTax}
|
||||
/>
|
||||
</Field>
|
||||
</FormSectionGrid>
|
||||
</FieldSet>
|
||||
<FieldSeparator className="my-4" />
|
||||
|
||||
<Card
|
||||
className={cn(disabled ? "bg-muted text-muted-foreground" : "bg-primary/10 text-primary")}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
{" "}
|
||||
{t(
|
||||
"proformas.update.taxes.disable_per_line",
|
||||
"Mismo IVA en todas las líneas de la proforma"
|
||||
)}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Puedes usar un tipo único para todos las líneas de detalle o permitir que cada línea
|
||||
tenga su propio IVA.
|
||||
</CardDescription>
|
||||
<CardAction>
|
||||
<Switch
|
||||
checked={taxCtrl.usesSingleTax}
|
||||
className="not-disabled:cursor-pointer"
|
||||
disabled={disabled || readOnly}
|
||||
onCheckedChange={(checked) =>
|
||||
checked ? taxCtrl.disablePerLineTaxes() : taxCtrl.enablePerLineTaxes()
|
||||
}
|
||||
/>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FieldSet>
|
||||
<FormSectionGrid>
|
||||
<SelectField
|
||||
className="md:col-span-4 md:col-start-1"
|
||||
deserialize={(value) => (value === null || value === "" ? null : Number(value))}
|
||||
disabled={disabled}
|
||||
inputClassName="bg-background"
|
||||
items={getProformaTaxOptions()}
|
||||
label={t("form_fields.proformas.default_tax_percentage.label", "IVA por defecto")}
|
||||
name="taxPercentage"
|
||||
onChange={(value) => {
|
||||
const parsed = parseProformaTaxPercentage(value as number | null);
|
||||
if (parsed !== null) {
|
||||
taxCtrl.updateTaxPercentage(parsed);
|
||||
}
|
||||
}}
|
||||
placeholder={t(
|
||||
"form_fields.proformas.default_tax_percentage.placeholder",
|
||||
"Selecciona IVA"
|
||||
)}
|
||||
readOnly={readOnly || taxCtrl.usesPerLineTax}
|
||||
serialize={(value) => (typeof value === "number" ? String(value) : "")}
|
||||
/>
|
||||
</FormSectionGrid>
|
||||
</FieldSet>
|
||||
<FieldSeparator className="my-4" />
|
||||
<FieldSet>
|
||||
<FieldLegend>Impuestos adicionales</FieldLegend>
|
||||
<FieldDescription>
|
||||
Activa opciones fiscales adicionales como recargo de equivalencia o retenciones
|
||||
(IRPF), según el tipo de cliente y normativa aplicable.
|
||||
</FieldDescription>
|
||||
{taxCtrl.hasRecPercentage ? (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{PercentageHelper.formatPercent(taxCtrl.recPercentage ?? 0)}
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
name="hasRecPercentage"
|
||||
onCheckedChange={taxCtrl.updateRecPercentage}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
|
||||
<FormSectionGrid>
|
||||
<SwitchField
|
||||
className="md:col-span-12 md:col-start-1"
|
||||
disabled={disabled}
|
||||
label={
|
||||
<>
|
||||
{t(
|
||||
"form_fields.proformas.has_equivalence_surcharge.label",
|
||||
"Recargo de equivalencia"
|
||||
)}
|
||||
<SwitchField
|
||||
className="md:col-span-12 md:col-start-1"
|
||||
disabled={disabled}
|
||||
label={t("form_fields.proformas.has_retention.label", "Incluir retención/IRPF")}
|
||||
name="hasRetentionPercentage"
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
|
||||
{taxCtrl.hasRecPercentage ? (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{PercentageHelper.formatPercent(taxCtrl.recPercentage ?? 0)}
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
name="hasRecPercentage"
|
||||
onCheckedChange={taxCtrl.updateRecPercentage}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
|
||||
<SwitchField
|
||||
className="md:col-span-12 md:col-start-1"
|
||||
disabled={disabled}
|
||||
label={t("form_fields.proformas.has_retention.label", "Incluir retención/IRPF")}
|
||||
name="hasRetentionPercentage"
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
|
||||
<SelectField
|
||||
className="md:col-span-4 md:col-start-1"
|
||||
deserialize={(value) => (value === null || value === "" ? null : Number(value))}
|
||||
disabled={disabled || !taxCtrl.hasRetentionPercentage}
|
||||
inputClassName="bg-background"
|
||||
items={getProformaRetentionOptions()}
|
||||
label={t("form_fields.proformas.retention_percentage.label", "Retención")}
|
||||
name="retentionPercentage"
|
||||
onChange={(value) => {
|
||||
const parsed = parseProformaRetentionPercentage(value as number | null);
|
||||
if (parsed !== null) {
|
||||
taxCtrl.updateRetentionPercentage(parsed);
|
||||
}
|
||||
}}
|
||||
placeholder={t(
|
||||
"form_fields.proformas.default_tax_percentage.placeholder",
|
||||
"Selecciona IVA"
|
||||
)}
|
||||
readOnly={readOnly || taxCtrl.usesPerLineTax}
|
||||
serialize={(value) => (typeof value === "number" ? String(value) : "")}
|
||||
/>
|
||||
</FormSectionGrid>
|
||||
</FieldSet>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<SelectField
|
||||
className="md:col-span-4 md:col-start-1"
|
||||
deserialize={(value) => (value === null || value === "" ? null : Number(value))}
|
||||
disabled={disabled || !taxCtrl.hasRetentionPercentage}
|
||||
inputClassName="bg-background"
|
||||
items={getProformaRetentionOptions()}
|
||||
label={t("form_fields.proformas.retention_percentage.label", "Retención")}
|
||||
name="retentionPercentage"
|
||||
onChange={(value) => {
|
||||
const parsed = parseProformaRetentionPercentage(value as number | null);
|
||||
if (parsed !== null) {
|
||||
taxCtrl.updateRetentionPercentage(parsed);
|
||||
}
|
||||
}}
|
||||
placeholder={t(
|
||||
"form_fields.proformas.default_tax_percentage.placeholder",
|
||||
"Selecciona IVA"
|
||||
)}
|
||||
readOnly={readOnly || taxCtrl.usesPerLineTax}
|
||||
serialize={(value) => (typeof value === "number" ? String(value) : "")}
|
||||
/>
|
||||
</FormSectionGrid>
|
||||
</FormSectionCard>
|
||||
);
|
||||
};
|
||||
|
||||
@ -61,7 +61,7 @@ export const ProformaUpdatePage = () => {
|
||||
return (
|
||||
<FormProvider {...updateCtrl.form}>
|
||||
<UnsavedChangesProvider isDirty={updateCtrl.form.formState.isDirty}>
|
||||
<AppHeader className="mx-auto max-w-7xl space-y-4">
|
||||
<AppHeader className="mx-auto max-w-[100rem]">
|
||||
<PageHeader
|
||||
description={t("pages.proformas.update.description")}
|
||||
onBackClick={() => navigateBack()}
|
||||
@ -84,7 +84,7 @@ export const ProformaUpdatePage = () => {
|
||||
/>
|
||||
</AppHeader>
|
||||
|
||||
<AppContent className="mx-auto max-w-7xl space-y-4">
|
||||
<AppContent className="mx-auto max-w-[100rem]">
|
||||
{updateCtrl.isUpdateError && (
|
||||
<ErrorAlert
|
||||
message={
|
||||
@ -105,6 +105,7 @@ export const ProformaUpdatePage = () => {
|
||||
onCreateCustomerClick={() => null}
|
||||
onReset={updateCtrl.resetForm}
|
||||
onSubmit={updateCtrl.onSubmit}
|
||||
paymentCtrl={updateCtrl.paymentCtrl}
|
||||
selectedCustomer={updateCtrl.selectedCustomer}
|
||||
taxCtrl={updateCtrl.taxCtrl}
|
||||
totalsCtrl={updateCtrl.totalsCtrl}
|
||||
|
||||
@ -80,6 +80,10 @@ export const buildUpdateProformaByIdParams = (
|
||||
data.payment_method_id = patch.paymentMethodId;
|
||||
}
|
||||
|
||||
if (ObjectHelper.hasOwn(patch, "paymentTermId")) {
|
||||
data.payment_term_id = patch.paymentTermId;
|
||||
}
|
||||
|
||||
if (ObjectHelper.hasOwn(patch, "globalDiscountPercentage")) {
|
||||
data.global_discount_percentage = PercentageDTOHelper.fromNumber(
|
||||
patch.globalDiscountPercentage!,
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -47,7 +48,6 @@
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"express": "^4.22.1",
|
||||
"lucide-react": "^1.8.0",
|
||||
"react-data-table-component": "^7.7.0",
|
||||
"react-hook-form": "^7.72.1",
|
||||
"react-i18next": "^17.0.2",
|
||||
"react-router-dom": "^7.14.0",
|
||||
|
||||
@ -5,7 +5,7 @@ import { CustomerRoutes } from "./customer-routes";
|
||||
export const MODULE_NAME = "Customers";
|
||||
const MODULE_VERSION = "1.0.0";
|
||||
|
||||
export const CustomersModuleManifiest: IModuleClient = {
|
||||
export const CustomersModuleManifest: IModuleClient = {
|
||||
name: MODULE_NAME,
|
||||
version: MODULE_VERSION,
|
||||
dependencies: ["auth", "Core"],
|
||||
@ -17,4 +17,4 @@ export const CustomersModuleManifiest: IModuleClient = {
|
||||
},
|
||||
};
|
||||
|
||||
export default CustomersModuleManifiest;
|
||||
export default CustomersModuleManifest;
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -498,3 +498,4 @@ export class SequelizeSupplierInvoiceDomainMapper extends SequelizeDomainMapper<
|
||||
);
|
||||
}
|
||||
}
|
||||
11111111
|
||||
@ -30,6 +30,7 @@
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -47,7 +48,6 @@
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"express": "^4.22.1",
|
||||
"lucide-react": "^1.8.0",
|
||||
"react-data-table-component": "^7.7.0",
|
||||
"react-hook-form": "^7.72.1",
|
||||
"react-i18next": "^17.0.2",
|
||||
"react-router-dom": "^7.14.0",
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "turbo build",
|
||||
"build": "turbo run build",
|
||||
"build:templates": "bash scripts/build-templates.sh",
|
||||
"build:api": "bash scripts/build-api.sh rodax --api",
|
||||
"dev": "turbo dev",
|
||||
@ -24,7 +24,7 @@
|
||||
"format:check": "biome format .",
|
||||
"check": "biome check .",
|
||||
"check:write": "biome check --write .",
|
||||
"typecheck": "turbo run typecheck"
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.4.11",
|
||||
@ -39,7 +39,7 @@
|
||||
"rimraf": "^6.1.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"turbo": "^2.9.6",
|
||||
"turbo": "^2.9.14",
|
||||
"typescript": "6.0.2"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -12,15 +12,16 @@
|
||||
"exports": {
|
||||
".": "./src/i18n.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/node": "^25.6.0",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"i18next": "26.0.4",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"i18next-http-backend": "^3.0.4",
|
||||
"react-i18next": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/node": "^25.6.0",
|
||||
"typescript": "^6.0.2"
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,7 @@
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/dinero.js": "1.9.1",
|
||||
"@types/node": "^25.6.0",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -5,9 +5,11 @@
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"ui:lint": "biome lint --fix",
|
||||
"check": "biome check .",
|
||||
"lint": "biome lint ."
|
||||
"lint": "biome lint .",
|
||||
"clean": "rimraf .turbo node_modules dist"
|
||||
},
|
||||
"exports": {
|
||||
"./helpers": "./src/helpers/index.ts",
|
||||
@ -20,10 +22,6 @@
|
||||
"./src/hooks/index.ts"
|
||||
]
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.11",
|
||||
"@repo/i18next": "workspace:*",
|
||||
@ -34,10 +32,10 @@
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"esbuild-plugin-react18": "^0.2.6",
|
||||
"esbuild-plugin-react18-css": "^0.0.4",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"rimraf": "^6.1.3",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"tsup": "^8.5.1",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
@ -58,6 +56,8 @@
|
||||
"cmdk": "^1.1.1",
|
||||
"esbuild-raw-plugin": "^0.3.1",
|
||||
"lucide-react": "^1.8.0",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"react-hook-form": "^7.72.1",
|
||||
"react-i18next": "^17.0.2",
|
||||
"react-router": "^7.14.0",
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { FieldLabel } from "@repo/shadcn-ui/components";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
interface FormFieldLabelProps extends React.ComponentProps<typeof FieldLabel> {
|
||||
required?: boolean;
|
||||
optional?: boolean;
|
||||
className?: string;
|
||||
htmlFor?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const FormFieldLabel = ({
|
||||
|
||||
@ -33,28 +33,30 @@ export const FormSectionCard = ({
|
||||
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader className={headerClassName}>
|
||||
<div className="flex items-start gap-3">
|
||||
{icon ? (
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-12 shrink-0 items-center justify-center rounded-md",
|
||||
disabled ? "bg-muted text-muted-foreground" : "bg-primary/10 text-primary"
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
{icon}{" "}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="space-y-1">
|
||||
{title ? <CardTitle className="font-semibold">{title}</CardTitle> : null}
|
||||
{description ? (
|
||||
<CardDescription className="font-medium">{description}</CardDescription>
|
||||
{hasHeader && (
|
||||
<CardHeader className={headerClassName}>
|
||||
<div className="flex items-start gap-3">
|
||||
{icon ? (
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-12 shrink-0 items-center justify-center rounded-md",
|
||||
disabled ? "bg-muted text-muted-foreground" : "bg-primary/10 text-primary"
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
{icon}{" "}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="space-y-1">
|
||||
{title ? <CardTitle className="font-semibold">{title}</CardTitle> : null}
|
||||
{description ? (
|
||||
<CardDescription className="font-medium">{description}</CardDescription>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CardHeader>
|
||||
)}
|
||||
|
||||
<CardContent className={contentClassName}>{children}</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -13,14 +13,14 @@ import { Controller, type FieldPath, type FieldValues, useFormContext } from "re
|
||||
|
||||
import { FormFieldLabel } from "./form-field-label.tsx";
|
||||
|
||||
interface SelectFieldItem {
|
||||
export type SelectFieldItem = {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
};
|
||||
|
||||
type SelectFieldValue = string | null;
|
||||
export type SelectFieldValue = string | null;
|
||||
|
||||
type SelectFieldProps<TFormValues extends FieldValues> = {
|
||||
export type SelectFieldProps<TFormValues extends FieldValues> = {
|
||||
name: FieldPath<TFormValues>;
|
||||
|
||||
label?: string;
|
||||
@ -104,7 +104,7 @@ export function SelectField<TFormValues extends FieldValues>({
|
||||
|
||||
<Select
|
||||
disabled={isDisabled}
|
||||
onValueChange={(value) =>
|
||||
onValueChange={(value: any) =>
|
||||
onChange &&
|
||||
onChange(deserializeValue(value)) &&
|
||||
field.onChange(deserializeValue(value))
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
"devDependencies": {
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/node": "^25.6.0",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -11,7 +11,7 @@ const config = {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
|
||||
extends: {},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@ -9,6 +9,9 @@
|
||||
"@erp/auth/*": [
|
||||
"modules/auth/src/*"
|
||||
],
|
||||
"@erp/catalogs/*": [
|
||||
"modules/catalogs/src/*"
|
||||
],
|
||||
"@erp/customers/*": [
|
||||
"modules/customers/src/*"
|
||||
],
|
||||
|
||||
84
plopfile.mjs
84
plopfile.mjs
@ -1,84 +0,0 @@
|
||||
/**
|
||||
* Generador de módulos ERP
|
||||
* - module: bounded context (carpeta en modules/)
|
||||
* - name: agregado principal (usa placeholders en plantillas)
|
||||
* - plural: rutas/tabla (override del plural generado)
|
||||
*/
|
||||
|
||||
import {
|
||||
camelCase,
|
||||
capitalCase,
|
||||
constantCase,
|
||||
dotCase,
|
||||
kebabCase,
|
||||
pascalCase,
|
||||
snakeCase,
|
||||
} from "change-case";
|
||||
|
||||
/**
|
||||
* Generador de módulos ERP
|
||||
* @remarks
|
||||
* - `module` -> bounded context (carpeta en `modules/`)
|
||||
* - `name` -> agregado principal (singular)
|
||||
* - `plural` -> plural del agregado para rutas/tablas (override manual si hace falta)
|
||||
* - Helpers -> registrados con los nombres usados en las plantillas (.hbs)
|
||||
*/
|
||||
export default function (plop) {
|
||||
/** Helpers de casing para usar en hbs */
|
||||
plop.setHelper("kebabCase", (s) => kebabCase(String(s || "")));
|
||||
plop.setHelper("camelCase", (s) => camelCase(String(s || "")));
|
||||
plop.setHelper("pascalCase", (s) => pascalCase(String(s || "")));
|
||||
plop.setHelper("snakeCase", (s) => snakeCase(String(s || "")));
|
||||
plop.setHelper("constantCase", (s) => constantCase(String(s || "")));
|
||||
plop.setHelper("capitalCase", (s) => capitalCase(String(s || "")));
|
||||
plop.setHelper("dotCase", (s) => dotCase(String(s || "")));
|
||||
|
||||
/** Validadores simples */
|
||||
const isKebab = (v) =>
|
||||
(!!v && /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(v)) ||
|
||||
"Usa kebab-case (empieza por letra; letras/números/guiones).";
|
||||
|
||||
plop.setGenerator("module", {
|
||||
description: "Crea un nuevo módulo (bounded context) con un agregado principal",
|
||||
prompts: [
|
||||
{
|
||||
type: "input",
|
||||
name: "module",
|
||||
message: "Bounded context (kebab-case, p.ej. 'customer-payments'):",
|
||||
validate: isKebab,
|
||||
filter: (v) => kebabCase(v),
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "name",
|
||||
message: "Agregado principal (kebab-case, p.ej. 'customer-payment'):",
|
||||
validate: isKebab,
|
||||
filter: (v) => kebabCase(v),
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "plural",
|
||||
message: "Plural del agregado (kebab-case) — ENTER para sufijo 's':",
|
||||
filter: (v, answers) => (v?.trim() ? kebabCase(v) : `${kebabCase(answers.name)}s`),
|
||||
validate: isKebab,
|
||||
},
|
||||
],
|
||||
actions: (answers) => {
|
||||
const dest = `modules/${kebabCase(answers.module)}`;
|
||||
/**
|
||||
* addMany copiará la plantilla completa en la carpeta del bounded context
|
||||
* usando los placeholders de las plantillas existentes (name/plural).
|
||||
*/
|
||||
const actions = [
|
||||
{
|
||||
type: "addMany",
|
||||
destination: dest,
|
||||
base: "templates/new-module",
|
||||
templateFiles: "templates/new-module/**",
|
||||
abortOnFail: true,
|
||||
},
|
||||
];
|
||||
return actions;
|
||||
},
|
||||
});
|
||||
}
|
||||
4641
pnpm-lock.yaml
4641
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
9
scripts/clean-and-install.sh
Normal file
9
scripts/clean-and-install.sh
Normal file
@ -0,0 +1,9 @@
|
||||
rm -rf node_modules
|
||||
rm -rf .turbo
|
||||
rm -rf pnpm-lock.yaml
|
||||
|
||||
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
||||
find . -name ".turbo" -type d -prune -exec rm -rf '{}' +
|
||||
|
||||
pnpm store prune
|
||||
pnpm install
|
||||
@ -1,17 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"chatgpt.openOnStartup": true,
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
"pnpm": true,
|
||||
"/^cd /home/rodax/Documentos/uecko-erp && ls -l node_modules/@repo/typescript-config/root\\.json && node -p \"require\\.resolve\\('@repo/typescript-config/root\\.json'\\)\"$/": {
|
||||
"approve": true,
|
||||
"matchCommandLine": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user