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

View File

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