/** * Cliente API — Módulo Tarifario (tarifarios, rutas, import Excel, costeo). */ import { api } from '$lib/api'; export type RateMode = 'aereo' | 'maritimo_fcl' | 'maritimo_lcl' | 'terrestre'; export interface RateBreak { from_qty: number; rate: number; } export interface RateLane { id: number; rate_sheet_id: number; origin: string | null; destination: string | null; region: string | null; equipment_type: string | null; rate_unit: string | null; min_charge: number | null; flat_rate: number | null; transit_days: number | null; notes: string | null; breaks: RateBreak[]; } export interface RateSheet { id: number; supplier_id: number | null; mode: RateMode; name: string; currency: string | null; valid_from: string | null; valid_to: string | null; default_origin: string | null; status: string; notes: string | null; source_file: string | null; created_by: string | null; updated_by: string | null; created_at: string; updated_at: string; lane_count: number | null; } export type RateSheetInput = Partial> & { mode: RateMode; name: string; }; export interface RateCharge { id: number; rate_sheet_id: number | null; rate_lane_id: number | null; concept: string; charge_type: string; value: number | null; condition: string | null; } export interface RateChargeInput { concept: string; charge_type: string; value?: number | null; condition?: string | null; rate_lane_id?: number | null; } export interface ImportPreviewRow { row: number; data: Record; ok: boolean; warnings: string[]; errors: string[]; } export interface ImportPreview { mode: RateMode; total: number; valid: number; rows: ImportPreviewRow[]; columns: string[]; } export interface CostRequest { mode: RateMode; origin?: string | null; destination?: string | null; on_date?: string | null; gross_weight_kg?: number | null; volume_m3?: number | null; length_cm?: number | null; width_cm?: number | null; height_cm?: number | null; equipment_type?: string | null; quantity?: number; dangerous?: boolean; } export interface CostChargeLine { concept: string; amount: number; } export interface CostOption { rate_sheet_id: number; rate_sheet_name: string; supplier_id: number | null; currency: string | null; chargeable: number | null; base_cost: number; charges: CostChargeLine[]; total_cost: number; transit_days: number | null; detail: string | null; } export interface CostResult { request: CostRequest; options: CostOption[]; } function qp(companyId: number, extra?: Record) { const qs = new URLSearchParams({ company_id: String(companyId) }); for (const [k, v] of Object.entries(extra ?? {})) if (v !== undefined && v !== '') qs.set(k, String(v)); return qs.toString(); } async function unwrap(p: Promise<{ data?: T; error?: string }>): Promise { const res = await p; if (res.error) throw new Error(res.error); return res.data as T; } export const rateSheetsAPI = { list: (companyId: number, params?: { mode?: string; supplier_id?: number }) => unwrap(api.get(`/v1/crm/rate-sheets?${qp(companyId, params)}`)), get: (id: number, companyId: number) => unwrap(api.get(`/v1/crm/rate-sheets/${id}?${qp(companyId)}`)), create: (data: RateSheetInput, companyId: number) => unwrap(api.post(`/v1/crm/rate-sheets?${qp(companyId)}`, data)), update: (id: number, data: Partial, companyId: number) => unwrap(api.patch(`/v1/crm/rate-sheets/${id}?${qp(companyId)}`, data)), remove: (id: number, companyId: number) => unwrap(api.delete(`/v1/crm/rate-sheets/${id}?${qp(companyId)}`)), lanes: (id: number, companyId: number) => unwrap(api.get(`/v1/crm/rate-sheets/${id}/lanes?${qp(companyId)}`)), addLane: (id: number, data: Partial, companyId: number) => unwrap(api.post(`/v1/crm/rate-sheets/${id}/lanes?${qp(companyId)}`, data)), removeLane: (id: number, laneId: number, companyId: number) => unwrap(api.delete(`/v1/crm/rate-sheets/${id}/lanes/${laneId}?${qp(companyId)}`)), charges: (id: number, companyId: number) => unwrap(api.get(`/v1/crm/rate-sheets/${id}/charges?${qp(companyId)}`)), addCharge: (id: number, data: RateChargeInput, companyId: number) => unwrap(api.post(`/v1/crm/rate-sheets/${id}/charges?${qp(companyId)}`, data)), removeCharge: (id: number, chargeId: number, companyId: number) => unwrap(api.delete(`/v1/crm/rate-sheets/${id}/charges/${chargeId}?${qp(companyId)}`)), /** Descarga la plantilla Excel del modo. */ async downloadTemplate(mode: RateMode, companyId: number): Promise { const blob = await api.getBlob(`/v1/crm/rate-sheets/template?${qp(companyId, { mode })}`); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `plantilla_tarifario_${mode}.xlsx`; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); }, async importPreview(mode: RateMode, file: File, companyId: number): Promise { const fd = new FormData(); fd.append('mode', mode); fd.append('file', file); const res = await (api as any).request(`/v1/crm/rate-sheets/import/preview?${qp(companyId)}`, { method: 'POST', body: fd }); if (res.error) throw new Error(res.error); return res.data as ImportPreview; }, async importSheet(companyId: number, header: { mode: RateMode; name: string; supplier_id?: number | null; currency?: string; valid_from?: string | null; valid_to?: string | null; default_origin?: string | null }, file: File): Promise { const fd = new FormData(); fd.append('mode', header.mode); fd.append('name', header.name); if (header.supplier_id != null) fd.append('supplier_id', String(header.supplier_id)); if (header.currency) fd.append('currency', header.currency); if (header.valid_from) fd.append('valid_from', header.valid_from); if (header.valid_to) fd.append('valid_to', header.valid_to); if (header.default_origin) fd.append('default_origin', header.default_origin); fd.append('file', file); const res = await (api as any).request(`/v1/crm/rate-sheets/import?${qp(companyId)}`, { method: 'POST', body: fd }); if (res.error) throw new Error(res.error); return res.data as RateSheet; }, quote: (req: CostRequest, companyId: number) => unwrap(api.post(`/v1/crm/rate-quote?${qp(companyId)}`, req)), /** Orígenes/destinos que existen en los tarifarios activos (para alinear el cotizador con las rutas cotizables). */ locations: (companyId: number, mode: RateMode) => unwrap<{ origins: string[]; destinations: string[] }>(api.get(`/v1/crm/rate-locations?${qp(companyId, { mode })}`)) };