Proformas
This commit is contained in:
parent
9b874eebf8
commit
f19ab6022b
@ -1,12 +1,12 @@
|
|||||||
import { Presenter } from "@erp/core/api";
|
import { Presenter } from "@erp/core/api";
|
||||||
|
import type { GetIssuedInvoiceByIdResponseDTO } from "@erp/customer-invoices/common";
|
||||||
import { toEmptyString } from "@repo/rdx-ddd";
|
import { toEmptyString } from "@repo/rdx-ddd";
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
import type { ArrayElement } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { GetIssueInvoiceByIdResponseDTO } from "../../../../common/dto";
|
|
||||||
import type { CustomerInvoiceItem, CustomerInvoiceItems } from "../../../domain";
|
import type { CustomerInvoiceItem, CustomerInvoiceItems } from "../../../domain";
|
||||||
|
|
||||||
type GetCustomerInvoiceItemByInvoiceIdResponseDTO = ArrayElement<
|
type GetCustomerInvoiceItemByInvoiceIdResponseDTO = ArrayElement<
|
||||||
GetIssueInvoiceByIdResponseDTO["items"]
|
GetIssuedInvoiceByIdResponseDTO["items"]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export class CustomerInvoiceItemsFullPresenter extends Presenter {
|
export class CustomerInvoiceItemsFullPresenter extends Presenter {
|
||||||
@ -49,7 +49,7 @@ export class CustomerInvoiceItemsFullPresenter extends Presenter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toOutput(invoiceItems: CustomerInvoiceItems): GetIssueInvoiceByIdResponseDTO["items"] {
|
toOutput(invoiceItems: CustomerInvoiceItems): GetIssuedInvoiceByIdResponseDTO["items"] {
|
||||||
return invoiceItems.map(this._mapItem);
|
return invoiceItems.map(this._mapItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Presenter } from "@erp/core/api";
|
import { Presenter } from "@erp/core/api";
|
||||||
import { toEmptyString } from "@repo/rdx-ddd";
|
import { toEmptyString } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
import type { GetIssueInvoiceByIdResponseDTO } from "../../../../common/dto";
|
import type { GetIssuedInvoiceByIdResponseDTO } from "../../../../common/dto";
|
||||||
import type { CustomerInvoice } from "../../../domain";
|
import type { CustomerInvoice } from "../../../domain";
|
||||||
|
|
||||||
import type { CustomerInvoiceItemsFullPresenter } from "./customer-invoice-items.full.presenter";
|
import type { CustomerInvoiceItemsFullPresenter } from "./customer-invoice-items.full.presenter";
|
||||||
@ -9,9 +9,9 @@ import type { RecipientInvoiceFullPresenter } from "./recipient-invoice.full.rep
|
|||||||
|
|
||||||
export class CustomerInvoiceFullPresenter extends Presenter<
|
export class CustomerInvoiceFullPresenter extends Presenter<
|
||||||
CustomerInvoice,
|
CustomerInvoice,
|
||||||
GetIssueInvoiceByIdResponseDTO
|
GetIssuedInvoiceByIdResponseDTO
|
||||||
> {
|
> {
|
||||||
toOutput(invoice: CustomerInvoice): GetIssueInvoiceByIdResponseDTO {
|
toOutput(invoice: CustomerInvoice): GetIssuedInvoiceByIdResponseDTO {
|
||||||
const itemsPresenter = this.presenterRegistry.getPresenter({
|
const itemsPresenter = this.presenterRegistry.getPresenter({
|
||||||
resource: "customer-invoice-items",
|
resource: "customer-invoice-items",
|
||||||
projection: "FULL",
|
projection: "FULL",
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Presenter } from "@erp/core/api";
|
import { Presenter } from "@erp/core/api";
|
||||||
import { DomainValidationError, toEmptyString } from "@repo/rdx-ddd";
|
import { DomainValidationError, toEmptyString } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
import type { GetIssueInvoiceByIdResponseDTO } from "../../../../common/dto";
|
import type { GetIssuedInvoiceByIdResponseDTO } from "../../../../common/dto";
|
||||||
import type { CustomerInvoice, InvoiceRecipient } from "../../../domain";
|
import type { CustomerInvoice, InvoiceRecipient } from "../../../domain";
|
||||||
|
|
||||||
type GetRecipientInvoiceByInvoiceIdResponseDTO = GetIssueInvoiceByIdResponseDTO["recipient"];
|
type GetRecipientInvoiceByInvoiceIdResponseDTO = GetIssuedInvoiceByIdResponseDTO["recipient"];
|
||||||
|
|
||||||
export class RecipientInvoiceFullPresenter extends Presenter {
|
export class RecipientInvoiceFullPresenter extends Presenter {
|
||||||
toOutput(invoice: CustomerInvoice): GetRecipientInvoiceByInvoiceIdResponseDTO {
|
toOutput(invoice: CustomerInvoice): GetRecipientInvoiceByInvoiceIdResponseDTO {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
||||||
import { type IPresenterOutputParams, Presenter } from "@erp/core/api";
|
import { type IPresenterOutputParams, Presenter } from "@erp/core/api";
|
||||||
import type { GetIssueInvoiceByIdResponseDTO } from "@erp/customer-invoices/common";
|
import type { GetIssuedInvoiceByIdResponseDTO } from "@erp/customer-invoices/common";
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
import type { ArrayElement } from "@repo/rdx-utils";
|
||||||
|
|
||||||
type CustomerInvoiceItemsDTO = GetIssueInvoiceByIdResponseDTO["items"];
|
type CustomerInvoiceItemsDTO = GetIssuedInvoiceByIdResponseDTO["items"];
|
||||||
type CustomerInvoiceItemDTO = ArrayElement<CustomerInvoiceItemsDTO>;
|
type CustomerInvoiceItemDTO = ArrayElement<CustomerInvoiceItemsDTO>;
|
||||||
|
|
||||||
export class CustomerInvoiceItemsReportPersenter extends Presenter<
|
export class CustomerInvoiceItemsReportPersenter extends Presenter<
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { type JsonTaxCatalogProvider, MoneyDTOHelper, SpainTaxCatalogProvider } from "@erp/core";
|
import { type JsonTaxCatalogProvider, MoneyDTOHelper, SpainTaxCatalogProvider } from "@erp/core";
|
||||||
import { type IPresenterOutputParams, Presenter } from "@erp/core/api";
|
import { type IPresenterOutputParams, Presenter } from "@erp/core/api";
|
||||||
import type { GetIssueInvoiceByIdResponseDTO } from "@erp/customer-invoices/common";
|
import type { GetIssuedInvoiceByIdResponseDTO } from "@erp/customer-invoices/common";
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
import type { ArrayElement } from "@repo/rdx-utils";
|
||||||
|
|
||||||
type CustomerInvoiceTaxesDTO = GetIssueInvoiceByIdResponseDTO["taxes"];
|
type CustomerInvoiceTaxesDTO = GetIssuedInvoiceByIdResponseDTO["taxes"];
|
||||||
type CustomerInvoiceTaxDTO = ArrayElement<CustomerInvoiceTaxesDTO>;
|
type CustomerInvoiceTaxDTO = ArrayElement<CustomerInvoiceTaxesDTO>;
|
||||||
|
|
||||||
export class CustomerInvoiceTaxesReportPresenter extends Presenter<
|
export class CustomerInvoiceTaxesReportPresenter extends Presenter<
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { DateHelper, MoneyDTOHelper, PercentageDTOHelper } from "@erp/core";
|
import { DateHelper, MoneyDTOHelper, PercentageDTOHelper } from "@erp/core";
|
||||||
import { Presenter } from "@erp/core/api";
|
import { Presenter } from "@erp/core/api";
|
||||||
|
|
||||||
import type { GetIssueInvoiceByIdResponseDTO } from "../../../../common/dto";
|
import type { GetIssuedInvoiceByIdResponseDTO } from "../../../../common/dto";
|
||||||
|
|
||||||
export class CustomerInvoiceReportPresenter extends Presenter<
|
export class CustomerInvoiceReportPresenter extends Presenter<
|
||||||
GetIssueInvoiceByIdResponseDTO,
|
GetIssuedInvoiceByIdResponseDTO,
|
||||||
unknown
|
unknown
|
||||||
> {
|
> {
|
||||||
private _formatPaymentMethodDTO(
|
private _formatPaymentMethodDTO(
|
||||||
paymentMethod?: GetIssueInvoiceByIdResponseDTO["payment_method"]
|
paymentMethod?: GetIssuedInvoiceByIdResponseDTO["payment_method"]
|
||||||
) {
|
) {
|
||||||
if (!paymentMethod) {
|
if (!paymentMethod) {
|
||||||
return "";
|
return "";
|
||||||
@ -17,7 +17,7 @@ export class CustomerInvoiceReportPresenter extends Presenter<
|
|||||||
return paymentMethod.payment_description ?? "";
|
return paymentMethod.payment_description ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
toOutput(invoiceDTO: GetIssueInvoiceByIdResponseDTO) {
|
toOutput(invoiceDTO: GetIssuedInvoiceByIdResponseDTO) {
|
||||||
const itemsPresenter = this.presenterRegistry.getPresenter({
|
const itemsPresenter = this.presenterRegistry.getPresenter({
|
||||||
resource: "customer-invoice-items",
|
resource: "customer-invoice-items",
|
||||||
projection: "REPORT",
|
projection: "REPORT",
|
||||||
|
|||||||
@ -3,14 +3,14 @@ import type { Criteria } from "@repo/rdx-criteria/server";
|
|||||||
import { toEmptyString } from "@repo/rdx-ddd";
|
import { toEmptyString } from "@repo/rdx-ddd";
|
||||||
import type { ArrayElement, Collection } from "@repo/rdx-utils";
|
import type { ArrayElement, Collection } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { ListIssueInvoicesResponseDTO } from "../../../../common/dto";
|
import type { ListIssuedInvoicesResponseDTO } from "../../../../common/dto";
|
||||||
import type { CustomerInvoiceListDTO } from "../../../infrastructure";
|
import type { CustomerInvoiceListDTO } from "../../../infrastructure";
|
||||||
|
|
||||||
export class ListCustomerInvoicesPresenter extends Presenter {
|
export class ListCustomerInvoicesPresenter extends Presenter {
|
||||||
protected _mapInvoice(invoice: CustomerInvoiceListDTO) {
|
protected _mapInvoice(invoice: CustomerInvoiceListDTO) {
|
||||||
const recipientDTO = invoice.recipient.toObjectString();
|
const recipientDTO = invoice.recipient.toObjectString();
|
||||||
|
|
||||||
const invoiceDTO: ArrayElement<ListIssueInvoicesResponseDTO["items"]> = {
|
const invoiceDTO: ArrayElement<ListIssuedInvoicesResponseDTO["items"]> = {
|
||||||
id: invoice.id.toString(),
|
id: invoice.id.toString(),
|
||||||
company_id: invoice.companyId.toString(),
|
company_id: invoice.companyId.toString(),
|
||||||
is_proforma: invoice.isProforma,
|
is_proforma: invoice.isProforma,
|
||||||
@ -50,7 +50,7 @@ export class ListCustomerInvoicesPresenter extends Presenter {
|
|||||||
toOutput(params: {
|
toOutput(params: {
|
||||||
customerInvoices: Collection<CustomerInvoiceListDTO>;
|
customerInvoices: Collection<CustomerInvoiceListDTO>;
|
||||||
criteria: Criteria;
|
criteria: Criteria;
|
||||||
}): ListIssueInvoicesResponseDTO {
|
}): ListIssuedInvoicesResponseDTO {
|
||||||
const { customerInvoices, criteria } = params;
|
const { customerInvoices, criteria } = params;
|
||||||
|
|
||||||
const invoices = customerInvoices.map((invoice) => this._mapInvoice(invoice));
|
const invoices = customerInvoices.map((invoice) => this._mapInvoice(invoice));
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<CustomerInvoiceNumber, Error> - El agregado construido o un error si falla la creación.
|
* @returns Result<CustomerInvoiceNumber, Error> - El agregado construido o un error si falla la creación.
|
||||||
*/
|
*/
|
||||||
async getNextIssueInvoiceNumber(
|
async getNextIssuedInvoiceNumber(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
series: Maybe<CustomerInvoiceSerie>,
|
series: Maybe<CustomerInvoiceSerie>,
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
@ -80,7 +80,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<CustomerInvoice, Error> - La factura guardada o un error si falla la operación.
|
* @returns Result<CustomerInvoice, Error> - La factura guardada o un error si falla la operación.
|
||||||
*/
|
*/
|
||||||
async createIssueInvoiceInCompany(
|
async createIssuedInvoiceInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoice: CustomerInvoice,
|
invoice: CustomerInvoice,
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
@ -90,7 +90,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
return Result.fail(result.error);
|
return Result.fail(result.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getIssueInvoiceByIdInCompany(companyId, invoice.id, transaction);
|
return this.getIssuedInvoiceByIdInCompany(companyId, invoice.id, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,7 +157,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Comprueba si existe o no en persistencia una proforma con el ID proporcionado
|
* Comprueba si existe o no en persistencia una factura con el ID proporcionado
|
||||||
*
|
*
|
||||||
* @param companyId - Identificador de la empresa a la que pertenece la factura.
|
* @param companyId - Identificador de la empresa a la que pertenece la factura.
|
||||||
* @param invoiceId - Identificador UUID de la factura.
|
* @param invoiceId - Identificador UUID de la factura.
|
||||||
@ -165,7 +165,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
* @returns Result<Boolean, Error> - Existe la factura o no.
|
* @returns Result<Boolean, Error> - Existe la factura o no.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async existsIssueInvoiceByIdInCompany(
|
async existsIssuedInvoiceByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: Transaction
|
||||||
@ -178,7 +178,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
/**
|
/**
|
||||||
* Obtiene una colección de proformas que cumplen con los filtros definidos en un objeto Criteria.
|
* Obtiene una colección de proformas que cumplen con los filtros definidos en un objeto Criteria.
|
||||||
*
|
*
|
||||||
* @param companyId - Identificador de la empresa a la que pertenece la factura.
|
* @param companyId - Identificador de la empresa a la que pertenece la proforma.
|
||||||
* @param criteria - Objeto con condiciones de filtro, paginación y orden.
|
* @param criteria - Objeto con condiciones de filtro, paginación y orden.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<Collection<ProformasListDTO>, Error> - Colección de proformas o error.
|
* @returns Result<Collection<ProformasListDTO>, Error> - Colección de proformas o error.
|
||||||
@ -203,7 +203,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<Collection<CustomerInvoiceListDTO>, Error> - Colección de facturas o error.
|
* @returns Result<Collection<CustomerInvoiceListDTO>, Error> - Colección de facturas o error.
|
||||||
*/
|
*/
|
||||||
async findIssueInvoiceByCriteriaInCompany(
|
async findIssuedInvoiceByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: Transaction
|
transaction?: Transaction
|
||||||
@ -222,7 +222,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<CustomerInvoice, Error> - Factura encontrada o error.
|
* @returns Result<CustomerInvoice, Error> - Factura encontrada o error.
|
||||||
*/
|
*/
|
||||||
async getIssueInvoiceByIdInCompany(
|
async getIssuedInvoiceByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: Transaction
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export * from "./issue-invoices";
|
export * from "./issued-invoices";
|
||||||
export * from "./proformas";
|
export * from "./proformas";
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
export * from "./get-issue-invoice.use-case";
|
|
||||||
export * from "./list-issue-invoices.use-case";
|
|
||||||
export * from "./report-issue-invoice.use-case";
|
|
||||||
@ -5,19 +5,19 @@ import { Result } from "@repo/rdx-utils";
|
|||||||
import type { CustomerInvoiceFullPresenter } from "../../presenters/domain";
|
import type { CustomerInvoiceFullPresenter } from "../../presenters/domain";
|
||||||
import type { CustomerInvoiceApplicationService } from "../../services";
|
import type { CustomerInvoiceApplicationService } from "../../services";
|
||||||
|
|
||||||
type GetIssueInvoiceUseCaseInput = {
|
type GetIssuedInvoiceUseCaseInput = {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
invoice_id: string;
|
invoice_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class GetIssueInvoiceUseCase {
|
export class GetIssuedInvoiceUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly service: CustomerInvoiceApplicationService,
|
private readonly service: CustomerInvoiceApplicationService,
|
||||||
private readonly transactionManager: ITransactionManager,
|
private readonly transactionManager: ITransactionManager,
|
||||||
private readonly presenterRegistry: IPresenterRegistry
|
private readonly presenterRegistry: IPresenterRegistry
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(params: GetIssueInvoiceUseCaseInput) {
|
public execute(params: GetIssuedInvoiceUseCaseInput) {
|
||||||
const { invoice_id, companyId } = params;
|
const { invoice_id, companyId } = params;
|
||||||
|
|
||||||
const idOrError = UniqueID.create(invoice_id);
|
const idOrError = UniqueID.create(invoice_id);
|
||||||
@ -33,7 +33,7 @@ export class GetIssueInvoiceUseCase {
|
|||||||
|
|
||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
const invoiceOrError = await this.service.getIssueInvoiceByIdInCompany(
|
const invoiceOrError = await this.service.getIssuedInvoiceByIdInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
invoiceId,
|
invoiceId,
|
||||||
transaction
|
transaction
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./get-issued-invoice.use-case";
|
||||||
|
export * from "./list-issued-invoices.use-case";
|
||||||
|
export * from "./report-issued-invoice.use-case";
|
||||||
@ -4,16 +4,16 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
import type { Transaction } from "sequelize";
|
||||||
|
|
||||||
import type { ListIssueInvoicesResponseDTO } from "../../../../common/dto";
|
import type { ListIssuedInvoicesResponseDTO } from "../../../../common/dto";
|
||||||
import type { ListCustomerInvoicesPresenter } from "../../presenters";
|
import type { ListCustomerInvoicesPresenter } from "../../presenters";
|
||||||
import type { CustomerInvoiceApplicationService } from "../../services";
|
import type { CustomerInvoiceApplicationService } from "../../services";
|
||||||
|
|
||||||
type ListIssueInvoicesUseCaseInput = {
|
type ListIssuedInvoicesUseCaseInput = {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
criteria: Criteria;
|
criteria: Criteria;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ListIssueInvoicesUseCase {
|
export class ListIssuedInvoicesUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly service: CustomerInvoiceApplicationService,
|
private readonly service: CustomerInvoiceApplicationService,
|
||||||
private readonly transactionManager: ITransactionManager,
|
private readonly transactionManager: ITransactionManager,
|
||||||
@ -21,8 +21,8 @@ export class ListIssueInvoicesUseCase {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(
|
public execute(
|
||||||
params: ListIssueInvoicesUseCaseInput
|
params: ListIssuedInvoicesUseCaseInput
|
||||||
): Promise<Result<ListIssueInvoicesResponseDTO, Error>> {
|
): Promise<Result<ListIssuedInvoicesResponseDTO, Error>> {
|
||||||
const { criteria, companyId } = params;
|
const { criteria, companyId } = params;
|
||||||
const presenter = this.presenterRegistry.getPresenter({
|
const presenter = this.presenterRegistry.getPresenter({
|
||||||
resource: "customer-invoice",
|
resource: "customer-invoice",
|
||||||
@ -31,7 +31,7 @@ export class ListIssueInvoicesUseCase {
|
|||||||
|
|
||||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
return this.transactionManager.complete(async (transaction: Transaction) => {
|
||||||
try {
|
try {
|
||||||
const result = await this.service.findIssueInvoiceByCriteriaInCompany(
|
const result = await this.service.findIssuedInvoiceByCriteriaInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
criteria,
|
criteria,
|
||||||
transaction
|
transaction
|
||||||
@ -5,19 +5,19 @@ import { Result } from "@repo/rdx-utils";
|
|||||||
import type { CustomerInvoiceApplicationService } from "../../services";
|
import type { CustomerInvoiceApplicationService } from "../../services";
|
||||||
import type { CustomerInvoiceReportPDFPresenter } from "../proformas";
|
import type { CustomerInvoiceReportPDFPresenter } from "../proformas";
|
||||||
|
|
||||||
type ReportIssueInvoiceUseCaseInput = {
|
type ReportIssuedInvoiceUseCaseInput = {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
invoice_id: string;
|
invoice_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ReportIssueInvoiceUseCase {
|
export class ReportIssuedInvoiceUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly service: CustomerInvoiceApplicationService,
|
private readonly service: CustomerInvoiceApplicationService,
|
||||||
private readonly transactionManager: ITransactionManager,
|
private readonly transactionManager: ITransactionManager,
|
||||||
private readonly presenterRegistry: IPresenterRegistry
|
private readonly presenterRegistry: IPresenterRegistry
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async execute(params: ReportIssueInvoiceUseCaseInput) {
|
public async execute(params: ReportIssuedInvoiceUseCaseInput) {
|
||||||
const { invoice_id, companyId } = params;
|
const { invoice_id, companyId } = params;
|
||||||
|
|
||||||
const idOrError = UniqueID.create(invoice_id);
|
const idOrError = UniqueID.create(invoice_id);
|
||||||
@ -35,7 +35,7 @@ export class ReportIssueInvoiceUseCase {
|
|||||||
|
|
||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
const invoiceOrError = await this.service.getIssueInvoiceByIdInCompany(
|
const invoiceOrError = await this.service.getIssuedInvoiceByIdInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
invoiceId,
|
invoiceId,
|
||||||
transaction
|
transaction
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
import type { CustomerInvoiceFullPresenter } from "../../presenters";
|
import type { CustomerInvoiceFullPresenter } from "../../presenters";
|
||||||
import type { CustomerInvoiceApplicationService } from "../../services";
|
import type { CustomerInvoiceApplicationService } from "../../services";
|
||||||
|
|
||||||
type IssueCustomerInvoiceUseCaseInput = {
|
type IssueProformaUseCaseInput = {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
proforma_id: string;
|
proforma_id: string;
|
||||||
};
|
};
|
||||||
@ -23,7 +23,7 @@ type IssueCustomerInvoiceUseCaseInput = {
|
|||||||
* - Marca la proforma como "issued"
|
* - Marca la proforma como "issued"
|
||||||
* - Persiste ambas dentro de la misma transacción
|
* - Persiste ambas dentro de la misma transacción
|
||||||
*/
|
*/
|
||||||
export class IssueProformaInvoiceUseCase {
|
export class IssueProformaUseCase {
|
||||||
private readonly issueDomainService: IssueCustomerInvoiceDomainService;
|
private readonly issueDomainService: IssueCustomerInvoiceDomainService;
|
||||||
private readonly proformaDomainService: ProformaCustomerInvoiceDomainService;
|
private readonly proformaDomainService: ProformaCustomerInvoiceDomainService;
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ export class IssueProformaInvoiceUseCase {
|
|||||||
this.proformaDomainService = new ProformaCustomerInvoiceDomainService();
|
this.proformaDomainService = new ProformaCustomerInvoiceDomainService();
|
||||||
}
|
}
|
||||||
|
|
||||||
public execute(params: IssueCustomerInvoiceUseCaseInput) {
|
public execute(params: IssueProformaUseCaseInput) {
|
||||||
const { proforma_id, companyId } = params;
|
const { proforma_id, companyId } = params;
|
||||||
const idOrError = UniqueID.create(proforma_id);
|
const idOrError = UniqueID.create(proforma_id);
|
||||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||||
@ -59,7 +59,7 @@ export class IssueProformaInvoiceUseCase {
|
|||||||
const proforma = proformaResult.data;
|
const proforma = proformaResult.data;
|
||||||
|
|
||||||
/** 2. Generar nueva factura */
|
/** 2. Generar nueva factura */
|
||||||
const nextNumberResult = await this.service.getNextIssueInvoiceNumber(
|
const nextNumberResult = await this.service.getNextIssuedInvoiceNumber(
|
||||||
companyId,
|
companyId,
|
||||||
proforma.series,
|
proforma.series,
|
||||||
transaction
|
transaction
|
||||||
@ -74,7 +74,7 @@ export class IssueProformaInvoiceUseCase {
|
|||||||
if (issuedInvoiceOrError.isFailure) return Result.fail(issuedInvoiceOrError.error);
|
if (issuedInvoiceOrError.isFailure) return Result.fail(issuedInvoiceOrError.error);
|
||||||
|
|
||||||
/** 5. Guardar la nueva factura */
|
/** 5. Guardar la nueva factura */
|
||||||
const saveInvoiceResult = await this.service.createIssueInvoiceInCompany(
|
const saveInvoiceResult = await this.service.createIssuedInvoiceInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
issuedInvoiceOrError.data,
|
issuedInvoiceOrError.data,
|
||||||
transaction
|
transaction
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
import type { Transaction } from "sequelize";
|
||||||
|
|
||||||
import type { ListIssueInvoicesResponseDTO } from "../../../../common/dto";
|
import type { ListIssuedInvoicesResponseDTO } from "../../../../common/dto";
|
||||||
import type { ListCustomerInvoicesPresenter } from "../../presenters";
|
import type { ListCustomerInvoicesPresenter } from "../../presenters";
|
||||||
import type { CustomerInvoiceApplicationService } from "../../services";
|
import type { CustomerInvoiceApplicationService } from "../../services";
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ export class ListProformasUseCase {
|
|||||||
|
|
||||||
public execute(
|
public execute(
|
||||||
params: ListProformasUseCaseInput
|
params: ListProformasUseCaseInput
|
||||||
): Promise<Result<ListIssueInvoicesResponseDTO, Error>> {
|
): Promise<Result<ListIssuedInvoicesResponseDTO, Error>> {
|
||||||
const { criteria, companyId } = params;
|
const { criteria, companyId } = params;
|
||||||
const presenter = this.presenterRegistry.getPresenter({
|
const presenter = this.presenterRegistry.getPresenter({
|
||||||
resource: "customer-invoice",
|
resource: "customer-invoice",
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { Tax } from "@erp/core/api";
|
import type { Tax } from "@erp/core/api";
|
||||||
import { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
||||||
import { Collection } from "@repo/rdx-utils";
|
import { Collection } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ItemAmount } from "../../value-objects";
|
import { ItemAmount } from "../../value-objects";
|
||||||
import { ItemTaxes } from "../item-taxes";
|
import { ItemTaxes } from "../item-taxes";
|
||||||
import { CustomerInvoiceItem } from "./customer-invoice-item";
|
|
||||||
|
import type { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||||
|
|
||||||
export interface CustomerInvoiceItemsProps {
|
export interface CustomerInvoiceItemsProps {
|
||||||
items?: CustomerInvoiceItem[];
|
items?: CustomerInvoiceItem[];
|
||||||
@ -39,8 +41,9 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
|||||||
// Antes de añadir un nuevo item, debo comprobar que el item a añadir
|
// Antes de añadir un nuevo item, debo comprobar que el item a añadir
|
||||||
// tiene el mismo "currencyCode" y "languageCode" que la colección de items.
|
// tiene el mismo "currencyCode" y "languageCode" que la colección de items.
|
||||||
if (
|
if (
|
||||||
!this._languageCode.equals(item.languageCode) ||
|
!(
|
||||||
!this._currencyCode.equals(item.currencyCode)
|
this._languageCode.equals(item.languageCode) && this._currencyCode.equals(item.currencyCode)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
City,
|
type City,
|
||||||
Country,
|
type Country,
|
||||||
Name,
|
type Name,
|
||||||
PostalCode,
|
type PostalCode,
|
||||||
Province,
|
type Province,
|
||||||
Street,
|
type Street,
|
||||||
TINNumber,
|
type TINNumber,
|
||||||
ValueObject,
|
ValueObject,
|
||||||
toEmptyString,
|
toEmptyString,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
export interface InvoiceRecipientProps {
|
export interface InvoiceRecipientProps {
|
||||||
name: Name;
|
name: Name;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
|||||||
import type { Transaction } from "sequelize";
|
import type { Transaction } from "sequelize";
|
||||||
|
|
||||||
import { buildCustomerInvoiceDependencies, models, proformasRouter } from "./infrastructure";
|
import { buildCustomerInvoiceDependencies, models, proformasRouter } from "./infrastructure";
|
||||||
import { issueInvoicesRouter } from "./infrastructure/express/issue-invoices.routes";
|
import { issuedInvoicesRouter } from "./infrastructure/express/issued-invoices.routes";
|
||||||
|
|
||||||
export const customerInvoicesAPIModule: IModuleServer = {
|
export const customerInvoicesAPIModule: IModuleServer = {
|
||||||
name: "customer-invoices",
|
name: "customer-invoices",
|
||||||
@ -14,7 +14,7 @@ export const customerInvoicesAPIModule: IModuleServer = {
|
|||||||
// const contacts = getService<ContactsService>("contacts");
|
// const contacts = getService<ContactsService>("contacts");
|
||||||
const { logger } = params;
|
const { logger } = params;
|
||||||
proformasRouter(params);
|
proformasRouter(params);
|
||||||
issueInvoicesRouter(params);
|
issuedInvoicesRouter(params);
|
||||||
logger.info("🚀 CustomerInvoices module initialized", { label: this.name });
|
logger.info("🚀 CustomerInvoices module initialized", { label: this.name });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -20,14 +20,14 @@ import {
|
|||||||
CustomerInvoiceReportPresenter,
|
CustomerInvoiceReportPresenter,
|
||||||
CustomerInvoiceTaxesReportPresenter,
|
CustomerInvoiceTaxesReportPresenter,
|
||||||
DeleteProformaUseCase,
|
DeleteProformaUseCase,
|
||||||
GetIssueInvoiceUseCase,
|
GetIssuedInvoiceUseCase,
|
||||||
GetProformaUseCase,
|
GetProformaUseCase,
|
||||||
IssueProformaInvoiceUseCase,
|
IssueProformaUseCase,
|
||||||
ListCustomerInvoicesPresenter,
|
ListCustomerInvoicesPresenter,
|
||||||
ListIssueInvoicesUseCase,
|
ListIssuedInvoicesUseCase,
|
||||||
ListProformasUseCase,
|
ListProformasUseCase,
|
||||||
RecipientInvoiceFullPresenter,
|
RecipientInvoiceFullPresenter,
|
||||||
ReportIssueInvoiceUseCase,
|
ReportIssuedInvoiceUseCase,
|
||||||
ReportProformaUseCase,
|
ReportProformaUseCase,
|
||||||
UpdateProformaUseCase,
|
UpdateProformaUseCase,
|
||||||
} from "../application";
|
} from "../application";
|
||||||
@ -52,12 +52,12 @@ export type CustomerInvoiceDeps = {
|
|||||||
update_proforma: () => UpdateProformaUseCase;
|
update_proforma: () => UpdateProformaUseCase;
|
||||||
delete_proforma: () => DeleteProformaUseCase;
|
delete_proforma: () => DeleteProformaUseCase;
|
||||||
report_proforma: () => ReportProformaUseCase;
|
report_proforma: () => ReportProformaUseCase;
|
||||||
issue_proforma: () => IssueProformaInvoiceUseCase;
|
issue_proforma: () => IssueProformaUseCase;
|
||||||
changeStatus_proforma: () => ChangeStatusProformaUseCase;
|
changeStatus_proforma: () => ChangeStatusProformaUseCase;
|
||||||
|
|
||||||
list_issue_invoices: () => ListIssueInvoicesUseCase;
|
list_issued_invoices: () => ListIssuedInvoicesUseCase;
|
||||||
get_issue_invoice: () => GetIssueInvoiceUseCase;
|
get_issued_invoice: () => GetIssuedInvoiceUseCase;
|
||||||
report_issue_invoice: () => ReportIssueInvoiceUseCase;
|
report_issued_invoice: () => ReportIssuedInvoiceUseCase;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -144,16 +144,16 @@ export function buildCustomerInvoiceDependencies(params: ModuleParams): Customer
|
|||||||
report_proforma: () =>
|
report_proforma: () =>
|
||||||
new ReportProformaUseCase(appService, transactionManager, presenterRegistry),
|
new ReportProformaUseCase(appService, transactionManager, presenterRegistry),
|
||||||
issue_proforma: () =>
|
issue_proforma: () =>
|
||||||
new IssueProformaInvoiceUseCase(appService, transactionManager, presenterRegistry),
|
new IssueProformaUseCase(appService, transactionManager, presenterRegistry),
|
||||||
changeStatus_proforma: () => new ChangeStatusProformaUseCase(appService, transactionManager),
|
changeStatus_proforma: () => new ChangeStatusProformaUseCase(appService, transactionManager),
|
||||||
|
|
||||||
// Issue Invoices
|
// Issue Invoices
|
||||||
list_issue_invoices: () =>
|
list_issued_invoices: () =>
|
||||||
new ListIssueInvoicesUseCase(appService, transactionManager, presenterRegistry),
|
new ListIssuedInvoicesUseCase(appService, transactionManager, presenterRegistry),
|
||||||
get_issue_invoice: () =>
|
get_issued_invoice: () =>
|
||||||
new GetIssueInvoiceUseCase(appService, transactionManager, presenterRegistry),
|
new GetIssuedInvoiceUseCase(appService, transactionManager, presenterRegistry),
|
||||||
report_issue_invoice: () =>
|
report_issued_invoice: () =>
|
||||||
new ReportIssueInvoiceUseCase(appService, transactionManager, presenterRegistry),
|
new ReportIssuedInvoiceUseCase(appService, transactionManager, presenterRegistry),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export * from "./issue-invoices";
|
export * from "./issued-invoices";
|
||||||
export * from "./proformas";
|
export * from "./proformas";
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
export * from "./get-issue-invoice.controller";
|
|
||||||
export * from "./list-issue-invoices.controller";
|
|
||||||
export * from "./report-issue-invoice.controller";
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||||
|
|
||||||
import type { GetIssueInvoiceUseCase } from "../../../../application";
|
import type { GetIssuedInvoiceUseCase } from "../../../../application";
|
||||||
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
||||||
|
|
||||||
export class GetIssueInvoiceController extends ExpressController {
|
export class GetIssueInvoiceController extends ExpressController {
|
||||||
public constructor(private readonly useCase: GetIssueInvoiceUseCase) {
|
public constructor(private readonly useCase: GetIssuedInvoiceUseCase) {
|
||||||
super();
|
super();
|
||||||
this.errorMapper = customerInvoicesApiErrorMapper;
|
this.errorMapper = customerInvoicesApiErrorMapper;
|
||||||
|
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./get-issued-invoice.controller";
|
||||||
|
export * from "./list-issued-invoices.controller";
|
||||||
|
export * from "./report-issued-invoice.controller";
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||||
import { Criteria } from "@repo/rdx-criteria/server";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
|
|
||||||
import type { ListIssueInvoicesUseCase } from "../../../../application";
|
import type { ListIssuedInvoicesUseCase } from "../../../../application";
|
||||||
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
||||||
|
|
||||||
export class ListIssueInvoicesController extends ExpressController {
|
export class ListIssuedInvoicesController extends ExpressController {
|
||||||
public constructor(private readonly useCase: ListIssueInvoicesUseCase) {
|
public constructor(private readonly useCase: ListIssuedInvoicesUseCase) {
|
||||||
super();
|
super();
|
||||||
this.errorMapper = customerInvoicesApiErrorMapper;
|
this.errorMapper = customerInvoicesApiErrorMapper;
|
||||||
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||||
|
|
||||||
import type { ReportIssueInvoiceUseCase } from "../../../../application";
|
import type { ReportIssuedInvoiceUseCase } from "../../../../application";
|
||||||
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
||||||
|
|
||||||
export class ReportIssueInvoiceController extends ExpressController {
|
export class ReportIssuedInvoiceController extends ExpressController {
|
||||||
public constructor(private readonly useCase: ReportIssueInvoiceUseCase) {
|
public constructor(private readonly useCase: ReportIssuedInvoiceUseCase) {
|
||||||
super();
|
super();
|
||||||
this.errorMapper = customerInvoicesApiErrorMapper;
|
this.errorMapper = customerInvoicesApiErrorMapper;
|
||||||
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||||
|
|
||||||
import type { IssueProformaInvoiceUseCase } from "../../../../application";
|
import type { IssueProformaUseCase } from "../../../../application";
|
||||||
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
||||||
|
|
||||||
export class IssueProformaController extends ExpressController {
|
export class IssueProformaController extends ExpressController {
|
||||||
public constructor(private readonly useCase: IssueProformaInvoiceUseCase) {
|
public constructor(private readonly useCase: IssueProformaUseCase) {
|
||||||
super();
|
super();
|
||||||
this.errorMapper = customerInvoicesApiErrorMapper;
|
this.errorMapper = customerInvoicesApiErrorMapper;
|
||||||
|
|
||||||
|
|||||||
@ -11,9 +11,11 @@ import {
|
|||||||
import {
|
import {
|
||||||
type CustomerInvoiceIdAlreadyExistsError,
|
type CustomerInvoiceIdAlreadyExistsError,
|
||||||
type EntityIsNotProformaError,
|
type EntityIsNotProformaError,
|
||||||
|
type InvalidProformaTransitionError,
|
||||||
type ProformaCannotBeConvertedToInvoiceError,
|
type ProformaCannotBeConvertedToInvoiceError,
|
||||||
isCustomerInvoiceIdAlreadyExistsError,
|
isCustomerInvoiceIdAlreadyExistsError,
|
||||||
isEntityIsNotProformaError,
|
isEntityIsNotProformaError,
|
||||||
|
isInvalidProformaTransitionError,
|
||||||
isProformaCannotBeConvertedToInvoiceError,
|
isProformaCannotBeConvertedToInvoiceError,
|
||||||
isProformaCannotBeDeletedError,
|
isProformaCannotBeDeletedError,
|
||||||
} from "../../domain";
|
} from "../../domain";
|
||||||
@ -38,6 +40,15 @@ const entityIsNotProformaError: ErrorToApiRule = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const proformaTransitionRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: (e) => isInvalidProformaTransitionError(e),
|
||||||
|
build: (e) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(e as InvalidProformaTransitionError).message || "Invalid transition for proforma."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
const proformaConversionRule: ErrorToApiRule = {
|
const proformaConversionRule: ErrorToApiRule = {
|
||||||
priority: 120,
|
priority: 120,
|
||||||
matches: (e) => isProformaCannotBeConvertedToInvoiceError(e),
|
matches: (e) => isProformaCannotBeConvertedToInvoiceError(e),
|
||||||
@ -62,4 +73,5 @@ export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.def
|
|||||||
.register(invoiceDuplicateRule)
|
.register(invoiceDuplicateRule)
|
||||||
.register(entityIsNotProformaError)
|
.register(entityIsNotProformaError)
|
||||||
.register(proformaConversionRule)
|
.register(proformaConversionRule)
|
||||||
.register(proformaCannotBeDeletedRule);
|
.register(proformaCannotBeDeletedRule)
|
||||||
|
.register(proformaTransitionRule);
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export * from "./issue-invoices.routes";
|
export * from "./issued-invoices.routes";
|
||||||
export * from "./proformas.routes";
|
export * from "./proformas.routes";
|
||||||
|
|||||||
@ -6,18 +6,18 @@ import type { Sequelize } from "sequelize";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
GetIssueInvoiceByIdRequestSchema,
|
GetIssueInvoiceByIdRequestSchema,
|
||||||
ListIssueInvoicesRequestSchema,
|
ListIssuedInvoicesRequestSchema,
|
||||||
ReportIssueInvoiceByIdRequestSchema,
|
ReportIssueInvoiceByIdRequestSchema,
|
||||||
} from "../../../common/dto";
|
} from "../../../common/dto";
|
||||||
import { buildCustomerInvoiceDependencies } from "../dependencies";
|
import { buildCustomerInvoiceDependencies } from "../dependencies";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GetIssueInvoiceController,
|
GetIssueInvoiceController,
|
||||||
ListIssueInvoicesController,
|
ListIssuedInvoicesController,
|
||||||
ReportIssueInvoiceController,
|
ReportIssuedInvoiceController,
|
||||||
} from "./controllers";
|
} from "./controllers";
|
||||||
|
|
||||||
export const issueInvoicesRouter = (params: ModuleParams) => {
|
export const issuedInvoicesRouter = (params: ModuleParams) => {
|
||||||
const { app, baseRoutePath, logger } = params as {
|
const { app, baseRoutePath, logger } = params as {
|
||||||
app: Application;
|
app: Application;
|
||||||
database: Sequelize;
|
database: Sequelize;
|
||||||
@ -49,10 +49,10 @@ export const issueInvoicesRouter = (params: ModuleParams) => {
|
|||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(ListIssueInvoicesRequestSchema, "params"),
|
validateRequest(ListIssuedInvoicesRequestSchema, "params"),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.list_issue_invoices();
|
const useCase = deps.useCases.list_issued_invoices();
|
||||||
const controller = new ListIssueInvoicesController(useCase /*, deps.presenters.list */);
|
const controller = new ListIssuedInvoicesController(useCase /*, deps.presenters.list */);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -62,7 +62,7 @@ export const issueInvoicesRouter = (params: ModuleParams) => {
|
|||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(GetIssueInvoiceByIdRequestSchema, "params"),
|
validateRequest(GetIssueInvoiceByIdRequestSchema, "params"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.get_issue_invoice();
|
const useCase = deps.useCases.get_issued_invoice();
|
||||||
const controller = new GetIssueInvoiceController(useCase);
|
const controller = new GetIssueInvoiceController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
@ -73,11 +73,11 @@ export const issueInvoicesRouter = (params: ModuleParams) => {
|
|||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(ReportIssueInvoiceByIdRequestSchema, "params"),
|
validateRequest(ReportIssueInvoiceByIdRequestSchema, "params"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.report_issue_invoice();
|
const useCase = deps.useCases.report_issued_invoice();
|
||||||
const controller = new ReportIssueInvoiceController(useCase);
|
const controller = new ReportIssuedInvoiceController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
app.use(`${baseRoutePath}/issue-invoices`, router);
|
app.use(`${baseRoutePath}/issued-invoices`, router);
|
||||||
};
|
};
|
||||||
@ -1,5 +1,9 @@
|
|||||||
import { type RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api";
|
import { type RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api";
|
||||||
import { type ModuleParams, validateRequest } from "@erp/core/api";
|
import { type ModuleParams, validateRequest } from "@erp/core/api";
|
||||||
|
import type { ILogger } from "@repo/rdx-logger";
|
||||||
|
import { type Application, type NextFunction, type Request, type Response, Router } from "express";
|
||||||
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChangeStatusProformaByIdParamsRequestSchema,
|
ChangeStatusProformaByIdParamsRequestSchema,
|
||||||
ChangeStatusProformaByIdRequestSchema,
|
ChangeStatusProformaByIdRequestSchema,
|
||||||
@ -11,11 +15,7 @@ import {
|
|||||||
ReportProformaByIdRequestSchema,
|
ReportProformaByIdRequestSchema,
|
||||||
UpdateProformaByIdParamsRequestSchema,
|
UpdateProformaByIdParamsRequestSchema,
|
||||||
UpdateProformaByIdRequestSchema,
|
UpdateProformaByIdRequestSchema,
|
||||||
} from "@erp/customer-invoices/common";
|
} from "../../../common";
|
||||||
import type { ILogger } from "@repo/rdx-logger";
|
|
||||||
import { type Application, type NextFunction, type Request, type Response, Router } from "express";
|
|
||||||
import type { Sequelize } from "sequelize";
|
|
||||||
|
|
||||||
import { buildCustomerInvoiceDependencies } from "../dependencies";
|
import { buildCustomerInvoiceDependencies } from "../dependencies";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -23,7 +23,7 @@ import {
|
|||||||
CreateProformaController,
|
CreateProformaController,
|
||||||
DeleteProformaController,
|
DeleteProformaController,
|
||||||
GetProformaController,
|
GetProformaController,
|
||||||
IssueProformaController,
|
IssueProformaController as IssuedProformaController,
|
||||||
ListProformasController,
|
ListProformasController,
|
||||||
ReportProformaController,
|
ReportProformaController,
|
||||||
UpdateProformaController,
|
UpdateProformaController,
|
||||||
@ -150,7 +150,7 @@ export const proformasRouter = (params: ModuleParams) => {
|
|||||||
|
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.issue_proforma();
|
const useCase = deps.useCases.issue_proforma();
|
||||||
const controller = new IssueProformaController(useCase);
|
const controller = new IssuedProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export * from "./issue-invoices";
|
export * from "./issued-invoices";
|
||||||
export * from "./proformas";
|
export * from "./proformas";
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
export * from "./get-issue-invoice-by-id.request.dto";
|
|
||||||
export * from "./list-issue-invoices.request.dto";
|
|
||||||
export * from "./report-issue-invoice-by-id.request.dto";
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import { CriteriaSchema } from "@erp/core";
|
|
||||||
import { z } from "zod/v4";
|
|
||||||
|
|
||||||
export const ListIssueInvoicesRequestSchema = CriteriaSchema;
|
|
||||||
export type ListIssueInvoicesRequestDTO = z.infer<typeof ListIssueInvoicesRequestSchema>;
|
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./get-issued-invoice-by-id.request.dto";
|
||||||
|
export * from "./list-issued-invoices.request.dto";
|
||||||
|
export * from "./report-issued-invoice-by-id.request.dto";
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
import { CriteriaSchema } from "@erp/core";
|
||||||
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const ListIssuedInvoicesRequestSchema = CriteriaSchema;
|
||||||
|
export type ListIssuedInvoicesRequestDTO = z.infer<typeof ListIssuedInvoicesRequestSchema>;
|
||||||
@ -1,2 +1,2 @@
|
|||||||
export * from "./issue-invoices";
|
export * from "./issued-invoices";
|
||||||
export * from "./proformas";
|
export * from "./proformas";
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./get-issue-invoice-by-id.response.dto";
|
|
||||||
export * from "./list-issue-invoices.response.dto";
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { MetadataSchema, MoneySchema, PercentageSchema, QuantitySchema } from "@erp/core";
|
import { MetadataSchema, MoneySchema, PercentageSchema, QuantitySchema } from "@erp/core";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
export const GetIssueInvoiceByIdResponseSchema = z.object({
|
export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
company_id: z.uuid(),
|
company_id: z.uuid(),
|
||||||
|
|
||||||
@ -79,4 +79,4 @@ export const GetIssueInvoiceByIdResponseSchema = z.object({
|
|||||||
metadata: MetadataSchema.optional(),
|
metadata: MetadataSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type GetIssueInvoiceByIdResponseDTO = z.infer<typeof GetIssueInvoiceByIdResponseSchema>;
|
export type GetIssuedInvoiceByIdResponseDTO = z.infer<typeof GetIssuedInvoiceByIdResponseSchema>;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./get-issued-invoice-by-id.response.dto";
|
||||||
|
export * from "./list-issued-invoices.response.dto";
|
||||||
@ -6,7 +6,7 @@ import {
|
|||||||
} from "@erp/core";
|
} from "@erp/core";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
export const ListIssueInvoicesResponseSchema = createPaginatedListSchema(
|
export const ListIssuedInvoicesResponseSchema = createPaginatedListSchema(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
company_id: z.uuid(),
|
company_id: z.uuid(),
|
||||||
@ -51,4 +51,4 @@ export const ListIssueInvoicesResponseSchema = createPaginatedListSchema(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ListIssueInvoicesResponseDTO = z.infer<typeof ListIssueInvoicesResponseSchema>;
|
export type ListIssuedInvoicesResponseDTO = z.infer<typeof ListIssuedInvoicesResponseSchema>;
|
||||||
@ -1,16 +1,20 @@
|
|||||||
{
|
{
|
||||||
"common": {
|
"common": {
|
||||||
|
"more_actions": "More actions",
|
||||||
"append_empty_row": "Append row",
|
"append_empty_row": "Append row",
|
||||||
"append_empty_row_tooltip": "Append a empty row",
|
"append_empty_row_tooltip": "Append a empty row",
|
||||||
|
"edit_row": "Edit",
|
||||||
"duplicate_row": "Duplicate",
|
"duplicate_row": "Duplicate",
|
||||||
"duplicate_selected_rows": "Duplicate",
|
"duplicate_selected_rows": "Duplicate",
|
||||||
"duplicate_selected_rows_tooltip": "Duplicate selected row(s)",
|
"duplicate_selected_rows_tooltip": "Duplicate selected row(s)",
|
||||||
"remove_selected_rows": "Remove",
|
"remove_selected_rows": "Remove",
|
||||||
"remove_selected_rows_tooltip": "Remove selected row(s)",
|
"remove_selected_rows_tooltip": "Remove selected row(s)",
|
||||||
|
"download_pdf": "Download PDF",
|
||||||
|
"send_email": "Send email",
|
||||||
|
|
||||||
"insert_row_above": "Insert row above",
|
"insert_row_above": "Insert row above",
|
||||||
"insert_row_below": "Insert row below",
|
"insert_row_below": "Insert row below",
|
||||||
"remove_row": "Remove",
|
"delete_row": "Delete",
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
|
|
||||||
"rows_selected": "{{count}} fila(s) seleccionadas.",
|
"rows_selected": "{{count}} fila(s) seleccionadas.",
|
||||||
@ -22,12 +26,15 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"status": {
|
"proformas": {
|
||||||
"draft": "Draft",
|
"status": {
|
||||||
"issued": "Issued",
|
"all": "All",
|
||||||
"sent": "Sent",
|
"draft": "Draft",
|
||||||
"received": "Received",
|
"sent": "Sent",
|
||||||
"rejected": "Rejected"
|
"approved": "Approved",
|
||||||
|
"rejected": "Rejected",
|
||||||
|
"issued": "Issued"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
@ -40,9 +47,11 @@
|
|||||||
"grid_columns": {
|
"grid_columns": {
|
||||||
"invoice_number": "Inv. number",
|
"invoice_number": "Inv. number",
|
||||||
"series": "Serie",
|
"series": "Serie",
|
||||||
|
"reference": "Reference",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"invoice_date": "Proforma date",
|
"invoice_date": "Proforma date",
|
||||||
"operation_date": "Operation date",
|
"operation_date": "Operation date",
|
||||||
|
"recipient": "Customer",
|
||||||
"recipient_tin": "TIN",
|
"recipient_tin": "TIN",
|
||||||
"recipient_name": "Customer name",
|
"recipient_name": "Customer name",
|
||||||
"recipient_street": "Street",
|
"recipient_street": "Street",
|
||||||
@ -50,7 +59,10 @@
|
|||||||
"recipient_province": "Province",
|
"recipient_province": "Province",
|
||||||
"recipient_postal_code": "Postal code",
|
"recipient_postal_code": "Postal code",
|
||||||
"recipient_country": "Country",
|
"recipient_country": "Country",
|
||||||
"total_amount": "Total price"
|
"subtotal_amount": "Subtotal",
|
||||||
|
"discount_amount": "Discount",
|
||||||
|
"taxes_amount": "Taxes",
|
||||||
|
"total_amount": "Total"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
{
|
{
|
||||||
"common": {
|
"common": {
|
||||||
|
"more_actions": "Más acciones",
|
||||||
"append_empty_row": "Añadir fila",
|
"append_empty_row": "Añadir fila",
|
||||||
"append_empty_row_tooltip": "Añadir una fila vacía",
|
"append_empty_row_tooltip": "Añadir una fila vacía",
|
||||||
|
"edit_row": "Modificar",
|
||||||
"duplicate_row": "Duplicar",
|
"duplicate_row": "Duplicar",
|
||||||
"duplicate_selected_rows": "Duplicar",
|
"duplicate_selected_rows": "Duplicar",
|
||||||
"duplicate_selected_rows_tooltip": "Duplicar fila(s) seleccionada(s)",
|
"duplicate_selected_rows_tooltip": "Duplicar fila(s) seleccionada(s)",
|
||||||
"remove_selected_rows": "Eliminar",
|
"remove_selected_rows": "Eliminar",
|
||||||
"remove_selected_rows_tooltip": "Eliminar fila(s) seleccionada(s)",
|
"remove_selected_rows_tooltip": "Eliminar fila(s) seleccionada(s)",
|
||||||
|
"download_pdf": "Descargar en PDF",
|
||||||
|
"send_email": "Enviar por email",
|
||||||
|
|
||||||
"insert_row_above": "Insertar fila arriba",
|
"insert_row_above": "Insertar fila arriba",
|
||||||
"insert_row_below": "Insertar fila abajo",
|
"insert_row_below": "Insertar fila abajo",
|
||||||
"remove_row": "Eliminar",
|
"delete_row": "Eliminar",
|
||||||
"actions": "Acciones",
|
"actions": "Acciones",
|
||||||
|
|
||||||
"rows_selected": "{{count}} fila(s) seleccionadas.",
|
"rows_selected": "{{count}} fila(s) seleccionadas.",
|
||||||
@ -21,12 +25,15 @@
|
|||||||
"clear": "Limpiar"
|
"clear": "Limpiar"
|
||||||
},
|
},
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"status": {
|
"proformas": {
|
||||||
"draft": "Borrador",
|
"status": {
|
||||||
"issued": "Emitida",
|
"all": "Todas",
|
||||||
"sent": "Enviada",
|
"draft": "Borradores",
|
||||||
"received": "Recibida",
|
"sent": "Enviadas",
|
||||||
"rejected": "Rechazada"
|
"approved": "Aprovadas",
|
||||||
|
"rejected": "Rechazadas",
|
||||||
|
"issued": "Emitidas"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
@ -39,9 +46,11 @@
|
|||||||
"grid_columns": {
|
"grid_columns": {
|
||||||
"invoice_number": "Nº proforma",
|
"invoice_number": "Nº proforma",
|
||||||
"series": "Serie",
|
"series": "Serie",
|
||||||
|
"reference": "Reference",
|
||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"invoice_date": "Fecha de proforma",
|
"invoice_date": "Fecha de proforma",
|
||||||
"operation_date": "Fecha de operación",
|
"operation_date": "Fecha de operación",
|
||||||
|
"recipient": "Cliente",
|
||||||
"recipient_tin": "NIF/CIF",
|
"recipient_tin": "NIF/CIF",
|
||||||
"recipient_name": "Cliente",
|
"recipient_name": "Cliente",
|
||||||
"recipient_street": "Dirección",
|
"recipient_street": "Dirección",
|
||||||
@ -49,6 +58,9 @@
|
|||||||
"recipient_province": "Provincia",
|
"recipient_province": "Provincia",
|
||||||
"recipient_postal_code": "Código postal",
|
"recipient_postal_code": "Código postal",
|
||||||
"recipient_country": "País",
|
"recipient_country": "País",
|
||||||
|
"subtotal_amount": "Subtotal",
|
||||||
|
"discount_amount": "Descuentos",
|
||||||
|
"taxes_amount": "Impuestos",
|
||||||
"total_amount": "Importe total"
|
"total_amount": "Importe total"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
GetIssueInvoiceByIdResponseDTO,
|
GetIssuedInvoiceByIdResponseDTO,
|
||||||
UpdateCustomerInvoiceByIdRequestDTO,
|
UpdateCustomerInvoiceByIdRequestDTO,
|
||||||
} from "../../common";
|
} from "../../common";
|
||||||
import type { InvoiceContextValue } from "../context";
|
import type { InvoiceContextValue } from "../context";
|
||||||
@ -11,7 +11,7 @@ import type { InvoiceFormData } from "../schemas/invoice.form.schema";
|
|||||||
* Convierte el DTO completo de API a datos numéricos para el formulario.
|
* Convierte el DTO completo de API a datos numéricos para el formulario.
|
||||||
*/
|
*/
|
||||||
export const invoiceDtoToFormAdapter = {
|
export const invoiceDtoToFormAdapter = {
|
||||||
fromDto(dto: GetIssueInvoiceByIdResponseDTO, context: InvoiceContextValue): InvoiceFormData {
|
fromDto(dto: GetIssuedInvoiceByIdResponseDTO, context: InvoiceContextValue): InvoiceFormData {
|
||||||
const { taxCatalog } = context;
|
const { taxCatalog } = context;
|
||||||
return {
|
return {
|
||||||
invoice_number: dto.invoice_number,
|
invoice_number: dto.invoice_number,
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
@source "./components";
|
@source "**/*.{ts,tsx}";
|
||||||
@source "./pages";
|
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./use-issue-invoice-query";
|
|
||||||
export * from "./use-issue-invoices-query";
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import {
|
|
||||||
GetIssueInvoiceByIdResponseSchema,
|
|
||||||
ListIssueInvoicesResponseSchema,
|
|
||||||
} from "@erp/customer-invoices/common";
|
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
|
||||||
import type { z } from "zod/v4";
|
|
||||||
|
|
||||||
// IssueInvoices
|
|
||||||
export const IssueInvoiceSchema = GetIssueInvoiceByIdResponseSchema.omit({
|
|
||||||
metadata: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type IssueInvoice = z.infer<typeof IssueInvoiceSchema>;
|
|
||||||
export type IssueInvoiceRecipient = IssueInvoice["recipient"];
|
|
||||||
export type IssueInvoiceItem = ArrayElement<IssueInvoice["items"]>;
|
|
||||||
|
|
||||||
// Resultado de consulta con criteria (paginado, etc.)
|
|
||||||
export const IssueInvoiceSummaryPageSchema = ListIssueInvoicesResponseSchema.omit({
|
|
||||||
metadata: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type IssueInvoiceSummaryPage = z.infer<typeof IssueInvoiceSummaryPageSchema>;
|
|
||||||
export type IssueInvoiceSummary = Omit<ArrayElement<IssueInvoiceSummaryPage["items"]>, "metadata">;
|
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./use-issued-invoice-query";
|
||||||
|
export * from "./use-issued-invoices-query";
|
||||||
@ -1,26 +1,26 @@
|
|||||||
import { useDataSource } from "@erp/core/hooks";
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
import { type DefaultError, type QueryKey, useQuery } from "@tanstack/react-query";
|
import { type DefaultError, type QueryKey, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import type { IssueInvoice } from "../issue-invoice.api.schema";
|
import type { IssueInvoice } from "../issued-invoice.api.schema";
|
||||||
|
|
||||||
export const ISSUE_INVOICE_QUERY_KEY = (id: string): QueryKey => ["issue-invoice", id] as const;
|
export const ISSUED_INVOICE_QUERY_KEY = (id: string): QueryKey => ["issued-invoice", id] as const;
|
||||||
|
|
||||||
type InvoiceQueryOptions = {
|
type InvoiceQueryOptions = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useIssueInvoiceQuery = (issueInvoiceId?: string, options?: InvoiceQueryOptions) => {
|
export const useIssuedInvoiceQuery = (issuedInvoiceId?: string, options?: InvoiceQueryOptions) => {
|
||||||
const dataSource = useDataSource();
|
const dataSource = useDataSource();
|
||||||
const enabled = (options?.enabled ?? true) && !!issueInvoiceId;
|
const enabled = (options?.enabled ?? true) && !!issuedInvoiceId;
|
||||||
|
|
||||||
return useQuery<IssueInvoice, DefaultError>({
|
return useQuery<IssueInvoice, DefaultError>({
|
||||||
queryKey: ISSUE_INVOICE_QUERY_KEY(issueInvoiceId ?? "unknown"),
|
queryKey: ISSUED_INVOICE_QUERY_KEY(issuedInvoiceId ?? "unknown"),
|
||||||
queryFn: async (context) => {
|
queryFn: async (context) => {
|
||||||
const { signal } = context;
|
const { signal } = context;
|
||||||
if (!issueInvoiceId) {
|
if (!issuedInvoiceId) {
|
||||||
if (!issueInvoiceId) throw new Error("issueInvoiceId is required");
|
if (!issuedInvoiceId) throw new Error("issueInvoiceId is required");
|
||||||
}
|
}
|
||||||
return await dataSource.getOne<IssueInvoice>("issue-invoices", issueInvoiceId, {
|
return await dataSource.getOne<IssueInvoice>("issued-invoices", issuedInvoiceId, {
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -2,10 +2,10 @@ import type { CriteriaDTO } from "@erp/core";
|
|||||||
import { useDataSource } from "@erp/core/hooks";
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
import { type DefaultError, type QueryKey, useQuery } from "@tanstack/react-query";
|
import { type DefaultError, type QueryKey, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import type { IssueInvoiceSummaryPage } from "../issue-invoice.api.schema";
|
import type { IssuedInvoicesummaryPage } from "../issued-invoice.api.schema";
|
||||||
|
|
||||||
export const ISSUE_INVOICES_QUERY_KEY = (criteria: CriteriaDTO): QueryKey => [
|
export const ISSUED_INVOICES_QUERY_KEY = (criteria: CriteriaDTO): QueryKey => [
|
||||||
"issue-invoice",
|
"issued-invoice",
|
||||||
{
|
{
|
||||||
pageNumber: criteria.pageNumber ?? 0,
|
pageNumber: criteria.pageNumber ?? 0,
|
||||||
pageSize: criteria.pageSize ?? 10,
|
pageSize: criteria.pageSize ?? 10,
|
||||||
@ -16,21 +16,21 @@ export const ISSUE_INVOICES_QUERY_KEY = (criteria: CriteriaDTO): QueryKey => [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
type IssueInvoicesQueryOptions = {
|
type IssuedInvoicesQueryOptions = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
criteria?: CriteriaDTO;
|
criteria?: CriteriaDTO;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Obtener todas las facturas
|
// Obtener todas las facturas
|
||||||
export const useIssueInvoicesQuery = (options?: IssueInvoicesQueryOptions) => {
|
export const useIssuedInvoicesQuery = (options?: IssuedInvoicesQueryOptions) => {
|
||||||
const dataSource = useDataSource();
|
const dataSource = useDataSource();
|
||||||
const enabled = options?.enabled ?? true;
|
const enabled = options?.enabled ?? true;
|
||||||
const criteria = options?.criteria ?? {};
|
const criteria = options?.criteria ?? {};
|
||||||
|
|
||||||
return useQuery<IssueInvoiceSummaryPage, DefaultError>({
|
return useQuery<IssuedInvoicesummaryPage, DefaultError>({
|
||||||
queryKey: ISSUE_INVOICES_QUERY_KEY(criteria),
|
queryKey: ISSUED_INVOICES_QUERY_KEY(criteria),
|
||||||
queryFn: async ({ signal }) => {
|
queryFn: async ({ signal }) => {
|
||||||
return await dataSource.getList<IssueInvoiceSummaryPage>("issue-invoices", {
|
return await dataSource.getList<IssuedInvoicesummaryPage>("issued-invoices", {
|
||||||
signal,
|
signal,
|
||||||
...criteria,
|
...criteria,
|
||||||
});
|
});
|
||||||
@ -1,16 +1,16 @@
|
|||||||
import { MoneyDTOHelper, PercentageDTOHelper, formatCurrency } from "@erp/core";
|
import { MoneyDTOHelper, PercentageDTOHelper, formatCurrency } from "@erp/core";
|
||||||
|
|
||||||
import type { IssueInvoiceSummaryPage } from "./issue-invoice.api.schema";
|
import type { IssuedInvoicesummaryPage } from "./issued-invoice.api.schema";
|
||||||
import type {
|
import type {
|
||||||
IssueInvoiceSummaryData,
|
IssuedInvoicesummaryData,
|
||||||
IssueInvoiceSummaryPageData,
|
IssuedInvoicesummaryPageData,
|
||||||
} from "./issue-invoice-resume.form.schema";
|
} from "./issued-invoice-resume.form.schema";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte el DTO completo de API a datos numéricos para el formulario.
|
* Convierte el DTO completo de API a datos numéricos para el formulario.
|
||||||
*/
|
*/
|
||||||
export const IssueInvoiceResumeDtoAdapter = {
|
export const IssueInvoiceResumeDtoAdapter = {
|
||||||
fromDto(pageDto: IssueInvoiceSummaryPage, context?: unknown): IssueInvoiceSummaryPageData {
|
fromDto(pageDto: IssuedInvoicesummaryPage, context?: unknown): IssuedInvoicesummaryPageData {
|
||||||
return {
|
return {
|
||||||
...pageDto,
|
...pageDto,
|
||||||
items: pageDto.items.map(
|
items: pageDto.items.map(
|
||||||
@ -64,7 +64,7 @@ export const IssueInvoiceResumeDtoAdapter = {
|
|||||||
),
|
),
|
||||||
|
|
||||||
//taxes: dto.taxes,
|
//taxes: dto.taxes,
|
||||||
}) as unknown as IssueInvoiceSummaryData
|
}) as unknown as IssuedInvoicesummaryData
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { IssueInvoiceSummary, IssueInvoiceSummaryPage } from "./issue-invoice.api.schema";
|
import type { IssuedInvoicesummary, IssuedInvoicesummaryPage } from "./issued-invoice.api.schema";
|
||||||
|
|
||||||
export type IssueInvoiceSummaryData = IssueInvoiceSummary & {
|
export type IssuedInvoicesummaryData = IssuedInvoicesummary & {
|
||||||
subtotal_amount_fmt: string;
|
subtotal_amount_fmt: string;
|
||||||
subtotal_amount: number;
|
subtotal_amount: number;
|
||||||
|
|
||||||
@ -20,6 +20,6 @@ export type IssueInvoiceSummaryData = IssueInvoiceSummary & {
|
|||||||
total_amount: number;
|
total_amount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IssueInvoiceSummaryPageData = IssueInvoiceSummaryPage & {
|
export type IssuedInvoicesummaryPageData = IssuedInvoicesummaryPage & {
|
||||||
items: IssueInvoiceSummary[];
|
items: IssuedInvoicesummary[];
|
||||||
};
|
};
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
GetIssuedInvoiceByIdResponseSchema,
|
||||||
|
ListIssuedInvoicesResponseSchema,
|
||||||
|
} from "@erp/customer-invoices/common";
|
||||||
|
import type { ArrayElement } from "@repo/rdx-utils";
|
||||||
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
|
// IssuedInvoices
|
||||||
|
export const IssuedInvoiceschema = GetIssuedInvoiceByIdResponseSchema.omit({
|
||||||
|
metadata: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type IssueInvoice = z.infer<typeof IssuedInvoiceschema>;
|
||||||
|
export type IssueInvoiceRecipient = IssueInvoice["recipient"];
|
||||||
|
export type IssueInvoiceItem = ArrayElement<IssueInvoice["items"]>;
|
||||||
|
|
||||||
|
// Resultado de consulta con criteria (paginado, etc.)
|
||||||
|
export const IssuedInvoicesummaryPageSchema = ListIssuedInvoicesResponseSchema.omit({
|
||||||
|
metadata: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type IssuedInvoicesummaryPage = z.infer<typeof IssuedInvoicesummaryPageSchema>;
|
||||||
|
export type IssuedInvoicesummary = Omit<
|
||||||
|
ArrayElement<IssuedInvoicesummaryPage["items"]>,
|
||||||
|
"metadata"
|
||||||
|
>;
|
||||||
@ -5,7 +5,6 @@ import { useNavigate } from "react-router-dom";
|
|||||||
|
|
||||||
import { usePinnedPreviewSheet } from "../../hooks";
|
import { usePinnedPreviewSheet } from "../../hooks";
|
||||||
import { useTranslation } from "../../i18n";
|
import { useTranslation } from "../../i18n";
|
||||||
import { useProformasGridColumns } from "../../proformas/pages/list/use-proformas-grid-columns";
|
|
||||||
import type { InvoiceSummaryFormData, InvoicesPageFormData } from "../../schemas";
|
import type { InvoiceSummaryFormData, InvoicesPageFormData } from "../../schemas";
|
||||||
|
|
||||||
export type InvoiceUpdateCompProps = {
|
export type InvoiceUpdateCompProps = {
|
||||||
|
|||||||
@ -10,8 +10,6 @@ import { invoiceResumeDtoToFormAdapter } from "../../adapters/invoice-resume-dto
|
|||||||
import { useInvoicesQuery } from "../../hooks";
|
import { useInvoicesQuery } from "../../hooks";
|
||||||
import { useTranslation } from "../../i18n";
|
import { useTranslation } from "../../i18n";
|
||||||
|
|
||||||
import { InvoicesListGrid } from "./invoices-list-grid";
|
|
||||||
|
|
||||||
export const InvoiceListPage = () => {
|
export const InvoiceListPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -95,16 +93,7 @@ export const InvoiceListPage = () => {
|
|||||||
<AppContent>
|
<AppContent>
|
||||||
<div className="flex flex-col w-full h-full py-3">
|
<div className="flex flex-col w-full h-full py-3">
|
||||||
<div className={"flex-1"}>
|
<div className={"flex-1"}>
|
||||||
<InvoicesListGrid
|
<>hola</>
|
||||||
invoicesPage={invoicesPageData}
|
|
||||||
loading={isLoading}
|
|
||||||
onPageChange={handlePageChange}
|
|
||||||
onPageSizeChange={handlePageSizeChange}
|
|
||||||
onSearchChange={handleSearchChange}
|
|
||||||
pageIndex={pageIndex}
|
|
||||||
pageSize={pageSize}
|
|
||||||
searchValue={search}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AppContent>
|
</AppContent>
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
|
export * from "./use-proformas-grid-columns";
|
||||||
export * from "./use-proformas-list";
|
export * from "./use-proformas-list";
|
||||||
|
|||||||
@ -0,0 +1,443 @@
|
|||||||
|
import { formatDate } from "@erp/core/client";
|
||||||
|
import { DataTableColumnHeader } from "@repo/rdx-ui/components";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
import type { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import {
|
||||||
|
CopyIcon,
|
||||||
|
DownloadIcon,
|
||||||
|
EditIcon,
|
||||||
|
MailIcon,
|
||||||
|
MoreVerticalIcon,
|
||||||
|
Trash2Icon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../../../i18n";
|
||||||
|
import type { ProformaSummaryData } from "../../../schema";
|
||||||
|
import { ProformaStatusBadge } from "../ui";
|
||||||
|
|
||||||
|
type GridActionHandlers = {
|
||||||
|
onEdit?: (proforma: ProformaSummaryData) => void;
|
||||||
|
onDuplicate?: (proforma: ProformaSummaryData) => void;
|
||||||
|
onDownloadPdf?: (proforma: ProformaSummaryData) => void;
|
||||||
|
onSendEmail?: (proforma: ProformaSummaryData) => void;
|
||||||
|
onDelete?: (proforma: ProformaSummaryData) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useProformasGridColumns(
|
||||||
|
actionHandlers: GridActionHandlers = {}
|
||||||
|
): ColumnDef<ProformaSummaryData, unknown>[] {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { onEdit, onDuplicate, onDownloadPdf, onSendEmail, onDelete } = actionHandlers;
|
||||||
|
|
||||||
|
return React.useMemo<ColumnDef<ProformaSummaryData>[]>(
|
||||||
|
() => [
|
||||||
|
// Nº
|
||||||
|
{
|
||||||
|
accessorKey: "invoice_number",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-left"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.invoice_number")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="font-semibold text-left text-primary">{row.original.invoice_number}</div>
|
||||||
|
),
|
||||||
|
enableHiding: false,
|
||||||
|
enableSorting: false,
|
||||||
|
size: 160,
|
||||||
|
minSize: 120,
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.invoice_number"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Estado
|
||||||
|
{
|
||||||
|
accessorKey: "status",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-left"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.status")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => <ProformaStatusBadge status={row.original.status} />,
|
||||||
|
enableSorting: false,
|
||||||
|
size: 140,
|
||||||
|
minSize: 120,
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.status"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "recipient",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-left"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.recipient")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
accessorFn: (row) => row.recipient.name, // para ordenar/buscar por nombre
|
||||||
|
enableHiding: false,
|
||||||
|
size: 140,
|
||||||
|
minSize: 120,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const c = row.original.recipient;
|
||||||
|
return (
|
||||||
|
<div className="flex items-start gap-1 my-1.5">
|
||||||
|
<div className="min-w-0 grid gap-1">
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<span className="font-medium truncate text-primary">{c.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
{c.tin && <span className="font-base truncate">{c.tin}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.recipient"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Serie
|
||||||
|
{
|
||||||
|
accessorKey: "series",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-left"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.series")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => <div className="font-normal text-left">{row.original.series}</div>,
|
||||||
|
enableSorting: false,
|
||||||
|
size: 120,
|
||||||
|
minSize: 100,
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.series"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Referencia
|
||||||
|
{
|
||||||
|
accessorKey: "reference",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-left"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.reference")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => <div className="font-medium text-left">{row.original.reference}</div>,
|
||||||
|
enableSorting: false,
|
||||||
|
size: 120,
|
||||||
|
minSize: 100,
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.reference"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fecha factura
|
||||||
|
{
|
||||||
|
accessorKey: "invoice_date",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-left tabular-nums"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.invoice_date")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="font-medium text-left tabular-nums">
|
||||||
|
{formatDate(row.original.invoice_date)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
size: 140,
|
||||||
|
minSize: 120,
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.invoice_date"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Fecha operación
|
||||||
|
{
|
||||||
|
accessorKey: "operation_date",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-left tabular-nums"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.operation_date")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="font-medium text-left tabular-nums">
|
||||||
|
{formatDate(row.original.operation_date)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
size: 140,
|
||||||
|
minSize: 120,
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.operation_date"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Subtotal amount
|
||||||
|
{
|
||||||
|
accessorKey: "subtotal_amount_fmt",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-right tabular-nums"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.subtotal_amount")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="font-medium text-right tabular-nums">
|
||||||
|
{row.original.subtotal_amount_fmt}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
size: 120,
|
||||||
|
minSize: 100,
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.subtotal_amount"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Discount amount
|
||||||
|
{
|
||||||
|
accessorKey: "discount_amount_fmt",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-right tabular-nums"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.discount_amount")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="font-medium text-right tabular-nums">
|
||||||
|
{row.original.discount_amount_fmt}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
size: 120,
|
||||||
|
minSize: 100,
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.discount_amount"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Taxes amount
|
||||||
|
{
|
||||||
|
accessorKey: "taxes_amount_fmt",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-right tabular-nums"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.taxes_amount")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="font-medium text-right tabular-nums">{row.original.taxes_amount_fmt}</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
size: 120,
|
||||||
|
minSize: 100,
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.taxes_amount"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Total amount
|
||||||
|
{
|
||||||
|
accessorKey: "total_amount_fmt",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
className="text-right tabular-nums"
|
||||||
|
column={column}
|
||||||
|
title={t("pages.proformas.list.grid_columns.total_amount")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="font-semibold text-right tabular-nums">
|
||||||
|
{row.original.total_amount_fmt}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
size: 140,
|
||||||
|
minSize: 120,
|
||||||
|
meta: {
|
||||||
|
title: t("pages.proformas.list.grid_columns.total_amount"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─────────────────────────────
|
||||||
|
// Acciones
|
||||||
|
// ─────────────────────────────
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
header: () => <span className="sr-only">{t("common.actions")}</span>,
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
size: 110,
|
||||||
|
minSize: 96,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const proforma = row.original;
|
||||||
|
const stop = (e: React.MouseEvent | React.KeyboardEvent) => e.stopPropagation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonGroup>
|
||||||
|
{/* Editar (acción primaria) */}
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
aria-label={t("common.edit_row")}
|
||||||
|
className="cursor-pointer text-muted-foreground hover:text-primary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onEdit?.(proforma);
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<EditIcon aria-hidden="true" className="size-4 " />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{t("common.edit_row")}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{/* Duplicar */}
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
aria-label={t("common.duplicate_row")}
|
||||||
|
className="cursor-pointer text-muted-foreground hover:text-primary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDuplicate?.(proforma);
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<CopyIcon aria-hidden="true" className="size-4 " />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{t("common.duplicate_row")}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{/* Descargar en PDF */}
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
aria-label={t("common.download_pdf")}
|
||||||
|
className="cursor-pointer text-muted-foreground hover:text-primary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDownloadPdf?.(proforma);
|
||||||
|
}}
|
||||||
|
size="icon-sm"
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<DownloadIcon aria-hidden="true" className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{t("common.download_pdf")}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
aria-label={t("common.delete_row")}
|
||||||
|
className="cursor-pointer text-destructive hover:bg-destructive/90 hover:text-white"
|
||||||
|
onClick={() => onDelete?.(proforma)}
|
||||||
|
size="icon-sm"
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<Trash2Icon aria-hidden="true" className="size-4" />
|
||||||
|
<span className="sr-only">{t("common.delete_row")}</span>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{t("common.delete_row")}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{/* Menú demás acciones */}
|
||||||
|
{/** biome-ignore lint/suspicious/noSelfCompare: <Desactivado por ahora> */}
|
||||||
|
{false === false && (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
aria-label={t("common.more_actions")}
|
||||||
|
className="cursor-pointer text-muted-foreground hover:text-primary"
|
||||||
|
onClick={stop}
|
||||||
|
size="sm"
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<MoreVerticalIcon aria-hidden="true" className="size-4" />
|
||||||
|
<span className="sr-only">{t("common.more_actions")}</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-48">
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => onDuplicate?.(proforma)}
|
||||||
|
>
|
||||||
|
<CopyIcon className="mr-2 size-4" />
|
||||||
|
{t("common.duplicate_row")}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => onDownloadPdf?.(proforma)}
|
||||||
|
>
|
||||||
|
<DownloadIcon className="mr-2 size-4" />
|
||||||
|
{t("common.download_pdf")}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => onSendEmail?.(proforma)}
|
||||||
|
>
|
||||||
|
<MailIcon className="mr-2 size-4" />
|
||||||
|
{t("common.send_email")}
|
||||||
|
</DropdownMenuItem>{" "}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="text-destructive focus:text-destructive-foreground focus:bg-destructive cursor-pointer"
|
||||||
|
onClick={() => onDelete?.(proforma)}
|
||||||
|
>
|
||||||
|
<Trash2Icon className="mr-2 size-4 text-destructive focus:text-destructive-foreground" />
|
||||||
|
{t("common.delete_row")}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
title: t("common.actions"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t, onEdit, onDuplicate, onDownloadPdf, onSendEmail, onDelete]
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -11,19 +11,23 @@ export const useProformasList = () => {
|
|||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
const [status, setStatus] = useState("all");
|
||||||
|
|
||||||
const debouncedQ = useDebounce(search, 300);
|
const debouncedQ = useDebounce(search, 300);
|
||||||
|
|
||||||
const criteria = useMemo<CriteriaDTO>(
|
const criteria = useMemo<CriteriaDTO>(() => {
|
||||||
() => ({
|
const baseFilters =
|
||||||
|
status !== "all" ? [{ field: "status", operator: "CONTAINS", value: status }] : [];
|
||||||
|
|
||||||
|
return {
|
||||||
q: debouncedQ || "",
|
q: debouncedQ || "",
|
||||||
pageSize,
|
pageSize,
|
||||||
pageNumber: pageIndex,
|
pageNumber: pageIndex,
|
||||||
order: "desc",
|
order: "desc",
|
||||||
orderBy: "invoice_date",
|
orderBy: "invoice_date",
|
||||||
}),
|
filters: baseFilters,
|
||||||
[pageSize, pageIndex, debouncedQ]
|
};
|
||||||
);
|
}, [pageSize, pageIndex, debouncedQ, status]);
|
||||||
|
|
||||||
const query = useProformasQuery({ criteria });
|
const query = useProformasQuery({ criteria });
|
||||||
const data = useMemo(
|
const data = useMemo(
|
||||||
@ -33,6 +37,8 @@ export const useProformasList = () => {
|
|||||||
|
|
||||||
const setSearchValue = (value: string) => setSearch(value.trim().replace(/\s+/g, " "));
|
const setSearchValue = (value: string) => setSearch(value.trim().replace(/\s+/g, " "));
|
||||||
|
|
||||||
|
const setStatusFilter = (newStatus: string) => setStatus(newStatus);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...query,
|
...query,
|
||||||
data,
|
data,
|
||||||
@ -42,5 +48,6 @@ export const useProformasList = () => {
|
|||||||
setPageIndex,
|
setPageIndex,
|
||||||
setPageSize,
|
setPageSize,
|
||||||
setSearchValue,
|
setSearchValue,
|
||||||
|
setStatusFilter,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -51,6 +51,7 @@ export const ProformaListPage = () => {
|
|||||||
onPageChange={list.setPageIndex}
|
onPageChange={list.setPageIndex}
|
||||||
onPageSizeChange={list.setPageSize}
|
onPageSizeChange={list.setPageSize}
|
||||||
onSearchChange={list.setSearchValue}
|
onSearchChange={list.setSearchValue}
|
||||||
|
onStatusFilterChange={list.setStatusFilter}
|
||||||
pageIndex={list.pageIndex}
|
pageIndex={list.pageIndex}
|
||||||
pageSize={list.pageSize}
|
pageSize={list.pageSize}
|
||||||
searchValue={list.search}
|
searchValue={list.search}
|
||||||
|
|||||||
@ -1,114 +0,0 @@
|
|||||||
import type { CriteriaDTO } from "@erp/core";
|
|
||||||
import { PageHeader } from "@erp/core/components";
|
|
||||||
import { ErrorAlert } from "@erp/customers/components";
|
|
||||||
import { AppContent, AppHeader, BackHistoryButton, useDebounce } from "@repo/rdx-ui/components";
|
|
||||||
import { Button } from "@repo/shadcn-ui/components";
|
|
||||||
import { PlusIcon } from "lucide-react";
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import { useTranslation } from "../../../i18n";
|
|
||||||
import { useProformasQuery } from "../../hooks";
|
|
||||||
import { ProformaSummaryDtoAdapter } from "../../../adapters/proforma-summary-dto.adapter";
|
|
||||||
|
|
||||||
import { ProformasGrid } from "./proformas-grid";
|
|
||||||
|
|
||||||
export const ProformaListPage = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
|
||||||
const [pageSize, setPageSize] = useState(10);
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
const debouncedQ = useDebounce(search, 300);
|
|
||||||
|
|
||||||
const criteria = useMemo(
|
|
||||||
() =>
|
|
||||||
({
|
|
||||||
q: debouncedQ || "",
|
|
||||||
pageSize,
|
|
||||||
pageNumber: pageIndex,
|
|
||||||
order: "desc",
|
|
||||||
orderBy: "invoice_date",
|
|
||||||
}) as CriteriaDTO,
|
|
||||||
[pageSize, pageIndex, debouncedQ]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, isLoading, isError, error } = useProformasQuery({
|
|
||||||
criteria,
|
|
||||||
});
|
|
||||||
|
|
||||||
const proformaPageData = useMemo(() => {
|
|
||||||
if (!data) return undefined;
|
|
||||||
return ProformaSummaryDtoAdapter.fromDto(data);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const handlePageChange = (newPageIndex: number) => {
|
|
||||||
setPageIndex(newPageIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePageSizeChange = (newSize: number) => {
|
|
||||||
setPageSize(newSize);
|
|
||||||
setPageIndex(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearchChange = (value: string) => {
|
|
||||||
// Normalización ligera: recorta y colapsa espacios internos
|
|
||||||
const cleaned = value.trim().replace(/\s+/g, " ");
|
|
||||||
setSearch(cleaned);
|
|
||||||
setPageIndex(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isError || !proformaPageData) {
|
|
||||||
return (
|
|
||||||
<AppContent>
|
|
||||||
<ErrorAlert
|
|
||||||
message={(error as Error)?.message || "Error al cargar el listado"}
|
|
||||||
title={t("pages.proformas.list.loadErrorTitle")}
|
|
||||||
/>
|
|
||||||
<BackHistoryButton />
|
|
||||||
</AppContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<AppHeader>
|
|
||||||
<PageHeader
|
|
||||||
description={t("pages.proformas.list.description")}
|
|
||||||
rightSlot={
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Button
|
|
||||||
aria-label={t("pages.proformas.create.title")}
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => navigate("/proformas/create")}
|
|
||||||
variant={"default"}
|
|
||||||
>
|
|
||||||
<PlusIcon aria-hidden className="mr-2 h-4 w-4" />
|
|
||||||
{t("pages.proformas.create.title")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
title={t("pages.proformas.list.title")}
|
|
||||||
/>
|
|
||||||
</AppHeader>
|
|
||||||
<AppContent>
|
|
||||||
<div className="flex flex-col w-full h-full py-3">
|
|
||||||
<div className={"flex-1"}>
|
|
||||||
<ProformasGrid
|
|
||||||
data={proformaPageData}
|
|
||||||
loading={isLoading}
|
|
||||||
onPageChange={handlePageChange}
|
|
||||||
onPageSizeChange={handlePageSizeChange}
|
|
||||||
onSearchChange={handleSearchChange}
|
|
||||||
pageIndex={pageIndex}
|
|
||||||
pageSize={pageSize}
|
|
||||||
searchValue={search}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AppContent>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,196 +0,0 @@
|
|||||||
import { SimpleSearchInput } from "@erp/core/components";
|
|
||||||
import { DataTable, SkeletonDataTable } from "@repo/rdx-ui/components";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@repo/shadcn-ui/components";
|
|
||||||
import { FileDownIcon, FilterIcon } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import { usePinnedPreviewSheet } from "../../../hooks";
|
|
||||||
import { useTranslation } from "../../../i18n";
|
|
||||||
import type { InvoiceSummaryFormData } from "../../../schemas";
|
|
||||||
import type { ProformaSummaryPageData } from "../../schema/proforma-summary.web.schema";
|
|
||||||
|
|
||||||
import { useProformasGridColumns } from "./use-proformas-grid-columns";
|
|
||||||
|
|
||||||
export type ProformaGridProps = {
|
|
||||||
data: ProformaSummaryPageData;
|
|
||||||
loading?: boolean;
|
|
||||||
|
|
||||||
pageIndex: number;
|
|
||||||
pageSize: number;
|
|
||||||
onPageChange?: (pageNumber: number) => void;
|
|
||||||
onPageSizeChange?: (pageSize: number) => void;
|
|
||||||
|
|
||||||
searchValue: string;
|
|
||||||
onSearchChange: (value: string) => void;
|
|
||||||
|
|
||||||
onRowClick?: (
|
|
||||||
row: ProformaSummaryPageData,
|
|
||||||
index: number,
|
|
||||||
event: React.MouseEvent<HTMLTableRowElement>
|
|
||||||
) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create new GridExample component
|
|
||||||
export const ProformasGrid = ({
|
|
||||||
data,
|
|
||||||
|
|
||||||
loading,
|
|
||||||
pageIndex,
|
|
||||||
pageSize,
|
|
||||||
onPageChange,
|
|
||||||
onPageSizeChange,
|
|
||||||
searchValue,
|
|
||||||
onSearchChange,
|
|
||||||
onRowClick,
|
|
||||||
}: ProformaGridProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { items, total_items } = data;
|
|
||||||
|
|
||||||
// Hook con Sheet de shadcn
|
|
||||||
const preview = usePinnedPreviewSheet<InvoiceSummaryFormData>({
|
|
||||||
persistKey: "invoice-preview-pin",
|
|
||||||
widthClass: "w-[500px]",
|
|
||||||
});
|
|
||||||
|
|
||||||
const [statusFilter, setStatusFilter] = useState("todas");
|
|
||||||
|
|
||||||
const columns = useProformasGridColumns({
|
|
||||||
onEdit: (proforma) => navigate(`/proformas/${proforma.id}/edit`),
|
|
||||||
onDuplicate: (proforma) => null, //duplicateInvoice(inv.id),
|
|
||||||
onDownloadPdf: (proforma) => null, //downloadInvoicePdf(inv.id),
|
|
||||||
onSendEmail: (proforma) => null, //sendInvoiceEmail(inv.id),
|
|
||||||
onDelete: (proforma) => null, //confirmDelete(inv.id),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Navegación accesible (click o teclado)
|
|
||||||
/*const goToRow = useCallback(
|
|
||||||
(id: string, newTab = false) => {
|
|
||||||
const url = `/customer-invoices/${id}/edit`;
|
|
||||||
newTab ? window.open(url, "_blank", "noopener,noreferrer") : navigate(url);
|
|
||||||
},
|
|
||||||
[navigate]
|
|
||||||
);*/
|
|
||||||
|
|
||||||
/*const onRowClicked = useCallback(
|
|
||||||
(e: RowClickedEvent<any>) => {
|
|
||||||
if (!e.data) return;
|
|
||||||
const newTab = e.event instanceof MouseEvent && (e.event.metaKey || e.event.ctrlKey);
|
|
||||||
goToRow(e.data.id, newTab);
|
|
||||||
},
|
|
||||||
[goToRow]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onCellKeyDown = useCallback(
|
|
||||||
(e: CellKeyDownEvent<any>) => {
|
|
||||||
if (!e.data) return;
|
|
||||||
|
|
||||||
const ev = e.event;
|
|
||||||
if (!(ev && ev instanceof KeyboardEvent)) return;
|
|
||||||
|
|
||||||
const key = ev.key;
|
|
||||||
if (key === "Enter" || key === " ") {
|
|
||||||
ev.preventDefault();
|
|
||||||
goToRow(e.data.id);
|
|
||||||
}
|
|
||||||
if ((ev.ctrlKey || ev.metaKey) && key === "Enter") {
|
|
||||||
ev.preventDefault();
|
|
||||||
goToRow(e.data.id, true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[goToRow]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRowClick = useCallback(
|
|
||||||
(invoice: InvoiceSummaryFormData, _i: number, e: React.MouseEvent) => {
|
|
||||||
const url = `/customer-invoices/${invoice.id}/edit`;
|
|
||||||
if (e.metaKey || e.ctrlKey) {
|
|
||||||
window.open(url, "_blank", "noopener,noreferrer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
preview.open(invoice);
|
|
||||||
},
|
|
||||||
[preview]
|
|
||||||
);*/
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<SkeletonDataTable
|
|
||||||
columns={columns.length}
|
|
||||||
footerProps={{ pageIndex, pageSize, totalItems: total_items ?? 0 }}
|
|
||||||
rows={Math.max(6, pageSize)}
|
|
||||||
showFooter
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render principal
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
{/* Barra de filtros */}
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 mb-6">
|
|
||||||
<SimpleSearchInput loading={loading} onSearchChange={onSearchChange} />
|
|
||||||
<Select onValueChange={setStatusFilter} value={statusFilter}>
|
|
||||||
<SelectTrigger className="w-full sm:w-48 bg-white border-gray-200 shadow-sm">
|
|
||||||
<FilterIcon className="mr-2 h-4 w-4" />
|
|
||||||
<SelectValue placeholder="Estado" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="todas">Todas</SelectItem>
|
|
||||||
<SelectItem value="pagada">Borradores</SelectItem>
|
|
||||||
<SelectItem value="pendiente">Enviadas</SelectItem>
|
|
||||||
<SelectItem value="vencida">Aprobadas</SelectItem>
|
|
||||||
<SelectItem value="vencida">Rechazadas</SelectItem>
|
|
||||||
<SelectItem value="vencida">Emitidas</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Button
|
|
||||||
className="border-blue-200 text-blue-600 hover:bg-blue-50 bg-transparent"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
<FileDownIcon className="mr-2 h-4 w-4" />
|
|
||||||
Exportar
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="relative flex">
|
|
||||||
<div className={preview.isPinned ? "flex-1 mr-[500px]" : "flex-1"}>
|
|
||||||
<DataTable
|
|
||||||
columns={columns}
|
|
||||||
data={items}
|
|
||||||
enablePagination
|
|
||||||
enableRowSelection
|
|
||||||
manualPagination
|
|
||||||
onPageChange={onPageChange}
|
|
||||||
onPageSizeChange={onPageSizeChange}
|
|
||||||
//onRowClick={handleRowClick}
|
|
||||||
pageIndex={pageIndex}
|
|
||||||
pageSize={pageSize}
|
|
||||||
readOnly
|
|
||||||
totalItems={total_items}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/*<preview.Preview>
|
|
||||||
{({ item, isPinned, close, togglePin }) => (
|
|
||||||
<InvoicePreviewPanel
|
|
||||||
invoice={item}
|
|
||||||
isPinned={isPinned}
|
|
||||||
onClose={close}
|
|
||||||
onTogglePin={togglePin}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</preview.Preview>*/}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1 +1,2 @@
|
|||||||
|
export * from "./proforma-status-badge";
|
||||||
export * from "./proformas-grid";
|
export * from "./proformas-grid";
|
||||||
|
|||||||
@ -0,0 +1,68 @@
|
|||||||
|
import { Badge } from "@repo/shadcn-ui/components";
|
||||||
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../../../i18n";
|
||||||
|
|
||||||
|
export type ProformaStatus = "draft" | "sent" | "approved" | "rejected" | "issued";
|
||||||
|
|
||||||
|
export type ProformaStatusBadgeProps = {
|
||||||
|
status: string | ProformaStatus; // permitir cualquier valor
|
||||||
|
dotVisible?: boolean;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusColorConfig: Record<ProformaStatus, { badge: string; dot: string }> = {
|
||||||
|
draft: {
|
||||||
|
badge:
|
||||||
|
"bg-gray-500/10 dark:bg-gray-500/20 hover:bg-gray-500/10 text-gray-600 border-gray-400/60",
|
||||||
|
dot: "bg-gray-500",
|
||||||
|
},
|
||||||
|
sent: {
|
||||||
|
badge:
|
||||||
|
"bg-amber-500/10 dark:bg-amber-500/20 hover:bg-amber-500/10 text-amber-500 border-amber-600/60",
|
||||||
|
dot: "bg-amber-500",
|
||||||
|
},
|
||||||
|
approved: {
|
||||||
|
badge:
|
||||||
|
"bg-emerald-500/10 dark:bg-emerald-500/20 hover:bg-emerald-500/10 text-emerald-500 border-emerald-600/60",
|
||||||
|
dot: "bg-emerald-500",
|
||||||
|
},
|
||||||
|
rejected: {
|
||||||
|
badge: "bg-red-500/10 dark:bg-red-500/20 hover:bg-red-500/10 text-red-500 border-red-600/60",
|
||||||
|
dot: "bg-red-500",
|
||||||
|
},
|
||||||
|
issued: {
|
||||||
|
badge:
|
||||||
|
"bg-blue-600/10 dark:bg-blue-600/20 hover:bg-blue-600/10 text-blue-500 border-blue-600/60",
|
||||||
|
dot: "bg-blue-500",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProformaStatusBadge = forwardRef<HTMLDivElement, ProformaStatusBadgeProps>(
|
||||||
|
({ status, dotVisible, className, ...props }, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const normalizedStatus = status.toLowerCase() as ProformaStatus;
|
||||||
|
const config = statusColorConfig[normalizedStatus];
|
||||||
|
const commonClassName =
|
||||||
|
"transition-colors duration-200 cursor-pointer shadow-none rounded-full";
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return (
|
||||||
|
<Badge className={cn(commonClassName, className)} ref={ref} {...props}>
|
||||||
|
{status}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge className={cn(commonClassName, config.badge, className)} {...props}>
|
||||||
|
{dotVisible && <div className={cn("h-1.5 w-1.5 rounded-full mr-2", config.dot)} />}
|
||||||
|
{t(`catalog.proformas.status.${normalizedStatus}`, { defaultValue: status })}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ProformaStatusBadge.displayName = "ProformaStatusBadge";
|
||||||
@ -1,18 +1,18 @@
|
|||||||
import { SimpleSearchInput } from "@erp/core/components";
|
import { SimpleSearchInput } from "@erp/core/components";
|
||||||
import { DataTable, SkeletonDataTable } from "@repo/rdx-ui/components";
|
import { DataTable, SkeletonDataTable } from "@repo/rdx-ui/components";
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
import { FileDownIcon, FilterIcon } from "lucide-react";
|
import { FilterIcon } from "lucide-react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
import type { ProformaSummaryPageData } from "../../../schema/proforma-summary.web.schema";
|
import type { ProformaSummaryPageData } from "../../../schema/proforma-summary.web.schema";
|
||||||
import { useProformasGridColumns } from "../use-proformas-grid-columns";
|
import { useProformasGridColumns } from "../hooks";
|
||||||
|
|
||||||
interface ProformasGridProps {
|
interface ProformasGridProps {
|
||||||
data: ProformaSummaryPageData;
|
data: ProformaSummaryPageData;
|
||||||
@ -25,6 +25,7 @@ interface ProformasGridProps {
|
|||||||
onPageSizeChange: (s: number) => void;
|
onPageSizeChange: (s: number) => void;
|
||||||
onRowClick?: (id: string) => void;
|
onRowClick?: (id: string) => void;
|
||||||
onExportClick?: () => void;
|
onExportClick?: () => void;
|
||||||
|
onStatusFilterChange?: (newStatus: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProformasGrid = ({
|
export const ProformasGrid = ({
|
||||||
@ -38,10 +39,19 @@ export const ProformasGrid = ({
|
|||||||
onPageSizeChange,
|
onPageSizeChange,
|
||||||
onRowClick,
|
onRowClick,
|
||||||
onExportClick,
|
onExportClick,
|
||||||
|
onStatusFilterChange,
|
||||||
}: ProformasGridProps) => {
|
}: ProformasGridProps) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { items, total_items } = data;
|
const { items, total_items } = data;
|
||||||
const columns = useProformasGridColumns();
|
|
||||||
|
const columns = useProformasGridColumns({
|
||||||
|
onEdit: (proforma) => navigate(`/proformas/${proforma.id}/edit`),
|
||||||
|
onDuplicate: (proforma) => null, //duplicateInvoice(inv.id),
|
||||||
|
onDownloadPdf: (proforma) => null, //downloadInvoicePdf(inv.id),
|
||||||
|
onSendEmail: (proforma) => null, //sendInvoiceEmail(inv.id),
|
||||||
|
onDelete: (proforma) => null, //confirmDelete(inv.id),
|
||||||
|
});
|
||||||
|
|
||||||
if (loading)
|
if (loading)
|
||||||
return (
|
return (
|
||||||
@ -55,33 +65,37 @@ export const ProformasGrid = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col sm:flex-row gap-4 mb-6">
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
<SimpleSearchInput onSearchChange={onSearchChange} value={searchValue} />
|
<SimpleSearchInput loading={loading} onSearchChange={onSearchChange} />
|
||||||
<Select defaultValue="all">
|
<Select defaultValue="all" onValueChange={onStatusFilterChange}>
|
||||||
<SelectTrigger className="w-full sm:w-48">
|
<SelectTrigger className="w-full sm:w-48">
|
||||||
<FilterIcon aria-hidden className="mr-2 size-4" />
|
<FilterIcon aria-hidden className="mr-2 size-4" />
|
||||||
<SelectValue placeholder={t("filters.status")} />
|
<SelectValue placeholder={t("filters.status")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">{t("filters.all")}</SelectItem>
|
<SelectItem value="all">{t("catalog.proformas.status.all")}</SelectItem>
|
||||||
<SelectItem value="draft">{t("filters.draft")}</SelectItem>
|
<SelectItem value="draft">{t("catalog.proformas.status.draft")}</SelectItem>
|
||||||
<SelectItem value="sent">{t("filters.sent")}</SelectItem>
|
<SelectItem value="sent">{t("catalog.proformas.status.sent")}</SelectItem>
|
||||||
|
<SelectItem value="approved">{t("catalog.proformas.status.approved")}</SelectItem>
|
||||||
|
<SelectItem value="rejected">{t("catalog.proformas.status.rejected")}</SelectItem>
|
||||||
|
<SelectItem value="issued">{t("catalog.proformas.status.issued")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Button onClick={onExportClick} variant="outline">
|
|
||||||
<FileDownIcon aria-hidden className="mr-2 size-4" />
|
|
||||||
{t("actions.export")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
columnVisibility={{
|
||||||
|
subtotal_amount_fmt: false,
|
||||||
|
discount_amount_fmt: false,
|
||||||
|
taxes_amount_fmt: false,
|
||||||
|
}}
|
||||||
data={items}
|
data={items}
|
||||||
enablePagination
|
enablePagination
|
||||||
manualPagination
|
manualPagination
|
||||||
onPageChange={onPageChange}
|
onPageChange={onPageChange}
|
||||||
onPageSizeChange={onPageSizeChange}
|
onPageSizeChange={onPageSizeChange}
|
||||||
onRowClick={(_, row) => onRowClick?.(row.id)}
|
onRowClick={(row, _index) => onRowClick?.(row.id)}
|
||||||
pageIndex={pageIndex}
|
pageIndex={pageIndex}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
totalItems={total_items}
|
totalItems={total_items}
|
||||||
|
|||||||
@ -1,314 +0,0 @@
|
|||||||
import { formatDate } from "@erp/core/client";
|
|
||||||
import { DataTableColumnHeader } from "@repo/rdx-ui/components";
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
Badge,
|
|
||||||
Button,
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "@repo/shadcn-ui/components";
|
|
||||||
import type { ColumnDef } from "@tanstack/react-table";
|
|
||||||
import {
|
|
||||||
Building2Icon,
|
|
||||||
CopyIcon,
|
|
||||||
DownloadIcon,
|
|
||||||
EditIcon,
|
|
||||||
MailIcon,
|
|
||||||
MoreVerticalIcon,
|
|
||||||
Trash2Icon,
|
|
||||||
User2Icon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import * as React from "react";
|
|
||||||
|
|
||||||
import { useTranslation } from "../../../i18n";
|
|
||||||
import type { InvoiceSummaryFormData } from "../../../schemas";
|
|
||||||
import { CustomerInvoiceStatusBadge } from "../../../shared/ui/components";
|
|
||||||
|
|
||||||
type GridActionHandlers = {
|
|
||||||
onEdit?: (invoice: InvoiceSummaryFormData) => void;
|
|
||||||
onDuplicate?: (invoice: InvoiceSummaryFormData) => void;
|
|
||||||
onDownloadPdf?: (invoice: InvoiceSummaryFormData) => void;
|
|
||||||
onSendEmail?: (invoice: InvoiceSummaryFormData) => void;
|
|
||||||
onDelete?: (invoice: InvoiceSummaryFormData) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
function initials(name: string) {
|
|
||||||
const parts = name.trim().split(/\s+/).slice(0, 2);
|
|
||||||
return parts.map((p) => p[0]?.toUpperCase() ?? "").join("") || "?";
|
|
||||||
}
|
|
||||||
|
|
||||||
const KindBadge = ({ isCompany }: { isCompany: boolean }) => (
|
|
||||||
<Badge className="gap-1 tracking-wide text-xs text-foreground/70" variant="outline">
|
|
||||||
{isCompany ? <Building2Icon className="size-3.5" /> : <User2Icon className="size-3.5" />}
|
|
||||||
{isCompany ? "Company" : "Person"}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Soft = ({ children }: { children: React.ReactNode }) => (
|
|
||||||
<span className="text-muted-foreground">{children}</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
export function useProformasGridColumns(
|
|
||||||
actionHandlers: GridActionHandlers = {}
|
|
||||||
): ColumnDef<InvoiceSummaryFormData>[] {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { onEdit, onDuplicate, onDownloadPdf, onSendEmail, onDelete } = actionHandlers;
|
|
||||||
|
|
||||||
return React.useMemo<ColumnDef<InvoiceSummaryFormData>[]>(
|
|
||||||
() => [
|
|
||||||
// Nº
|
|
||||||
{
|
|
||||||
accessorKey: "invoice_number",
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader
|
|
||||||
className="text-left"
|
|
||||||
column={column}
|
|
||||||
title={t("pages.proformas.list.grid_columns.invoice_number")}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="font-semibold text-left text-primary">{row.original.invoice_number}</div>
|
|
||||||
),
|
|
||||||
enableHiding: false,
|
|
||||||
enableSorting: false,
|
|
||||||
size: 160,
|
|
||||||
minSize: 120,
|
|
||||||
},
|
|
||||||
// Estado
|
|
||||||
{
|
|
||||||
accessorKey: "status",
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader
|
|
||||||
className="text-left"
|
|
||||||
column={column}
|
|
||||||
title={t("pages.proformas.list.grid_columns.status")}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => <CustomerInvoiceStatusBadge status={row.original.status} />,
|
|
||||||
enableSorting: false,
|
|
||||||
size: 140,
|
|
||||||
minSize: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "recipient",
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader
|
|
||||||
className="text-left"
|
|
||||||
column={column}
|
|
||||||
title={t("pages.list.grid_columns.recipient")}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
accessorFn: (row) => row.recipient.name, // para ordenar/buscar por nombre
|
|
||||||
enableHiding: false,
|
|
||||||
size: 140,
|
|
||||||
minSize: 120,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const c = row.original.recipient;
|
|
||||||
const isCompany = String(c.is_company).toLowerCase() === "true";
|
|
||||||
return (
|
|
||||||
<div className="flex items-start gap-1 my-1.5">
|
|
||||||
<Avatar className="size-10 hidden">
|
|
||||||
<AvatarFallback aria-label={c.name}>{initials(c.name)}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="min-w-0 grid gap-1">
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
|
||||||
<span className="font-medium truncate text-primary">{c.name}</span>
|
|
||||||
{c.trade_name && <Soft>({c.trade_name})</Soft>}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
|
||||||
{c.tin && <span className="font-base truncate">{c.tin}</span>}
|
|
||||||
<KindBadge isCompany={isCompany} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Serie
|
|
||||||
{
|
|
||||||
accessorKey: "series",
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader
|
|
||||||
className="text-left"
|
|
||||||
column={column}
|
|
||||||
title={t("pages.proformas.list.grid_columns.series")}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => <div className="font-normal text-left">{row.original.series}</div>,
|
|
||||||
enableSorting: false,
|
|
||||||
size: 120,
|
|
||||||
minSize: 100,
|
|
||||||
},
|
|
||||||
// Referencia
|
|
||||||
{
|
|
||||||
accessorKey: "reference",
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader
|
|
||||||
className="text-left"
|
|
||||||
column={column}
|
|
||||||
title={t("pages.proformas.list.grid_columns.reference")}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => <div className="font-medium text-left">{row.original.reference}</div>,
|
|
||||||
enableSorting: false,
|
|
||||||
size: 120,
|
|
||||||
minSize: 100,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Fecha factura
|
|
||||||
{
|
|
||||||
accessorKey: "invoice_date",
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader
|
|
||||||
className="text-left tabular-nums"
|
|
||||||
column={column}
|
|
||||||
title={t("pages.proformas.list.grid_columns.invoice_date")}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="font-medium text-left tabular-nums">
|
|
||||||
{formatDate(row.original.invoice_date)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
enableSorting: false,
|
|
||||||
size: 140,
|
|
||||||
minSize: 120,
|
|
||||||
},
|
|
||||||
// Fecha operación
|
|
||||||
{
|
|
||||||
accessorKey: "operation_date",
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader
|
|
||||||
className="text-left tabular-nums"
|
|
||||||
column={column}
|
|
||||||
title={t("pages.proformas.list.grid_columns.operation_date")}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="font-medium text-left tabular-nums">
|
|
||||||
{formatDate(row.original.operation_date)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
enableSorting: false,
|
|
||||||
size: 140,
|
|
||||||
minSize: 120,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Total
|
|
||||||
{
|
|
||||||
accessorKey: "total_amount_fmt",
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader
|
|
||||||
className="text-right tabular-nums"
|
|
||||||
column={column}
|
|
||||||
title={t("pages.proformas.list.grid_columns.total_amount")}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="font-semibold text-right tabular-nums">
|
|
||||||
{row.original.total_amount_fmt}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
enableSorting: false,
|
|
||||||
size: 140,
|
|
||||||
minSize: 120,
|
|
||||||
},
|
|
||||||
|
|
||||||
// ─────────────────────────────
|
|
||||||
// Acciones
|
|
||||||
// ─────────────────────────────
|
|
||||||
{
|
|
||||||
id: "actions",
|
|
||||||
header: () => <span className="sr-only">{t("common.actions")}</span>,
|
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
|
||||||
size: 110,
|
|
||||||
minSize: 96,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const invoice = row.original;
|
|
||||||
const stop = (e: React.MouseEvent | React.KeyboardEvent) => e.stopPropagation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-end gap-1 pr-1">
|
|
||||||
{/* Editar (acción primaria) */}
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
aria-label={t("common.edit")}
|
|
||||||
className="cursor-pointer text-muted-foreground hover:text-primary"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onEdit?.(invoice);
|
|
||||||
}}
|
|
||||||
size="sm"
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
<EditIcon aria-hidden="true" className="size-4" />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>{t("common.edit")}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* Menú demás acciones */}
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
aria-label={t("common.more_actions")}
|
|
||||||
className="cursor-pointer text-muted-foreground hover:text-primary"
|
|
||||||
onClick={stop}
|
|
||||||
size="sm"
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
<MoreVerticalIcon aria-hidden="true" className="size-4" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end" className="w-48">
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => onDuplicate?.(invoice)}
|
|
||||||
>
|
|
||||||
<CopyIcon className="mr-2 size-4" />
|
|
||||||
{t("common.duplicate")}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => onDownloadPdf?.(invoice)}
|
|
||||||
>
|
|
||||||
<DownloadIcon className="mr-2 size-4" />
|
|
||||||
{t("common.download_pdf")}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => onSendEmail?.(invoice)}
|
|
||||||
>
|
|
||||||
<MailIcon className="mr-2 size-4" />
|
|
||||||
{t("common.send_email")}
|
|
||||||
</DropdownMenuItem>{" "}
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="text-destructive focus:text-destructive-foreground focus:bg-destructive cursor-pointer"
|
|
||||||
onClick={() => onDelete?.(invoice)}
|
|
||||||
>
|
|
||||||
<Trash2Icon className="mr-2 size-4 text-destructive focus:text-destructive-foreground" />
|
|
||||||
{t("common.delete")}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[t, onEdit, onDuplicate, onDownloadPdf, onSendEmail, onDelete]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -13,7 +13,7 @@ export type ProformaSummaryData = ProformaSummary & {
|
|||||||
taxable_amount_fmt: string;
|
taxable_amount_fmt: string;
|
||||||
taxable_amount: number;
|
taxable_amount: number;
|
||||||
|
|
||||||
taxes_amoun_fmt: string;
|
taxes_amount_fmt: string;
|
||||||
taxes_amount: number;
|
taxes_amount: number;
|
||||||
|
|
||||||
total_amount_fmt: string;
|
total_amount_fmt: string;
|
||||||
@ -21,5 +21,5 @@ export type ProformaSummaryData = ProformaSummary & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ProformaSummaryPageData = ProformaSummaryPage & {
|
export type ProformaSummaryPageData = ProformaSummaryPage & {
|
||||||
items: ProformaSummary[];
|
items: ProformaSummaryData[];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
CreateProformaRequestSchema,
|
CreateProformaRequestSchema,
|
||||||
GetProformaByIdResponseSchema,
|
GetProformaByIdResponseSchema,
|
||||||
ListProformasResponseSchema,
|
type ListProformasResponseDTO,
|
||||||
UpdateProformaByIdRequestSchema,
|
UpdateProformaByIdRequestSchema,
|
||||||
} from "@erp/customer-invoices/common";
|
} from "@erp/customer-invoices/common";
|
||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
import type { ArrayElement } from "@repo/rdx-utils";
|
||||||
@ -23,9 +23,5 @@ export type CreateProformaInput = z.infer<typeof CreateProformaSchema>; // Cuerp
|
|||||||
export type UpdateProformaInput = z.infer<typeof UpdateProformaSchema>; // Cuerpo para actualizar
|
export type UpdateProformaInput = z.infer<typeof UpdateProformaSchema>; // Cuerpo para actualizar
|
||||||
|
|
||||||
// Resultado de consulta con criteria (paginado, etc.)
|
// Resultado de consulta con criteria (paginado, etc.)
|
||||||
export const ProformaSummaryPageSchema = ListProformasResponseSchema.omit({
|
export type ProformaSummaryPage = Omit<ListProformasResponseDTO, "metadata">;
|
||||||
metadata: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ProformaSummaryPage = z.infer<typeof ProformaSummaryPageSchema>;
|
|
||||||
export type ProformaSummary = Omit<ArrayElement<ProformaSummaryPage["items"]>, "metadata">;
|
export type ProformaSummary = Omit<ArrayElement<ProformaSummaryPage["items"]>, "metadata">;
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
import type { ArrayElement } from "@repo/rdx-utils";
|
import type { ArrayElement } from "@repo/rdx-utils";
|
||||||
import type { z } from "zod/v4";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { GetIssueInvoiceByIdResponseSchema, ListIssueInvoicesResponseSchema } from "../../common";
|
import { GetIssuedInvoiceByIdResponseSchema, ListIssuedInvoicesResponseSchema } from "../../common";
|
||||||
|
|
||||||
export const IssueInvoiceSchema = GetIssueInvoiceByIdResponseSchema.omit({
|
export const IssuedInvoiceschema = GetIssuedInvoiceByIdResponseSchema.omit({
|
||||||
metadata: true,
|
metadata: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type IssueInvoice = z.infer<typeof IssueInvoiceSchema>;
|
export type IssueInvoice = z.infer<typeof IssuedInvoiceschema>;
|
||||||
export type IssueInvoiceRecipient = IssueInvoice["recipient"];
|
export type IssueInvoiceRecipient = IssueInvoice["recipient"];
|
||||||
export type IssueInvoiceItem = ArrayElement<IssueInvoice["items"]>;
|
export type IssueInvoiceItem = ArrayElement<IssueInvoice["items"]>;
|
||||||
|
|
||||||
// Resultado de consulta con criteria (paginado, etc.)
|
// Resultado de consulta con criteria (paginado, etc.)
|
||||||
export const IssueInvoicesPageSchema = ListIssueInvoicesResponseSchema.omit({
|
export const IssuedInvoicesPageSchema = ListIssuedInvoicesResponseSchema.omit({
|
||||||
metadata: true,
|
metadata: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,67 +0,0 @@
|
|||||||
import { Badge } from "@repo/shadcn-ui/components";
|
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
|
||||||
import { forwardRef } from "react";
|
|
||||||
|
|
||||||
import { useTranslation } from "../../../i18n";
|
|
||||||
|
|
||||||
export type CustomerInvoiceStatus = "draft" | "sent" | "approved" | "rejected" | "issued";
|
|
||||||
|
|
||||||
export type CustomerInvoiceStatusBadgeProps = {
|
|
||||||
status: string | CustomerInvoiceStatus; // permitir cualquier valor
|
|
||||||
dotVisible?: boolean;
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const statusColorConfig: Record<CustomerInvoiceStatus, { badge: string; dot: string }> = {
|
|
||||||
draft: {
|
|
||||||
badge:
|
|
||||||
"bg-gray-500/10 dark:bg-gray-500/20 hover:bg-gray-500/10 text-gray-600 border-gray-400/60",
|
|
||||||
dot: "bg-gray-500",
|
|
||||||
},
|
|
||||||
sent: {
|
|
||||||
badge:
|
|
||||||
"bg-amber-500/10 dark:bg-amber-500/20 hover:bg-amber-500/10 text-amber-500 border-amber-600/60",
|
|
||||||
dot: "bg-amber-500",
|
|
||||||
},
|
|
||||||
approved: {
|
|
||||||
badge:
|
|
||||||
"bg-emerald-500/10 dark:bg-emerald-500/20 hover:bg-emerald-500/10 text-emerald-500 border-emerald-600/60",
|
|
||||||
dot: "bg-emerald-500",
|
|
||||||
},
|
|
||||||
rejected: {
|
|
||||||
badge: "bg-red-500/10 dark:bg-red-500/20 hover:bg-red-500/10 text-red-500 border-red-600/60",
|
|
||||||
dot: "bg-red-500",
|
|
||||||
},
|
|
||||||
issued: {
|
|
||||||
badge:
|
|
||||||
"bg-blue-600/10 dark:bg-blue-600/20 hover:bg-blue-600/10 text-blue-500 border-blue-600/60",
|
|
||||||
dot: "bg-blue-500",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CustomerInvoiceStatusBadge = forwardRef<
|
|
||||||
HTMLDivElement,
|
|
||||||
CustomerInvoiceStatusBadgeProps
|
|
||||||
>(({ status, dotVisible, className, ...props }, ref) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const normalizedStatus = status.toLowerCase() as CustomerInvoiceStatus;
|
|
||||||
const config = statusColorConfig[normalizedStatus];
|
|
||||||
const commonClassName = "transition-colors duration-200 cursor-pointer shadow-none rounded-full";
|
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
return (
|
|
||||||
<Badge className={cn(commonClassName, className)} ref={ref} {...props}>
|
|
||||||
{status}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Badge className={cn(commonClassName, config.badge, className)} {...props}>
|
|
||||||
{dotVisible && <div className={cn("h-1.5 w-1.5 rounded-full mr-2", config.dot)} />}
|
|
||||||
{t(`catalog.status.${normalizedStatus}`, { defaultValue: status })}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
CustomerInvoiceStatusBadge.displayName = "CustomerInvoiceStatusBadge";
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
|
export * from "../../../proformas/pages/list/ui/proforma-status-badge";
|
||||||
|
|
||||||
export * from "./customer-invoice-editor-skeleton";
|
export * from "./customer-invoice-editor-skeleton";
|
||||||
export * from "./customer-invoice-prices-card";
|
export * from "./customer-invoice-prices-card";
|
||||||
export * from "./customer-invoice-status-badge";
|
|
||||||
export * from "./customer-invoice-taxes-multi-select";
|
export * from "./customer-invoice-taxes-multi-select";
|
||||||
export * from "./editor";
|
export * from "./editor";
|
||||||
export * from "./editor/invoice-tax-summary";
|
export * from "./editor/invoice-tax-summary";
|
||||||
|
|||||||
@ -28,6 +28,6 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["src", "../core/src/common/helpers/date-helper.ts"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user