0 || throw new \InvalidArgumentException('Jumlah restok harus lebih dari nol.'); $newBalance = $this->applyMutation($productId, fn (int $current): int => $current + $quantity, $quantity, 'restock', $notes, $actor); $this->activity->log( 'stock_restocked', 'stocks', $productId, sprintf('Restok %d untuk produk %s (saldo menjadi %d).', $quantity, $this->productLabel($productId), $newBalance), $actor, ); } /** * Koreksi saldo ke nilai absolut baru. */ public function adjust(string $productId, int $newQuantity, ?string $notes = null, ?User $actor = null): void { $delta = $newQuantity - $this->currentQuantity($productId); if ($delta === 0) { return; } $this->applyMutation($productId, fn (): int => $newQuantity, $delta, 'adjustment', $notes, $actor); $this->activity->log( 'stock_adjusted', 'stocks', $productId, sprintf('Koreksi stok produk %s menjadi %d (selisih %+d).', $this->productLabel($productId), $newQuantity, $delta), $actor, ); } private function applyMutation(string $productId, callable $resolveNewBalance, int $movementQuantity, string $type, ?string $notes, ?User $actor): int { [$client, $asUser] = $this->scoped(); $stock = $client->listRecords('stocks', ['product_id' => 'eq.'.$productId, 'limit' => 1], asUser: $asUser)[0] ?? null; $current = (int) ($stock['quantity'] ?? 0); $newBalance = $resolveNewBalance($current); if ($stock === null) { $client->insertRecord('stocks', [[ 'product_id' => $productId, 'quantity' => max($newBalance, 0), ]], asUser: false); } else { $client->updateRecords('stocks', ['id' => $stock['id']], [ 'quantity' => $newBalance, ], asUser: false); } $mutation = array_filter([ 'product_id' => $productId, 'type' => $type, 'quantity' => $movementQuantity, 'notes' => $notes, 'created_by' => $actor?->id, ], fn ($value) => $value !== null && $value !== ''); $client->insertRecord('stock_movements', [$mutation], asUser: false); return $newBalance; } private function currentQuantity(string $productId): int { [$client, $asUser] = $this->scoped(); $stock = $client->listRecords('stocks', ['product_id' => 'eq.'.$productId, 'limit' => 1], asUser: $asUser)[0] ?? null; return (int) ($stock['quantity'] ?? 0); } private function productLabel(string $productId): string { $product = $this->products->find($productId); return trim(($product['code'] ?? '').' — '.($product['name'] ?? '?'), ' —') ?: '?'; } }