feat(cfdi): agrega columna y filtro no_identificacion en tabla Conceptos
- Backend (cfdi.service.ts): getConceptosList ahora soporta filtro noIdentificacion via cc.no_identificacion ILIKE - Frontend API (cfdi.ts): ConceptosFilters incluye noIdentificacion; se envía como query param - Frontend página (cfdi/page.tsx): * Nuevo estado noIdentificacion en conceptosFilters * Nueva columna 'No. Identificación' en header de tabla con Popover filtro * Celda no_identificacion renderizada en cada fila * Export a Excel respeta el nuevo filtro
This commit is contained in:
@@ -181,6 +181,7 @@ export async function getConceptosList(
|
|||||||
uuidLike?: string;
|
uuidLike?: string;
|
||||||
claveProdServ?: string;
|
claveProdServ?: string;
|
||||||
descripcionConcepto?: string;
|
descripcionConcepto?: string;
|
||||||
|
noIdentificacion?: string;
|
||||||
orderBy?: 'fecha' | 'importe';
|
orderBy?: 'fecha' | 'importe';
|
||||||
orderDir?: 'asc' | 'desc';
|
orderDir?: 'asc' | 'desc';
|
||||||
},
|
},
|
||||||
@@ -261,6 +262,10 @@ export async function getConceptosList(
|
|||||||
whereClause += ` AND cc.descripcion ILIKE $${paramIndex++}`;
|
whereClause += ` AND cc.descripcion ILIKE $${paramIndex++}`;
|
||||||
params.push(`%${filters.descripcionConcepto}%`);
|
params.push(`%${filters.descripcionConcepto}%`);
|
||||||
}
|
}
|
||||||
|
if (filters.noIdentificacion) {
|
||||||
|
whereClause += ` AND cc.no_identificacion ILIKE $${paramIndex++}`;
|
||||||
|
params.push(`%${filters.noIdentificacion}%`);
|
||||||
|
}
|
||||||
|
|
||||||
// Ordenamiento configurable. Default: fecha DESC, id ASC (estable).
|
// Ordenamiento configurable. Default: fecha DESC, id ASC (estable).
|
||||||
const orderDir = filters.orderDir === 'asc' ? 'ASC' : 'DESC';
|
const orderDir = filters.orderDir === 'asc' ? 'ASC' : 'DESC';
|
||||||
|
|||||||
@@ -325,10 +325,11 @@ export default function CfdiPage() {
|
|||||||
uuidLike: string;
|
uuidLike: string;
|
||||||
claveProdServ: string;
|
claveProdServ: string;
|
||||||
descripcionConcepto: string;
|
descripcionConcepto: string;
|
||||||
|
noIdentificacion: string;
|
||||||
orderBy?: 'fecha' | 'importe';
|
orderBy?: 'fecha' | 'importe';
|
||||||
orderDir?: 'asc' | 'desc';
|
orderDir?: 'asc' | 'desc';
|
||||||
}>({ uuidLike: '', claveProdServ: '', descripcionConcepto: '' });
|
}>({ uuidLike: '', claveProdServ: '', descripcionConcepto: '', noIdentificacion: '' });
|
||||||
const [conceptosOpenFilter, setConceptosOpenFilter] = useState<'uuid' | 'clave' | 'descripcion' | null>(null);
|
const [conceptosOpenFilter, setConceptosOpenFilter] = useState<'uuid' | 'clave' | 'descripcion' | 'noIdentificacion' | null>(null);
|
||||||
|
|
||||||
const conceptosQuery = useQuery({
|
const conceptosQuery = useQuery({
|
||||||
queryKey: ['cfdi-conceptos', filters, selectedContribuyenteId, conceptosFilters],
|
queryKey: ['cfdi-conceptos', filters, selectedContribuyenteId, conceptosFilters],
|
||||||
@@ -338,6 +339,7 @@ export default function CfdiPage() {
|
|||||||
uuidLike: conceptosFilters.uuidLike || undefined,
|
uuidLike: conceptosFilters.uuidLike || undefined,
|
||||||
claveProdServ: conceptosFilters.claveProdServ || undefined,
|
claveProdServ: conceptosFilters.claveProdServ || undefined,
|
||||||
descripcionConcepto: conceptosFilters.descripcionConcepto || undefined,
|
descripcionConcepto: conceptosFilters.descripcionConcepto || undefined,
|
||||||
|
noIdentificacion: conceptosFilters.noIdentificacion || undefined,
|
||||||
orderBy: conceptosFilters.orderBy,
|
orderBy: conceptosFilters.orderBy,
|
||||||
orderDir: conceptosFilters.orderDir,
|
orderDir: conceptosFilters.orderDir,
|
||||||
}),
|
}),
|
||||||
@@ -481,6 +483,7 @@ export default function CfdiPage() {
|
|||||||
uuidLike: conceptosFilters.uuidLike || undefined,
|
uuidLike: conceptosFilters.uuidLike || undefined,
|
||||||
claveProdServ: conceptosFilters.claveProdServ || undefined,
|
claveProdServ: conceptosFilters.claveProdServ || undefined,
|
||||||
descripcionConcepto: conceptosFilters.descripcionConcepto || undefined,
|
descripcionConcepto: conceptosFilters.descripcionConcepto || undefined,
|
||||||
|
noIdentificacion: conceptosFilters.noIdentificacion || undefined,
|
||||||
orderBy: conceptosFilters.orderBy,
|
orderBy: conceptosFilters.orderBy,
|
||||||
orderDir: conceptosFilters.orderDir,
|
orderDir: conceptosFilters.orderDir,
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -1647,6 +1650,28 @@ export default function CfdiPage() {
|
|||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
|
<th className="pb-3 font-medium">
|
||||||
|
<div className="flex items-center gap-1 justify-center">
|
||||||
|
No. Identificación
|
||||||
|
<Popover open={conceptosOpenFilter === 'noIdentificacion'} onOpenChange={(open) => setConceptosOpenFilter(open ? 'noIdentificacion' : null)}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button className={`p-1 rounded hover:bg-muted ${conceptosFilters.noIdentificacion ? 'text-primary' : ''}`}>
|
||||||
|
<Filter className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-64" align="start">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="font-medium text-sm">Filtrar por No. Identificación</h4>
|
||||||
|
<Input className="h-8 text-sm font-mono" placeholder="Ej: PROD-001" value={conceptosFilters.noIdentificacion} onChange={(e) => setConceptosFilters({ ...conceptosFilters, noIdentificacion: e.target.value })} />
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button size="sm" className="flex-1" onClick={() => { setFilters({ ...filters, page: 1 }); setConceptosOpenFilter(null); }}>Aplicar</Button>
|
||||||
|
{conceptosFilters.noIdentificacion && <Button size="sm" variant="outline" onClick={() => { setConceptosFilters({ ...conceptosFilters, noIdentificacion: '' }); setFilters({ ...filters, page: 1 }); }}>Limpiar</Button>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
<th className="pb-3 font-medium">RFC Emisor</th>
|
<th className="pb-3 font-medium">RFC Emisor</th>
|
||||||
<th className="pb-3 font-medium">RFC Receptor</th>
|
<th className="pb-3 font-medium">RFC Receptor</th>
|
||||||
<th className="pb-3 font-medium text-right">Cantidad</th>
|
<th className="pb-3 font-medium text-right">Cantidad</th>
|
||||||
@@ -1676,6 +1701,7 @@ export default function CfdiPage() {
|
|||||||
<td className="py-2 font-mono text-xs" title={row.uuid}>{row.uuid?.substring(0, 8) || '-'}</td>
|
<td className="py-2 font-mono text-xs" title={row.uuid}>{row.uuid?.substring(0, 8) || '-'}</td>
|
||||||
<td className="py-2 font-mono text-xs">{row.clave_prod_serv || '-'}</td>
|
<td className="py-2 font-mono text-xs">{row.clave_prod_serv || '-'}</td>
|
||||||
<td className="py-2 text-left max-w-[280px] truncate" title={row.descripcion}>{row.descripcion}</td>
|
<td className="py-2 text-left max-w-[280px] truncate" title={row.descripcion}>{row.descripcion}</td>
|
||||||
|
<td className="py-2 font-mono text-xs" title={row.no_identificacion || ''}>{row.no_identificacion || '-'}</td>
|
||||||
<td className="py-2 font-mono text-xs">{row.rfcEmisor}</td>
|
<td className="py-2 font-mono text-xs">{row.rfcEmisor}</td>
|
||||||
<td className="py-2 font-mono text-xs">{row.rfcReceptor}</td>
|
<td className="py-2 font-mono text-xs">{row.rfcReceptor}</td>
|
||||||
<td className="py-2 text-right">{Number(row.cantidad ?? 0).toLocaleString('es-MX', { minimumFractionDigits: 2 })}</td>
|
<td className="py-2 text-right">{Number(row.cantidad ?? 0).toLocaleString('es-MX', { minimumFractionDigits: 2 })}</td>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export interface ConceptosFilters extends CfdiFilters {
|
|||||||
uuidLike?: string;
|
uuidLike?: string;
|
||||||
claveProdServ?: string;
|
claveProdServ?: string;
|
||||||
descripcionConcepto?: string;
|
descripcionConcepto?: string;
|
||||||
|
noIdentificacion?: string;
|
||||||
orderBy?: 'fecha' | 'importe';
|
orderBy?: 'fecha' | 'importe';
|
||||||
orderDir?: 'asc' | 'desc';
|
orderDir?: 'asc' | 'desc';
|
||||||
}
|
}
|
||||||
@@ -58,6 +59,7 @@ export async function getConceptosList(filters: ConceptosFilters): Promise<Conce
|
|||||||
if (filters.uuidLike) params.set('uuidLike', filters.uuidLike);
|
if (filters.uuidLike) params.set('uuidLike', filters.uuidLike);
|
||||||
if (filters.claveProdServ) params.set('claveProdServ', filters.claveProdServ);
|
if (filters.claveProdServ) params.set('claveProdServ', filters.claveProdServ);
|
||||||
if (filters.descripcionConcepto) params.set('descripcionConcepto', filters.descripcionConcepto);
|
if (filters.descripcionConcepto) params.set('descripcionConcepto', filters.descripcionConcepto);
|
||||||
|
if (filters.noIdentificacion) params.set('noIdentificacion', filters.noIdentificacion);
|
||||||
if (filters.orderBy) params.set('orderBy', filters.orderBy);
|
if (filters.orderBy) params.set('orderBy', filters.orderBy);
|
||||||
if (filters.orderDir) params.set('orderDir', filters.orderDir);
|
if (filters.orderDir) params.set('orderDir', filters.orderDir);
|
||||||
const response = await apiClient.get<ConceptosListResponse>(`/cfdi/conceptos?${params}`);
|
const response = await apiClient.get<ConceptosListResponse>(`/cfdi/conceptos?${params}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user