104 detection: null, 105 description: null, 106 contrat_nombre: 1, 107 contrat_groupe_partage: null, 108 contrat_labels: [], 109 ...offer.fields, 110 }] }); 111 audit.offer_created = true; 112 journal.push(() => deleteByLocalId(context, 'offres', offer.local_id)); 113 } 114 await applyPricingPlan(context, pricingPlan, journal, audit); 115 for (const centre of centres) { 116 const centreId = clean(centre.local_id || centre.id); 117 const current = stringArray(centre.offres_proposees); 118 const next = selectedCentreIds.has(centreId) 119 ? unique([...current, offer.local_id]) 120 : current.filter((id) => id !== offer.local_id); 121 if (sameArray(current, next)) continue; 122 await context.call('tables', 'rows_update', { table: 'centres', id: Number(centre.id), fields: { offres_proposees: next } }); 123 journal.push(() => context.call('tables', 'rows_update', { table: 'centres', id: Number(centre.id), fields: { offres_proposees: current } })); 124 audit.centres_updated += 1; 125 } 126 return { ok: true, ...audit }; 127 } catch (error) { 128 const rollbackErrors = await rollback(journal); 129 if (rollbackErrors.length) { 130 throw new Error(`Enregistrement incomplet et restauration partielle (${rollbackErrors.length} erreur(s)). Vérification administrateur requise. Cause : ${message(error)}`); 131 } 132 throw new Error(`Aucune modification conservée. ${message(error)}`); 133 } 134} 135 136async function savePricingOnly(context, body, actor) { 137 const offerId = normalizeOfferId(body.offer_id); 138 const [offerResult, ruleResult] = await Promise.all([ 139 context.call('tables', 'rows_search', { table: 'offres', where: [{ col: 'local_id', op: 'eq', value: offerId }], limit: 20 }), 140 context.call('tables', 'rows_search', { table: 'offer_remuneration_rules', order_by: 'id asc', limit: 1000 }), 141 ]); 142 const offer = (offerResult?.rows || []).find((row) => clean(row.local_id || row.id) === offerId); 143 if (!offer) throw new Error(`Offre introuvable : ${offerId}.`); 144 const pricingPlan = buildPricingPlan(offerId, body.rules, body.removed_rule_ids, ruleResult?.rows || []); 145 const journal = []; 146 const audit = { action: 'save_pricing', offer_id: offerId, actor_id: actor.id, rules_inserted: 0, rules_updated: 0, rules_disabled: 0 }; 147 try { 148 await applyPricingPlan(context, pricingPlan, journal, audit); 149 return { ok: true, ...audit }; 150 } catch (error) { 151 const rollbackErrors = await rollback(journal); 152 if (rollbackErrors.length) throw new Error(`Tarifs partiellement restaurés (${rollbackErrors.length} erreur(s)). Vérification administrateur requise. Cause : ${message(error)}`); 153 throw new Error(`Aucune modification tarifaire conservée. ${message(error)}`); 154 } 155} 156 157function buildPricingPlan(offerId, inputRules, removedRuleIds, allRules) { 158 if (!Array.isArray(inputRules) || !inputRules.length) throw new Error('Ajoutez au moins une règle de prime.'); 159 const existing = allRules.filter((row) => clean(row.offre_id) === offerId); 160 const existingByLocalId = new Map(existing.map((row) => [clean(row.local_id || row.id), row])); 161 const removed = new Set(values(removedRuleIds)); 162 const now = new Date().toISOString(); 163 const usedLocalIds = new Set(allRules.map((row) => clean(row.local_id || row.id)).filter(Boolean)); 164 const seen = new Set(); 165 const updates = []; 166 const inserts = []; 167 for (let index = 0; index < inputRules.length; index += 1) { 168 const input = inputRules[index] || {}; 169 const suppliedId = clean(input.local_id); 170 if (suppliedId && removed.has(suppliedId)) throw new Error('Une règle supprimée ne peut pas être enregistrée simultanément.'); 171 const current = suppliedId ? existingByLocalId.get(suppliedId) : null; 172 if (suppliedId && !current) throw new Error(`Règle de rémunération introuvable : ${suppliedId}.`); 173 if (current && seen.has(suppliedId)) throw new Error('Une règle de rémunération est présente plusieurs fois.');