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",
|
"start": "NODE_ENV=production node --env-file=.env.production dist/index.js",
|
||||||
"dev": "node --import=tsx --watch src/index.ts",
|
"dev": "node --import=tsx --watch src/index.ts",
|
||||||
"clean": "rimraf .turbo node_modules dist",
|
"clean": "rimraf .turbo node_modules dist",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"check": "biome check .",
|
"check": "biome check .",
|
||||||
"lint": "biome lint .",
|
"lint": "biome lint .",
|
||||||
"format": "biome format --write"
|
"format": "biome format --write"
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
"version": "0.6.7",
|
"version": "0.6.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"dev": "vite --host --clearScreen false",
|
"dev": "vite --host --clearScreen false",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"build:rodax": "tsc && vite build --mode rodax",
|
"build:rodax": "tsc && vite build --mode rodax",
|
||||||
@ -24,13 +25,13 @@
|
|||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"date-fns": "^4.1.0",
|
|
||||||
"typescript": "~6.0.2",
|
"typescript": "~6.0.2",
|
||||||
"vite": "^8.0.8"
|
"vite": "^8.0.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@erp/auth": "workspace:*",
|
"@erp/auth": "workspace:*",
|
||||||
"@erp/core": "workspace:*",
|
"@erp/core": "workspace:*",
|
||||||
|
"@erp/catalogs": "workspace:*",
|
||||||
"@erp/customer-invoices": "workspace:*",
|
"@erp/customer-invoices": "workspace:*",
|
||||||
"@erp/customers": "workspace:*",
|
"@erp/customers": "workspace:*",
|
||||||
"@fontsource-variable/geist": "^5.2.8",
|
"@fontsource-variable/geist": "^5.2.8",
|
||||||
@ -41,6 +42,7 @@
|
|||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"@tanstack/react-query": "^5.98.0",
|
"@tanstack/react-query": "^5.98.0",
|
||||||
"axios": "^1.15.0",
|
"axios": "^1.15.0",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"dinero.js": "1.9.1",
|
"dinero.js": "1.9.1",
|
||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { AuthModuleManifiest } from "@erp/auth/client";
|
import { AuthModuleManifest } from "@erp/auth/client";
|
||||||
import CoreModuleManifiest, { type IModuleClient } from "@erp/core/client";
|
import { CatalogsModuleManifest } from "@erp/catalogs/client";
|
||||||
import { CustomerInvoicesModuleManifiest } from "@erp/customer-invoices/client";
|
import CoreModuleManifest, { type IModuleClient } from "@erp/core/client";
|
||||||
import { CustomersModuleManifiest } from "@erp/customers/client";
|
import { CustomerInvoicesModuleManifest } from "@erp/customer-invoices/client";
|
||||||
|
import { CustomersModuleManifest } from "@erp/customers/client";
|
||||||
|
|
||||||
export const modules: IModuleClient[] = [
|
export const modules: IModuleClient[] = [
|
||||||
AuthModuleManifiest,
|
AuthModuleManifest,
|
||||||
CoreModuleManifiest,
|
CoreModuleManifest,
|
||||||
CustomersModuleManifiest,
|
CatalogsModuleManifest,
|
||||||
CustomerInvoicesModuleManifiest,
|
CustomersModuleManifest,
|
||||||
|
CustomerInvoicesModuleManifest,
|
||||||
];
|
];
|
||||||
|
|||||||
10
biome.json
10
biome.json
@ -10,11 +10,6 @@
|
|||||||
"ignoreUnknown": true,
|
"ignoreUnknown": true,
|
||||||
"includes": [
|
"includes": [
|
||||||
"**",
|
"**",
|
||||||
"!!**/supplier-invoices",
|
|
||||||
"!!**/suppliers",
|
|
||||||
"!!**/auth",
|
|
||||||
"!!**/rdx-criteria",
|
|
||||||
"!!**/shadcn-ui",
|
|
||||||
"!!**/node_modules",
|
"!!**/node_modules",
|
||||||
"!!**/.next",
|
"!!**/.next",
|
||||||
"!!**/dist",
|
"!!**/dist",
|
||||||
@ -50,10 +45,7 @@
|
|||||||
"noInferrableTypes": "error",
|
"noInferrableTypes": "error",
|
||||||
"noNamespace": "error",
|
"noNamespace": "error",
|
||||||
"noNegationElse": "warn",
|
"noNegationElse": "warn",
|
||||||
"noNonNullAssertion": {
|
"noNonNullAssertion": "info",
|
||||||
"level": "info",
|
|
||||||
"fix": "none"
|
|
||||||
},
|
|
||||||
"noParameterAssign": "error",
|
"noParameterAssign": "error",
|
||||||
"noUnusedTemplateLiteral": "error",
|
"noUnusedTemplateLiteral": "error",
|
||||||
"noUselessElse": "warn",
|
"noUselessElse": "warn",
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
"react-dom": "^19.2.5",
|
"react-dom": "^19.2.5",
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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 passport from "passport";
|
||||||
import { ExtractJwt, Strategy as JwtStrategy } from "passport-jwt";
|
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,
|
IAuthService,
|
||||||
ITabContextService,
|
ITabContextService,
|
||||||
} from "../../../../../../apps/server/archive/contexts/auth/domain/services";
|
} 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";
|
const SECRET_KEY = process.env.JWT_SECRET || "supersecretkey";
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ export class PassportAuthProvider {
|
|||||||
const checkUserId = user.id.equals(userIdVO.data);
|
const checkUserId = user.id.equals(userIdVO.data);
|
||||||
const checkRoles = true; //user.hasRoles(roles);
|
const checkRoles = true; //user.hasRoles(roles);
|
||||||
|
|
||||||
if (!checkUserId || !checkRoles) {
|
if (!(checkUserId && checkRoles)) {
|
||||||
return Result.fail(new Error("Invalid token data"));
|
return Result.fail(new Error("Invalid token data"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { AuthRoutes } from "./auth-routes";
|
|||||||
const MODULE_NAME = "auth";
|
const MODULE_NAME = "auth";
|
||||||
const MODULE_VERSION = "1.0.0";
|
const MODULE_VERSION = "1.0.0";
|
||||||
|
|
||||||
export const AuthModuleManifiest: IModuleClient = {
|
export const AuthModuleManifest: IModuleClient = {
|
||||||
name: MODULE_NAME,
|
name: MODULE_NAME,
|
||||||
version: MODULE_VERSION,
|
version: MODULE_VERSION,
|
||||||
dependencies: ["core"],
|
dependencies: ["core"],
|
||||||
@ -22,4 +22,4 @@ export const AuthModuleManifiest: IModuleClient = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthModuleManifiest;
|
export default AuthModuleManifest;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/catalogs",
|
"name": "@erp/catalogs",
|
||||||
"description": "Catalogs module",
|
"description": "Catalogs module",
|
||||||
"version": "0.1.0",
|
"version": "0.6.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
@ -12,21 +12,45 @@
|
|||||||
"clean": "rimraf .turbo node_modules dist"
|
"clean": "rimraf .turbo node_modules dist"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/api/index.ts",
|
".": "./src/common/index.ts",
|
||||||
"./api": "./src/api/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": {
|
"peerDependencies": {
|
||||||
"@erp/core": "workspace:*",
|
"react": "^19.2.5",
|
||||||
"@erp/auth": "workspace:*",
|
"react-dom": "^19.2.5"
|
||||||
"@repo/rdx-ddd": "workspace:*",
|
|
||||||
"@repo/rdx-utils": "workspace:*",
|
|
||||||
"@repo/rdx-criteria": "workspace:*",
|
|
||||||
"express": "^4.22.1",
|
|
||||||
"sequelize": "^6.37.8",
|
|
||||||
"zod": "^4.3.6"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.21",
|
"@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"
|
"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 "./errors";
|
||||||
export * from "./payment-method.aggregate";
|
export * from "./payment-method.aggregate";
|
||||||
export * from "./payment-method-name";
|
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;
|
this.props.isActive = true;
|
||||||
return Result.ok(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";
|
} from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { PaymentMethodSummary } from "../../../../../application/payment-methods/models";
|
import type { PaymentMethodSummary } from "../../../../../application";
|
||||||
import type { PaymentMethodModel } from "../models";
|
import type { PaymentMethodModel } from "../models";
|
||||||
|
|
||||||
export class SequelizePaymentMethodSummaryMapper extends SequelizeQueryMapper<
|
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";
|
import { PaymentMethodSummarySchema } from "../shared";
|
||||||
|
|
||||||
export const ListPaymentMethodsResponseSchema = z.array(PaymentMethodSummarySchema);
|
export const ListPaymentMethodsResponseSchema = createPaginatedListSchema(
|
||||||
|
PaymentMethodSummarySchema
|
||||||
|
);
|
||||||
export type ListPaymentMethodsResponseDTO = z.infer<typeof ListPaymentMethodsResponseSchema>;
|
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";
|
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>;
|
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"],
|
"lib": ["ES2022"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
|
|||||||
@ -31,6 +31,7 @@
|
|||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
"react-dom": "^19.2.5",
|
"react-dom": "^19.2.5",
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { IModuleClient, ModuleClientParams } from "./lib";
|
|||||||
export const MODULE_NAME = "Core";
|
export const MODULE_NAME = "Core";
|
||||||
const MODULE_VERSION = "1.0.0";
|
const MODULE_VERSION = "1.0.0";
|
||||||
|
|
||||||
export const CoreModuleManifiest: IModuleClient = {
|
export const CoreModuleManifest: IModuleClient = {
|
||||||
name: MODULE_NAME,
|
name: MODULE_NAME,
|
||||||
version: MODULE_VERSION,
|
version: MODULE_VERSION,
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
@ -15,6 +15,6 @@ export const CoreModuleManifiest: IModuleClient = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CoreModuleManifiest;
|
export default CoreModuleManifest;
|
||||||
|
|
||||||
export * from "./lib";
|
export * from "./lib";
|
||||||
|
|||||||
@ -29,8 +29,10 @@
|
|||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
"react-dom": "^19.2.5",
|
"react-dom": "^19.2.5",
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -39,6 +41,7 @@
|
|||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@erp/auth": "workspace:*",
|
"@erp/auth": "workspace:*",
|
||||||
"@erp/core": "workspace:*",
|
"@erp/core": "workspace:*",
|
||||||
|
"@erp/catalogs": "workspace:*",
|
||||||
"@erp/customers": "workspace:*",
|
"@erp/customers": "workspace:*",
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@lglab/react-qr-code": "^1.4.10",
|
"@lglab/react-qr-code": "^1.4.10",
|
||||||
@ -52,7 +55,6 @@
|
|||||||
"@tanstack/react-query": "^5.98.0",
|
"@tanstack/react-query": "^5.98.0",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"date-fns": "^4.1.0",
|
|
||||||
"dinero.js": "1.9.1",
|
"dinero.js": "1.9.1",
|
||||||
"express": "^4.22.1",
|
"express": "^4.22.1",
|
||||||
"libphonenumber-js": "^1.12.41",
|
"libphonenumber-js": "^1.12.41",
|
||||||
|
|||||||
@ -46,6 +46,7 @@ export const CreateProformaRequestSchema = z.object({
|
|||||||
global_discount_percentage: PercentageSchema,
|
global_discount_percentage: PercentageSchema,
|
||||||
|
|
||||||
payment_method_id: z.uuid().nullable().optional(),
|
payment_method_id: z.uuid().nullable().optional(),
|
||||||
|
payment_term_id: z.uuid().nullable().optional(),
|
||||||
|
|
||||||
items: z.array(CreateProformaItemRequestSchema),
|
items: z.array(CreateProformaItemRequestSchema),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export const UpdateProformaByIdRequestSchema = z.object({
|
|||||||
global_discount_percentage: PercentageSchema.optional(),
|
global_discount_percentage: PercentageSchema.optional(),
|
||||||
|
|
||||||
payment_method_id: z.uuid().nullable().optional(),
|
payment_method_id: z.uuid().nullable().optional(),
|
||||||
|
payment_term_id: z.uuid().nullable().optional(),
|
||||||
|
|
||||||
// retención como código??? retencion_15
|
// retención como código??? retencion_15
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
} from "@erp/core";
|
} from "@erp/core";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { PaymentMethodRefSchema, TaxesBreakdownSchema } from "../../shared";
|
import { PaymentMethodRefSchema, PaymentTermRefSchema, TaxesBreakdownSchema } from "../../shared";
|
||||||
import {
|
import {
|
||||||
ProformaItemDetailSchema,
|
ProformaItemDetailSchema,
|
||||||
ProformaRecipientSummarySchema,
|
ProformaRecipientSummarySchema,
|
||||||
@ -41,6 +41,7 @@ export const GetProformaByIdResponseSchema = z.object({
|
|||||||
taxes: z.array(TaxesBreakdownSchema),
|
taxes: z.array(TaxesBreakdownSchema),
|
||||||
|
|
||||||
payment_method: PaymentMethodRefSchema.nullable(),
|
payment_method: PaymentMethodRefSchema.nullable(),
|
||||||
|
payment_term: PaymentTermRefSchema.nullable(),
|
||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
items_discount_amount: MoneySchema,
|
items_discount_amount: MoneySchema,
|
||||||
|
|||||||
@ -4,5 +4,4 @@ import type { z } from "zod/v4";
|
|||||||
import { ProformaSummarySchema } from "../../shared/proforma";
|
import { ProformaSummarySchema } from "../../shared/proforma";
|
||||||
|
|
||||||
export const ListProformasResponseSchema = createPaginatedListSchema(ProformaSummarySchema);
|
export const ListProformasResponseSchema = createPaginatedListSchema(ProformaSummarySchema);
|
||||||
|
|
||||||
export type ListProformasResponseDTO = z.infer<typeof ListProformasResponseSchema>;
|
export type ListProformasResponseDTO = z.infer<typeof ListProformasResponseSchema>;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
export * from "./issued-invoices";
|
export * from "./issued-invoices";
|
||||||
export * from "./item-position.dto";
|
export * from "./item-position.dto";
|
||||||
export * from "./payment-method-ref.dto";
|
export * from "./payment-method-ref.dto";
|
||||||
|
export * from "./payment-term-ref.dto";
|
||||||
export * from "./proforma";
|
export * from "./proforma";
|
||||||
export * from "./tax-combination-code.dto";
|
export * from "./tax-combination-code.dto";
|
||||||
export * from "./taxes-breakdown.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";
|
export const MODULE_NAME = "CustomerInvoices";
|
||||||
const MODULE_VERSION = "1.0.0";
|
const MODULE_VERSION = "1.0.0";
|
||||||
|
|
||||||
export const CustomerInvoicesModuleManifiest: IModuleClient = {
|
export const CustomerInvoicesModuleManifest: IModuleClient = {
|
||||||
name: MODULE_NAME,
|
name: MODULE_NAME,
|
||||||
version: MODULE_VERSION,
|
version: MODULE_VERSION,
|
||||||
dependencies: ["auth", "Core", "Customers"],
|
dependencies: ["auth", "Core", "Catalogs", "Customers"],
|
||||||
protected: true,
|
protected: true,
|
||||||
layout: "app",
|
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";
|
export * from "./proforma-fiscal-options.utils";
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export * from "./use-update-proforma-controller";
|
export * from "./use-update-proforma-controller";
|
||||||
export * from "./use-update-proforma-items-controller";
|
export * from "./use-update-proforma-items-controller";
|
||||||
export * from "./use-update-proforma-page-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-tax-controller";
|
||||||
export * from "./use-update-proforma-totals-controller";
|
export * from "./use-update-proforma-totals-controller";
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import {
|
|||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
import { useUpdateProformaItemsController } from "./use-update-proforma-items-controller";
|
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 { useUpdateProformaTaxController } from "./use-update-proforma-tax-controller";
|
||||||
import { useUpdateProformaTotalsController } from "./use-update-proforma-totals-controller";
|
import { useUpdateProformaTotalsController } from "./use-update-proforma-totals-controller";
|
||||||
|
|
||||||
@ -251,6 +252,8 @@ export const useUpdateProformaController = (
|
|||||||
form,
|
form,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const paymentCtrl = useUpdateProformaPaymentController();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// form
|
// form
|
||||||
formId,
|
formId,
|
||||||
@ -259,6 +262,7 @@ export const useUpdateProformaController = (
|
|||||||
itemsCtrl,
|
itemsCtrl,
|
||||||
taxCtrl,
|
taxCtrl,
|
||||||
totalsCtrl,
|
totalsCtrl,
|
||||||
|
paymentCtrl,
|
||||||
|
|
||||||
//
|
//
|
||||||
currencyCode,
|
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;
|
retentionPercentage: number | null;
|
||||||
|
|
||||||
paymentMethodId: string | null;
|
paymentMethodId: string | null;
|
||||||
|
paymentTermId: string | null;
|
||||||
|
|
||||||
items: ProformaItemUpdateForm[];
|
items: ProformaItemUpdateForm[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,7 @@ export const ProformaUpdateFormSchema = z
|
|||||||
retentionPercentage: z.number().nullable(),
|
retentionPercentage: z.number().nullable(),
|
||||||
|
|
||||||
paymentMethodId: z.string().nullable(),
|
paymentMethodId: z.string().nullable(),
|
||||||
|
paymentTermId: z.string().nullable(),
|
||||||
|
|
||||||
items: z.array(ProformaItemUpdateFormSchema).min(1),
|
items: z.array(ProformaItemUpdateFormSchema).min(1),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -38,6 +38,7 @@ export type ProformaUpdatePatch = {
|
|||||||
retentionPercentage?: number | null;
|
retentionPercentage?: number | null;
|
||||||
|
|
||||||
paymentMethodId?: string | null;
|
paymentMethodId?: string | null;
|
||||||
|
paymentTermId?: string | null;
|
||||||
|
|
||||||
languageCode?: string;
|
languageCode?: string;
|
||||||
currencyCode?: string;
|
currencyCode?: string;
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
|
export * from "./proforma-info-alert";
|
||||||
export * from "./proforma-update-skeleton";
|
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-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-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 { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers";
|
||||||
import { Button } from "@repo/shadcn-ui/components";
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
import { ProformaUpdateRecipientEditor } from ".";
|
import {
|
||||||
|
ProformaUpdatePaymentEditor,
|
||||||
|
ProformaUpdateRecipientEditor,
|
||||||
|
ProformaUpdateSettingsEditor,
|
||||||
|
} from ".";
|
||||||
|
|
||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
import type {
|
import type {
|
||||||
UseUpdateProformaItemsControllerResult,
|
UseUpdateProformaItemsControllerResult,
|
||||||
|
UseUpdateProformaPaymentControllerResult,
|
||||||
UseUpdateProformaTaxControllerResult,
|
UseUpdateProformaTaxControllerResult,
|
||||||
UseUpdateProformaTotalsControllerResult,
|
UseUpdateProformaTotalsControllerResult,
|
||||||
} from "../../controllers";
|
} from "../../controllers";
|
||||||
import { ProformaTotalsSummary } from "../blocks";
|
import { ProformaTotalsSummary } from "../blocks";
|
||||||
|
import { ProformaInfoAlert } from "../components";
|
||||||
|
|
||||||
import { ProformaUpdateHeaderEditor } from "./proforma-update-header-editor";
|
import { ProformaUpdateHeaderEditor } from "./proforma-update-header-editor";
|
||||||
import { ProformaUpdateItemsEditor } from "./proforma-update-items-editor";
|
import { ProformaUpdateItemsEditor } from "./proforma-update-items-editor";
|
||||||
@ -32,6 +38,7 @@ type ProformaUpdateEditorProps = {
|
|||||||
itemsCtrl: UseUpdateProformaItemsControllerResult;
|
itemsCtrl: UseUpdateProformaItemsControllerResult;
|
||||||
taxCtrl: UseUpdateProformaTaxControllerResult;
|
taxCtrl: UseUpdateProformaTaxControllerResult;
|
||||||
totalsCtrl: UseUpdateProformaTotalsControllerResult;
|
totalsCtrl: UseUpdateProformaTotalsControllerResult;
|
||||||
|
paymentCtrl: UseUpdateProformaPaymentControllerResult;
|
||||||
|
|
||||||
currencyCode?: string;
|
currencyCode?: string;
|
||||||
languageCode?: string;
|
languageCode?: string;
|
||||||
@ -48,6 +55,7 @@ export const ProformaUpdateEditorForm = ({
|
|||||||
itemsCtrl,
|
itemsCtrl,
|
||||||
taxCtrl,
|
taxCtrl,
|
||||||
totalsCtrl,
|
totalsCtrl,
|
||||||
|
paymentCtrl,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
languageCode,
|
languageCode,
|
||||||
}: ProformaUpdateEditorProps) => {
|
}: ProformaUpdateEditorProps) => {
|
||||||
@ -55,43 +63,64 @@ export const ProformaUpdateEditorForm = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="space-y-6"
|
className="space-y-6 space-x-6 2xl:space-y-12"
|
||||||
id={formId}
|
id={formId}
|
||||||
noValidate
|
noValidate
|
||||||
onKeyDown={preventEnterKeySubmitForm}
|
onKeyDown={preventEnterKeySubmitForm}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-12">
|
<ProformaInfoAlert
|
||||||
<ProformaUpdateHeaderEditor className="xl:col-span-9" disabled={isSubmitting} />
|
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
|
<ProformaUpdateRecipientEditor
|
||||||
className="xl:col-span-3"
|
className="xl:col-span-4"
|
||||||
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
|
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
inputClassName="bg-background"
|
onChangeCustomerClick={onChangeCustomerClick}
|
||||||
label={t("proformas.update.totals.globalDiscountPercentage", "Descuento global")}
|
onCreateCustomerClick={onCreateCustomerClick}
|
||||||
name="globalDiscountPercentage"
|
selectedCustomer={selectedCustomer}
|
||||||
/>
|
/>
|
||||||
}
|
</div>
|
||||||
showRec={taxCtrl.hasRecPercentage}
|
|
||||||
showRetention={taxCtrl.hasRetentionPercentage}
|
<ProformaUpdateItemsEditor
|
||||||
totals={totalsCtrl.totals}
|
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>
|
||||||
|
|
||||||
<div className="flex flex-col-reverse gap-3 border-t pt-4 sm:flex-row sm:justify-end">
|
<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
|
<DatePickerField
|
||||||
className="md:col-span-3 md:col-start-1"
|
className="md:col-span-3 md:col-start-1"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@ -5,21 +5,6 @@ import {
|
|||||||
SwitchField,
|
SwitchField,
|
||||||
} from "@repo/rdx-ui/components";
|
} from "@repo/rdx-ui/components";
|
||||||
import { PercentageHelper } from "@repo/rdx-utils";
|
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 { ReceiptTextIcon } from "lucide-react";
|
||||||
|
|
||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
@ -59,199 +44,159 @@ export const ProformaUpdateTaxEditor = ({
|
|||||||
icon={<ReceiptTextIcon className="size-5" />}
|
icon={<ReceiptTextIcon className="size-5" />}
|
||||||
title={t("form_groups.proformas.taxes.title", "Impuestos")}
|
title={t("form_groups.proformas.taxes.title", "Impuestos")}
|
||||||
>
|
>
|
||||||
<FieldSet>
|
<FormSectionGrid>
|
||||||
<FieldLegend className="text-primary">1. Régimen fiscal</FieldLegend>
|
<SelectField
|
||||||
<FieldDescription>
|
className="col-span-full"
|
||||||
Selecciona el régimen fiscal que aplica a esta proforma.
|
disabled={disabled}
|
||||||
</FieldDescription>
|
inputClassName="bg-background"
|
||||||
<FormSectionGrid>
|
items={[
|
||||||
<Field className="md:col-span-12 md:col-start-1" orientation="horizontal">
|
{ value: "01", label: "01: Operación de régimen general." },
|
||||||
<SelectField
|
{ value: "02", label: "02: Exportación." },
|
||||||
className="md:col-span-4 md:col-start-1"
|
{
|
||||||
disabled={disabled}
|
value: "03",
|
||||||
items={[
|
label:
|
||||||
{ value: "01", label: "01: Operación de régimen general." },
|
"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: "02", label: "02: Exportación." },
|
},
|
||||||
{
|
{ value: "04", label: "04: Régimen especial del oro de inversión." },
|
||||||
value: "03",
|
{ value: "05", label: "05: Régimen especial de las agencias de viajes." },
|
||||||
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: "06",
|
||||||
},
|
label: "06: Régimen especial grupo de entidades en IVA o IGIC (Nivel Avanzado)",
|
||||||
{ 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: "07", label: "07: Régimen especial del criterio de caja." },
|
||||||
{
|
{ value: "08", label: "08: Operaciones sujetas al IPSI/IVA o IGIC." },
|
||||||
value: "06",
|
{
|
||||||
label: "06: Régimen especial grupo de entidades en IVA o IGIC (Nivel Avanzado)",
|
value: "09",
|
||||||
},
|
label:
|
||||||
{ value: "07", label: "07: Régimen especial del criterio de caja." },
|
"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: "08", label: "08: Operaciones sujetas al IPSI/IVA o IGIC." },
|
},
|
||||||
{
|
{
|
||||||
value: "09",
|
value: "10",
|
||||||
label:
|
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)",
|
"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: "10",
|
{
|
||||||
label:
|
value: "14",
|
||||||
"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.",
|
label:
|
||||||
},
|
"14: Factura con IVA o IGIC pendiente de devengo en certificaciones de obra cuyo destinatario sea una Administración Pública.",
|
||||||
{ value: "11", label: "11: Operaciones de arrendamiento de local de negocio." },
|
},
|
||||||
{
|
{
|
||||||
value: "14",
|
value: "15",
|
||||||
label:
|
label:
|
||||||
"14: Factura con IVA o IGIC pendiente de devengo en certificaciones de obra cuyo destinatario sea una Administración Pública.",
|
"15: Factura con IVA o IGIC pendiente de devengo en operaciones de tracto sucesivo.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "15",
|
value: "17",
|
||||||
label:
|
label:
|
||||||
"15: Factura con IVA o IGIC pendiente de devengo en operaciones de tracto sucesivo.",
|
"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: "17",
|
value: "18",
|
||||||
label:
|
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",
|
"18: Recargo de equivalencia o régimen especial del pequeño empresario o profesional.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "18",
|
value: "19",
|
||||||
label:
|
label:
|
||||||
"18: Recargo de equivalencia o régimen especial del pequeño empresario o profesional.",
|
"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" },
|
||||||
value: "19",
|
]}
|
||||||
label:
|
label={t("form_fields.proformas.tax_regime_code.label", "Régimen fiscal")}
|
||||||
"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",
|
name="taxRegimeCode"
|
||||||
},
|
placeholder={t(
|
||||||
{ value: "20", label: "20: Régimen simplificado" },
|
"form_fields.proformas.tax_regime_code.placeholder",
|
||||||
]}
|
"Selecciona el régimen fiscal para esta proforma"
|
||||||
label={t("form_fields.proformas.tax_regime_code.label", "Régimen fiscal")}
|
)}
|
||||||
name="taxRegimeCode"
|
readOnly={readOnly || taxCtrl.usesPerLineTax}
|
||||||
placeholder={t(
|
/>
|
||||||
"form_fields.proformas.tax_regime_code.placeholder",
|
|
||||||
"Selecciona el régimen fiscal para esta proforma"
|
<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
|
{taxCtrl.hasRecPercentage ? (
|
||||||
className={cn(disabled ? "bg-muted text-muted-foreground" : "bg-primary/10 text-primary")}
|
<span className="text-sm text-muted-foreground">
|
||||||
>
|
{PercentageHelper.formatPercent(taxCtrl.recPercentage ?? 0)}
|
||||||
<CardHeader>
|
</span>
|
||||||
<CardTitle>
|
) : null}
|
||||||
{" "}
|
</>
|
||||||
{t(
|
}
|
||||||
"proformas.update.taxes.disable_per_line",
|
name="hasRecPercentage"
|
||||||
"Mismo IVA en todas las líneas de la proforma"
|
onCheckedChange={taxCtrl.updateRecPercentage}
|
||||||
)}
|
readOnly={readOnly}
|
||||||
</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>
|
|
||||||
|
|
||||||
<FormSectionGrid>
|
<SwitchField
|
||||||
<SwitchField
|
className="md:col-span-12 md:col-start-1"
|
||||||
className="md:col-span-12 md:col-start-1"
|
disabled={disabled}
|
||||||
disabled={disabled}
|
label={t("form_fields.proformas.has_retention.label", "Incluir retención/IRPF")}
|
||||||
label={
|
name="hasRetentionPercentage"
|
||||||
<>
|
readOnly={readOnly}
|
||||||
{t(
|
/>
|
||||||
"form_fields.proformas.has_equivalence_surcharge.label",
|
|
||||||
"Recargo de equivalencia"
|
|
||||||
)}
|
|
||||||
|
|
||||||
{taxCtrl.hasRecPercentage ? (
|
<SelectField
|
||||||
<span className="text-sm text-muted-foreground">
|
className="md:col-span-4 md:col-start-1"
|
||||||
{PercentageHelper.formatPercent(taxCtrl.recPercentage ?? 0)}
|
deserialize={(value) => (value === null || value === "" ? null : Number(value))}
|
||||||
</span>
|
disabled={disabled || !taxCtrl.hasRetentionPercentage}
|
||||||
) : null}
|
inputClassName="bg-background"
|
||||||
</>
|
items={getProformaRetentionOptions()}
|
||||||
}
|
label={t("form_fields.proformas.retention_percentage.label", "Retención")}
|
||||||
name="hasRecPercentage"
|
name="retentionPercentage"
|
||||||
onCheckedChange={taxCtrl.updateRecPercentage}
|
onChange={(value) => {
|
||||||
readOnly={readOnly}
|
const parsed = parseProformaRetentionPercentage(value as number | null);
|
||||||
/>
|
if (parsed !== null) {
|
||||||
|
taxCtrl.updateRetentionPercentage(parsed);
|
||||||
<SwitchField
|
}
|
||||||
className="md:col-span-12 md:col-start-1"
|
}}
|
||||||
disabled={disabled}
|
placeholder={t(
|
||||||
label={t("form_fields.proformas.has_retention.label", "Incluir retención/IRPF")}
|
"form_fields.proformas.default_tax_percentage.placeholder",
|
||||||
name="hasRetentionPercentage"
|
"Selecciona IVA"
|
||||||
readOnly={readOnly}
|
)}
|
||||||
/>
|
readOnly={readOnly || taxCtrl.usesPerLineTax}
|
||||||
|
serialize={(value) => (typeof value === "number" ? String(value) : "")}
|
||||||
<SelectField
|
/>
|
||||||
className="md:col-span-4 md:col-start-1"
|
</FormSectionGrid>
|
||||||
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>
|
|
||||||
</FormSectionCard>
|
</FormSectionCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -61,7 +61,7 @@ export const ProformaUpdatePage = () => {
|
|||||||
return (
|
return (
|
||||||
<FormProvider {...updateCtrl.form}>
|
<FormProvider {...updateCtrl.form}>
|
||||||
<UnsavedChangesProvider isDirty={updateCtrl.form.formState.isDirty}>
|
<UnsavedChangesProvider isDirty={updateCtrl.form.formState.isDirty}>
|
||||||
<AppHeader className="mx-auto max-w-7xl space-y-4">
|
<AppHeader className="mx-auto max-w-[100rem]">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
description={t("pages.proformas.update.description")}
|
description={t("pages.proformas.update.description")}
|
||||||
onBackClick={() => navigateBack()}
|
onBackClick={() => navigateBack()}
|
||||||
@ -84,7 +84,7 @@ export const ProformaUpdatePage = () => {
|
|||||||
/>
|
/>
|
||||||
</AppHeader>
|
</AppHeader>
|
||||||
|
|
||||||
<AppContent className="mx-auto max-w-7xl space-y-4">
|
<AppContent className="mx-auto max-w-[100rem]">
|
||||||
{updateCtrl.isUpdateError && (
|
{updateCtrl.isUpdateError && (
|
||||||
<ErrorAlert
|
<ErrorAlert
|
||||||
message={
|
message={
|
||||||
@ -105,6 +105,7 @@ export const ProformaUpdatePage = () => {
|
|||||||
onCreateCustomerClick={() => null}
|
onCreateCustomerClick={() => null}
|
||||||
onReset={updateCtrl.resetForm}
|
onReset={updateCtrl.resetForm}
|
||||||
onSubmit={updateCtrl.onSubmit}
|
onSubmit={updateCtrl.onSubmit}
|
||||||
|
paymentCtrl={updateCtrl.paymentCtrl}
|
||||||
selectedCustomer={updateCtrl.selectedCustomer}
|
selectedCustomer={updateCtrl.selectedCustomer}
|
||||||
taxCtrl={updateCtrl.taxCtrl}
|
taxCtrl={updateCtrl.taxCtrl}
|
||||||
totalsCtrl={updateCtrl.totalsCtrl}
|
totalsCtrl={updateCtrl.totalsCtrl}
|
||||||
|
|||||||
@ -80,6 +80,10 @@ export const buildUpdateProformaByIdParams = (
|
|||||||
data.payment_method_id = patch.paymentMethodId;
|
data.payment_method_id = patch.paymentMethodId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ObjectHelper.hasOwn(patch, "paymentTermId")) {
|
||||||
|
data.payment_term_id = patch.paymentTermId;
|
||||||
|
}
|
||||||
|
|
||||||
if (ObjectHelper.hasOwn(patch, "globalDiscountPercentage")) {
|
if (ObjectHelper.hasOwn(patch, "globalDiscountPercentage")) {
|
||||||
data.global_discount_percentage = PercentageDTOHelper.fromNumber(
|
data.global_discount_percentage = PercentageDTOHelper.fromNumber(
|
||||||
patch.globalDiscountPercentage!,
|
patch.globalDiscountPercentage!,
|
||||||
|
|||||||
@ -30,6 +30,7 @@
|
|||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
"react-dom": "^19.2.5",
|
"react-dom": "^19.2.5",
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -47,7 +48,6 @@
|
|||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"express": "^4.22.1",
|
"express": "^4.22.1",
|
||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
"react-data-table-component": "^7.7.0",
|
|
||||||
"react-hook-form": "^7.72.1",
|
"react-hook-form": "^7.72.1",
|
||||||
"react-i18next": "^17.0.2",
|
"react-i18next": "^17.0.2",
|
||||||
"react-router-dom": "^7.14.0",
|
"react-router-dom": "^7.14.0",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { CustomerRoutes } from "./customer-routes";
|
|||||||
export const MODULE_NAME = "Customers";
|
export const MODULE_NAME = "Customers";
|
||||||
const MODULE_VERSION = "1.0.0";
|
const MODULE_VERSION = "1.0.0";
|
||||||
|
|
||||||
export const CustomersModuleManifiest: IModuleClient = {
|
export const CustomersModuleManifest: IModuleClient = {
|
||||||
name: MODULE_NAME,
|
name: MODULE_NAME,
|
||||||
version: MODULE_VERSION,
|
version: MODULE_VERSION,
|
||||||
dependencies: ["auth", "Core"],
|
dependencies: ["auth", "Core"],
|
||||||
@ -17,4 +17,4 @@ export const CustomersModuleManifiest: IModuleClient = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CustomersModuleManifiest;
|
export default CustomersModuleManifest;
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -498,3 +498,4 @@ export class SequelizeSupplierInvoiceDomainMapper extends SequelizeDomainMapper<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11111111
|
||||||
@ -30,6 +30,7 @@
|
|||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
"react-dom": "^19.2.5",
|
"react-dom": "^19.2.5",
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -47,7 +48,6 @@
|
|||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"express": "^4.22.1",
|
"express": "^4.22.1",
|
||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
"react-data-table-component": "^7.7.0",
|
|
||||||
"react-hook-form": "^7.72.1",
|
"react-hook-form": "^7.72.1",
|
||||||
"react-i18next": "^17.0.2",
|
"react-i18next": "^17.0.2",
|
||||||
"react-router-dom": "^7.14.0",
|
"react-router-dom": "^7.14.0",
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo build",
|
"build": "turbo run build",
|
||||||
"build:templates": "bash scripts/build-templates.sh",
|
"build:templates": "bash scripts/build-templates.sh",
|
||||||
"build:api": "bash scripts/build-api.sh rodax --api",
|
"build:api": "bash scripts/build-api.sh rodax --api",
|
||||||
"dev": "turbo dev",
|
"dev": "turbo dev",
|
||||||
@ -24,7 +24,7 @@
|
|||||||
"format:check": "biome format .",
|
"format:check": "biome format .",
|
||||||
"check": "biome check .",
|
"check": "biome check .",
|
||||||
"check:write": "biome check --write .",
|
"check:write": "biome check --write .",
|
||||||
"typecheck": "turbo run typecheck"
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.4.11",
|
"@biomejs/biome": "2.4.11",
|
||||||
@ -39,7 +39,7 @@
|
|||||||
"rimraf": "^6.1.3",
|
"rimraf": "^6.1.3",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.2.2",
|
||||||
"turbo": "^2.9.6",
|
"turbo": "^2.9.14",
|
||||||
"typescript": "6.0.2"
|
"typescript": "6.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@ -12,15 +12,16 @@
|
|||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/i18n.ts"
|
".": "./src/i18n.ts"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@repo/typescript-config": "workspace:*",
|
||||||
|
"@types/node": "^25.6.0",
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
|
"typescript": "^6.0.2"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"i18next": "26.0.4",
|
"i18next": "26.0.4",
|
||||||
"i18next-browser-languagedetector": "^8.2.1",
|
"i18next-browser-languagedetector": "^8.2.1",
|
||||||
"i18next-http-backend": "^3.0.4",
|
"i18next-http-backend": "^3.0.4",
|
||||||
"react-i18next": "^17.0.2"
|
"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:*",
|
"@repo/typescript-config": "workspace:*",
|
||||||
"@types/dinero.js": "1.9.1",
|
"@types/dinero.js": "1.9.1",
|
||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -5,9 +5,11 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"ui:lint": "biome lint --fix",
|
"ui:lint": "biome lint --fix",
|
||||||
"check": "biome check .",
|
"check": "biome check .",
|
||||||
"lint": "biome lint ."
|
"lint": "biome lint .",
|
||||||
|
"clean": "rimraf .turbo node_modules dist"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
"./helpers": "./src/helpers/index.ts",
|
"./helpers": "./src/helpers/index.ts",
|
||||||
@ -20,10 +22,6 @@
|
|||||||
"./src/hooks/index.ts"
|
"./src/hooks/index.ts"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^19.2.5",
|
|
||||||
"react-dom": "^19.2.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.4.11",
|
"@biomejs/biome": "^2.4.11",
|
||||||
"@repo/i18next": "workspace:*",
|
"@repo/i18next": "workspace:*",
|
||||||
@ -34,10 +32,10 @@
|
|||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"esbuild-plugin-react18": "^0.2.6",
|
"esbuild-plugin-react18": "^0.2.6",
|
||||||
"esbuild-plugin-react18-css": "^0.0.4",
|
"esbuild-plugin-react18-css": "^0.0.4",
|
||||||
"react": "^19.2.5",
|
"rimraf": "^6.1.3",
|
||||||
"react-dom": "^19.2.5",
|
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.2.2",
|
||||||
"tsup": "^8.5.1",
|
"tsup": "^8.5.1",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
@ -58,6 +56,8 @@
|
|||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
"esbuild-raw-plugin": "^0.3.1",
|
"esbuild-raw-plugin": "^0.3.1",
|
||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
|
"react": "^19.2.5",
|
||||||
|
"react-dom": "^19.2.5",
|
||||||
"react-hook-form": "^7.72.1",
|
"react-hook-form": "^7.72.1",
|
||||||
"react-i18next": "^17.0.2",
|
"react-i18next": "^17.0.2",
|
||||||
"react-router": "^7.14.0",
|
"react-router": "^7.14.0",
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import { FieldLabel } from "@repo/shadcn-ui/components";
|
import { FieldLabel } from "@repo/shadcn-ui/components";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
interface FormFieldLabelProps extends React.ComponentProps<typeof FieldLabel> {
|
interface FormFieldLabelProps extends React.ComponentProps<typeof FieldLabel> {
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
optional?: boolean;
|
optional?: boolean;
|
||||||
|
className?: string;
|
||||||
|
htmlFor?: string;
|
||||||
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FormFieldLabel = ({
|
export const FormFieldLabel = ({
|
||||||
|
|||||||
@ -33,28 +33,30 @@ export const FormSectionCard = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={className}>
|
<Card className={className}>
|
||||||
<CardHeader className={headerClassName}>
|
{hasHeader && (
|
||||||
<div className="flex items-start gap-3">
|
<CardHeader className={headerClassName}>
|
||||||
{icon ? (
|
<div className="flex items-start gap-3">
|
||||||
<div
|
{icon ? (
|
||||||
className={cn(
|
<div
|
||||||
"flex size-12 shrink-0 items-center justify-center rounded-md",
|
className={cn(
|
||||||
disabled ? "bg-muted text-muted-foreground" : "bg-primary/10 text-primary"
|
"flex size-12 shrink-0 items-center justify-center rounded-md",
|
||||||
)}
|
disabled ? "bg-muted text-muted-foreground" : "bg-primary/10 text-primary"
|
||||||
>
|
)}
|
||||||
{" "}
|
>
|
||||||
{icon}{" "}
|
{" "}
|
||||||
</div>
|
{icon}{" "}
|
||||||
) : null}
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
{title ? <CardTitle className="font-semibold">{title}</CardTitle> : null}
|
|
||||||
{description ? (
|
|
||||||
<CardDescription className="font-medium">{description}</CardDescription>
|
|
||||||
) : null}
|
) : 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>
|
||||||
</div>
|
</CardHeader>
|
||||||
</CardHeader>
|
)}
|
||||||
|
|
||||||
<CardContent className={contentClassName}>{children}</CardContent>
|
<CardContent className={contentClassName}>{children}</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -13,14 +13,14 @@ import { Controller, type FieldPath, type FieldValues, useFormContext } from "re
|
|||||||
|
|
||||||
import { FormFieldLabel } from "./form-field-label.tsx";
|
import { FormFieldLabel } from "./form-field-label.tsx";
|
||||||
|
|
||||||
interface SelectFieldItem {
|
export type SelectFieldItem = {
|
||||||
value: string;
|
value: string;
|
||||||
label: 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>;
|
name: FieldPath<TFormValues>;
|
||||||
|
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -104,7 +104,7 @@ export function SelectField<TFormValues extends FieldValues>({
|
|||||||
|
|
||||||
<Select
|
<Select
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value: any) =>
|
||||||
onChange &&
|
onChange &&
|
||||||
onChange(deserializeValue(value)) &&
|
onChange(deserializeValue(value)) &&
|
||||||
field.onChange(deserializeValue(value))
|
field.onChange(deserializeValue(value))
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@repo/typescript-config": "workspace:*",
|
"@repo/typescript-config": "workspace:*",
|
||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
|
"rimraf": "^6.1.3",
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -6,12 +6,12 @@ const config = {
|
|||||||
"apps/**/*.{ts,tsx}",
|
"apps/**/*.{ts,tsx}",
|
||||||
"../../packages/shadcn-ui/src/**/*.{ts,tsx}",
|
"../../packages/shadcn-ui/src/**/*.{ts,tsx}",
|
||||||
"../../modules/**/*.{ts,tsx}",
|
"../../modules/**/*.{ts,tsx}",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
extends: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@ -9,6 +9,9 @@
|
|||||||
"@erp/auth/*": [
|
"@erp/auth/*": [
|
||||||
"modules/auth/src/*"
|
"modules/auth/src/*"
|
||||||
],
|
],
|
||||||
|
"@erp/catalogs/*": [
|
||||||
|
"modules/catalogs/src/*"
|
||||||
|
],
|
||||||
"@erp/customers/*": [
|
"@erp/customers/*": [
|
||||||
"modules/customers/src/*"
|
"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