diff --git a/README.MD b/README.MD
index bb3be24..181b7d6 100644
--- a/README.MD
+++ b/README.MD
@@ -1,6 +1,6 @@
# EPIWebview
-- **aktuellste stable Version:** 1.9.0
+- **aktuellste stable Version:** 1.10.2
- **Lizenz:** Creative Commons Attribution-NonCommercial-ShareAlike 4.0 (CC BY-NC-SA 4.0). Einsehbar unter: (LICENSE.MD)
- **Kompatibilität:** Erweiterung für **Epirent** und **CrewBrain**
@@ -17,7 +17,7 @@ Die Anwendung ist speziell für den Einsatz in Lagerprozessen entwickelt.
- **Check-In / Check-Out Übersicht**: Lagermonitor
- **Integration mit Epirent API**: Vollständig kompatibel mit bestehenden Epirent-Systemen.
-- **Integration mit Crewbrain**: Anzeige einer Aufgabenliste aus CrewBraingit
+- **Integration mit Crewbrain**: Anzeige einer Aufgabenliste aus CrewBrain
---
@@ -26,7 +26,7 @@ Die Anwendung ist speziell für den Einsatz in Lagerprozessen entwickelt.
## Systemanforderungen
- **Server:** PHP ≥ 8.2, Apache oder Nginx
- - Achtung !!: Für die Return of Invest Funktion oder den Warengruppencheck / Imagechecks sollten in der php.ini die max_execution_time und die maximale Dateigröße deutlich nach oben korrigiert werden. Die ROI Funktion kann gut und gerne 20 Minuten laden!
+ - Achtung !!: Für die Return of Invest Funktion oder den Warengruppencheck / Imagechecks sollten in der php.ini die max_execution_time und die maximale Dateigröße deutlich nach oben korrigiert werden. Die ROI Funktion kann gut und gerne 20 Minuten laden!. Foglende PHP Funktionen müssen aktiviert werden: curl, gzcompress, Imagick, gd
- **Client:** Aktueller Browser (Chrome, Edge, Firefox, Safari)
- **Datenquelle:** Bestehende Epirent-Installation mit aktivierter API sowie optional CrewBrain
diff --git a/dist/ROI.php b/dist/ROI.php
index 679873b..7cc548f 100644
--- a/dist/ROI.php
+++ b/dist/ROI.php
@@ -94,12 +94,7 @@ function getRentPrice(Epirent $Epi, int $productPk): float {
/**
* WICHTIG: Bundle-Auflösung NUR für virtuelle Bundles.
- * Damit ist die anteilige Bundlepreis-Berechnung wieder wie in der funktionierenden Version.
- *
* Ergebnis: [leafProductPk => amount]
- * - nur wenn is_virtual == true UND rent_fix/materials vorhanden
- * - sonst: leaf = [self=>1]
- * - Cycle-Guard über $stack
*/
function resolveBundleLeafMap(Epirent $Epi, int $productPk, array &$stack = []): array {
global $bundleLeafCache;
@@ -222,11 +217,10 @@ function getJournalByChapter(Epirent $Epi, int $chapterId): array {
}
/* =========================
- Interval aggregation (Peak/Histogram) – tagesbasiert (wie gehabt)
+ Interval aggregation (Peak/Histogram) – tagesbasiert
========================= */
-// eventsDirect[productPk][ymd] += deltaQty
-$eventsDirect = [];
+$eventsDirect = []; // eventsDirect[productPk][ymd] += deltaQty
$eventsIncl = []; // direct + bundle
function addIntervalEvent(array &$events, int $productPk, string $startYmd, string $endYmd, float $qty): void {
@@ -264,6 +258,8 @@ function computePeakAndHistogram(array $eventMap): array {
for ($i = 0; $i < count($dates); $i++) {
$d = $dates[$i];
$level += (float)$eventMap[$d];
+
+ // clamp, damit kein negativer Bestand "Tage" erzeugt
if ($level < 0) $level = 0;
$intLevel = (int)round($level);
@@ -300,10 +296,6 @@ $meta = []; // product meta [pk=>['product_no'=>..,'title'=>..]]
$allYears = [];
// Auftragsbasierte "Slots" (direct/incl) pro Produkt
-// slotAgg[productPk]['direct'][slot] += revenue
-// slotAgg[productPk]['incl'][slot] += revenue
-// slotAgg[productPk]['direct_ext'][slot] += extRevenue
-// slotAgg[productPk]['incl_ext'][slot] += extRevenue
$slotAgg = [];
function ensureMeta(Epirent $Epi, int $productPk, int $productNo, string $title): void {
@@ -329,7 +321,6 @@ function addRevenue(array &$pivotRef, int $productPk, int $year, float $revenueN
* Extern-Logik:
* - Amount External ist eine ANZAHL (nicht Umsatz).
* - Wir berechnen extRevenue = sum_total_net * (amount_external / amount_total) (wenn amount_total>0)
- * - fallback: 0
*/
function calcExternalRevenueNet(object $li): float {
$amountTotal = (float)($li->amount_total ?? 0);
@@ -346,103 +337,219 @@ function calcExternalRevenueNet(object $li): float {
}
/* =========================
- Slot (auftragsbasiert) – Intervall Coloring
+ Slot (auftragsbasiert) – Storno-sicher (SIGNED)
========================= */
/**
* Intervall-Item für Slotting (ein "Auftragsteil" pro Produkt)
+ * qty kann positiv oder negativ sein (negativ = Storno/Rückbuchung)
*/
-function addSlotItem(array &$slotItems, int $productPk, int $invoicePk, int $orderNo, string $startYmd, string $endYmd, float $qty, float $revenueNet, float $extRevenueNet): void {
+function addSlotItem(
+ array &$slotItems,
+ int $productPk,
+ int $invoicePk,
+ int $orderPk,
+ int $invoiceNo,
+ string $startYmd,
+ string $endYmd,
+ float $qty,
+ float $revenueNet,
+ float $extRevenueNet
+): void {
$startYmd = safeYmd($startYmd);
$endYmd = safeYmd($endYmd);
if ($productPk <= 0) return;
if (!$startYmd || !$endYmd) return;
- if ($qty <= 0) return;
+ if ($qty == 0.0) return;
+
+ if (!isset($slotItems[$productPk])) $slotItems[$productPk] = [];
$slotItems[$productPk][] = [
- 'invoice_pk' => $invoicePk,
- 'order_no' => $orderNo,
- 'start_ts' => ymdToTs($startYmd),
- 'end_ts' => ymdToTs($endYmd),
- 'qty' => (float)$qty,
- 'rev' => (float)$revenueNet,
- 'rev_ext' => (float)$extRevenueNet,
- 'start_ymd' => $startYmd,
- 'end_ymd' => $endYmd,
+ 'product_pk' => $productPk,
+ 'invoice_pk' => $invoicePk,
+ 'order_pk' => $orderPk,
+ 'invoice_no' => $invoiceNo,
+ 'start_ts' => ymdToTs($startYmd),
+ 'end_ts' => ymdToTs($endYmd),
+ 'qty' => (float)$qty, // signed erlaubt
+ 'rev' => (float)$revenueNet,
+ 'rev_ext' => (float)$extRevenueNet,
+ 'start_ymd' => $startYmd,
+ 'end_ymd' => $endYmd,
];
}
/**
- * Slotting-Regel:
- * - Aufträge werden nach start_ts sortiert,
- * - bei gleichem start: zuerst der der später endet bekommt den "zweiten" Slot (also: end_ts DESC),
- * - wenn dann noch gleich: nach order_no (oder invoice_pk) ASC.
- * - Belegung: für qty=2 werden 2 Slots gesucht; wenn Slots frei werden (end < start), wiederverwendbar.
+ * Slotting (SIGNED, Storno gibt Slots wieder frei):
+ * - Positive qty belegt Slots.
+ * - Negative qty bucht Umsatz zurück UND gibt Slots frei.
+ * - Priorität bei Storno: zuerst Slots mit gleichem Zeitraum (gleicher release_ts) abbauen.
*
- * Umsatzverteilung:
- * - pro Auftrag & Produkt wird der UMSATZ auf die belegten Slots gleichmäßig verteilt (rev/qty)
- * - analog für extern-umsatz.
+ * Ergebnis:
+ * - 'direct' => [slot => revenue]
+ * - 'direct_ext' => [slot => extRevenue]
*/
function computeOrderBasedSlots(array $itemsForProduct): array {
- if (empty($itemsForProduct)) return [
- 'direct' => [],
- 'direct_ext' => []
- ];
+ if (empty($itemsForProduct)) {
+ return ['direct' => [], 'direct_ext' => []];
+ }
+ // Sortierung: start ASC, end DESC, invoice_no ASC, invoice_pk ASC
usort($itemsForProduct, function($a, $b){
- if ($a['start_ts'] !== $b['start_ts']) return $a['start_ts'] <=> $b['start_ts'];
- if ($a['end_ts'] !== $b['end_ts']) return $b['end_ts'] <=> $a['end_ts']; // später endend zuerst
- if ($a['order_no'] !== $b['order_no']) return $a['order_no'] <=> $b['order_no'];
- return $a['invoice_pk'] <=> $b['invoice_pk'];
+ if (($a['start_ts'] ?? 0) !== ($b['start_ts'] ?? 0)) return ($a['start_ts'] ?? 0) <=> ($b['start_ts'] ?? 0);
+ if (($a['end_ts'] ?? 0) !== ($b['end_ts'] ?? 0)) return ($b['end_ts'] ?? 0) <=> ($a['end_ts'] ?? 0);
+ if (($a['invoice_no'] ?? 0) !== ($b['invoice_no'] ?? 0)) return ($a['invoice_no'] ?? 0) <=> ($b['invoice_no'] ?? 0);
+ return ($a['invoice_pk'] ?? 0) <=> ($b['invoice_pk'] ?? 0);
});
- $slotEnd = []; // slotIndex => end_ts
- $slotRevenue = []; // slotIndex => revenue
- $slotRevenueExt = []; // slotIndex => revenueExt
+ // Active slots: slotIndex => release_ts
+ $active = [];
+ // release_ts => [slotIndex, ...]
+ $activeByRelease = [];
+ // free slot indices
+ $freeSlots = [];
+
+ // Revenues per slot
+ $slotRevenue = [];
+ $slotRevenueExt = [];
+
+ $releaseTsFor = function(int $endTs): int {
+ // Ende inklusiv -> frei am Folgetag 00:00
+ return $endTs + 86400;
+ };
+
+ $freeExpired = function(int $currentStartTs) use (&$active, &$activeByRelease, &$freeSlots) {
+ if (empty($active)) return;
+ foreach ($active as $slot => $relTs) {
+ if ($relTs <= $currentStartTs) {
+ unset($active[$slot]);
+ if (isset($activeByRelease[$relTs])) {
+ $activeByRelease[$relTs] = array_values(array_filter(
+ $activeByRelease[$relTs],
+ fn($s) => (int)$s !== (int)$slot
+ ));
+ if (empty($activeByRelease[$relTs])) unset($activeByRelease[$relTs]);
+ }
+ $freeSlots[] = (int)$slot;
+ }
+ }
+ };
+
+ $takeFreeSlot = function() use (&$freeSlots, &$active): int {
+ if (!empty($freeSlots)) {
+ sort($freeSlots);
+ return (int)array_shift($freeSlots);
+ }
+ if (empty($active)) return 1;
+ $keys = array_keys($active);
+ return (int)(max($keys) + 1);
+ };
foreach ($itemsForProduct as $it) {
- $qty = (int)round($it['qty']);
- if ($qty <= 0) continue;
+ $startTs = (int)($it['start_ts'] ?? 0);
+ $endTs = (int)($it['end_ts'] ?? 0);
+ if ($startTs <= 0 || $endTs <= 0) continue;
+ if ($endTs < $startTs) continue;
- $revPer = ($it['rev'] ?? 0.0) / $qty;
- $extPer = ($it['rev_ext'] ?? 0.0) / $qty;
+ // vor jeder Aktion: abgelaufene Slots freigeben
+ $freeExpired($startTs);
- // finde freie Slots / neue Slots
- $assigned = [];
- for ($k=0; $k<$qty; $k++) {
- $slot = null;
+ $qtySigned = (float)($it['qty'] ?? 0.0);
+ if ($qtySigned == 0.0) continue;
- // freier Slot: end < start
- foreach ($slotEnd as $idx => $endTs) {
- if ($endTs < $it['start_ts']) { // streng <, damit gleicher Tag als parallel zählt
- $slot = (int)$idx;
- break;
+ $qtyAbs = (int)round(abs($qtySigned));
+ if ($qtyAbs <= 0) continue;
+
+ $rev = (float)($it['rev'] ?? 0.0);
+ $ext = (float)($it['rev_ext'] ?? 0.0);
+
+ $revPer = $rev / $qtyAbs;
+ $extPer = $ext / $qtyAbs;
+
+ $relTs = $releaseTsFor($endTs);
+
+ if ($qtySigned > 0) {
+ // --- Belegen ---
+ for ($k = 0; $k < $qtyAbs; $k++) {
+ $slot = $takeFreeSlot();
+
+ $active[$slot] = $relTs;
+ if (!isset($activeByRelease[$relTs])) $activeByRelease[$relTs] = [];
+ $activeByRelease[$relTs][] = $slot;
+
+ if (!isset($slotRevenue[$slot])) $slotRevenue[$slot] = 0.0;
+ if (!isset($slotRevenueExt[$slot])) $slotRevenueExt[$slot] = 0.0;
+
+ $slotRevenue[$slot] += $revPer;
+ $slotRevenueExt[$slot] += $extPer;
+ }
+ } else {
+ // --- Storno / Freigeben ---
+ // erst gleiche release_ts abbauen (damit "Rechnung+Storno im gleichen Zeitraum" sauber 0 Slots macht)
+ $candidates = $activeByRelease[$relTs] ?? [];
+
+ for ($k = 0; $k < $qtyAbs; $k++) {
+ $slot = null;
+
+ if (!empty($candidates)) {
+ rsort($candidates); // höchste Slotnummer zuerst abbauen
+ $slot = (int)array_shift($candidates);
+ } else {
+ if (!empty($active)) {
+ $keys = array_keys($active);
+ rsort($keys);
+ $slot = (int)$keys[0];
+ }
}
- }
- if ($slot === null) {
- $slot = count($slotEnd) + 1; // Slots sind 1-based
- }
- // reservieren
- $slotEnd[$slot] = $it['end_ts'];
- $assigned[] = $slot;
+ if ($slot === null) break;
- // Revenue sammeln
- if (!isset($slotRevenue[$slot])) $slotRevenue[$slot] = 0.0;
- if (!isset($slotRevenueExt[$slot])) $slotRevenueExt[$slot] = 0.0;
- $slotRevenue[$slot] += $revPer;
- $slotRevenueExt[$slot] += $extPer;
+ // Umsatz auf Slot zurückbuchen
+ if (!isset($slotRevenue[$slot])) $slotRevenue[$slot] = 0.0;
+ if (!isset($slotRevenueExt[$slot])) $slotRevenueExt[$slot] = 0.0;
+ $slotRevenue[$slot] += $revPer; // revPer ist i.d.R. negativ
+ $slotRevenueExt[$slot] += $extPer; // extPer ist i.d.R. negativ
+
+ // Slot freigeben
+ $oldRel = $active[$slot] ?? null;
+ unset($active[$slot]);
+
+ if ($oldRel !== null && isset($activeByRelease[$oldRel])) {
+ $activeByRelease[$oldRel] = array_values(array_filter(
+ $activeByRelease[$oldRel],
+ fn($s) => (int)$s !== (int)$slot
+ ));
+ if (empty($activeByRelease[$oldRel])) unset($activeByRelease[$oldRel]);
+ }
+
+ $freeSlots[] = $slot;
+ }
}
}
- ksort($slotRevenue);
- ksort($slotRevenueExt);
+ // --- Cleanup: Slots ohne Umsatz entfernen + neu durchnummerieren ---
+ $eps = 0.00001;
+ $allSlots = array_unique(array_merge(array_keys($slotRevenue), array_keys($slotRevenueExt)));
+ sort($allSlots);
- // runden (2 Nachkommastellen für Anzeige später, intern float ok)
- return [
- 'direct' => $slotRevenue,
- 'direct_ext' => $slotRevenueExt
- ];
+ $kept = [];
+ foreach ($allSlots as $s) {
+ $rev = (float)($slotRevenue[$s] ?? 0.0);
+ $ext = (float)($slotRevenueExt[$s] ?? 0.0);
+ if (abs($rev) < $eps && abs($ext) < $eps) continue;
+ $kept[] = $s;
+ }
+
+ $newRev = [];
+ $newExt = [];
+ $idx = 1;
+ foreach ($kept as $old) {
+ $newRev[$idx] = (float)($slotRevenue[$old] ?? 0.0);
+ $newExt[$idx] = (float)($slotRevenueExt[$old] ?? 0.0);
+ $idx++;
+ }
+
+ return ['direct' => $newRev, 'direct_ext' => $newExt];
}
/* =========================
@@ -454,13 +561,15 @@ function processLineItem(
array &$rows,
array &$slotItemsDirect,
array &$slotItemsIncl,
+ int $orderPk,
int $invoicePk,
int $invoiceNo,
string $invoiceDate,
int $invoiceYear,
?int $chapterId,
object $li,
- bool $isFromJournal
+ bool $isFromJournal,
+ bool $invoiceIsCredit // <-- wichtig: Storno-Rechnung (z.B. sum_net < 0)
): void {
global $pivot, $pivotB, $pivotExt, $pivotExtB, $allYears, $eventsDirect, $eventsIncl;
@@ -471,15 +580,23 @@ function processLineItem(
$title = (string)($li->title ?? '');
$productNo = (int)($li->product_no ?? 0);
- $qty = (float)($li->amount_total ?? 0);
- if ($qty <= 0) $qty = 0.0;
+ $qtyRaw = (float)($li->amount_total ?? 0);
+ if ($qtyRaw == 0.0) $qtyRaw = 0.0;
+
+ // Umsatz bleibt wie geliefert (kann negativ sein)
+ $revenueNet = (float)($li->sum_total_net ?? 0);
+ $extRevenueNet = calcExternalRevenueNet($li);
+
+ // Für Auslastung/Slots: wenn Storno-Rechnung, Menge "negativ signieren"
+ // (weil Epi bei Storno typischerweise Menge positiv lässt, aber Preise negativ macht)
+ $qtySigned = $qtyRaw;
+ if ($invoiceIsCredit && $qtySigned != 0.0) {
+ $qtySigned = -abs($qtySigned);
+ }
$dateStart = safeYmd((string)($li->date_start ?? '')) ?: null;
$dateEnd = safeYmd((string)($li->date_end ?? '')) ?: null;
- $revenueNet = (float)($li->sum_total_net ?? 0);
- $extRevenueNet = calcExternalRevenueNet($li);
-
ensureMeta($Epi, $productPk, $productNo, $title);
// Jahr: Rechnungsdatum
@@ -493,15 +610,16 @@ function processLineItem(
if ($extRevenueNet != 0.0 && $year) addRevenue($pivotExt, $productPk, $year, $extRevenueNet);
// Peak/Histogram direct + incl (tagesbasiert)
- if ($dateStart && $dateEnd && $qty > 0) {
- addIntervalEvent($eventsDirect, $productPk, $dateStart, $dateEnd, $qty);
- addIntervalEvent($eventsIncl, $productPk, $dateStart, $dateEnd, $qty);
+ // -> qtySigned sorgt dafür, dass Storno zeitgleich aufhebt
+ if ($dateStart && $dateEnd && $qtySigned != 0.0) {
+ addIntervalEvent($eventsDirect, $productPk, $dateStart, $dateEnd, $qtySigned);
+ addIntervalEvent($eventsIncl, $productPk, $dateStart, $dateEnd, $qtySigned);
}
- // Slotting: direct
- if ($dateStart && $dateEnd && $qty > 0) {
- addSlotItem($slotItemsDirect, $productPk, $invoicePk, $invoiceNo, $dateStart, $dateEnd, $qty, $revenueNet, $extRevenueNet);
- addSlotItem($slotItemsIncl, $productPk, $invoicePk, $invoiceNo, $dateStart, $dateEnd, $qty, $revenueNet, $extRevenueNet);
+ // Slotting: direct (signed qty)
+ if ($dateStart && $dateEnd && $qtySigned != 0.0) {
+ addSlotItem($slotItemsDirect, $productPk, $invoicePk, $orderPk, $invoiceNo, $dateStart, $dateEnd, $qtySigned, $revenueNet, $extRevenueNet);
+ addSlotItem($slotItemsIncl, $productPk, $invoicePk, $orderPk, $invoiceNo, $dateStart, $dateEnd, $qtySigned, $revenueNet, $extRevenueNet);
}
// Debug Row
@@ -517,11 +635,13 @@ function processLineItem(
'title' => $title,
'date_start' => $dateStart ?? '',
'date_end' => $dateEnd ?? '',
- 'qty' => $qty,
+ 'qty' => $qtyRaw,
+ 'qty_signed' => $qtySigned,
'revenue_net' => $revenueNet,
'ext_rev_net' => $extRevenueNet,
'amount_ext' => (float)($li->amount_external ?? 0),
'amount_total'=> (float)($li->amount_total ?? 0),
+ 'is_credit' => $invoiceIsCredit ? 1 : 0,
];
/* ===== Bundle-Auflösung (nur virtuelle Bundles!) ===== */
@@ -549,20 +669,19 @@ function processLineItem(
}
}
- // Auslastung allokieren (Peak/Histogramm inkl Bundle)
- if ($dateStart && $dateEnd && $qty > 0) {
+ // Auslastung allokieren (Peak/Histogramm inkl Bundle) – signed qty!
+ if ($dateStart && $dateEnd && $qtySigned != 0.0) {
foreach ($leafMap as $leafPk => $amt) {
$leafPk = (int)$leafPk;
$amt = (float)$amt;
if ($leafPk <= 0 || $amt <= 0) continue;
ensureMeta($Epi, $leafPk, 0, '');
- addIntervalEvent($eventsIncl, $leafPk, $dateStart, $dateEnd, $qty * $amt);
+ addIntervalEvent($eventsIncl, $leafPk, $dateStart, $dateEnd, $qtySigned * $amt);
}
}
- // Slotting allokieren (inkl Bundle): wir erzeugen SlotItems für Leaf-Produkte
- if ($dateStart && $dateEnd && $qty > 0) {
- // Revenue pro "Bundle-Item" wird in allocateBundleRevenue bereits verteilt.
+ // Slotting allokieren (inkl Bundle) – signed qty!
+ if ($dateStart && $dateEnd && $qtySigned != 0.0) {
$alloc = ($revenueNet != 0.0) ? allocateBundleRevenue($Epi, $productPk, $revenueNet) : [];
$allocExt = ($extRevenueNet != 0.0) ? allocateBundleRevenue($Epi, $productPk, $extRevenueNet) : [];
@@ -574,11 +693,10 @@ function processLineItem(
$leafRev = (float)($alloc[$leafPk] ?? 0.0);
$leafExt = (float)($allocExt[$leafPk] ?? 0.0);
- // qty_leaf = qty * amt
- $qtyLeaf = $qty * $amt;
+ $qtyLeafSigned = $qtySigned * $amt;
ensureMeta($Epi, $leafPk, 0, '');
- addSlotItem($slotItemsIncl, $leafPk, $invoicePk, $invoiceNo, $dateStart, $dateEnd, $qtyLeaf, $leafRev, $leafExt);
+ addSlotItem($slotItemsIncl, $leafPk, $invoicePk, $orderPk, $invoiceNo, $dateStart, $dateEnd, $qtyLeafSigned, $leafRev, $leafExt);
}
}
}
@@ -605,6 +723,10 @@ foreach ($invoiceList as $inv) {
$invoiceDate = (string)($invObj->invoice_date ?? '');
$invoiceYear = yearFromDate($invoiceDate) ?? 0;
$invoiceNo = (int)($invObj->invoice_no ?? 0);
+ $orderPk = (int)($invObj->order_pk ?? 0);
+
+ // Storno-Erkennung (Credit Note): sum_net < 0
+ $invoiceIsCredit = ((float)($invObj->sum_net ?? 0.0)) < 0.0;
$orderItems = $invObj->order_items ?? [];
if (!is_array($orderItems)) $orderItems = [];
@@ -619,14 +741,14 @@ foreach ($invoiceList as $inv) {
$journalItems = getJournalByChapter($Epi, $chapterId);
foreach ($journalItems as $ji) {
- processLineItem($Epi, $rows, $slotItemsDirect, $slotItemsIncl, $invoicePk, $invoiceNo, $invoiceDate, $invoiceYear, $chapterId, $ji, true);
+ processLineItem($Epi, $rows, $slotItemsDirect, $slotItemsIncl, $orderPk, $invoicePk, $invoiceNo, $invoiceDate, $invoiceYear, $chapterId, $ji, true, $invoiceIsCredit);
}
continue;
}
// Direkte Artikelposition ohne Kapitel (type=0)
if ($oiType === 0 && (int)($oi->product_pk ?? 0) > 0) {
- processLineItem($Epi, $rows, $slotItemsDirect, $slotItemsIncl, $invoicePk, $invoiceNo, $invoiceDate, $invoiceYear, null, $oi, false);
+ processLineItem($Epi, $rows, $slotItemsDirect, $slotItemsIncl, $orderPk, $invoicePk, $invoiceNo, $invoiceDate, $invoiceYear, null, $oi, false, $invoiceIsCredit);
continue;
}
}
@@ -717,7 +839,6 @@ foreach ($meta as $productPk => $m) {
$row['hist_direct'] = $pd['hist'];
$row['hist_incl'] = $pi['hist'];
- // nur anzeigen, wenn was los ist
$hasAny = (
$row['total_direct'] != 0.0 ||
$row['total_incl'] != 0.0 ||
@@ -850,13 +971,14 @@ $writer->save($excelFilePath);
========================= */
function slotsToDisplay(array $slotMap): array {
- // slotMap: [slot=>revenue]
ksort($slotMap);
$out = [];
foreach ($slotMap as $slot => $rev) {
$slot = (int)$slot;
if ($slot <= 0) continue;
- $out[] = ['slot'=>$slot, 'rev'=>(float)$rev];
+ $v = (float)$rev;
+ if (abs($v) < 0.0000001) $v = 0.0;
+ $out[] = ['slot'=>$slot, 'rev'=>$v];
}
return $out;
}
@@ -884,11 +1006,9 @@ function slotsToDisplay(array $slotMap): array {