/// migrate( (app) => { // ── 1. Create collection (rules set to empty string = no filter) ─ var auditCol = new Collection({ name: 'roFinancialAudit', type: 'base', schema: [ new TextField({ name: 'roId', required: true }), new TextField({ name: 'field', required: true }), new JSONField({ name: 'from', required: false }), new JSONField({ name: 'to', required: false }), new TextField({ name: 'by', required: false }), new TextField({ name: 'at', required: true }), new TextField({ name: 'prevHash', required: false }), new TextField({ name: 'hash', required: true }), ], listRule: '', viewRule: '', createRule: null, updateRule: null, deleteRule: null, }); app.save(auditCol); console.log('[M25] created roFinancialAudit collection'); // ── 2. Hook: diff financial changes ────────────────────────────── app.onRecordUpdateRequest(function(e) { if (e.collection.name !== 'repairOrders') return; if (!e.oldRecord) return; var oldFinancial = e.oldRecord.get('financial'); var newFinancial = e.record.get('financial'); if (!oldFinancial && !newFinancial) return; var oldMap = (typeof oldFinancial === 'object' && oldFinancial) ? oldFinancial : {}; var newMap = (typeof newFinancial === 'object' && newFinancial) ? newFinancial : {}; var allKeys = []; var seen = {}; Object.keys(oldMap).concat(Object.keys(newMap)).forEach(function(k) { if (!seen[k]) { seen[k] = true; allKeys.push(k); } }); var changedKeys = []; allKeys.forEach(function(key) { var oldVal = JSON.stringify(oldMap[key] !== undefined ? oldMap[key] : null); var newVal = JSON.stringify(newMap[key] !== undefined ? newMap[key] : null); if (oldVal !== newVal) changedKeys.push(key); }); if (changedKeys.length === 0) return; var prevHash = ''; try { var lastEntry = e.dao.findFirstRecordByFilter( 'roFinancialAudit', 'roId = "' + e.record.id.replace(/"/g, '') + '"', { sort: '-at' }, ); if (lastEntry) prevHash = lastEntry.get('hash') || ''; } catch (_) {} var roId = e.record.id; var by = e.record.get('advisorName') || ''; var now = new Date().toISOString(); function simpleHash(str) { var h = 0; for (var i = 0; i < str.length; i++) { h = ((h << 5) - h) + str.charCodeAt(i); h = h & h; } return (h >>> 0).toString(16); } var auditCollection = app.findCollectionByNameOrId('roFinancialAudit'); changedKeys.forEach(function(key) { var fromVal = oldMap[key] !== undefined ? oldMap[key] : null; var toVal = newMap[key] !== undefined ? newMap[key] : null; var payload = roId + key + now + JSON.stringify(fromVal) + JSON.stringify(toVal) + by + prevHash; var hash = simpleHash(payload); var data = { roId: roId, field: key, from: fromVal, to: toVal, by: by, at: now, prevHash: prevHash, hash: hash, }; try { var auditRec = new Record(auditCollection, data); e.dao.saveRecord(auditRec); } catch (err) { console.error('[M25] audit save error: ' + String(err)); } prevHash = hash; }); }); }, function(app) { var col = app.findCollectionByNameOrId('roFinancialAudit'); if (col) { app.deleteCollection(col); console.log('[M25] removed roFinancialAudit collection'); } } );