ROI: Stornierungen klammern jetzt die Zugehörige Buchung aus
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# EPIWebview
|
# 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)
|
- **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**
|
- **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
|
- **Check-In / Check-Out Übersicht**: Lagermonitor
|
||||||
- **Integration mit Epirent API**: Vollständig kompatibel mit bestehenden Epirent-Systemen.
|
- **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
|
## Systemanforderungen
|
||||||
|
|
||||||
- **Server:** PHP ≥ 8.2, Apache oder Nginx
|
- **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)
|
- **Client:** Aktueller Browser (Chrome, Edge, Firefox, Safari)
|
||||||
- **Datenquelle:** Bestehende Epirent-Installation mit aktivierter API sowie optional CrewBrain
|
- **Datenquelle:** Bestehende Epirent-Installation mit aktivierter API sowie optional CrewBrain
|
||||||
|
|
||||||
|
|||||||
382
dist/ROI.php
vendored
382
dist/ROI.php
vendored
@@ -94,12 +94,7 @@ function getRentPrice(Epirent $Epi, int $productPk): float {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* WICHTIG: Bundle-Auflösung NUR für virtuelle Bundles.
|
* WICHTIG: Bundle-Auflösung NUR für virtuelle Bundles.
|
||||||
* Damit ist die anteilige Bundlepreis-Berechnung wieder wie in der funktionierenden Version.
|
|
||||||
*
|
|
||||||
* Ergebnis: [leafProductPk => amount]
|
* 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 {
|
function resolveBundleLeafMap(Epirent $Epi, int $productPk, array &$stack = []): array {
|
||||||
global $bundleLeafCache;
|
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[productPk][ymd] += deltaQty
|
||||||
$eventsDirect = [];
|
|
||||||
$eventsIncl = []; // direct + bundle
|
$eventsIncl = []; // direct + bundle
|
||||||
|
|
||||||
function addIntervalEvent(array &$events, int $productPk, string $startYmd, string $endYmd, float $qty): void {
|
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++) {
|
for ($i = 0; $i < count($dates); $i++) {
|
||||||
$d = $dates[$i];
|
$d = $dates[$i];
|
||||||
$level += (float)$eventMap[$d];
|
$level += (float)$eventMap[$d];
|
||||||
|
|
||||||
|
// clamp, damit kein negativer Bestand "Tage" erzeugt
|
||||||
if ($level < 0) $level = 0;
|
if ($level < 0) $level = 0;
|
||||||
|
|
||||||
$intLevel = (int)round($level);
|
$intLevel = (int)round($level);
|
||||||
@@ -300,10 +296,6 @@ $meta = []; // product meta [pk=>['product_no'=>..,'title'=>..]]
|
|||||||
$allYears = [];
|
$allYears = [];
|
||||||
|
|
||||||
// Auftragsbasierte "Slots" (direct/incl) pro Produkt
|
// 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 = [];
|
$slotAgg = [];
|
||||||
|
|
||||||
function ensureMeta(Epirent $Epi, int $productPk, int $productNo, string $title): void {
|
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:
|
* Extern-Logik:
|
||||||
* - Amount External ist eine ANZAHL (nicht Umsatz).
|
* - Amount External ist eine ANZAHL (nicht Umsatz).
|
||||||
* - Wir berechnen extRevenue = sum_total_net * (amount_external / amount_total) (wenn amount_total>0)
|
* - Wir berechnen extRevenue = sum_total_net * (amount_external / amount_total) (wenn amount_total>0)
|
||||||
* - fallback: 0
|
|
||||||
*/
|
*/
|
||||||
function calcExternalRevenueNet(object $li): float {
|
function calcExternalRevenueNet(object $li): float {
|
||||||
$amountTotal = (float)($li->amount_total ?? 0);
|
$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)
|
* 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);
|
$startYmd = safeYmd($startYmd);
|
||||||
$endYmd = safeYmd($endYmd);
|
$endYmd = safeYmd($endYmd);
|
||||||
if ($productPk <= 0) return;
|
if ($productPk <= 0) return;
|
||||||
if (!$startYmd || !$endYmd) return;
|
if (!$startYmd || !$endYmd) return;
|
||||||
if ($qty <= 0) return;
|
if ($qty == 0.0) return;
|
||||||
|
|
||||||
|
if (!isset($slotItems[$productPk])) $slotItems[$productPk] = [];
|
||||||
|
|
||||||
$slotItems[$productPk][] = [
|
$slotItems[$productPk][] = [
|
||||||
'invoice_pk' => $invoicePk,
|
'product_pk' => $productPk,
|
||||||
'order_no' => $orderNo,
|
'invoice_pk' => $invoicePk,
|
||||||
'start_ts' => ymdToTs($startYmd),
|
'order_pk' => $orderPk,
|
||||||
'end_ts' => ymdToTs($endYmd),
|
'invoice_no' => $invoiceNo,
|
||||||
'qty' => (float)$qty,
|
'start_ts' => ymdToTs($startYmd),
|
||||||
'rev' => (float)$revenueNet,
|
'end_ts' => ymdToTs($endYmd),
|
||||||
'rev_ext' => (float)$extRevenueNet,
|
'qty' => (float)$qty, // signed erlaubt
|
||||||
'start_ymd' => $startYmd,
|
'rev' => (float)$revenueNet,
|
||||||
'end_ymd' => $endYmd,
|
'rev_ext' => (float)$extRevenueNet,
|
||||||
|
'start_ymd' => $startYmd,
|
||||||
|
'end_ymd' => $endYmd,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slotting-Regel:
|
* Slotting (SIGNED, Storno gibt Slots wieder frei):
|
||||||
* - Aufträge werden nach start_ts sortiert,
|
* - Positive qty belegt Slots.
|
||||||
* - bei gleichem start: zuerst der der später endet bekommt den "zweiten" Slot (also: end_ts DESC),
|
* - Negative qty bucht Umsatz zurück UND gibt Slots frei.
|
||||||
* - wenn dann noch gleich: nach order_no (oder invoice_pk) ASC.
|
* - Priorität bei Storno: zuerst Slots mit gleichem Zeitraum (gleicher release_ts) abbauen.
|
||||||
* - Belegung: für qty=2 werden 2 Slots gesucht; wenn Slots frei werden (end < start), wiederverwendbar.
|
|
||||||
*
|
*
|
||||||
* Umsatzverteilung:
|
* Ergebnis:
|
||||||
* - pro Auftrag & Produkt wird der UMSATZ auf die belegten Slots gleichmäßig verteilt (rev/qty)
|
* - 'direct' => [slot => revenue]
|
||||||
* - analog für extern-umsatz.
|
* - 'direct_ext' => [slot => extRevenue]
|
||||||
*/
|
*/
|
||||||
function computeOrderBasedSlots(array $itemsForProduct): array {
|
function computeOrderBasedSlots(array $itemsForProduct): array {
|
||||||
if (empty($itemsForProduct)) return [
|
if (empty($itemsForProduct)) {
|
||||||
'direct' => [],
|
return ['direct' => [], 'direct_ext' => []];
|
||||||
'direct_ext' => []
|
}
|
||||||
];
|
|
||||||
|
|
||||||
|
// Sortierung: start ASC, end DESC, invoice_no ASC, invoice_pk ASC
|
||||||
usort($itemsForProduct, function($a, $b){
|
usort($itemsForProduct, function($a, $b){
|
||||||
if ($a['start_ts'] !== $b['start_ts']) return $a['start_ts'] <=> $b['start_ts'];
|
if (($a['start_ts'] ?? 0) !== ($b['start_ts'] ?? 0)) return ($a['start_ts'] ?? 0) <=> ($b['start_ts'] ?? 0);
|
||||||
if ($a['end_ts'] !== $b['end_ts']) return $b['end_ts'] <=> $a['end_ts']; // später endend zuerst
|
if (($a['end_ts'] ?? 0) !== ($b['end_ts'] ?? 0)) return ($b['end_ts'] ?? 0) <=> ($a['end_ts'] ?? 0);
|
||||||
if ($a['order_no'] !== $b['order_no']) return $a['order_no'] <=> $b['order_no'];
|
if (($a['invoice_no'] ?? 0) !== ($b['invoice_no'] ?? 0)) return ($a['invoice_no'] ?? 0) <=> ($b['invoice_no'] ?? 0);
|
||||||
return $a['invoice_pk'] <=> $b['invoice_pk'];
|
return ($a['invoice_pk'] ?? 0) <=> ($b['invoice_pk'] ?? 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
$slotEnd = []; // slotIndex => end_ts
|
// Active slots: slotIndex => release_ts
|
||||||
$slotRevenue = []; // slotIndex => revenue
|
$active = [];
|
||||||
$slotRevenueExt = []; // slotIndex => revenueExt
|
// 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) {
|
foreach ($itemsForProduct as $it) {
|
||||||
$qty = (int)round($it['qty']);
|
$startTs = (int)($it['start_ts'] ?? 0);
|
||||||
if ($qty <= 0) continue;
|
$endTs = (int)($it['end_ts'] ?? 0);
|
||||||
|
if ($startTs <= 0 || $endTs <= 0) continue;
|
||||||
|
if ($endTs < $startTs) continue;
|
||||||
|
|
||||||
$revPer = ($it['rev'] ?? 0.0) / $qty;
|
// vor jeder Aktion: abgelaufene Slots freigeben
|
||||||
$extPer = ($it['rev_ext'] ?? 0.0) / $qty;
|
$freeExpired($startTs);
|
||||||
|
|
||||||
// finde freie Slots / neue Slots
|
$qtySigned = (float)($it['qty'] ?? 0.0);
|
||||||
$assigned = [];
|
if ($qtySigned == 0.0) continue;
|
||||||
for ($k=0; $k<$qty; $k++) {
|
|
||||||
$slot = null;
|
|
||||||
|
|
||||||
// freier Slot: end < start
|
$qtyAbs = (int)round(abs($qtySigned));
|
||||||
foreach ($slotEnd as $idx => $endTs) {
|
if ($qtyAbs <= 0) continue;
|
||||||
if ($endTs < $it['start_ts']) { // streng <, damit gleicher Tag als parallel zählt
|
|
||||||
$slot = (int)$idx;
|
$rev = (float)($it['rev'] ?? 0.0);
|
||||||
break;
|
$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
|
if ($slot === null) break;
|
||||||
$slotEnd[$slot] = $it['end_ts'];
|
|
||||||
$assigned[] = $slot;
|
|
||||||
|
|
||||||
// Revenue sammeln
|
// Umsatz auf Slot zurückbuchen
|
||||||
if (!isset($slotRevenue[$slot])) $slotRevenue[$slot] = 0.0;
|
if (!isset($slotRevenue[$slot])) $slotRevenue[$slot] = 0.0;
|
||||||
if (!isset($slotRevenueExt[$slot])) $slotRevenueExt[$slot] = 0.0;
|
if (!isset($slotRevenueExt[$slot])) $slotRevenueExt[$slot] = 0.0;
|
||||||
$slotRevenue[$slot] += $revPer;
|
$slotRevenue[$slot] += $revPer; // revPer ist i.d.R. negativ
|
||||||
$slotRevenueExt[$slot] += $extPer;
|
$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);
|
// --- Cleanup: Slots ohne Umsatz entfernen + neu durchnummerieren ---
|
||||||
ksort($slotRevenueExt);
|
$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)
|
$kept = [];
|
||||||
return [
|
foreach ($allSlots as $s) {
|
||||||
'direct' => $slotRevenue,
|
$rev = (float)($slotRevenue[$s] ?? 0.0);
|
||||||
'direct_ext' => $slotRevenueExt
|
$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 &$rows,
|
||||||
array &$slotItemsDirect,
|
array &$slotItemsDirect,
|
||||||
array &$slotItemsIncl,
|
array &$slotItemsIncl,
|
||||||
|
int $orderPk,
|
||||||
int $invoicePk,
|
int $invoicePk,
|
||||||
int $invoiceNo,
|
int $invoiceNo,
|
||||||
string $invoiceDate,
|
string $invoiceDate,
|
||||||
int $invoiceYear,
|
int $invoiceYear,
|
||||||
?int $chapterId,
|
?int $chapterId,
|
||||||
object $li,
|
object $li,
|
||||||
bool $isFromJournal
|
bool $isFromJournal,
|
||||||
|
bool $invoiceIsCredit // <-- wichtig: Storno-Rechnung (z.B. sum_net < 0)
|
||||||
): void {
|
): void {
|
||||||
global $pivot, $pivotB, $pivotExt, $pivotExtB, $allYears, $eventsDirect, $eventsIncl;
|
global $pivot, $pivotB, $pivotExt, $pivotExtB, $allYears, $eventsDirect, $eventsIncl;
|
||||||
|
|
||||||
@@ -471,15 +580,23 @@ function processLineItem(
|
|||||||
$title = (string)($li->title ?? '');
|
$title = (string)($li->title ?? '');
|
||||||
$productNo = (int)($li->product_no ?? 0);
|
$productNo = (int)($li->product_no ?? 0);
|
||||||
|
|
||||||
$qty = (float)($li->amount_total ?? 0);
|
$qtyRaw = (float)($li->amount_total ?? 0);
|
||||||
if ($qty <= 0) $qty = 0.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;
|
$dateStart = safeYmd((string)($li->date_start ?? '')) ?: null;
|
||||||
$dateEnd = safeYmd((string)($li->date_end ?? '')) ?: null;
|
$dateEnd = safeYmd((string)($li->date_end ?? '')) ?: null;
|
||||||
|
|
||||||
$revenueNet = (float)($li->sum_total_net ?? 0);
|
|
||||||
$extRevenueNet = calcExternalRevenueNet($li);
|
|
||||||
|
|
||||||
ensureMeta($Epi, $productPk, $productNo, $title);
|
ensureMeta($Epi, $productPk, $productNo, $title);
|
||||||
|
|
||||||
// Jahr: Rechnungsdatum
|
// Jahr: Rechnungsdatum
|
||||||
@@ -493,15 +610,16 @@ function processLineItem(
|
|||||||
if ($extRevenueNet != 0.0 && $year) addRevenue($pivotExt, $productPk, $year, $extRevenueNet);
|
if ($extRevenueNet != 0.0 && $year) addRevenue($pivotExt, $productPk, $year, $extRevenueNet);
|
||||||
|
|
||||||
// Peak/Histogram direct + incl (tagesbasiert)
|
// Peak/Histogram direct + incl (tagesbasiert)
|
||||||
if ($dateStart && $dateEnd && $qty > 0) {
|
// -> qtySigned sorgt dafür, dass Storno zeitgleich aufhebt
|
||||||
addIntervalEvent($eventsDirect, $productPk, $dateStart, $dateEnd, $qty);
|
if ($dateStart && $dateEnd && $qtySigned != 0.0) {
|
||||||
addIntervalEvent($eventsIncl, $productPk, $dateStart, $dateEnd, $qty);
|
addIntervalEvent($eventsDirect, $productPk, $dateStart, $dateEnd, $qtySigned);
|
||||||
|
addIntervalEvent($eventsIncl, $productPk, $dateStart, $dateEnd, $qtySigned);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slotting: direct
|
// Slotting: direct (signed qty)
|
||||||
if ($dateStart && $dateEnd && $qty > 0) {
|
if ($dateStart && $dateEnd && $qtySigned != 0.0) {
|
||||||
addSlotItem($slotItemsDirect, $productPk, $invoicePk, $invoiceNo, $dateStart, $dateEnd, $qty, $revenueNet, $extRevenueNet);
|
addSlotItem($slotItemsDirect, $productPk, $invoicePk, $orderPk, $invoiceNo, $dateStart, $dateEnd, $qtySigned, $revenueNet, $extRevenueNet);
|
||||||
addSlotItem($slotItemsIncl, $productPk, $invoicePk, $invoiceNo, $dateStart, $dateEnd, $qty, $revenueNet, $extRevenueNet);
|
addSlotItem($slotItemsIncl, $productPk, $invoicePk, $orderPk, $invoiceNo, $dateStart, $dateEnd, $qtySigned, $revenueNet, $extRevenueNet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug Row
|
// Debug Row
|
||||||
@@ -517,11 +635,13 @@ function processLineItem(
|
|||||||
'title' => $title,
|
'title' => $title,
|
||||||
'date_start' => $dateStart ?? '',
|
'date_start' => $dateStart ?? '',
|
||||||
'date_end' => $dateEnd ?? '',
|
'date_end' => $dateEnd ?? '',
|
||||||
'qty' => $qty,
|
'qty' => $qtyRaw,
|
||||||
|
'qty_signed' => $qtySigned,
|
||||||
'revenue_net' => $revenueNet,
|
'revenue_net' => $revenueNet,
|
||||||
'ext_rev_net' => $extRevenueNet,
|
'ext_rev_net' => $extRevenueNet,
|
||||||
'amount_ext' => (float)($li->amount_external ?? 0),
|
'amount_ext' => (float)($li->amount_external ?? 0),
|
||||||
'amount_total'=> (float)($li->amount_total ?? 0),
|
'amount_total'=> (float)($li->amount_total ?? 0),
|
||||||
|
'is_credit' => $invoiceIsCredit ? 1 : 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
/* ===== Bundle-Auflösung (nur virtuelle Bundles!) ===== */
|
/* ===== Bundle-Auflösung (nur virtuelle Bundles!) ===== */
|
||||||
@@ -549,20 +669,19 @@ function processLineItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auslastung allokieren (Peak/Histogramm inkl Bundle)
|
// Auslastung allokieren (Peak/Histogramm inkl Bundle) – signed qty!
|
||||||
if ($dateStart && $dateEnd && $qty > 0) {
|
if ($dateStart && $dateEnd && $qtySigned != 0.0) {
|
||||||
foreach ($leafMap as $leafPk => $amt) {
|
foreach ($leafMap as $leafPk => $amt) {
|
||||||
$leafPk = (int)$leafPk;
|
$leafPk = (int)$leafPk;
|
||||||
$amt = (float)$amt;
|
$amt = (float)$amt;
|
||||||
if ($leafPk <= 0 || $amt <= 0) continue;
|
if ($leafPk <= 0 || $amt <= 0) continue;
|
||||||
ensureMeta($Epi, $leafPk, 0, '');
|
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
|
// Slotting allokieren (inkl Bundle) – signed qty!
|
||||||
if ($dateStart && $dateEnd && $qty > 0) {
|
if ($dateStart && $dateEnd && $qtySigned != 0.0) {
|
||||||
// Revenue pro "Bundle-Item" wird in allocateBundleRevenue bereits verteilt.
|
|
||||||
$alloc = ($revenueNet != 0.0) ? allocateBundleRevenue($Epi, $productPk, $revenueNet) : [];
|
$alloc = ($revenueNet != 0.0) ? allocateBundleRevenue($Epi, $productPk, $revenueNet) : [];
|
||||||
$allocExt = ($extRevenueNet != 0.0) ? allocateBundleRevenue($Epi, $productPk, $extRevenueNet) : [];
|
$allocExt = ($extRevenueNet != 0.0) ? allocateBundleRevenue($Epi, $productPk, $extRevenueNet) : [];
|
||||||
|
|
||||||
@@ -574,11 +693,10 @@ function processLineItem(
|
|||||||
$leafRev = (float)($alloc[$leafPk] ?? 0.0);
|
$leafRev = (float)($alloc[$leafPk] ?? 0.0);
|
||||||
$leafExt = (float)($allocExt[$leafPk] ?? 0.0);
|
$leafExt = (float)($allocExt[$leafPk] ?? 0.0);
|
||||||
|
|
||||||
// qty_leaf = qty * amt
|
$qtyLeafSigned = $qtySigned * $amt;
|
||||||
$qtyLeaf = $qty * $amt;
|
|
||||||
|
|
||||||
ensureMeta($Epi, $leafPk, 0, '');
|
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 ?? '');
|
$invoiceDate = (string)($invObj->invoice_date ?? '');
|
||||||
$invoiceYear = yearFromDate($invoiceDate) ?? 0;
|
$invoiceYear = yearFromDate($invoiceDate) ?? 0;
|
||||||
$invoiceNo = (int)($invObj->invoice_no ?? 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 ?? [];
|
$orderItems = $invObj->order_items ?? [];
|
||||||
if (!is_array($orderItems)) $orderItems = [];
|
if (!is_array($orderItems)) $orderItems = [];
|
||||||
@@ -619,14 +741,14 @@ foreach ($invoiceList as $inv) {
|
|||||||
$journalItems = getJournalByChapter($Epi, $chapterId);
|
$journalItems = getJournalByChapter($Epi, $chapterId);
|
||||||
|
|
||||||
foreach ($journalItems as $ji) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direkte Artikelposition ohne Kapitel (type=0)
|
// Direkte Artikelposition ohne Kapitel (type=0)
|
||||||
if ($oiType === 0 && (int)($oi->product_pk ?? 0) > 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;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -717,7 +839,6 @@ foreach ($meta as $productPk => $m) {
|
|||||||
$row['hist_direct'] = $pd['hist'];
|
$row['hist_direct'] = $pd['hist'];
|
||||||
$row['hist_incl'] = $pi['hist'];
|
$row['hist_incl'] = $pi['hist'];
|
||||||
|
|
||||||
// nur anzeigen, wenn was los ist
|
|
||||||
$hasAny = (
|
$hasAny = (
|
||||||
$row['total_direct'] != 0.0 ||
|
$row['total_direct'] != 0.0 ||
|
||||||
$row['total_incl'] != 0.0 ||
|
$row['total_incl'] != 0.0 ||
|
||||||
@@ -850,13 +971,14 @@ $writer->save($excelFilePath);
|
|||||||
========================= */
|
========================= */
|
||||||
|
|
||||||
function slotsToDisplay(array $slotMap): array {
|
function slotsToDisplay(array $slotMap): array {
|
||||||
// slotMap: [slot=>revenue]
|
|
||||||
ksort($slotMap);
|
ksort($slotMap);
|
||||||
$out = [];
|
$out = [];
|
||||||
foreach ($slotMap as $slot => $rev) {
|
foreach ($slotMap as $slot => $rev) {
|
||||||
$slot = (int)$slot;
|
$slot = (int)$slot;
|
||||||
if ($slot <= 0) continue;
|
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;
|
return $out;
|
||||||
}
|
}
|
||||||
@@ -884,11 +1006,9 @@ function slotsToDisplay(array $slotMap): array {
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.kpi-updated { font-size: .82rem; opacity: .85; }
|
|
||||||
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
||||||
.btn-xs { padding:.15rem .4rem; font-size:.78rem; }
|
.btn-xs { padding:.15rem .4rem; font-size:.78rem; }
|
||||||
.nowrap { white-space: nowrap; }
|
.nowrap { white-space: nowrap; }
|
||||||
.small-muted { font-size: .82rem; opacity:.85; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -974,37 +1094,28 @@ function slotsToDisplay(array $slotMap): array {
|
|||||||
<td><?php echo htmlspecialchars((string)$r['title']); ?></td>
|
<td><?php echo htmlspecialchars((string)$r['title']); ?></td>
|
||||||
|
|
||||||
<td class="nowrap">
|
<td class="nowrap">
|
||||||
<?php
|
<?php echo $peakDirect; ?>
|
||||||
echo $peakDirect;
|
<span class="text-muted">(<?php echo $peakIncl; ?>)</span>
|
||||||
echo ' <span class="text-muted">(' . $peakIncl . ')</span>';
|
|
||||||
?>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="nowrap">
|
<td class="nowrap">
|
||||||
<?php
|
<?php echo formatEuro($sumDirect); ?>
|
||||||
echo formatEuro($sumDirect);
|
<span class="text-muted">(<?php echo formatEuro($sumIncl); ?>)</span>
|
||||||
echo ' <span class="text-muted">(' . formatEuro($sumIncl) . ')</span>';
|
|
||||||
?>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="nowrap">
|
<td class="nowrap">
|
||||||
<?php
|
<?php echo formatEuro($extDirect); ?>
|
||||||
echo formatEuro($extDirect);
|
<span class="text-muted">(<?php echo formatEuro($extIncl); ?>)</span>
|
||||||
echo ' <span class="text-muted">(' . formatEuro($extIncl) . ')</span>';
|
|
||||||
?>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<?php foreach ($years as $y): ?>
|
<?php foreach ($years as $y): ?>
|
||||||
<?php
|
<?php
|
||||||
$d = (float)$r['years'][$y]['direct'];
|
$d = (float)$r['years'][$y]['direct'];
|
||||||
$iVal = (float)$r['years'][$y]['incl'];
|
$iVal = (float)$r['years'][$y]['incl'];
|
||||||
|
|
||||||
$de = (float)$r['years_ext'][$y]['direct'];
|
|
||||||
$ie = (float)$r['years_ext'][$y]['incl'];
|
|
||||||
?>
|
?>
|
||||||
<td class="nowrap">
|
<td class="nowrap">
|
||||||
<div><?php echo formatEuro($d); ?> <span class="text-muted">(<?php echo formatEuro($iVal); ?>)</span></div>
|
<?php echo formatEuro($d); ?>
|
||||||
<!--<div class="small-muted">ext: <?php echo formatEuro($de); ?> <span class="text-muted">(<?php echo formatEuro($ie); ?>)</span></div>-->
|
<span class="text-muted">(<?php echo formatEuro($iVal); ?>)</span>
|
||||||
</td>
|
</td>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
@@ -1024,7 +1135,7 @@ function slotsToDisplay(array $slotMap): array {
|
|||||||
data-hist="<?php echo htmlspecialchars($histInclB64); ?>"
|
data-hist="<?php echo htmlspecialchars($histInclB64); ?>"
|
||||||
data-peak="<?php echo (int)$peakIncl; ?>"
|
data-peak="<?php echo (int)$peakIncl; ?>"
|
||||||
data-mode="incl">
|
data-mode="incl">
|
||||||
Histogramm inkl. Bundle
|
inkl. Bundle
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@@ -1033,7 +1144,7 @@ function slotsToDisplay(array $slotMap): array {
|
|||||||
class="btn btn-outline-dark btn-xs js-slots"
|
class="btn btn-outline-dark btn-xs js-slots"
|
||||||
data-title="<?php echo htmlspecialchars((string)$r['title']); ?>"
|
data-title="<?php echo htmlspecialchars((string)$r['title']); ?>"
|
||||||
data-slots="<?php echo htmlspecialchars($slotsB64); ?>">
|
data-slots="<?php echo htmlspecialchars($slotsB64); ?>">
|
||||||
Slots (Auftragsbasiert)
|
Slots
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1045,8 +1156,9 @@ function slotsToDisplay(array $slotMap): array {
|
|||||||
Jahr-Zuordnung Umsatz: <span class="mono">invoice_date</span>.
|
Jahr-Zuordnung Umsatz: <span class="mono">invoice_date</span>.
|
||||||
Peak/Histogramm: Zeitraum <span class="mono">date_start..date_end</span> (tagesbasiert).
|
Peak/Histogramm: Zeitraum <span class="mono">date_start..date_end</span> (tagesbasiert).
|
||||||
Werte in Klammern = inkl. Bundle-Anteil (nur <b>virtuelle</b> Bundles werden zerlegt).
|
Werte in Klammern = inkl. Bundle-Anteil (nur <b>virtuelle</b> Bundles werden zerlegt).
|
||||||
Extern-Umsatz: <span class="mono">sum_total_net * (amount_external/amount_total)</span> (amount_external ist Menge).
|
Extern-Umsatz: <span class="mono">sum_total_net * (amount_external/amount_total)</span>.
|
||||||
Slots: auftragsbasierte Slotbelegung nach Start, dann Ende DESC, dann Auftragsnr.
|
Slots: auftragsbasiert, SIGNED (Storno gibt Slots frei und bucht auf die gleichen Zeiträume zurück).
|
||||||
|
Storno-Erkennung: Rechnung mit <span class="mono">sum_net < 0</span>.
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1073,9 +1185,11 @@ function slotsToDisplay(array $slotMap): array {
|
|||||||
<th>Start</th>
|
<th>Start</th>
|
||||||
<th>Ende</th>
|
<th>Ende</th>
|
||||||
<th>Menge</th>
|
<th>Menge</th>
|
||||||
|
<th>Menge (signed)</th>
|
||||||
<th>Ext-Menge</th>
|
<th>Ext-Menge</th>
|
||||||
<th>Umsatz netto</th>
|
<th>Umsatz netto</th>
|
||||||
<th>Umsatz extern</th>
|
<th>Umsatz extern</th>
|
||||||
|
<th>Credit</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -1093,16 +1207,18 @@ function slotsToDisplay(array $slotMap): array {
|
|||||||
<td class="mono"><?php echo htmlspecialchars((string)$r['date_start']); ?></td>
|
<td class="mono"><?php echo htmlspecialchars((string)$r['date_start']); ?></td>
|
||||||
<td class="mono"><?php echo htmlspecialchars((string)$r['date_end']); ?></td>
|
<td class="mono"><?php echo htmlspecialchars((string)$r['date_end']); ?></td>
|
||||||
<td class="mono"><?php echo htmlspecialchars((string)$r['qty']); ?></td>
|
<td class="mono"><?php echo htmlspecialchars((string)$r['qty']); ?></td>
|
||||||
|
<td class="mono"><?php echo htmlspecialchars((string)($r['qty_signed'] ?? $r['qty'])); ?></td>
|
||||||
<td class="mono"><?php echo htmlspecialchars((string)($r['amount_ext'] ?? '0')); ?></td>
|
<td class="mono"><?php echo htmlspecialchars((string)($r['amount_ext'] ?? '0')); ?></td>
|
||||||
<td class="nowrap"><?php echo formatEuro((float)$r['revenue_net']); ?></td>
|
<td class="nowrap"><?php echo formatEuro((float)$r['revenue_net']); ?></td>
|
||||||
<td class="nowrap"><?php echo formatEuro((float)$r['ext_rev_net']); ?></td>
|
<td class="nowrap"><?php echo formatEuro((float)$r['ext_rev_net']); ?></td>
|
||||||
|
<td class="mono"><?php echo (int)($r['is_credit'] ?? 0); ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
Wenn ein Artikel “ohne Kapitel” fehlt, muss er hier als Quelle <b>direct</b> auftauchen.
|
|
||||||
Ext-Menge ist Anzahl; Umsatz extern ist anteilig berechnet.
|
Ext-Menge ist Anzahl; Umsatz extern ist anteilig berechnet.
|
||||||
|
Menge(signed) ist für Peak/Slots relevant (Storno-Rechnung -> negativ).
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1127,7 +1243,7 @@ function slotsToDisplay(array $slotMap): array {
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<canvas id="histChart" height="140"></canvas>
|
<canvas id="histChart" height="140"></canvas>
|
||||||
<div class="mt-2 text-muted">
|
<div class="mt-2 text-muted">
|
||||||
X-Achse: gleichzeitig vermietete Stückzahl (1..Peak) | Y-Achse: Tage
|
X-Achse: gleichzeitig vermietete Stückzahl (1..Peak) | Y-Achse: Tage
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1148,6 +1264,7 @@ function slotsToDisplay(array $slotMap): array {
|
|||||||
<div class="mb-2 text-muted">
|
<div class="mb-2 text-muted">
|
||||||
Slot-Umsatz ist <b>auftragsbasiert</b>: Umsatz einer Position wird gleichmäßig auf die belegten Slots verteilt.
|
Slot-Umsatz ist <b>auftragsbasiert</b>: Umsatz einer Position wird gleichmäßig auf die belegten Slots verteilt.
|
||||||
Werte in Klammern = inkl. Bundle-Anteil (nur virtuelle Bundles).
|
Werte in Klammern = inkl. Bundle-Anteil (nur virtuelle Bundles).
|
||||||
|
Storno bucht Umsatz auf die gleichen Slots zurück und gibt die Slots frei (SIGNED).
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -1228,7 +1345,6 @@ function openSlots(title, payload) {
|
|||||||
const directExt = payload.direct_ext || [];
|
const directExt = payload.direct_ext || [];
|
||||||
const inclExt = payload.incl_ext || [];
|
const inclExt = payload.incl_ext || [];
|
||||||
|
|
||||||
// maps slot->rev
|
|
||||||
const mapD = {};
|
const mapD = {};
|
||||||
const mapI = {};
|
const mapI = {};
|
||||||
const mapDE = {};
|
const mapDE = {};
|
||||||
@@ -1289,9 +1405,7 @@ function openSlots(title, payload) {
|
|||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
animation: false,
|
animation: false,
|
||||||
scales: {
|
scales: { y: { beginAtZero: true } }
|
||||||
y: { beginAtZero: true }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1321,4 +1435,4 @@ $(function() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user