Stückkosten
This commit is contained in:
1 parent
c816ea3ac9
commit
d7c55d444b
5 files changed
+321
-6
No files matched your search
@@ -66,9 +66,14 @@ No bundler — keep everything in global scope as the file already does.
|
|||||||
`query` selects the dataset. The datasets are large, so **exactly one dataset is
|
`query` selects the dataset. The datasets are large, so **exactly one dataset is
|
||||||
requested per call**, for the dashboard currently shown (no prefetching).
|
requested per call**, for the dashboard currently shown (no prefetching).
|
||||||
- Query keys: `"Summe je Abgangsland"` (Umsatz je Abgangsland), `"VAT Summary"`,
|
- Query keys: `"Summe je Abgangsland"` (Umsatz je Abgangsland), `"VAT Summary"`,
|
||||||
`"ODOO Rechnungen pro Monat"`, `"Alle Rechnungen pro Monat"` and `"Quartal Schweiz"`.
|
`"ODOO Rechnungen pro Monat"`, `"Alle Rechnungen pro Monat"`, `"Quartal Schweiz"`,
|
||||||
Each view's `query` lives in the `VIEWS` map, which maps the active dashboard to its
|
`"FIFO Lager Bewertung"` and `"Produkt Materialkosten"`. Each view's `query` lives in
|
||||||
`normalize` function; `quarter: true` marks the dashboards that use a quarter period.
|
the `VIEWS` map, which maps the active dashboard to its `normalize` function;
|
||||||
|
`quarter: true` marks the dashboards that use a quarter period. The FIFO and
|
||||||
|
Produkt-Materialkosten responses are `[{ id, query, row_count, data: [...] }]`
|
||||||
|
(unwrapped in `normalizeFifo` / `normalizeMaterialkosten`). In *Produkt Materialkosten*,
|
||||||
|
`remaining_qty` is the remaining quantity of that cost bracket (the last cost bracket, not FIFO),
|
||||||
|
not the product total.
|
||||||
- `extractArray` accepts the dataset either as a bare array or wrapped under the
|
- `extractArray` accepts the dataset either as a bare array or wrapped under the
|
||||||
query key (case-insensitive). If the shape changes upstream, adjust the normalizers.
|
query key (case-insensitive). If the shape changes upstream, adjust the normalizers.
|
||||||
- The webhook returns **German-formatted strings**, e.g. `"90.717,93 €"` and
|
- The webhook returns **German-formatted strings**, e.g. `"90.717,93 €"` and
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ The left sidebar groups reports by topic:
|
|||||||
- **Amazon** → *Umsatz je Abgangsland* (Brutto inkl. USt je Abgangsland)
|
- **Amazon** → *Umsatz je Abgangsland* (Brutto inkl. USt je Abgangsland)
|
||||||
- **Amazon** → *VAT Summary* (USt-Übersicht je Land)
|
- **Amazon** → *VAT Summary* (USt-Übersicht je Land)
|
||||||
- **ODOO** → *Rechnungen pro Monat* (Pivot über Odoo-Rechnungen)
|
- **ODOO** → *Rechnungen pro Monat* (Pivot über Odoo-Rechnungen)
|
||||||
|
- **ODOO** → *FIFO Lager Bewertung* (Lagerwert FIFO)
|
||||||
|
- **ODOO** → *Produkt Materialkosten* (Materialkosten je Produkt)
|
||||||
- **Gesamt** → *Alle Rechnungen pro Monat* (PivotTable.js-Experiment)
|
- **Gesamt** → *Alle Rechnungen pro Monat* (PivotTable.js-Experiment)
|
||||||
- **Gesamt** → *Quartal Schweiz* (AMAZON + ODOO, Lieferung in die Schweiz)
|
- **Gesamt** → *Quartal Schweiz* (AMAZON + ODOO, Lieferung in die Schweiz)
|
||||||
|
|
||||||
@@ -60,6 +62,8 @@ Gesamt row:
|
|||||||
- *Rechnungen pro Monat* → `odoo-rechnungen_YYYY-MMM.csv`
|
- *Rechnungen pro Monat* → `odoo-rechnungen_YYYY-MMM.csv`
|
||||||
- *Alle Rechnungen pro Monat* → `gesamt-rechnungen-pivot_YYYY-MMM.csv`
|
- *Alle Rechnungen pro Monat* → `gesamt-rechnungen-pivot_YYYY-MMM.csv`
|
||||||
- *Quartal Schweiz* → `schweiz-quartal_YYYY-QN.csv`
|
- *Quartal Schweiz* → `schweiz-quartal_YYYY-QN.csv`
|
||||||
|
- *FIFO Lager Bewertung* → `fifo-lager_YYYY-MMM.csv`
|
||||||
|
- *Produkt Materialkosten* → `materialkosten_YYYY-MMM.csv`
|
||||||
|
|
||||||
The button is disabled while no dataset is loaded for the current dashboard.
|
The button is disabled while no dataset is loaded for the current dashboard.
|
||||||
|
|
||||||
@@ -81,6 +85,7 @@ Rechnungen pro Monat* filters by **Tag**, **Bestellnummer**, **Lieferland** and
|
|||||||
For *Quartal Schweiz* the shared **Zeitraum** control shows a **quarter**
|
For *Quartal Schweiz* the shared **Zeitraum** control shows a **quarter**
|
||||||
selector (Q1–Q4) instead of the month selector; the year selector is shared.
|
selector (Q1–Q4) instead of the month selector; the year selector is shared.
|
||||||
|
|
||||||
|
|
||||||
## Pivot (Experiment)
|
## Pivot (Experiment)
|
||||||
|
|
||||||
The three invoice dashboards (*ODOO Rechnungen pro Monat*, *Alle Rechnungen pro
|
The three invoice dashboards (*ODOO Rechnungen pro Monat*, *Alle Rechnungen pro
|
||||||
@@ -118,6 +123,8 @@ selected with the `query` field:
|
|||||||
- *Rechnungen pro Monat* → `query: "ODOO Rechnungen pro Monat"`
|
- *Rechnungen pro Monat* → `query: "ODOO Rechnungen pro Monat"`
|
||||||
- *Alle Rechnungen pro Monat* → `query: "Alle Rechnungen pro Monat"`
|
- *Alle Rechnungen pro Monat* → `query: "Alle Rechnungen pro Monat"`
|
||||||
- *Quartal Schweiz* → `query: "Quartal Schweiz"` (sends `quarter` instead of `month`)
|
- *Quartal Schweiz* → `query: "Quartal Schweiz"` (sends `quarter` instead of `month`)
|
||||||
|
- *FIFO Lager Bewertung* → `query: "FIFO Lager Bewertung"` (response is `[{ id, query, row_count, data: [...] }]`)
|
||||||
|
- *Produkt Materialkosten* → `query: "Produkt Materialkosten"` (one row per product; `remaining_qty` = remaining of that cost bracket, not the product total)
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,15 @@ const els = {
|
|||||||
chFilterLieferland: document.getElementById("ch-filter-lieferland"),
|
chFilterLieferland: document.getElementById("ch-filter-lieferland"),
|
||||||
chFilterNummer: document.getElementById("ch-filter-rechnungsnummer"),
|
chFilterNummer: document.getElementById("ch-filter-rechnungsnummer"),
|
||||||
chFilterQuelle: document.getElementById("ch-filter-quelle"),
|
chFilterQuelle: document.getElementById("ch-filter-quelle"),
|
||||||
|
// ODOO · FIFO Lager Bewertung
|
||||||
|
fifoKpis: document.getElementById("fifo-kpis"),
|
||||||
|
fifoTable: document.querySelector("#fifo-table tbody"),
|
||||||
|
fifoInfo: document.getElementById("fifo-info"),
|
||||||
|
fifoState: document.getElementById("fifo-state"),
|
||||||
|
// ODOO · Produkt Materialkosten
|
||||||
|
mkTable: document.querySelector("#mk-table tbody"),
|
||||||
|
mkInfo: document.getElementById("mk-info"),
|
||||||
|
mkState: document.getElementById("mk-state"),
|
||||||
// Auth
|
// Auth
|
||||||
authOverlay: document.getElementById("auth-overlay"),
|
authOverlay: document.getElementById("auth-overlay"),
|
||||||
authForm: document.getElementById("auth-form"),
|
authForm: document.getElementById("auth-form"),
|
||||||
@@ -195,6 +204,10 @@ function formatQuantity(value) {
|
|||||||
return value.toLocaleString("de-DE");
|
return value.toLocaleString("de-DE");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDate(value) {
|
||||||
|
return value ? String(value).slice(0, 10) : "";
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Data ---------- */
|
/* ---------- Data ---------- */
|
||||||
|
|
||||||
function extractArray(payload, key) {
|
function extractArray(payload, key) {
|
||||||
@@ -313,6 +326,89 @@ function normalizeRechnungen(rows) {
|
|||||||
return { invoices, totals: computeRechnungenTotals(invoices) };
|
return { invoices, totals: computeRechnungenTotals(invoices) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pickLocalized(value) {
|
||||||
|
if (value == null) return "";
|
||||||
|
if (typeof value !== "object") return String(value);
|
||||||
|
return value.de_DE ?? value.de_CH ?? value.en_US ?? Object.values(value)[0] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFifo(rows) {
|
||||||
|
// The webhook returns [{ id, query, row_count, data: [...] }].
|
||||||
|
let items = Array.isArray(rows) ? rows : [];
|
||||||
|
if (items.length && items[0] && Array.isArray(items[0].data)) {
|
||||||
|
items = items.flatMap((r) => (Array.isArray(r.data) ? r.data : []));
|
||||||
|
}
|
||||||
|
|
||||||
|
const products = items.map((row) => {
|
||||||
|
const unitCost = parseAmount(row.unit_cost);
|
||||||
|
const qty = parseAmount(row.remaining_qty);
|
||||||
|
return {
|
||||||
|
productId: row.product_id,
|
||||||
|
code: row.default_code ?? "",
|
||||||
|
name: pickLocalized(row.product_name),
|
||||||
|
categId: row.categ_id,
|
||||||
|
unitCost,
|
||||||
|
qty,
|
||||||
|
value: unitCost * qty,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const totals = products.reduce(
|
||||||
|
(acc, p) => {
|
||||||
|
acc.value += p.value;
|
||||||
|
acc.qty += p.qty;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ value: 0, qty: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
return { products, totals, productCount: new Set(products.map((p) => p.productId)).size };
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMaterialkosten(rows) {
|
||||||
|
// Response: [{ id, query, row_count, data: [...] }], one row per product.
|
||||||
|
// remaining_qty = remaining quantity of that cost bracket, NOT the total.
|
||||||
|
let items = Array.isArray(rows) ? rows : [];
|
||||||
|
if (items.length && items[0] && Array.isArray(items[0].data)) {
|
||||||
|
items = items.flatMap((r) => (Array.isArray(r.data) ? r.data : []));
|
||||||
|
}
|
||||||
|
|
||||||
|
const products = items.map((row) => {
|
||||||
|
const unitCost = parseAmount(row.unit_cost);
|
||||||
|
const qty = parseAmount(row.remaining_qty);
|
||||||
|
return {
|
||||||
|
productId: row.product_id,
|
||||||
|
code: row.default_code ?? "",
|
||||||
|
name: pickLocalized(row.product_name),
|
||||||
|
categId: row.categ_id,
|
||||||
|
unitCost,
|
||||||
|
qty,
|
||||||
|
value: unitCost * qty,
|
||||||
|
createDate: formatDate(row.create_date),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort by category, then product name.
|
||||||
|
products.sort((a, b) => {
|
||||||
|
const ca = Number(a.categId) || 0;
|
||||||
|
const cb = Number(b.categId) || 0;
|
||||||
|
if (ca !== cb) return ca - cb;
|
||||||
|
return String(a.name).localeCompare(String(b.name), "de");
|
||||||
|
});
|
||||||
|
|
||||||
|
const totals = products.reduce(
|
||||||
|
(acc, p) => {
|
||||||
|
acc.value += p.value;
|
||||||
|
acc.qty += p.qty;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ value: 0, qty: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
return { products, totals, productCount: new Set(products.map((p) => p.productId)).size };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ---------- ODOO Rechnungen pro Monat: filters ---------- */
|
/* ---------- ODOO Rechnungen pro Monat: filters ---------- */
|
||||||
|
|
||||||
let rechnungenFilter = { tag: "", bestellnummer: "", lieferland: "", nummer: "", quelle: "" };
|
let rechnungenFilter = { tag: "", bestellnummer: "", lieferland: "", nummer: "", quelle: "" };
|
||||||
@@ -646,6 +742,90 @@ function renderRechnungenKpis(data) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderFifo(data) {
|
||||||
|
const v = viewEls();
|
||||||
|
if (!v) return;
|
||||||
|
const { products, totals, productCount } = data;
|
||||||
|
|
||||||
|
v.kpis.innerHTML = `
|
||||||
|
<div class="kpi primary">
|
||||||
|
<div class="kpi-label">Lagerwert (FIFO)</div>
|
||||||
|
<div class="kpi-value">${formatEuro(totals.value)}</div>
|
||||||
|
<div class="kpi-sub">Summe Stückkosten × Restmenge</div>
|
||||||
|
</div>
|
||||||
|
<div class="kpi">
|
||||||
|
<div class="kpi-label">Produkte</div>
|
||||||
|
<div class="kpi-value">${productCount}</div>
|
||||||
|
<div class="kpi-sub">verschiedene Artikel</div>
|
||||||
|
</div>
|
||||||
|
<div class="kpi">
|
||||||
|
<div class="kpi-label">Menge</div>
|
||||||
|
<div class="kpi-value">${formatQuantity(totals.qty)}</div>
|
||||||
|
<div class="kpi-sub">Restmenge gesamt</div>
|
||||||
|
</div>
|
||||||
|
<div class="kpi">
|
||||||
|
<div class="kpi-label">Positionen</div>
|
||||||
|
<div class="kpi-value">${products.length}</div>
|
||||||
|
<div class="kpi-sub">FIFO-Schichten</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
if (els.fifoInfo) els.fifoInfo.textContent = `${products.length} Positionen`;
|
||||||
|
|
||||||
|
v.table.innerHTML = "";
|
||||||
|
products.forEach((p) => {
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>${p.name}</td>
|
||||||
|
<td>${p.code}</td>
|
||||||
|
<td class="num">${p.categId ?? ""}</td>
|
||||||
|
<td class="num">${formatEuro(p.unitCost)}</td>
|
||||||
|
<td class="num">${formatQuantity(p.qty)}</td>
|
||||||
|
<td class="num">${formatEuro(p.value)}</td>`;
|
||||||
|
v.table.appendChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
tr.className = "total";
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>Gesamt</td>
|
||||||
|
<td></td>
|
||||||
|
<td class="num"></td>
|
||||||
|
<td class="num"></td>
|
||||||
|
<td class="num">${formatQuantity(totals.qty)}</td>
|
||||||
|
<td class="num">${formatEuro(totals.value)}</td>`;
|
||||||
|
v.table.appendChild(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMaterialkosten(data) {
|
||||||
|
const v = viewEls();
|
||||||
|
if (!v) return;
|
||||||
|
const { products, totals } = data;
|
||||||
|
|
||||||
|
if (els.mkInfo) els.mkInfo.textContent = `${products.length} Positionen`;
|
||||||
|
|
||||||
|
v.table.innerHTML = "";
|
||||||
|
products.forEach((p) => {
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>${p.name}</td>
|
||||||
|
<td class="num">${p.categId ?? ""}</td>
|
||||||
|
<td>${p.createDate}</td>
|
||||||
|
<td class="num">${formatEuro(p.unitCost)}</td>
|
||||||
|
<td class="num">${formatQuantity(p.qty)}</td>`;
|
||||||
|
v.table.appendChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
tr.className = "total";
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>Gesamt</td>
|
||||||
|
<td class="num"></td>
|
||||||
|
<td></td>
|
||||||
|
<td class="num"></td>
|
||||||
|
<td class="num">${formatQuantity(totals.qty)}</td>`;
|
||||||
|
v.table.appendChild(tr);
|
||||||
|
}
|
||||||
|
|
||||||
function renderRechnungenChart(data) {
|
function renderRechnungenChart(data) {
|
||||||
const v = viewEls();
|
const v = viewEls();
|
||||||
if (!v || !v.chart) return;
|
if (!v || !v.chart) return;
|
||||||
@@ -792,6 +972,12 @@ function viewEls() {
|
|||||||
if (currentView === "odoo-rechnungen") {
|
if (currentView === "odoo-rechnungen") {
|
||||||
return { kpis: els.odooKpis, chart: els.odooChart, legend: els.odooLegend, state: els.odooState, pivot: document.getElementById("odoo-pivot") };
|
return { kpis: els.odooKpis, chart: els.odooChart, legend: els.odooLegend, state: els.odooState, pivot: document.getElementById("odoo-pivot") };
|
||||||
}
|
}
|
||||||
|
if (currentView === "fifo-lager") {
|
||||||
|
return { kpis: els.fifoKpis, table: els.fifoTable, state: els.fifoState };
|
||||||
|
}
|
||||||
|
if (currentView === "materialkosten") {
|
||||||
|
return { table: els.mkTable, state: els.mkState };
|
||||||
|
}
|
||||||
if (currentView === "gesamt-rechnungen-pivot") {
|
if (currentView === "gesamt-rechnungen-pivot") {
|
||||||
return { kpis: els.pivotKpis, state: els.pivotState, pivot: document.getElementById("pivot") };
|
return { kpis: els.pivotKpis, state: els.pivotState, pivot: document.getElementById("pivot") };
|
||||||
}
|
}
|
||||||
@@ -809,6 +995,10 @@ function renderActiveView() {
|
|||||||
renderVat(lastData.data);
|
renderVat(lastData.data);
|
||||||
} else if (currentView === "odoo-rechnungen" || currentView === "gesamt-rechnungen-pivot" || currentView === "schweiz-quartal") {
|
} else if (currentView === "odoo-rechnungen" || currentView === "gesamt-rechnungen-pivot" || currentView === "schweiz-quartal") {
|
||||||
renderRechnungenPivot(applyRechnungenFilters(lastData.data));
|
renderRechnungenPivot(applyRechnungenFilters(lastData.data));
|
||||||
|
} else if (currentView === "fifo-lager") {
|
||||||
|
renderFifo(lastData.data);
|
||||||
|
} else if (currentView === "materialkosten") {
|
||||||
|
renderMaterialkosten(lastData.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -828,7 +1018,7 @@ function showSkeleton() {
|
|||||||
const v = viewEls();
|
const v = viewEls();
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
v.state.classList.add("hidden");
|
v.state.classList.add("hidden");
|
||||||
v.kpis.innerHTML = skeletonKpis();
|
if (v.kpis) v.kpis.innerHTML = skeletonKpis();
|
||||||
if (v.chart) v.chart.innerHTML = skeletonBars();
|
if (v.chart) v.chart.innerHTML = skeletonBars();
|
||||||
if (v.legend) v.legend.innerHTML = "";
|
if (v.legend) v.legend.innerHTML = "";
|
||||||
if (v.table) v.table.innerHTML = "";
|
if (v.table) v.table.innerHTML = "";
|
||||||
@@ -845,7 +1035,7 @@ function showError(message) {
|
|||||||
function clearViews() {
|
function clearViews() {
|
||||||
const v = viewEls();
|
const v = viewEls();
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
v.kpis.innerHTML = "";
|
if (v.kpis) v.kpis.innerHTML = "";
|
||||||
if (v.chart) v.chart.innerHTML = "";
|
if (v.chart) v.chart.innerHTML = "";
|
||||||
if (v.legend) v.legend.innerHTML = "";
|
if (v.legend) v.legend.innerHTML = "";
|
||||||
if (v.table) v.table.innerHTML = "";
|
if (v.table) v.table.innerHTML = "";
|
||||||
@@ -1009,6 +1199,34 @@ function buildRechnungenCsv(data) {
|
|||||||
return lines.join("\r\n");
|
return lines.join("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildFifoCsv(data) {
|
||||||
|
const header = ["Produkt", "Code", "Kategorie", "Stückkosten", "Menge", "Wert"];
|
||||||
|
const lines = [header.map(csvEscape).join(";")];
|
||||||
|
data.products.forEach((p) => {
|
||||||
|
lines.push([
|
||||||
|
p.name, p.code, p.categId ?? "", formatEuro(p.unitCost), formatQuantity(p.qty), formatEuro(p.value),
|
||||||
|
].map(csvEscape).join(";"));
|
||||||
|
});
|
||||||
|
lines.push([
|
||||||
|
"Gesamt", "", "", "", formatQuantity(data.totals.qty), formatEuro(data.totals.value),
|
||||||
|
].map(csvEscape).join(";"));
|
||||||
|
return lines.join("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMaterialkostenCsv(data) {
|
||||||
|
const header = ["Produkt", "Kategorie", "Einlagerungsdatum", "Stückkosten", "Restmenge (Staffel)"];
|
||||||
|
const lines = [header.map(csvEscape).join(";")];
|
||||||
|
data.products.forEach((p) => {
|
||||||
|
lines.push([
|
||||||
|
p.name, p.categId ?? "", p.createDate, formatEuro(p.unitCost), formatQuantity(p.qty),
|
||||||
|
].map(csvEscape).join(";"));
|
||||||
|
});
|
||||||
|
lines.push([
|
||||||
|
"Gesamt", "", "", "", formatQuantity(data.totals.qty),
|
||||||
|
].map(csvEscape).join(";"));
|
||||||
|
return lines.join("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
function downloadCsv() {
|
function downloadCsv() {
|
||||||
if (!lastData || lastData.view !== currentView) return;
|
if (!lastData || lastData.view !== currentView) return;
|
||||||
|
|
||||||
@@ -1022,6 +1240,12 @@ function downloadCsv() {
|
|||||||
} else if (currentView === "amazon-vat-summary") {
|
} else if (currentView === "amazon-vat-summary") {
|
||||||
csv = buildVatCsv(lastData.data);
|
csv = buildVatCsv(lastData.data);
|
||||||
filename = `vat-summary_${period}.csv`;
|
filename = `vat-summary_${period}.csv`;
|
||||||
|
} else if (currentView === "materialkosten") {
|
||||||
|
csv = buildMaterialkostenCsv(lastData.data);
|
||||||
|
filename = `materialkosten_${period}.csv`;
|
||||||
|
} else if (currentView === "fifo-lager") {
|
||||||
|
csv = buildFifoCsv(lastData.data);
|
||||||
|
filename = `fifo-lager_${period}.csv`;
|
||||||
} else if (currentView === "odoo-rechnungen" || currentView === "gesamt-rechnungen-pivot" || currentView === "schweiz-quartal") {
|
} else if (currentView === "odoo-rechnungen" || currentView === "gesamt-rechnungen-pivot" || currentView === "schweiz-quartal") {
|
||||||
csv = buildRechnungenCsv(applyRechnungenFilters(lastData.data));
|
csv = buildRechnungenCsv(applyRechnungenFilters(lastData.data));
|
||||||
filename = `${currentView}_${period}.csv`;
|
filename = `${currentView}_${period}.csv`;
|
||||||
@@ -1048,7 +1272,9 @@ function updateDownloadState() {
|
|||||||
currentView === "amazon-vat-summary" ||
|
currentView === "amazon-vat-summary" ||
|
||||||
currentView === "odoo-rechnungen" ||
|
currentView === "odoo-rechnungen" ||
|
||||||
currentView === "gesamt-rechnungen-pivot" ||
|
currentView === "gesamt-rechnungen-pivot" ||
|
||||||
currentView === "schweiz-quartal";
|
currentView === "schweiz-quartal" ||
|
||||||
|
currentView === "fifo-lager" ||
|
||||||
|
currentView === "materialkosten";
|
||||||
els.download.disabled = !(lastData && lastData.view === currentView && supported);
|
els.download.disabled = !(lastData && lastData.view === currentView && supported);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,6 +1300,18 @@ const VIEWS = {
|
|||||||
query: "ODOO Rechnungen pro Monat",
|
query: "ODOO Rechnungen pro Monat",
|
||||||
normalize: normalizeRechnungen,
|
normalize: normalizeRechnungen,
|
||||||
},
|
},
|
||||||
|
"fifo-lager": {
|
||||||
|
title: "FIFO Lager Bewertung",
|
||||||
|
subtitle: "ODOO · Lagerwert (FIFO)",
|
||||||
|
query: "FIFO Lager Bewertung",
|
||||||
|
normalize: normalizeFifo,
|
||||||
|
},
|
||||||
|
"materialkosten": {
|
||||||
|
title: "Produkt Materialkosten",
|
||||||
|
subtitle: "ODOO · Materialkosten je Produkt (Kostenstaffel)",
|
||||||
|
query: "Produkt Materialkosten",
|
||||||
|
normalize: normalizeMaterialkosten,
|
||||||
|
},
|
||||||
"gesamt-rechnungen-pivot": {
|
"gesamt-rechnungen-pivot": {
|
||||||
title: "Alle Rechnungen pro Monat",
|
title: "Alle Rechnungen pro Monat",
|
||||||
subtitle: "Gesamt · Pivot",
|
subtitle: "Gesamt · Pivot",
|
||||||
|
|||||||
+64
@@ -71,6 +71,12 @@
|
|||||||
<a class="submenu-item" href="#" data-view="odoo-rechnungen">
|
<a class="submenu-item" href="#" data-view="odoo-rechnungen">
|
||||||
ODOO Rechnungen pro Monat
|
ODOO Rechnungen pro Monat
|
||||||
</a>
|
</a>
|
||||||
|
<a class="submenu-item" href="#" data-view="fifo-lager">
|
||||||
|
FIFO Lager Bewertung
|
||||||
|
</a>
|
||||||
|
<a class="submenu-item" href="#" data-view="materialkosten">
|
||||||
|
Produkt Materialkosten
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -277,6 +283,64 @@
|
|||||||
<div class="state" id="odoo-state"></div>
|
<div class="state" id="odoo-state"></div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- View: ODOO · FIFO Lager Bewertung -->
|
||||||
|
<main id="view-fifo-lager" class="view hidden">
|
||||||
|
<section class="kpis" id="fifo-kpis"></section>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<div class="panel-head">
|
||||||
|
<h2>FIFO Lager Bewertung</h2>
|
||||||
|
<span class="muted" id="fifo-info"></span>
|
||||||
|
</div>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table id="fifo-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Produkt</th>
|
||||||
|
<th>Code</th>
|
||||||
|
<th class="num">Kategorie</th>
|
||||||
|
<th class="num">Stückkosten</th>
|
||||||
|
<th class="num">Menge</th>
|
||||||
|
<th class="num">Wert</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="state" id="fifo-state"></div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- View: ODOO · Produkt Materialkosten -->
|
||||||
|
<main id="view-materialkosten" class="view hidden">
|
||||||
|
<section class="panel">
|
||||||
|
<div class="panel-head">
|
||||||
|
<h2>Produkt Materialkosten</h2>
|
||||||
|
<span class="muted" id="mk-info"></span>
|
||||||
|
</div>
|
||||||
|
<p class="muted panel-note" id="mk-note">
|
||||||
|
Restmenge = verbleibende Menge der jeweiligen Kostenstaffel – nicht der Gesamtbestand des Produkts. Letzte Kostenstaffel, nicht FIFO.
|
||||||
|
</p>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table id="mk-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Produkt</th>
|
||||||
|
<th class="num">Kategorie</th>
|
||||||
|
<th>Einlagerungsdatum</th>
|
||||||
|
<th class="num">Stückkosten</th>
|
||||||
|
<th class="num" title="Verbleibende Menge der jeweiligen Kostenstaffel – nicht der Gesamtbestand des Produkts. Letzte Kostenstaffel, nicht FIFO.">Restmenge (Staffel)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="state" id="mk-state"></div>
|
||||||
|
</main>
|
||||||
|
|
||||||
<!-- View: Gesamt · Alle Rechnungen pro Monat -->
|
<!-- View: Gesamt · Alle Rechnungen pro Monat -->
|
||||||
<main id="view-gesamt-rechnungen-pivot" class="view hidden">
|
<main id="view-gesamt-rechnungen-pivot" class="view hidden">
|
||||||
<section class="kpis" id="pivot-kpis"></section>
|
<section class="kpis" id="pivot-kpis"></section>
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px
|
|||||||
}
|
}
|
||||||
.panel-head h2 { margin: 0; font-size: 16px; letter-spacing: -.01em; }
|
.panel-head h2 { margin: 0; font-size: 16px; letter-spacing: -.01em; }
|
||||||
.muted { color: var(--muted); font-size: 13px; }
|
.muted { color: var(--muted); font-size: 13px; }
|
||||||
|
.panel-note { margin: 0 0 14px; }
|
||||||
|
|
||||||
.legend { display: flex; gap: 14px; flex-wrap: wrap; }
|
.legend { display: flex; gap: 14px; flex-wrap: wrap; }
|
||||||
.legend-item { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--muted); }
|
.legend-item { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--muted); }
|
||||||
|
|||||||
Reference in new issue
Block a user