// ── Dispatch Settings — lecture/écriture du DocType Single ERPNext ─────────── import { BASE_URL } from 'src/config/erpnext' import { getCSRF } from './auth' const DOCTYPE = 'Dispatch Settings' const NAME = 'Dispatch Settings' function isDocTypeError (body) { const s = JSON.stringify(body) return s.includes('dispatch_settings') || s.includes('DoesNotExist') || s.includes('No module named') } export async function fetchSettings () { const r = await fetch(`${BASE_URL}/api/resource/${DOCTYPE}/${NAME}`, { credentials: 'include', }) if (!r.ok) { const body = await r.json().catch(() => ({})) if (r.status === 404 || isDocTypeError(body)) throw new Error('DOCTYPE_NOT_FOUND') throw new Error(`Erreur HTTP ${r.status}`) } const body = await r.json() // Frappe peut retourner 200 avec une exception dans le corps if (body.exc_type || body.exception) { if (isDocTypeError(body)) throw new Error('DOCTYPE_NOT_FOUND') throw new Error(body.exc_type || 'Erreur Frappe') } return body.data } export async function saveSettings (payload) { const csrf = await getCSRF() const r = await fetch(`${BASE_URL}/api/resource/${DOCTYPE}/${NAME}`, { method: 'PUT', credentials: 'include', headers: { 'Content-Type': 'application/json', 'X-Frappe-CSRF-Token': csrf || '' }, body: JSON.stringify(payload), }) const body = await r.json().catch(() => ({})) if (!r.ok || body.exc_type || body.exception) { if (isDocTypeError(body)) throw new Error('DOCTYPE_NOT_FOUND') throw new Error(body._error_message || body.exc_type || `Erreur HTTP ${r.status}`) } return body } // ── Création du DocType via API (bouton Initialiser dans l'admin) ───────────── const DOCTYPE_FIELDS = [ { fieldname: 'erp_section', fieldtype: 'Section Break', label: 'ERPNext / Frappe' }, { fieldname: 'erp_url', fieldtype: 'Data', label: 'URL du serveur', default: 'http://localhost:8080' }, { fieldname: 'erp_api_key', fieldtype: 'Data', label: 'API Key' }, { fieldname: 'erp_api_secret', fieldtype: 'Password', label: 'API Secret' }, { fieldname: 'mapbox_section', fieldtype: 'Section Break', label: 'Mapbox' }, { fieldname: 'mapbox_token', fieldtype: 'Data', label: 'Token public (pk_)' }, { fieldname: 'twilio_section', fieldtype: 'Section Break', label: 'Twilio — SMS' }, { fieldname: 'twilio_account_sid', fieldtype: 'Data', label: 'Account SID' }, { fieldname: 'twilio_auth_token', fieldtype: 'Password', label: 'Auth Token' }, { fieldname: 'twilio_from_number', fieldtype: 'Data', label: 'Numéro expéditeur' }, { fieldname: 'stripe_section', fieldtype: 'Section Break', label: 'Stripe — Paiements' }, { fieldname: 'stripe_mode', fieldtype: 'Select', label: 'Mode', options: 'test\nlive', default: 'test' }, { fieldname: 'stripe_publishable_key', fieldtype: 'Data', label: 'Clé publique (pk_)' }, { fieldname: 'stripe_secret_key', fieldtype: 'Password', label: 'Clé secrète (sk_)' }, { fieldname: 'stripe_webhook_secret',fieldtype: 'Password', label: 'Webhook Secret (whsec_)' }, { fieldname: 'n8n_section', fieldtype: 'Section Break', label: 'n8n — Automatisation' }, { fieldname: 'n8n_url', fieldtype: 'Data', label: 'URL n8n', default: 'http://localhost:5678' }, { fieldname: 'n8n_api_key', fieldtype: 'Password', label: 'API Key n8n' }, { fieldname: 'n8n_webhook_base', fieldtype: 'Data', label: 'Base URL webhooks', default: 'http://localhost:5678/webhook' }, { fieldname: 'sms_section', fieldtype: 'Section Break', label: 'Templates SMS' }, { fieldname: 'sms_enroute', fieldtype: 'Text', label: 'Technicien en route', default: 'Bonjour {client_name}, votre technicien {tech_name} est en route et arrivera dans environ {eta} minutes. Réf: {job_id}' }, { fieldname: 'sms_completed', fieldtype: 'Text', label: 'Service complété', default: 'Bonjour {client_name}, votre service ({job_id}) a été complété avec succès. Merci de votre confiance !' }, { fieldname: 'sms_assigned', fieldtype: 'Text', label: 'Job assigné (technicien)', default: 'Nouveau job assigné : {job_id} — {client_name}, {address}. Durée estimée : {duration}h.' }, ] export async function createDocType () { const csrf = await getCSRF() const r = await fetch(`${BASE_URL}/api/resource/DocType`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', 'X-Frappe-CSRF-Token': csrf || '' }, body: JSON.stringify({ name: DOCTYPE, module: 'Core', custom: 1, is_single: 1, track_changes: 0, fields: DOCTYPE_FIELDS, permissions: [ { role: 'System Manager', read: 1, write: 1, create: 1 }, { role: 'Administrator', read: 1, write: 1, create: 1 }, ], }), }) const body = await r.json().catch(() => ({})) if (!r.ok || body.exc_type) { throw new Error(body._error_message || body.exc_type || `Erreur HTTP ${r.status}`) } return body }