Files
Mitgliederverwaltung/lib/Service/InventoryReportService.php
T
shahondin1624 53b3fd945a
Database Portability Tests / Integration (mysql) (push) Has been skipped
Database Portability Tests / Integration (postgres) (push) Has been skipped
Database Portability Tests / Integration (sqlite) (push) Has been skipped
Database Portability Tests / Verify no MySQL-specific SQL (push) Successful in 5s
Database Portability Tests / Unit Tests (PlatformHelper) (push) Failing after 43s
Release v0.3.2: Inventory tracking, test infra, and bugfixes
Inventory:
- General material, stock items, and sales CRUD
- Category management with CategoryPicker component
- Inventory reports service
- Database migrations and mappers

Frontend:
- Inventory view with tabs (Allgemeinmaterial, Verkaufsmaterial, Verkäufe)
- Forms for general material, stock items, and sales
- Search bar fix: flexbox wrapper with inline icon replaces broken NcTextField icon slot

Tests:
- All 1,491 tests pass (zero errors, warnings, deprecations)
- BundleImportServiceTest: fixed ZipArchive empty-file deprecation
- BundleImportService: null-coalescing for targetFields
- PHPUnit test infra: make test target via container

CLAUDE.md:
- Added Testing section as PR gating criterion
2026-04-22 10:15:22 +02:00

172 lines
6.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace OCA\Mitgliederverwaltung\Service;
use OCA\Mitgliederverwaltung\Db\GeneralMaterialMapper;
use OCA\Mitgliederverwaltung\Db\InventoryCategoryMapper;
use OCA\Mitgliederverwaltung\Db\InventoryItemCategoryMapper;
use OCA\Mitgliederverwaltung\Db\StockItemMapper;
use OCA\Mitgliederverwaltung\Db\StockVariantMapper;
use OCA\Mitgliederverwaltung\Db\SaleRecordMapper;
use Psr\Log\LoggerInterface;
/**
* Service for generating inventory reports.
*
* Part of Issue #165 (Inventory Tracking).
*/
class InventoryReportService {
private GeneralMaterialMapper $generalMaterialMapper;
private InventoryCategoryMapper $categoryMapper;
private InventoryItemCategoryMapper $itemCategoryMapper;
private StockItemMapper $stockItemMapper;
private StockVariantMapper $stockVariantMapper;
private SaleRecordMapper $saleRecordMapper;
private LoggerInterface $logger;
public function __construct(
GeneralMaterialMapper $generalMaterialMapper,
InventoryCategoryMapper $categoryMapper,
InventoryItemCategoryMapper $itemCategoryMapper,
StockItemMapper $stockItemMapper,
StockVariantMapper $stockVariantMapper,
SaleRecordMapper $saleRecordMapper,
LoggerInterface $logger
) {
$this->generalMaterialMapper = $generalMaterialMapper;
$this->categoryMapper = $categoryMapper;
$this->itemCategoryMapper = $itemCategoryMapper;
$this->stockItemMapper = $stockItemMapper;
$this->stockVariantMapper = $stockVariantMapper;
$this->saleRecordMapper = $saleRecordMapper;
$this->logger = $logger;
}
/**
* Generate condition report data.
*
* @return array{summary: array, rows: array, headers: string[]}
*/
public function generateConditionReport(): array {
$items = $this->generalMaterialMapper->findAll();
// Build summary by category
$summary = [];
foreach ($items as $item) {
$catIds = $this->itemCategoryMapper->findByItemId($item->getId());
if (count($catIds) === 0) {
$catName = '(keine Kategorie)';
} else {
$catNames = [];
foreach ($catIds as $cat) {
try {
$c = $this->categoryMapper->findById((int)$cat->getCategoryId());
$catNames[] = $c->getName();
} catch (\Exception $e) {
// ignore
}
}
$catName = implode(', ', $catNames);
}
if (!isset($summary[$catName])) {
$summary[$catName] = 0;
}
$summary[$catName] += 1;
}
// Build detail rows for items needing repair (condition ≤ 2 or NULL)
$rows = [];
$needingRepair = $this->generalMaterialMapper->findNeedingRepair();
foreach ($needingRepair as $item) {
$catIds = $this->itemCategoryMapper->findByItemId($item->getId());
$catNames = [];
foreach ($catIds as $cat) {
try {
$c = $this->categoryMapper->findById((int)$cat->getCategoryId());
$catNames[] = $c->getName();
} catch (\Exception $e) {
// ignore
}
}
$conditionLabel = $item->getCondition() === null
? 'Nicht bewertet'
: $item->getCondition() . '/5';
$rows[] = [
'name' => $item->getName(),
'condition' => $conditionLabel,
'categories' => implode(', ', $catNames) ?: '',
'notes' => $item->getNotes() ?? '',
];
}
return [
'summary' => $summary,
'rows' => $rows,
'headers' => ['Artikel', 'Zustand', 'Kategorie', 'Notizen'],
];
}
/**
* Generate sales report data.
*
* @param string|null $dateFrom Start date (YYYY-MM-DD)
* @param string|null $dateTo End date (YYYY-MM-DD)
* @return array{title: string, summary: array, rows: array, headers: string[]}
*/
public function generateSalesReport(?string $dateFrom = null, ?string $dateTo = null): array {
$sales = $this->saleRecordMapper->findByDateRange($dateFrom, $dateTo);
$rows = [];
$totalRevenue = 0.0;
foreach ($sales as $sale) {
$itemName = 'Unbekannt';
try {
$stockItem = $this->stockItemMapper->findById((int)$sale->getStockItemId());
$itemName = $stockItem->getName();
} catch (\Exception $e) {
// ignore, keep default name
}
$variantName = '';
if ($sale->getVariantId() !== null) {
try {
$variant = $this->stockVariantMapper->findById((int)$sale->getVariantId());
$variantName = ' ' . $variant->getLabel();
} catch (\Exception $e) {
// ignore
}
}
$totalRevenue += (float)$sale->getTotalPrice();
$rows[] = [
'date' => $sale->getDate(),
'item' => $itemName . $variantName,
'quantity' => (string)$sale->getQuantity(),
'unit_price' => number_format((float)$sale->getUnitPrice(), 2, ',', '.') . ' €',
'total_price' => number_format((float)$sale->getTotalPrice(), 2, ',', '.') . ' €',
];
}
$title = 'Inventur-Verkäufe';
if ($dateFrom !== null || $dateTo !== null) {
$title .= ' (' . ($dateFrom ?? 'Anfang') . ' ' . ($dateTo ?? 'Ende') . ')';
}
return [
'title' => $title,
'summary' => [
'total_sales' => count($sales),
'total_revenue' => number_format($totalRevenue, 2, ',', '.') . ' €',
],
'rows' => $rows,
'headers' => ['Datum', 'Artikel', 'Menge', 'Stückpreis', 'Gesamtsumme'],
];
}
}