import { restoreBusinessPlanSnapshot } from './business-financial-plan.ts'; const empty = (value: unknown) => value == null || value === '' || (Array.isArray(value) && value.length === 0); function canonical(value: unknown): unknown { if (Array.isArray(value)) return value.map(canonical); if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).sort(([a],[b])=>a.localeCompare(b)).map(([key,item])=>[key,canonical(item)])); return value; } // Change tracking only, never an authorization, integrity or legal-acceptance token. No personal values in metadata. function signature(value: unknown): string { const text=JSON.stringify(canonical(value)) ?? 'undefined';let hash=14695981039346656037n; for(let index=0;index, source:Record, raw:unknown, allowed?:string[]): {patch:Record; metadata:string; inherited:string[]; conflicts:string[]} { let previous:Record={}; try { const saved=JSON.parse(String(raw ?? ''));if(saved.version===1 && saved.scope===scope && saved.inherited && typeof saved.inherited==='object') previous=saved.inherited; } catch { /* Legacy inputs are retained, not overwritten. */ } const next={...previous};const patch:Record={};const inherited:string[]=[];const conflicts:string[]=[]; const geo=['province_id','district_id','admin_post_id','locality_id']; const geoConflict=geo.some(key=>(!empty(values[key]) || key in previous) && signature(values[key])!==signature(source[key]) && previous[key]!==signature(values[key])); for(const [key,value] of Object.entries(source)) { if ((allowed && !allowed.includes(key)) || (empty(value) && !(key in previous))) continue; const current=values[key]; const track = (item:unknown) => key==='parcels' ? Object.fromEntries(geo.map(field=>[field,(item as Array>)?.[0]?.[field] ?? ''])) : item; const owned=previous[key]===signature(track(current)); const vacant=key==='parcels' ? geo.every(field=>empty((current as Array>)?.[0]?.[field])) : empty(current); if ((geo.includes(key) && geoConflict) || (!owned && (!vacant || key in previous))) { if(signature(current)!==signature(value)) conflicts.push(key); continue; } if(key==='parcels') { const parcels=current as Array>|undefined; if(!owned && parcels?.length && geo.some(field=>!empty(parcels[0]?.[field]))) {conflicts.push(key);continue;} } if(signature(track(current))!==signature(track(value))) { if(key==='parcels') { const parcels=(current as Array>) ?? []; patch[key]=[{...parcels[0],...Object.fromEntries(geo.map(field=>[field,(value as Array>)[0]?.[field] ?? '']))},...parcels.slice(1)]; } else patch[key]=value; } next[key]=signature(track(value));inherited.push(key); } return {patch,metadata:JSON.stringify({version:1,scope,inherited:next}),inherited,conflicts}; } export function inheritSimulatorFields>(draft:T): T { const source:Record={}; const plan=draft.business_plan_snapshot ? restoreBusinessPlanSnapshot(String(draft.business_plan_snapshot)) : null; if(plan?.currency) source.currency=plan.currency; const currency=draft.currency || source.currency; if(currency) {source.fiscal_currency=currency;source.opportunity_currency=currency;source.published_iva_currency=currency;} if(draft.tax_residency_country) source.fiscal_residency_country=draft.tax_residency_country; if(draft.fiscal_reference_date) source.published_iva_date=draft.fiscal_reference_date; let settings:Record={start:'',basis:'unknown',declared:false,auto:false}; let settingsValid=!draft.monthly_iva_settings; try {const saved=JSON.parse(String(draft.monthly_iva_settings ?? ''));if(saved && !Array.isArray(saved) && typeof saved.start==='string' && ['net','included','unknown'].includes(saved.basis) && typeof saved.declared==='boolean' && typeof saved.auto==='boolean') {settings=saved;settingsValid=true;}} catch { /* Invalid stored inputs remain intact. */ } const start=String(draft.expected_start_date ?? ''); if(/^\d{4}-\d{2}-\d{2}$/.test(start)) source.monthly_iva_start=start.slice(0,7); else if(/^\d{4}$/.test(String(draft.fiscal_period)) && draft.fiscal_periodicity==='monthly' && /^(0?[1-9]|1[0-2])$/.test(String(draft.fiscal_month))) source.monthly_iva_start=`${draft.fiscal_period}-${String(draft.fiscal_month).padStart(2,'0')}`; if(!settingsValid) delete source.monthly_iva_start; // Legacy drafts without a compatible source need no inheritance marker or autosave churn. if (!Object.keys(source).length && !draft.compatible_fields_snapshot) return draft; const values={...draft,monthly_iva_start:settings.start ?? ''}; const patch=inheritCompatibleFields('simulator-compatible-fields',values,source,draft.compatible_fields_snapshot); const next:Record={...draft,...patch.patch,compatible_fields_snapshot:patch.metadata}; if('monthly_iva_start' in patch.patch) next.monthly_iva_settings=JSON.stringify({...settings,start:patch.patch.monthly_iva_start}); delete next.monthly_iva_start; return JSON.stringify(next)===JSON.stringify(draft) ? draft : next as T; }