Oleada UX 3: POS (repetir ultima venta, aviso de paquetes, atajos, caja desde POS), Ventas (cancelacion auditada con reversion de monedero y stock, detalle inline, presets de fecha), Cortes (historial comparativo y export)

This commit is contained in:
2026-09-17 19:26:02 +00:00
parent b4f91527f1
commit ac5896b1fc
10 changed files with 1328 additions and 57 deletions

View File

@@ -388,6 +388,46 @@ export interface Sale {
refund_amount?: number;
refund_reason?: string;
refunded_at?: string | null;
notes?: string;
cancel_reason?: string;
cancelled_by?: string;
cancelled_at?: string | null;
cancel_points_reversed?: number;
cancel_points_restored?: number;
}
// "Repetir última venta" (POS): línea de la última venta con el precio vigente
export interface LastSaleLine {
kind: 'service' | 'item';
service_id: number | null;
item_id: number | null;
name: string;
description: string;
quantity: number;
price_last: number;
price_current: number | null;
price_changed: boolean;
missing: boolean;
active: boolean;
stock: number | null;
unit: string | null;
}
export interface LastSaleResponse {
status: string;
sale: Sale | null;
lines: LastSaleLine[];
warnings: string[];
}
export interface PatientPackage {
service_id: number;
name: string;
package_sessions: number;
total: number;
used: number;
remaining: number;
next_date: string | null;
}
export interface MySaleItem {
@@ -464,7 +504,7 @@ export interface Birthday {
export type InventoryKind = 'producto' | 'consumible';
export type InventoryLevel = 'out' | 'critical' | 'low' | 'optimal';
export type InventoryMoveType = 'compra' | 'venta' | 'baja' | 'ajuste';
export type InventoryMoveType = 'compra' | 'venta' | 'baja' | 'ajuste' | 'cancelacion';
export interface InventoryItem {
id: number;
@@ -1350,6 +1390,24 @@ export const odooApi = {
return data;
},
// Cancelación con nota de crédito (permiso admin/gerencia)
async cancelSale(id: number, payload: { reason: string; restore_stock?: boolean }): Promise<{ status: string; sale: Sale; result: { points_reversed: number; points_restored: number; stock_moves: number } }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/sales/${id}/cancel`, payload);
return data;
},
// Última venta del paciente con precios actuales del catálogo (POS: repetir venta)
async getLastSale(patientId: number): Promise<LastSaleResponse> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/sales/last`, { params: { patient_id: patientId } });
return data;
},
// Paquetes del paciente con sesiones restantes (POS: aviso en el ticket)
async getPatientPackages(patientId: number): Promise<{ status: string; packages: PatientPackage[] }> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/patients/${patientId}/packages`);
return data;
},
// Cortes de caja
async getCashClosings(date?: string): Promise<{ status: string; cash_closings: CashClosing[] }> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/cash_closings`, { params: date ? { date } : {} });