fix: rewrite booking sheet parser to match actual VotC sheet structure

- Fix venue header detection ('Occupancy Commons Hub' not just 'Commons Hub')
- Dynamically find room # and bed type columns (not hardcoded positions)
- Carry room numbers down for multi-bed rooms
- Expand range from A:G to A:L to capture all 4 week columns
- Change default tab name to 'VotC26 Occupancy'
- Add BOOKING_SHEET_ID to docker-compose.yml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-15 13:14:57 -04:00
parent 78b2ba0499
commit 396b6d1c7e
2 changed files with 34 additions and 13 deletions

View File

@ -101,17 +101,20 @@ async function parseBookingSheet() {
} }
const sheetId = process.env.BOOKING_SHEET_ID || process.env.GOOGLE_SHEET_ID; const sheetId = process.env.BOOKING_SHEET_ID || process.env.GOOGLE_SHEET_ID;
const sheetName = process.env.BOOKING_SHEET_TAB || 'Booking Sheet'; const sheetName = process.env.BOOKING_SHEET_TAB || 'VotC26 Occupancy';
const response = await sheets.spreadsheets.values.get({ const response = await sheets.spreadsheets.values.get({
spreadsheetId: sheetId, spreadsheetId: sheetId,
range: `${sheetName}!A:G`, range: `${sheetName}!A:L`,
}); });
const rows = response.data.values || []; const rows = response.data.values || [];
const beds = []; const beds = [];
let currentVenue = null; let currentVenue = null;
let weekColIndexes = {}; let weekColIndexes = {};
let currentRoom = null;
let bedTypeCol = -1;
let roomCol = -1;
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
const row = rows[i]; const row = rows[i];
@ -119,34 +122,50 @@ async function parseBookingSheet() {
// Empty row — reset venue context // Empty row — reset venue context
currentVenue = null; currentVenue = null;
weekColIndexes = {}; weekColIndexes = {};
currentRoom = null;
bedTypeCol = -1;
roomCol = -1;
continue; continue;
} }
const firstCell = (row[0] || '').toString().trim(); const firstCell = (row[0] || '').toString().trim();
// Check if this is a venue header // Check if this is a venue header
if (firstCell === 'Commons Hub' || firstCell === 'Herrnhof Villa') { if (firstCell.startsWith('Occupancy Commons Hub') || firstCell === 'Commons Hub') {
currentVenue = firstCell; currentVenue = 'Commons Hub';
weekColIndexes = {}; weekColIndexes = {};
currentRoom = null;
continue;
}
if (firstCell === 'Herrnhof Villa') {
currentVenue = 'Herrnhof Villa';
weekColIndexes = {};
currentRoom = null;
continue; continue;
} }
// Check if this is the column header row (contains "Room" and week columns) // Check if this is the column header row (look for week headers)
if (firstCell.toLowerCase() === 'room' && currentVenue) { if (currentVenue && !Object.keys(weekColIndexes).length) {
let foundWeeks = false;
for (let c = 0; c < row.length; c++) { for (let c = 0; c < row.length; c++) {
const header = (row[c] || '').toString().trim(); const header = (row[c] || '').toString().trim();
if (WEEK_COLUMNS.includes(header)) { if (WEEK_COLUMNS.includes(header)) {
weekColIndexes[header] = c; weekColIndexes[header] = c;
foundWeeks = true;
} }
if (header.toLowerCase() === 'room #' || header.toLowerCase() === 'room') roomCol = c;
if (header.toLowerCase() === 'bed type') bedTypeCol = c;
} }
continue; if (foundWeeks) continue;
} }
// If we have a venue and week columns, this is a bed row // If we have a venue and week columns, this is a bed row
if (currentVenue && Object.keys(weekColIndexes).length > 0 && firstCell) { if (currentVenue && Object.keys(weekColIndexes).length > 0 && roomCol >= 0 && bedTypeCol >= 0) {
const room = firstCell; const roomCell = (row[roomCol] || '').toString().trim();
const bedType = (row[1] || '').toString().trim(); if (roomCell) currentRoom = roomCell;
if (!bedType) continue;
const bedType = (row[bedTypeCol] || '').toString().trim();
if (!bedType || !currentRoom) continue;
const occupancy = {}; const occupancy = {};
for (const [week, colIdx] of Object.entries(weekColIndexes)) { for (const [week, colIdx] of Object.entries(weekColIndexes)) {
@ -156,7 +175,7 @@ async function parseBookingSheet() {
beds.push({ beds.push({
venue: currentVenue, venue: currentVenue,
room, room: currentRoom,
bedType, bedType,
rowIndex: i, rowIndex: i,
weekColumns: { ...weekColIndexes }, weekColumns: { ...weekColIndexes },
@ -265,7 +284,7 @@ async function assignBooking(guestName, accommodationType, selectedWeeks) {
// Write guest name to the selected week columns // Write guest name to the selected week columns
const sheets = await getSheetsClient(); const sheets = await getSheetsClient();
const sheetId = process.env.BOOKING_SHEET_ID || process.env.GOOGLE_SHEET_ID; const sheetId = process.env.BOOKING_SHEET_ID || process.env.GOOGLE_SHEET_ID;
const sheetName = process.env.BOOKING_SHEET_TAB || 'Booking Sheet'; const sheetName = process.env.BOOKING_SHEET_TAB || 'VotC26 Occupancy';
// Convert week values to column headers // Convert week values to column headers
const weekHeaders = selectedWeeks.map(w => `Week ${w.replace('week', '')}`); const weekHeaders = selectedWeeks.map(w => `Week ${w.replace('week', '')}`);

View File

@ -20,6 +20,8 @@ services:
- SMTP_USER=contact@valleyofthecommons.com - SMTP_USER=contact@valleyofthecommons.com
- SMTP_PASS=${SMTP_PASS} - SMTP_PASS=${SMTP_PASS}
- EMAIL_FROM=Valley of the Commons <contact@valleyofthecommons.com> - EMAIL_FROM=Valley of the Commons <contact@valleyofthecommons.com>
- BOOKING_SHEET_ID=1kjVy5jfGSG2vcavqkbrw_CHSn4-HY-OE3NAgyjUUpkk
- BOOKING_SHEET_TAB=VotC26 Occupancy
depends_on: depends_on:
votc-db: votc-db:
condition: service_healthy condition: service_healthy