ROI: Neuester Artikelname als Hauptname, ältere Namen als Tooltip
Bei gleicher Artikelnummer (product_pk) kann sich der Name im Laufe der Zeit ändern. Bisher wurde der Name der ERSTEN gefundenen Rechnungszeile festgehalten – jetzt wird pro Produkt eine Titel-Historie mit dem letzten zugehörigen invoice_date gesammelt. In der Pivot-Tabelle erscheint der neueste Name als Hauptname; frühere Namen werden über ein i-Icon neben dem Namen als Hover-Tooltip angezeigt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vendored
+57
-6
@@ -318,7 +318,7 @@ $allYears = [];
|
|||||||
// Auftragsbasierte "Slots" (direct/incl) pro Produkt
|
// Auftragsbasierte "Slots" (direct/incl) pro Produkt
|
||||||
$slotAgg = [];
|
$slotAgg = [];
|
||||||
|
|
||||||
function ensureMeta(Epirent $Epi, int $productPk, int $productNo, string $title): void {
|
function ensureMeta(Epirent $Epi, int $productPk, int $productNo, string $title, string $invoiceDate = ''): void {
|
||||||
global $meta;
|
global $meta;
|
||||||
if (!isset($meta[$productPk])) {
|
if (!isset($meta[$productPk])) {
|
||||||
$p = getProduct($Epi, $productPk);
|
$p = getProduct($Epi, $productPk);
|
||||||
@@ -326,8 +326,19 @@ function ensureMeta(Epirent $Epi, int $productPk, int $productNo, string $title)
|
|||||||
'product_pk' => $productPk,
|
'product_pk' => $productPk,
|
||||||
'product_no' => $productNo ?: (int)($p->product_no ?? 0),
|
'product_no' => $productNo ?: (int)($p->product_no ?? 0),
|
||||||
'title' => $title ?: (string)($p->name ?? ''),
|
'title' => $title ?: (string)($p->name ?? ''),
|
||||||
|
// titles: [title => letztes invoice_date, an dem dieser Titel auftauchte]
|
||||||
|
'titles' => [],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Titel-Historie: nur Titel erfassen, die tatsächlich auf einer Rechnung standen,
|
||||||
|
// damit Umbenennungen (gleiche Artikelnummer, neuer Name) sichtbar bleiben.
|
||||||
|
if ($title !== '' && $invoiceDate !== '') {
|
||||||
|
$prev = $meta[$productPk]['titles'][$title] ?? '';
|
||||||
|
if ($invoiceDate > $prev) {
|
||||||
|
$meta[$productPk]['titles'][$title] = $invoiceDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addRevenue(array &$pivotRef, int $productPk, int $year, float $revenueNet): void {
|
function addRevenue(array &$pivotRef, int $productPk, int $year, float $revenueNet): void {
|
||||||
@@ -617,7 +628,7 @@ function processLineItem(
|
|||||||
$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;
|
||||||
|
|
||||||
ensureMeta($Epi, $productPk, $productNo, $title);
|
ensureMeta($Epi, $productPk, $productNo, $title, $invoiceDate);
|
||||||
|
|
||||||
// Jahr: Rechnungsdatum
|
// Jahr: Rechnungsdatum
|
||||||
$year = $invoiceYear;
|
$year = $invoiceYear;
|
||||||
@@ -828,10 +839,23 @@ $pivotRows = [];
|
|||||||
foreach ($meta as $productPk => $m) {
|
foreach ($meta as $productPk => $m) {
|
||||||
$productPk = (int)$productPk;
|
$productPk = (int)$productPk;
|
||||||
|
|
||||||
|
// Titel-Historie auswerten: neuester Titel als Hauptname, restliche als "früher"-Liste
|
||||||
|
$titlesMap = $m['titles'] ?? [];
|
||||||
|
if (!empty($titlesMap)) {
|
||||||
|
arsort($titlesMap); // nach letztem invoice_date absteigend
|
||||||
|
$orderedTitles = array_keys($titlesMap);
|
||||||
|
$newestTitle = (string)$orderedTitles[0];
|
||||||
|
$olderTitles = array_slice($orderedTitles, 1);
|
||||||
|
} else {
|
||||||
|
$newestTitle = (string)($m['title'] ?? '');
|
||||||
|
$olderTitles = [];
|
||||||
|
}
|
||||||
|
|
||||||
$row = [
|
$row = [
|
||||||
'product_pk' => $productPk,
|
'product_pk' => $productPk,
|
||||||
'product_no' => (int)($m['product_no'] ?? 0),
|
'product_no' => (int)($m['product_no'] ?? 0),
|
||||||
'title' => (string)($m['title'] ?? ''),
|
'title' => $newestTitle,
|
||||||
|
'older_titles' => $olderTitles,
|
||||||
'years' => [],
|
'years' => [],
|
||||||
'years_ext' => [],
|
'years_ext' => [],
|
||||||
'total_direct'=> 0.0,
|
'total_direct'=> 0.0,
|
||||||
@@ -1179,7 +1203,22 @@ function slotsToDisplay(array $slotMap): array {
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="mono"><?php echo (int)$r['product_pk']; ?></td>
|
<td class="mono"><?php echo (int)$r['product_pk']; ?></td>
|
||||||
<td class="mono"><?php echo (int)$r['product_no']; ?></td>
|
<td class="mono"><?php echo (int)$r['product_no']; ?></td>
|
||||||
<td><?php echo htmlspecialchars((string)$r['title']); ?></td>
|
<td>
|
||||||
|
<?php echo htmlspecialchars((string)$r['title']); ?>
|
||||||
|
<?php if (!empty($r['older_titles'])):
|
||||||
|
$ttHtml = 'Frühere Namen:<br>• ' .
|
||||||
|
implode('<br>• ', array_map(
|
||||||
|
fn($t) => htmlspecialchars((string)$t, ENT_QUOTES),
|
||||||
|
$r['older_titles']
|
||||||
|
));
|
||||||
|
?>
|
||||||
|
<i class="fas fa-info-circle text-muted ml-1 js-name-history"
|
||||||
|
style="cursor: help;"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
data-html="true"
|
||||||
|
title="<?php echo htmlspecialchars($ttHtml, ENT_QUOTES); ?>"></i>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td class="nowrap">
|
<td class="nowrap">
|
||||||
<?php echo $peakDirect; ?>
|
<?php echo $peakDirect; ?>
|
||||||
@@ -1499,11 +1538,23 @@ function openSlots(title, payload) {
|
|||||||
$('#slotsModal').modal('show');
|
$('#slotsModal').modal('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function attachTooltips(context) {
|
||||||
|
$(context || document).find('[data-toggle="tooltip"]').each(function() {
|
||||||
|
if (!$(this).data('bs.tooltip')) {
|
||||||
|
$(this).tooltip({ container: 'body', html: true, boundary: 'window' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
// scrollX: DataTables kümmert sich selbst um horizontales Scrollen der Tabelle,
|
// scrollX: DataTables kümmert sich selbst um horizontales Scrollen der Tabelle,
|
||||||
// damit Search/Length/Pagination NICHT mit dem Scrollbalken mitwandern.
|
// damit Search/Length/Pagination NICHT mit dem Scrollbalken mitwandern.
|
||||||
$('#pivotTable').DataTable({ pageLength: 50, order: [[4,'desc']], scrollX: true });
|
const pivotDT = $('#pivotTable').DataTable({ pageLength: 50, order: [[4,'desc']], scrollX: true });
|
||||||
$('#detailTable').DataTable({ pageLength: 50, order: [[1,'desc']], scrollX: true });
|
const detailDT = $('#detailTable').DataTable({ pageLength: 50, order: [[1,'desc']], scrollX: true });
|
||||||
|
|
||||||
|
// Tooltips (z.B. für Namens-Historie) auch nach Pagination/Sortierung erneut anhängen
|
||||||
|
pivotDT.on('draw', () => attachTooltips('#pivotTable'));
|
||||||
|
attachTooltips();
|
||||||
|
|
||||||
$(document).on('click', '.js-hist', function() {
|
$(document).on('click', '.js-hist', function() {
|
||||||
const title = $(this).data('title') || '';
|
const title = $(this).data('title') || '';
|
||||||
|
|||||||
Reference in New Issue
Block a user