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
109 lines
3.1 KiB
PHP
109 lines
3.1 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace OCA\Mitgliederverwaltung\Service;
|
||
|
||
use DateTime;
|
||
use OCA\Mitgliederverwaltung\Db\SaleRecord;
|
||
use OCA\Mitgliederverwaltung\Db\SaleRecordMapper;
|
||
use OCP\AppFramework\Db\DoesNotExistException;
|
||
use OCP\DB\Exception;
|
||
use Psr\Log\LoggerInterface;
|
||
|
||
/**
|
||
* Service layer for inventory sale records.
|
||
*
|
||
* Part of Issue #165 (Inventory Tracking).
|
||
*/
|
||
class SaleService {
|
||
|
||
private SaleRecordMapper $saleRecordMapper;
|
||
private AuditService $auditService;
|
||
private LoggerInterface $logger;
|
||
|
||
public function __construct(
|
||
SaleRecordMapper $saleRecordMapper,
|
||
AuditService $auditService,
|
||
LoggerInterface $logger
|
||
) {
|
||
$this->saleRecordMapper = $saleRecordMapper;
|
||
$this->auditService = $auditService;
|
||
$this->logger = $logger;
|
||
}
|
||
|
||
/**
|
||
* @return SaleRecord[]
|
||
* @throws Exception
|
||
*/
|
||
public function getSales(): array {
|
||
return $this->saleRecordMapper->findAll();
|
||
}
|
||
|
||
/**
|
||
* @throws DoesNotExistException
|
||
* @throws Exception
|
||
*/
|
||
public function getSaleById(int $id): SaleRecord {
|
||
return $this->saleRecordMapper->findById($id);
|
||
}
|
||
|
||
/**
|
||
* @throws Exception
|
||
*/
|
||
public function createSale(array $data): SaleRecord {
|
||
$qty = (int)($data['quantity'] ?? 0);
|
||
$unitPrice = (string)($data['unit_price'] ?? '0.00');
|
||
// Calculate total price = quantity × unit_price
|
||
$totalPrice = number_format((float)$unitPrice * $qty, 2, '.', '');
|
||
|
||
$record = new SaleRecord();
|
||
$record->setStockItemId($data['stock_item_id'] ?? null);
|
||
$record->setVariantId($data['variant_id'] ?? null);
|
||
$record->setDate($data['date'] ?? (new DateTime())->format('Y-m-d'));
|
||
$record->setQuantity($qty);
|
||
$record->setUnitPrice($unitPrice);
|
||
$record->setTotalPrice($totalPrice);
|
||
$record->setNotes($data['notes'] ?? null);
|
||
$record->setCreatedAt((new DateTime())->format('Y-m-d H:i:s'));
|
||
$this->saleRecordMapper->insert($record);
|
||
|
||
$this->auditService->logCreate([
|
||
'stock_item_id' => $record->getStockItemId(),
|
||
'variant_id' => $record->getVariantId(),
|
||
'date' => $record->getDate(),
|
||
'quantity' => $record->getQuantity(),
|
||
'unit_price' => $record->getUnitPrice(),
|
||
'total_price' => $record->getTotalPrice(),
|
||
], 'inventory_sale', $record->getId());
|
||
|
||
return $record;
|
||
}
|
||
|
||
/**
|
||
* @throws DoesNotExistException
|
||
* @throws Exception
|
||
*/
|
||
public function deleteSale(int $id): void {
|
||
$record = $this->saleRecordMapper->findById($id);
|
||
$this->saleRecordMapper->delete($record);
|
||
|
||
$this->auditService->logDelete('inventory_sale', $id);
|
||
}
|
||
|
||
/**
|
||
* @return SaleRecord[]
|
||
* @throws Exception
|
||
*/
|
||
public function getSalesByDateRange(?string $from, ?string $to): array {
|
||
return $this->saleRecordMapper->findByDateRange($from, $to);
|
||
}
|
||
|
||
/**
|
||
* @throws Exception
|
||
*/
|
||
public function countSalesByDateRange(?string $from, ?string $to): int {
|
||
return $this->saleRecordMapper->countByDateRange($from, $to);
|
||
}
|
||
}
|