267 lines
9.8 KiB
PHP
267 lines
9.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Product;
|
|
use App\Models\ProductVariant;
|
|
use App\Models\StockMutation;
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class StockMutationService
|
|
{
|
|
private const QUALITY_MAP = [
|
|
'stock' => 'good',
|
|
'reject_stock' => 'reject',
|
|
'retail_stock' => 'retail',
|
|
];
|
|
|
|
public function paginated(Model $model, int $perPage = 20): LengthAwarePaginator
|
|
{
|
|
return StockMutation::query()
|
|
->where('stockable_type', get_class($model))
|
|
->where('stockable_id', $model->id)
|
|
->with('user:id,username,email')
|
|
->latest()
|
|
->paginate($perPage);
|
|
}
|
|
|
|
public function paginatedAll(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
|
|
{
|
|
return StockMutation::query()
|
|
->where('stockable_type', ProductVariant::class)
|
|
->with([
|
|
'user:id,username,email',
|
|
'user.userProfile:id,user_id,full_name',
|
|
'stockable' => fn ($q) => $q->withTrashed()->select('id', 'product_id', 'name'),
|
|
'stockable.product' => fn ($q) => $q->withTrashed()->select('id', 'name'),
|
|
])
|
|
->when($search, function ($q) use ($search) {
|
|
$q->where(function ($sq) use ($search) {
|
|
$sq->whereHasMorph('stockable', [ProductVariant::class], function ($vq) use ($search) {
|
|
$vq->withTrashed()
|
|
->where('name', 'like', "%{$search}%")
|
|
->orWhereHas('product', fn ($pq) => $pq->withTrashed()->where('name', 'like', "%{$search}%"));
|
|
})->orWhere('description', 'like', "%{$search}%");
|
|
});
|
|
})
|
|
->when($filters['product_id'] ?? null, function ($q, $productId) {
|
|
$q->whereHasMorph('stockable', [ProductVariant::class], fn ($vq) => $vq->withTrashed()->where('product_id', $productId));
|
|
})
|
|
->when($filters['type'] ?? null, fn ($q, $type) => $q->where('type', $type))
|
|
->when($filters['stock_quality'] ?? null, fn ($q, $quality) => $q->where('stock_quality', $quality))
|
|
->when($filters['date_from'] ?? null, fn ($q, $dateFrom) => $q->whereDate('created_at', '>=', $dateFrom))
|
|
->when($filters['date_to'] ?? null, fn ($q, $dateTo) => $q->whereDate('created_at', '<=', $dateTo))
|
|
->orderBy($sort, $direction)
|
|
->paginate($perPage);
|
|
}
|
|
|
|
public function getFilterOptions(): array
|
|
{
|
|
return [
|
|
'products' => Product::query()
|
|
->select(['id', 'name'])
|
|
->whereHas('productVariants.stockMutations')
|
|
->orderBy('name')
|
|
->get(),
|
|
'typeOptions' => [
|
|
['value' => 'in', 'label' => 'Stok Masuk'],
|
|
['value' => 'out', 'label' => 'Stok Keluar'],
|
|
],
|
|
'stockQualityOptions' => [
|
|
['value' => 'good', 'label' => 'Bagus'],
|
|
['value' => 'reject', 'label' => 'Reject'],
|
|
['value' => 'retail', 'label' => 'Ecer'],
|
|
],
|
|
];
|
|
}
|
|
|
|
public static function qualityFromField(string $field): string
|
|
{
|
|
return self::QUALITY_MAP[$field] ?? 'good';
|
|
}
|
|
|
|
public function record(
|
|
Model $model,
|
|
string $type,
|
|
int $quantity,
|
|
int $stockBefore,
|
|
int $stockAfter,
|
|
string $stockQuality,
|
|
string $description,
|
|
?Model $source = null,
|
|
): StockMutation {
|
|
return StockMutation::create([
|
|
'user_id' => auth()->id(),
|
|
'stockable_type' => get_class($model),
|
|
'stockable_id' => $model->id,
|
|
'type' => $type,
|
|
'quantity' => $quantity,
|
|
'stock_before' => $stockBefore,
|
|
'stock_after' => $stockAfter,
|
|
'stock_quality' => $stockQuality,
|
|
'description' => $description,
|
|
'source_type' => $source ? get_class($source) : null,
|
|
'source_id' => $source?->id,
|
|
]);
|
|
}
|
|
|
|
public function recordInitial(Model $model, array $stockData, string $description = 'Stok awal'): void
|
|
{
|
|
$userId = auth()->id();
|
|
|
|
foreach (self::QUALITY_MAP as $field => $quality) {
|
|
$quantity = (int) ($stockData[$field] ?? 0);
|
|
if ($quantity > 0) {
|
|
StockMutation::create([
|
|
'user_id' => $userId,
|
|
'stockable_type' => get_class($model),
|
|
'stockable_id' => $model->id,
|
|
'type' => 'in',
|
|
'quantity' => $quantity,
|
|
'stock_before' => 0,
|
|
'stock_after' => $quantity,
|
|
'stock_quality' => $quality,
|
|
'description' => $description,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function recordAdjustment(Model $model, array $oldData, array $newData, string $description = 'Penyesuaian stok'): void
|
|
{
|
|
$userId = auth()->id();
|
|
|
|
foreach (self::QUALITY_MAP as $field => $quality) {
|
|
$old = (int) ($oldData[$field] ?? 0);
|
|
$new = (int) ($newData[$field] ?? 0);
|
|
$diff = $new - $old;
|
|
|
|
if ($diff !== 0) {
|
|
StockMutation::create([
|
|
'user_id' => $userId,
|
|
'stockable_type' => get_class($model),
|
|
'stockable_id' => $model->id,
|
|
'type' => $diff > 0 ? 'in' : 'out',
|
|
'quantity' => $diff,
|
|
'stock_before' => $old,
|
|
'stock_after' => $new,
|
|
'stock_quality' => $quality,
|
|
'description' => $description,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function recordBulkInitial(Collection $variants, array $stockDataMap, string $description = 'Stok awal'): void
|
|
{
|
|
$userId = auth()->id();
|
|
$now = now();
|
|
$mutations = [];
|
|
|
|
foreach ($variants as $variant) {
|
|
$stockData = $stockDataMap[$variant->id] ?? [];
|
|
foreach (self::QUALITY_MAP as $field => $quality) {
|
|
$quantity = (int) ($stockData[$field] ?? 0);
|
|
if ($quantity > 0) {
|
|
$mutations[] = [
|
|
'user_id' => $userId,
|
|
'stockable_type' => ProductVariant::class,
|
|
'stockable_id' => $variant->id,
|
|
'type' => 'in',
|
|
'quantity' => $quantity,
|
|
'stock_before' => 0,
|
|
'stock_after' => $quantity,
|
|
'stock_quality' => $quality,
|
|
'description' => $description,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($mutations !== []) {
|
|
DB::table('stock_mutations')->insert($mutations);
|
|
}
|
|
}
|
|
|
|
public function recordBulkAdjustment(Collection $variants, array $oldDataMap, array $newDataMap, string $description = 'Penyesuaian stok'): void
|
|
{
|
|
$userId = auth()->id();
|
|
$now = now();
|
|
$mutations = [];
|
|
|
|
foreach ($variants as $variant) {
|
|
$oldData = $oldDataMap[$variant->id] ?? [];
|
|
$newData = $newDataMap[$variant->id] ?? [];
|
|
|
|
foreach (self::QUALITY_MAP as $field => $quality) {
|
|
$old = (int) ($oldData[$field] ?? 0);
|
|
$new = (int) ($newData[$field] ?? 0);
|
|
$diff = $new - $old;
|
|
|
|
if ($diff !== 0) {
|
|
$mutations[] = [
|
|
'user_id' => $userId,
|
|
'stockable_type' => ProductVariant::class,
|
|
'stockable_id' => $variant->id,
|
|
'type' => $diff > 0 ? 'in' : 'out',
|
|
'quantity' => $diff,
|
|
'stock_before' => $old,
|
|
'stock_after' => $new,
|
|
'stock_quality' => $quality,
|
|
'description' => $description,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($mutations !== []) {
|
|
DB::table('stock_mutations')->insert($mutations);
|
|
}
|
|
}
|
|
|
|
public function recordTransfer(
|
|
Model $model,
|
|
int $quantity,
|
|
string $fromQuality,
|
|
string $toQuality,
|
|
int $fromBefore,
|
|
int $toBefore,
|
|
string $description = 'Transfer stok',
|
|
): void {
|
|
DB::transaction(function () use ($model, $quantity, $fromQuality, $toQuality, $fromBefore, $toBefore, $description) {
|
|
$userId = auth()->id();
|
|
|
|
StockMutation::create([
|
|
'user_id' => $userId,
|
|
'stockable_type' => get_class($model),
|
|
'stockable_id' => $model->id,
|
|
'type' => 'out',
|
|
'quantity' => -$quantity,
|
|
'stock_before' => $fromBefore,
|
|
'stock_after' => $fromBefore - $quantity,
|
|
'stock_quality' => $fromQuality,
|
|
'description' => $description,
|
|
]);
|
|
|
|
StockMutation::create([
|
|
'user_id' => $userId,
|
|
'stockable_type' => get_class($model),
|
|
'stockable_id' => $model->id,
|
|
'type' => 'in',
|
|
'quantity' => $quantity,
|
|
'stock_before' => $toBefore,
|
|
'stock_after' => $toBefore + $quantity,
|
|
'stock_quality' => $toQuality,
|
|
'description' => $description,
|
|
]);
|
|
});
|
|
}
|
|
}
|