Files
spq-v2/pb_migrations/1739999000007_add_appointment_vin_duration.js
2026-07-12 10:01:39 -04:00

99 lines
2.9 KiB
JavaScript

/// <reference path="../pb_data/types.d.ts" />
//
// M7 — Add structured VIN and duration fields to appointments.
//
// Older scan imports stored these values inside `notes` as:
// VIN: <value> | Duration: <number> min
// This migration adds first-class columns, backfills them when missing, and
// removes those machine-generated note fragments while preserving any real text.
//
function compactNoteParts(raw) {
return String(raw || '')
.split('|')
.map((part) => part.trim())
.filter(Boolean);
}
migrate(
(db) => {
const col = db.findCollectionByNameOrId('appointments');
if (!col) {
console.warn('[M7] appointments collection not found — skipping');
return;
}
if (!col.fields.find((f) => f.name === 'vin')) {
col.fields.push(new TextField({ name: 'vin', required: false }));
db.save(col);
console.log('[M7] added vin field to appointments');
}
if (!col.fields.find((f) => f.name === 'durationMinutes')) {
col.fields.push(new NumberField({
name: 'durationMinutes',
required: false,
min: 0,
}));
db.save(col);
console.log('[M7] added durationMinutes field to appointments');
}
const records = Array.from(db.findRecordsByFilter('appointments', '1=1', 'id', 500));
let backfilled = 0;
for (const rec of records) {
const rawNotes = String(rec.get('notes') || '');
const vinMatch = rawNotes.match(/(?:^|\|)\s*VIN:\s*([^|]+?)\s*(?=\||$)/i);
const durationMatch = rawNotes.match(/(?:^|\|)\s*Duration:\s*(\d+)\s*min\s*(?=\||$)/i);
const nextVin = String(rec.get('vin') || '').trim();
const rawDuration = rec.get('durationMinutes');
const nextDuration = rawDuration == null || rawDuration === '' ? null : Number(rawDuration);
let changed = false;
if (!nextVin && vinMatch?.[1]) {
rec.set('vin', String(vinMatch[1]).trim());
changed = true;
}
if ((nextDuration == null || Number.isNaN(nextDuration)) && durationMatch?.[1]) {
rec.set('durationMinutes', Number(durationMatch[1]));
changed = true;
}
const cleanedNotes = compactNoteParts(rawNotes)
.filter((part) => !/^VIN:\s*/i.test(part) && !/^Duration:\s*\d+\s*min$/i.test(part))
.join(' | ');
if (cleanedNotes !== rawNotes.trim()) {
rec.set('notes', cleanedNotes);
changed = true;
}
if (changed) {
db.saveRecord(rec);
backfilled++;
}
}
console.log(`[M7] updated ${backfilled} appointment record(s)`);
},
(db) => {
const col = db.findCollectionByNameOrId('appointments');
if (!col) return;
const durationIdx = col.fields.findIndex((f) => f.name === 'durationMinutes');
if (durationIdx >= 0) {
col.fields.splice(durationIdx, 1);
db.save(col);
}
const vinIdx = col.fields.findIndex((f) => f.name === 'vin');
if (vinIdx >= 0) {
col.fields.splice(vinIdx, 1);
db.save(col);
}
}
);