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
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
164 lines
6.2 KiB
PHP
164 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace OCA\Mitgliederverwaltung\Tests\Service;
|
|
|
|
use OCA\Mitgliederverwaltung\Db\GeneralMaterial;
|
|
use OCA\Mitgliederverwaltung\Db\GeneralMaterialMapper;
|
|
use OCA\Mitgliederverwaltung\Db\InventoryCategory;
|
|
use OCA\Mitgliederverwaltung\Db\InventoryCategoryMapper;
|
|
use OCA\Mitgliederverwaltung\Db\InventoryItemCategory;
|
|
use OCA\Mitgliederverwaltung\Db\InventoryItemCategoryMapper;
|
|
use OCA\Mitgliederverwaltung\Db\StockItem;
|
|
use OCA\Mitgliederverwaltung\Db\StockItemMapper;
|
|
use OCA\Mitgliederverwaltung\Db\StockVariant;
|
|
use OCA\Mitgliederverwaltung\Db\StockVariantMapper;
|
|
use OCA\Mitgliederverwaltung\Db\SaleRecord;
|
|
use OCA\Mitgliederverwaltung\Db\SaleRecordMapper;
|
|
use OCA\Mitgliederverwaltung\Service\InventoryReportService;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
class InventoryReportServiceTest extends TestCase {
|
|
|
|
private InventoryReportService $service;
|
|
private MockObject $generalMaterialMapper;
|
|
private MockObject $categoryMapper;
|
|
private MockObject $itemCategoryMapper;
|
|
private MockObject $stockItemMapper;
|
|
private MockObject $stockVariantMapper;
|
|
private MockObject $saleRecordMapper;
|
|
private LoggerInterface&MockObject $logger;
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
|
|
$this->generalMaterialMapper = $this->createMock(GeneralMaterialMapper::class);
|
|
$this->categoryMapper = $this->createMock(InventoryCategoryMapper::class);
|
|
$this->itemCategoryMapper = $this->createMock(InventoryItemCategoryMapper::class);
|
|
$this->stockItemMapper = $this->createMock(StockItemMapper::class);
|
|
$this->stockVariantMapper = $this->createMock(StockVariantMapper::class);
|
|
$this->saleRecordMapper = $this->createMock(SaleRecordMapper::class);
|
|
$this->logger = $this->createMock(LoggerInterface::class);
|
|
|
|
$this->service = new InventoryReportService(
|
|
$this->generalMaterialMapper,
|
|
$this->categoryMapper,
|
|
$this->itemCategoryMapper,
|
|
$this->stockItemMapper,
|
|
$this->stockVariantMapper,
|
|
$this->saleRecordMapper,
|
|
$this->logger
|
|
);
|
|
}
|
|
|
|
private function generalMaterialEntity(int $id = 1, string $name = 'Zelt A', ?int $condition = 1): GeneralMaterial {
|
|
$entity = new GeneralMaterial();
|
|
$entity->setId($id);
|
|
$entity->setName($name);
|
|
$entity->setCondition($condition);
|
|
$entity->setNotes(null);
|
|
return $entity;
|
|
}
|
|
|
|
private function categoryEntity(int $id = 1, string $name = 'Zelte'): InventoryCategory {
|
|
$entity = new InventoryCategory();
|
|
$entity->setId($id);
|
|
$entity->setName($name);
|
|
return $entity;
|
|
}
|
|
|
|
private function itemCategoryEntity(int $itemId = 1, int $categoryId = 1): InventoryItemCategory {
|
|
$entity = new InventoryItemCategory();
|
|
$entity->setItemId($itemId);
|
|
$entity->setCategoryId($categoryId);
|
|
return $entity;
|
|
}
|
|
|
|
private function saleEntity(int $id = 1, int $stockItemId = 1, string $quantity = '2', string $unitPrice = '25.00'): SaleRecord {
|
|
$entity = new SaleRecord();
|
|
$entity->setId($id);
|
|
$entity->setStockItemId($stockItemId);
|
|
$entity->setVariantId(null);
|
|
$entity->setDate('2026-04-01');
|
|
$entity->setQuantity((int)$quantity);
|
|
$entity->setUnitPrice($unitPrice);
|
|
$entity->setTotalPrice((string)((float)$quantity * (float)$unitPrice));
|
|
$entity->setNotes(null);
|
|
$entity->setCreatedAt('2026-04-01 00:00:00');
|
|
return $entity;
|
|
}
|
|
|
|
public function testGenerateSalesReport_dateFiltered(): void {
|
|
$sales = [
|
|
$this->saleEntity(1, 1, '2', '25.00'),
|
|
$this->saleEntity(2, 1, '1', '30.00'),
|
|
];
|
|
|
|
$this->saleRecordMapper->method('findByDateRange')
|
|
->with('2026-01-01', '2026-03-31')
|
|
->willReturn($sales);
|
|
|
|
$report = $this->service->generateSalesReport('2026-01-01', '2026-03-31');
|
|
|
|
$this->assertStringContainsString('2026-01-01', $report['title']);
|
|
$this->assertCount(2, $report['rows']);
|
|
$this->assertArrayHasKey('total_sales', $report['summary']);
|
|
$this->assertSame(2, (int)$report['summary']['total_sales']);
|
|
}
|
|
|
|
public function testGenerateConditionReport_summaryByCategory(): void {
|
|
$item1 = $this->generalMaterialEntity(1, 'Zelt A', 3);
|
|
$item2 = $this->generalMaterialEntity(2, 'Zelt B', 4);
|
|
|
|
$this->generalMaterialMapper->method('findAll')
|
|
->willReturn([$item1, $item2]);
|
|
|
|
$itemCat1 = $this->itemCategoryEntity(1, 1);
|
|
$this->itemCategoryMapper->method('findByItemId')
|
|
->willReturnCallback(function($itemId) use ($itemCat1) {
|
|
if ($itemId === 1) {
|
|
return [$itemCat1];
|
|
}
|
|
return [];
|
|
});
|
|
|
|
$catEntity = $this->categoryEntity(1, 'Zelte');
|
|
$this->categoryMapper->method('findById')
|
|
->willReturn($catEntity);
|
|
|
|
// Also need findNeedingRepair to return empty
|
|
$this->generalMaterialMapper->method('findNeedingRepair')
|
|
->willReturn([]);
|
|
|
|
$report = $this->service->generateConditionReport();
|
|
|
|
$this->assertArrayHasKey('Zelte', $report['summary']);
|
|
$this->assertSame(1, $report['summary']['Zelte']);
|
|
}
|
|
|
|
public function testGenerateConditionReport_needsRepairOnly(): void {
|
|
// One good item, one needing repair
|
|
$goodItem = $this->generalMaterialEntity(1, 'Zelt A', 4);
|
|
$badItem = $this->generalMaterialEntity(2, 'Zelt B', 1);
|
|
|
|
$this->generalMaterialMapper->method('findAll')
|
|
->willReturn([$goodItem, $badItem]);
|
|
|
|
$this->itemCategoryMapper->method('findByItemId')
|
|
->willReturn([]);
|
|
|
|
$this->generalMaterialMapper->method('findNeedingRepair')
|
|
->willReturn([$badItem]);
|
|
|
|
$report = $this->service->generateConditionReport();
|
|
|
|
// Only the bad item should be in rows
|
|
$this->assertCount(1, $report['rows']);
|
|
$this->assertSame('Zelt B', $report['rows'][0]['name']);
|
|
$this->assertSame('1/5', $report['rows'][0]['condition']);
|
|
}
|
|
}
|