const SHEET_NAME = 'FusionMed';
const STORAGE_SHEET = 'Lagerorte';
const INV_SHEET = 'Bestand';
const AUDIT_SHEET = 'Protokoll';
const MAX_IMPLICIT_ARTICLE_DROP = 1;
function doGet() {
try {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var articles = dedupeArticles(readSheet(ss, SHEET_NAME));
var nextOrder = articles.reduce(function(m,a){
return Math.max(m, Number(a.orderNum)||0);
}, 0) + 1;
var storageAreas = readSheet(ss, STORAGE_SHEET);
var invRows = readSheet(ss, INV_SHEET);
var inventory = {};
invRows.forEach(function(r) {
if (!r.articleId || !r.storageAreaId) return;
if (!inventory[r.articleId]) inventory[r.articleId] = {};
inventory[r.articleId][r.storageAreaId] = Number(r.quantity) || 0;
});
var auditLog = dedupeAuditLog(readSheet(ss, AUDIT_SHEET));
return respond({ articles: articles, nextOrder: nextOrder,
storageAreas: storageAreas, inventory: inventory,
auditLog: auditLog, auditSupported: true });
} catch(e) { return respond({ error: String(e) }); }
}
function doPost(e) {
try {
var data = JSON.parse(e.postData.contents);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var currentArticles = dedupeArticles(readSheet(ss, SHEET_NAME));
var articles = dedupeArticles(data.articles || []);
var allowedBulkClear = data.allowDestructive === true &&
data.destructiveAction === 'clearAll' &&
articles.length === 0;
if (!allowedBulkClear &&
currentArticles.length > 0 &&
articles.length < currentArticles.length - MAX_IMPLICIT_ARTICLE_DROP) {
return respond({
error: 'destructive_sync_blocked',
message: 'Sync blockiert: Der Speichervorgang würde ' +
(currentArticles.length - articles.length) +
' Artikel entfernen. Einzelne Löschungen sind erlaubt; Massenlöschungen müssen explizit bestätigt werden.',
currentCount: currentArticles.length,
incomingCount: articles.length
});
}
writeSheet(ss, SHEET_NAME,
['id','orderNum','barcode','name','description',
'imagePacked','imageUnpacked','createdAt'],
articles);
writeSheet(ss, STORAGE_SHEET, ['id','name'],
data.storageAreas || []);
var invRows = [];
var inv = data.inventory || {};
Object.keys(inv).forEach(function(aId) {
Object.keys(inv[aId] || {}).forEach(function(sId) {
invRows.push({ articleId: aId, storageAreaId: sId,
quantity: inv[aId][sId] });
});
});
writeSheet(ss, INV_SHEET,
['articleId','storageAreaId','quantity'], invRows);
var auditLog = dedupeAuditLog((data.auditLog || []).concat(readSheet(ss, AUDIT_SHEET)));
writeSheet(ss, AUDIT_SHEET,
['id','ts','userId','username','action','details'], auditLog);
return respond({ ok: true, count: articles.length,
auditCount: auditLog.length, auditSupported: true });
} catch(e) { return respond({ error: String(e) }); }
}
function readSheet(ss, name) {
var sheet = ss.getSheetByName(name) || ss.insertSheet(name);
var rows = sheet.getDataRange().getValues();
if (rows.length <= 1) return [];
var h = rows[0].map(String);
return rows.slice(1).map(function(row) {
var obj = {};
h.forEach(function(k,i){ obj[k] = row[i]!=null ? String(row[i]) : ''; });
return obj;
});
}
function writeSheet(ss, name, headers, rows) {
var sheet = ss.getSheetByName(name) || ss.insertSheet(name);
sheet.clearContents();
sheet.getRange(1, 1, Math.max(rows.length + 1, 1), headers.length).setNumberFormat('@');
sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
if (rows.length) {
sheet.getRange(2, 1, rows.length, headers.length).setValues(rows.map(function(r) {
return headers.map(function(h){
return r[h]!=null ? String(r[h]) : '';
});
}));
}
}
function dedupeArticles(rows) {
var seen = {};
return (rows || []).filter(function(r) {
if (!r || !r.id) return false;
var name = String(r.name || '').trim().replace(/\s+/g, ' ').toLowerCase();
var key = String(r.id) + '|' + name + '|' + String(r.orderNum || '');
if (seen[key]) return false;
seen[key] = true;
return true;
});
}
function dedupeAuditLog(rows) {
var seen = {};
return (rows || []).filter(function(r) {
if (!r || !r.id || seen[r.id]) return false;
seen[r.id] = true;
return true;
}).sort(function(a, b) {
return String(b.ts || '').localeCompare(String(a.ts || ''));
}).slice(0, 2000);
}
function respond(data) {
return ContentService
.createTextOutput(JSON.stringify(data))
.setMimeType(ContentService.MimeType.JSON);
}