/**
* @OnlyCurrentDoc
* This script is the complete backend for the Store Management System.
* It connects to a Google Sheet with 12 specific tabs and handles all data
operations.
*/
// ============================================
// GLOBAL SHEET DEFINITIONS
// ============================================
const ss = [Link]();
const itemsSheet = [Link]("Items");
const vendorsSheet = [Link]("Vendors");
const departmentsSheet = [Link]("Departments");
const measuresSheet = [Link]("Measures");
const gstsSheet = [Link]("GSTs");
const invoicesSheet = [Link]("Invoices");
const invoiceItemsSheet = [Link]("InvoiceItems");
const requisitionsSheet = [Link]("Requisitions");
const requisitionItemsSheet = [Link]("RequisitionItems");
const settingsSheet = [Link]("Settings");
const challansSheet = [Link]("Challans");
const miscItemsSheet = [Link]("MiscItems");
// ============================================
// HELPER FUNCTION
// ============================================
/**
* Converts data from a sheet into an array of JavaScript objects.
* REPAIRED: This function now correctly parses JSON data within cells.
*/
const sheetToObjects = (sheet, sheetNameForError) => {
try {
if (!sheet) throw new Error(`Sheet "${sheetNameForError}" not found.`);
const data = [Link]().getValues();
if ([Link] <= 1) return [];
const headers = [Link]();
return [Link](row => {
let obj = {};
[Link]((header, i) => {
// Check if the cell contains a JSON string (for challan items)
if (header === 'items' && typeof row[i] === 'string' &&
row[i].startsWith('[')) {
try {
obj[header] = [Link](row[i]);
} catch (e) {
obj[header] = []; // Default to empty array on parse error
}
} else {
obj[header] = row[i];
}
});
return obj;
});
} catch (e) {
throw new Error(`Failed to process sheet "${sheetNameForError}". Check its
headers and data format. Original error: ${[Link]}`);
}
};
// ============================================
// WEB APP INITIALIZATION
// ============================================
function doGet(e) {
return [Link]('Index')
.setTitle("Store Management System")
.setXFrameOptionsMode([Link]);
}
function getSpreadsheetUrl() {
return [Link]();
}
/**
* Fetches all initial data from all 12 sheets for the frontend.
* This function has extremely robust error checking.
*/
function getInitialData() {
try {
// --- ROBUST SHEET VERIFICATION ---
const requiredSheets = ["Items", "Vendors", "Departments", "Measures", "GSTs",
"Invoices", "InvoiceItems", "Requisitions", "RequisitionItems", "Settings",
"Challans", "MiscItems"];
for (const sheetName of requiredSheets) {
if () {
throw new Error(`Sheet named "${sheetName}" was not found. Please check
spelling and capitalization.`);
}
}
const items = sheetToObjects(itemsSheet, "Items");
const vendors = sheetToObjects(vendorsSheet, "Vendors");
const departments = sheetToObjects(departmentsSheet, "Departments");
const measures = measuresSheet ?
[Link]().getValues().slice(1).flat() : [];
const gsts = gstsSheet ? [Link]().getValues().slice(1).flat() :
[];
const challans = sheetToObjects(challansSheet, "Challans");
const miscItems = sheetToObjects(miscItemsSheet, "MiscItems");
const settingsData = sheetToObjects(settingsSheet, "Settings");
let miscItemDefaults = {}, nextId = {};
try {
const miscDefaultsValue = [Link](s => [Link] ===
'miscItemDefaults')?.value;
if (miscDefaultsValue) miscItemDefaults = [Link](miscDefaultsValue);
} catch (e) { /* Ignore parsing error */ }
try {
const nextIdValue = [Link](s => [Link] === 'nextId')?.value;
if (nextIdValue) nextId = [Link](nextIdValue);
} catch (e) { /* Ignore parsing error */ }
const allInvoiceItems = sheetToObjects(invoiceItemsSheet, "InvoiceItems");
const invoiceItemsByInvoiceId = [Link]((acc, item) => {
if (!acc[item.invoice_id]) acc[item.invoice_id] = [];
acc[item.invoice_id].push(item);
return acc;
}, {});
const invoices = sheetToObjects(invoicesSheet, "Invoices").map(inv => {
[Link] = invoiceItemsByInvoiceId[[Link]] || [];
return inv;
});
const allRequisitionItems = sheetToObjects(requisitionItemsSheet,
"RequisitionItems");
const requisitionItemsByReqId = [Link]((acc, item) => {
if (!acc[item.requisition_id]) acc[item.requisition_id] = [];
acc[item.requisition_id].push(item);
return acc;
}, {});
const requisitions = sheetToObjects(requisitionsSheet, "Requisitions").map(req
=> {
[Link] = requisitionItemsByReqId[[Link]] || [];
return req;
});
return { items, vendors, departments, measures, gsts, invoices, requisitions,
challans, miscItems, miscItemDefaults, nextId };
} catch (e) {
[Link]("getInitialData CRITICAL ERROR: " + [Link]());
return { error: [Link] };
}
}
// ============================================
// DATA SAVING FUNCTIONS
// ============================================
function clearSheet(sheet) {
if (sheet && [Link]() > 1) {
[Link](2, 1, [Link]() - 1,
[Link]()).clearContent();
}
}
function saveAllData(data) {
try {
if (!data) { throw new Error("No data object was received from the
client."); }
const allSheets = [ itemsSheet, vendorsSheet, departmentsSheet,
measuresSheet, gstsSheet, challansSheet, invoicesSheet, invoiceItemsSheet,
requisitionsSheet, requisitionItemsSheet, miscItemsSheet, settingsSheet ];
[Link](sheet => clearSheet(sheet));
if ([Link] && [Link] > 0) {
const itemsData = [Link](i => [[Link], [Link], [Link],
[Link], [Link], [Link], [Link], [Link]]);
if([Link] > 0) [Link](2, 1, [Link],
itemsData[0].length).setValues(itemsData);
}
if ([Link] && [Link] > 0) {
const vendorsData = [Link](v => [[Link], [Link]]);
if([Link] > 0) [Link](2, 1,
[Link], vendorsData[0].length).setValues(vendorsData);
}
if ([Link] && [Link] > 0) {
const deptsData = [Link](d => [[Link], [Link]]);
if([Link] > 0) [Link](2, 1,
[Link], deptsData[0].length).setValues(deptsData);
}
if ([Link] && [Link] > 0) {
[Link](2, 1, [Link],
1).setValues([Link](m => [m]));
}
if ([Link] && [Link] > 0) {
[Link](2, 1, [Link], 1).setValues([Link](g
=> [g]));
}
if ([Link] && [Link] > 0) {
const miscData = [Link](m => [[Link], [Link], [Link]]);
if([Link] > 0) [Link](2, 1, [Link],
miscData[0].length).setValues(miscData);
}
if ([Link] && [Link] > 0) {
const challansData = [Link](c => [[Link], [Link], [Link],
[Link], [Link], [Link]([Link])]);
if([Link] > 0) [Link](2, 1,
[Link], challansData[0].length).setValues(challansData);
}
if ([Link] && [Link]([Link])) {
[Link](inv => {
[Link]([[Link], [Link], [Link], [Link],
[Link], [Link], [Link]]);
if ([Link] && [Link]([Link])) {
[Link](item =>
{ [Link]([[Link], [Link], [Link], [Link], [Link],
[Link], [Link]]); });
}
});
}
if ([Link] && [Link]([Link])) {
[Link](req => {
[Link]([[Link], [Link], [Link],
[Link], [Link]]);
if ([Link] && [Link]([Link])) {
[Link](item =>
{ [Link]([[Link], [Link], [Link], [Link],
[Link], [Link]]); });
}
});
}
if ([Link]) { [Link](['nextId',
[Link]([Link])]); }
if ([Link]) { [Link](['miscItemDefaults',
[Link]([Link])]); }
return "All data saved successfully!";
} catch (e) {
[Link]("saveAllData Error: " + [Link]() + " at " + [Link]);
return `Error saving data: ${[Link]}`;
}
}
function restoreData(data) {
return saveAllData(data);
}