/** * Composable for subscription management actions. * Handles status toggling, frequency changes, recurring settings, drag reorder, and field saves. */ import { ref } from 'vue' import { Notify } from 'quasar' import { authFetch } from 'src/api/auth' import { BASE_URL } from 'src/config/erpnext' import { formatMoney } from 'src/composables/useFormatters' // Frappe REST update — supports both Service Subscription (legacy) and native Subscription async function updateSub (name, fields) { // Detect which doctype based on name pattern: SUB-* = Service Subscription, ASUB-* = native Subscription const doctype = name.startsWith('ASUB-') ? 'Subscription' : 'Service Subscription' // Map normalized field names back to target doctype fields const mapped = {} for (const [k, v] of Object.entries(fields)) { if (doctype === 'Service Subscription') { // Map template field names → Service Subscription field names if (k === 'actual_price') mapped.monthly_price = v else if (k === 'custom_description') mapped.plan_name = v else if (k === 'billing_frequency') mapped.billing_cycle = v === 'A' ? 'Annuel' : 'Mensuel' else if (k === 'status') mapped.status = v === 'Active' ? 'Actif' : v === 'Cancelled' ? 'Annulé' : v else if (k === 'cancelation_date') mapped.cancellation_date = v else if (k === 'cancel_at_period_end') { /* not applicable to Service Subscription */ } else mapped[k] = v } else { mapped[k] = v } } const res = await authFetch(BASE_URL + '/api/resource/' + encodeURIComponent(doctype) + '/' + encodeURIComponent(name), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(mapped), }) if (!res.ok) { const err = await res.text() throw new Error('Update failed: ' + err) } return res.json() } /** * @param {import('vue').Ref} subscriptions - Reactive subscriptions list * @param {import('vue').Ref} customer - Reactive customer object * @param {import('vue').Ref} comments - Reactive comments list * @param {Function} invalidateCache - Cache invalidation from useSubscriptionGroups */ export function useSubscriptionActions (subscriptions, customer, comments, invalidateCache) { const subSaving = ref(null) const togglingRecurring = ref(null) // Log a subscription change as a Comment on the Customer for audit trail async function logSubChange (sub, message) { try { const label = sub.custom_description || sub.item_name || sub.item_code || sub.name const body = { doctype: 'Comment', comment_type: 'Comment', reference_doctype: 'Customer', reference_name: customer.value.name, content: `[${sub.name}] ${label} — ${message}`, } const res = await authFetch(BASE_URL + '/api/resource/Comment', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) if (res.ok) { const data = await res.json() comments.value.unshift(data.data) } } catch {} } async function toggleSubStatus (sub) { const action = sub.name + ':status' if (subSaving.value) return subSaving.value = action const newStatus = sub.status === 'Cancelled' ? 'Active' : 'Cancelled' const today = new Date().toISOString().slice(0, 10) try { const updates = { status: newStatus } if (newStatus === 'Cancelled') { updates.cancelation_date = today if (!sub.end_date) updates.end_date = sub.current_invoice_end || today } else { updates.cancelation_date = null } await updateSub(sub.name, updates) sub.status = newStatus if (updates.end_date) sub.end_date = updates.end_date const msg = newStatus === 'Cancelled' ? `Service désactivé le ${today}` : `Service réactivé le ${today}` logSubChange(sub, msg) if (sub.service_location) invalidateCache(sub.service_location) Notify.create({ type: 'positive', message: msg, timeout: 2500 }) } catch (e) { Notify.create({ type: 'negative', message: 'Erreur: ' + (e.message || 'impossible de modifier le statut'), timeout: 4000 }) } finally { subSaving.value = null } } async function toggleFrequency (sub) { const action = sub.name + ':freq' if (subSaving.value) return subSaving.value = action const oldFreq = sub.billing_frequency const newFreq = oldFreq === 'A' ? 'M' : 'A' try { await updateSub(sub.name, { billing_frequency: newFreq }) const msg = newFreq === 'A' ? `Fréquence changée: Mensuel → Annuel` : `Fréquence changée: Annuel → Mensuel` logSubChange(sub, msg) if (sub.service_location) invalidateCache(sub.service_location) sub.billing_frequency = newFreq } catch {} finally { subSaving.value = null } } async function toggleRecurring (sub) { if (togglingRecurring.value) return togglingRecurring.value = sub.name const newVal = Number(sub.cancel_at_period_end) ? 0 : 1 try { const updates = { cancel_at_period_end: newVal } if (newVal && !sub.end_date) updates.end_date = sub.current_invoice_end || new Date().toISOString().slice(0, 10) await updateSub(sub.name, updates) sub.cancel_at_period_end = newVal if (updates.end_date) sub.end_date = updates.end_date const msg = newVal ? 'Récurrence désactivée' : 'Récurrence activée' logSubChange(sub, msg) Notify.create({ type: 'positive', message: msg, timeout: 2500 }) } catch { Notify.create({ type: 'negative', message: 'Erreur: impossible de modifier le récurrent', timeout: 3000 }) } finally { togglingRecurring.value = null } } async function toggleRecurringModal (doc) { const newVal = Number(doc.cancel_at_period_end) ? 0 : 1 try { const updates = { cancel_at_period_end: newVal } if (newVal && !doc.end_date) updates.end_date = doc.current_invoice_end || new Date().toISOString().slice(0, 10) await updateSub(doc.name, updates) doc.cancel_at_period_end = newVal if (updates.end_date) doc.end_date = updates.end_date const listSub = subscriptions.value.find(s => s.name === doc.name) if (listSub) { listSub.cancel_at_period_end = newVal if (updates.end_date) listSub.end_date = updates.end_date } const msg = newVal ? 'Récurrence désactivée' : 'Récurrence activée' logSubChange(doc, msg) Notify.create({ type: 'positive', message: msg, timeout: 2500 }) } catch (e) { Notify.create({ type: 'negative', message: 'Erreur: ' + (e.message || 'impossible de modifier'), timeout: 3000 }) } } async function saveSubField (doc, field) { try { const oldVal = subscriptions.value.find(s => s.name === doc.name)?.[field] await updateSub(doc.name, { [field]: doc[field] ?? '' }) const listSub = subscriptions.value.find(s => s.name === doc.name) if (listSub) { listSub[field] = doc[field] if (listSub.service_location) invalidateCache(listSub.service_location) } const fieldLabels = { actual_price: 'Prix', custom_description: 'Description' } if (fieldLabels[field]) { const newVal = doc[field] ?? '' const msg = field === 'actual_price' ? `${fieldLabels[field]} modifié: ${formatMoney(oldVal)} → ${formatMoney(newVal)}` : `${fieldLabels[field]} modifié: "${oldVal || '—'}" → "${newVal || '—'}"` logSubChange(doc, msg) } } catch {} } function onSubDragChange (evt, locName) { if (evt.added || evt.removed) invalidateCache(locName) } return { subSaving, togglingRecurring, toggleSubStatus, toggleFrequency, toggleRecurring, toggleRecurringModal, saveSubField, logSubChange, onSubDragChange, } }