diff --git a/public/core/data-layer-v1773.js b/public/core/data-layer-v1773.js index 39ac3d4..b43c4b4 100644 --- a/public/core/data-layer-v1773.js +++ b/public/core/data-layer-v1773.js @@ -118,19 +118,20 @@ return {key,name:String(row?.name||identity?.name||data?.name||''),phone:String(row?.phone||identity?.phone||data?.phone||''),latestAddress,addresses:latestAddress?[latestAddress]:[],orderIds:[],orderCount:0,totalSpent:0,loyalty:Object.prototype.hasOwnProperty.call(data,'loyalty')?clone(data.loyalty):null,communication:Object.prototype.hasOwnProperty.call(data,'communication')?clone(data.communication):null,serverVersion:Number(row?.version||0)||null,serverUpdatedAt:String(row?.updated_at||''),serverSourceKeys:Array.isArray(row?.source_keys)?row.source_keys.map(String):[],serverSourceCount:Number(row?.source_count||0)||1,canonicalRowPresent:Boolean(row?.canonical_row_present)}; } function serverListClients(){return currentServerRows().map(serverProfile).filter(Boolean)} - function mergeClientSources(){ - const local=buildClients(),byKey=new Map(local.map(item=>[item.key,{...clone(item),dataSource:'local'}])); - for(const server of serverListClients()){ - if(server.key.startsWith('o:')&&!byKey.has(server.key))continue; - const current=byKey.get(server.key)||{key:server.key,name:'',phone:'',latestAddress:'',addresses:[],orderIds:[],orderCount:0,totalSpent:0,loyalty:null,communication:null}; - const addresses=[...new Set([...(current.addresses||[]),...(server.addresses||[]),server.latestAddress].map(v=>String(v||'').trim()).filter(Boolean))]; - byKey.set(server.key,{...current,name:server.name||current.name,phone:server.phone||current.phone,latestAddress:server.latestAddress||current.latestAddress,addresses,loyalty:server.loyalty??current.loyalty,communication:server.communication??current.communication,serverVersion:server.serverVersion,serverUpdatedAt:server.serverUpdatedAt,serverSourceKeys:server.serverSourceKeys,serverSourceCount:server.serverSourceCount,canonicalRowPresent:server.canonicalRowPresent,dataSource:'server+local'}); + function mergeClientSources(local,server){ + local=local||buildClients();server=server||serverListClients(); + const byKey=new Map(local.map(item=>[item.key,{...clone(item),dataSource:'local'}])); + for(const row of server){ + if(row.key.startsWith('o:')&&!byKey.has(row.key))continue; + const current=byKey.get(row.key)||{key:row.key,name:'',phone:'',latestAddress:'',addresses:[],orderIds:[],orderCount:0,totalSpent:0,loyalty:null,communication:null}; + const addresses=[...new Set([...(current.addresses||[]),...(row.addresses||[]),row.latestAddress].map(v=>String(v||'').trim()).filter(Boolean))]; + byKey.set(row.key,{...current,name:row.name||current.name,phone:row.phone||current.phone,latestAddress:row.latestAddress||current.latestAddress,addresses,loyalty:row.loyalty??current.loyalty,communication:row.communication??current.communication,serverVersion:row.serverVersion,serverUpdatedAt:row.serverUpdatedAt,serverSourceKeys:row.serverSourceKeys,serverSourceCount:row.serverSourceCount,canonicalRowPresent:row.canonicalRowPresent,dataSource:'server+local'}); } return [...byKey.values()].sort((a,b)=>(b.orderCount-a.orderCount)||String(a.name||a.key).localeCompare(String(b.name||b.key),'ru')); } function listClients(options={}){const source=typeof options==='string'?options:String(options?.source||'auto');if(source==='local'||source==='legacy')return clone(buildClients());if(source==='server')return clone(serverListClients());return clone(mergeClientSources())} function compareClientSources(){ - const local=buildClients(),server=serverListClients(),merged=mergeClientSources(),serverKeys=new Set(server.map(x=>x.key)),localKeys=new Set(local.map(x=>x.key)); + const local=buildClients(),server=serverListClients(),merged=mergeClientSources(local,server),serverKeys=new Set(server.map(x=>x.key)),localKeys=new Set(local.map(x=>x.key)); const metricMismatches=local.map(item=>{const m=merged.find(x=>x.key===item.key);return !m||m.orderCount!==item.orderCount||Number(m.totalSpent)!==Number(item.totalSpent)?item.key:null}).filter(Boolean); return {localCount:local.length,serverCount:server.length,mergedCount:merged.length,serverPreferred:server.length>0,missingOnServer:[...localKeys].filter(k=>!serverKeys.has(k)),serverOnly:[...serverKeys].filter(k=>!localKeys.has(k)),legacyServerFallbackRows:server.filter(x=>x.key.startsWith('o:')).length,orderMetricsEqual:metricMismatches.length===0,metricMismatches}; } @@ -171,8 +172,8 @@ function setClientLoyalty(key,value,{pushServer=true}={}){writeLegacyMap(LEGACY_LOYALTY_KEY,String(key),value);adoptLegacy({pushServer:false,reason:'client.loyalty'});if(pushServer)scheduleServerPush([String(key)]);return clone(getClient(key)?.loyalty||null)} function setClientCommunication(key,value,{pushServer=true}={}){writeLegacyMap(LEGACY_COMM_KEY,String(key),value);adoptLegacy({pushServer:false,reason:'client.communication'});if(pushServer)scheduleServerPush([String(key)]);return clone(getClient(key)?.communication||null)} function serverPayload(profile){return {schemaVersion:1,key:profile.key,identity:{name:String(profile.name||''),phone:String(profile.phone||''),latestAddress:String(profile.latestAddress||'')},loyalty:profile.loyalty?clone(profile.loyalty):null,communication:profile.communication?clone(profile.communication):null,source:'data-layer-v1772',updatedAt:new Date().toISOString()}} - async function pushClient(key){ - const profile=getClient(key);if(!profile)return {status:'missing',key};if(!isSignedIn())return {status:'local',key};if(isSupportReadOnly()||!canWrite('clients.edit'))return {status:'read-only',key}; + async function pushClient(key,knownProfile){ + const profile=knownProfile||getClient(key);if(!profile)return {status:'missing',key};if(!isSignedIn())return {status:'local',key};if(isSupportReadOnly()||!canWrite('clients.edit'))return {status:'read-only',key}; const c=cloud()?.getClient?.(),ws=workspace();if(!c?.rpc||!ws?.id)return {status:'offline',key}; const cached=readClientCache()[key]||{},expected=Number(cached.serverVersion||0)||null; const {data,error}=await c.rpc('sun_v17_save_client_v1772',{p_workspace:ws.id,p_client_key:key,p_profile:serverPayload(profile),p_expected_version:expected,p_client_id:cloud()?.getClientId?.()||'browser'});if(error)throw error; @@ -181,7 +182,7 @@ } const pendingServerKeys=new Set();let serverPushTimer=0; function scheduleServerPush(keys){for(const key of keys||[])if(key)pendingServerKeys.add(String(key));if(serverPushTimer)return;serverPushTimer=setTimeout(async()=>{serverPushTimer=0;const keys=[...pendingServerKeys];pendingServerKeys.clear();for(const key of keys){try{await pushClient(key)}catch(error){console.warn('[Caterium Clients] server save failed',key,error?.message||error)}}},250)} - async function pushAllClients(){const result=[];for(const profile of listClients()){try{result.push(await pushClient(profile.key))}catch(error){result.push({status:'error',key:profile.key,error:String(error?.message||error)})}}return result} + async function pushAllClients(){const result=[];for(const profile of listClients()){try{result.push(await pushClient(profile.key,profile))}catch(error){result.push({status:'error',key:profile.key,error:String(error?.message||error)})}}return result} let bridgeSuppressed=false; function installLegacyClientBridge(){