dstpabuaran.com/app/Services/Admin/Manage/CuttingService.php

359 lines
14 KiB
PHP

<?php
namespace App\Services\Admin\Manage;
use App\Models\Cutting;
use App\Models\CuttingMaterial;
use App\Models\CuttingMaterialCombination;
use App\Models\CuttingResult;
use App\Models\RawMaterialPrice;
use App\Services\Admin\Master\RawMaterial\RawMaterialService;
use App\Services\Concerns\RegistersMedia;
use App\Services\S3PresignedService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class CuttingService
{
use RegistersMedia;
public function __construct(
private S3PresignedService $s3Service,
private RawMaterialService $rawMaterialService,
) {}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
{
$paginator = Cutting::query()
->select(['id', 'created_by_id', 'status', 'description', 'total_material_cost', 'cost_per_unit', 'created_at'])
->with([
'createdBy:id',
'createdBy.userProfile:id,user_id,full_name',
'cuttingResults:id,cutting_id,product_name,cutting_result,sample,original_outside_sample',
'cuttingMaterials:id,cutting_id,raw_material_price_id,material_usage,material_result,combination_id',
'cuttingMaterials.rawMaterialPrice:id,raw_material_id,variant,price,stock',
'cuttingMaterials.rawMaterialPrice.rawMaterial:id,name,unit',
'cuttingMaterialCombinations:id,cutting_id,material_result',
])
->when($search, function ($q) use ($search) {
$q->whereHas('cuttingResults', fn ($rq) => $rq->where('product_name', 'like', "%{$search}%"))
->orWhere('description', 'like', "%{$search}%");
})
->orderBy($sort, $direction)
->paginate($perPage);
$paginator->getCollection()->each(function (Cutting $cutting) {
$cuttingMedia = $cutting->getFirstMedia('photos');
$cutting->photo_url = $cuttingMedia
? $this->s3Service->getTemporaryUrl($cuttingMedia->file_name)
: null;
$cutting->cuttingMaterials->each(function (CuttingMaterial $material) {
$media = $material->rawMaterialPrice?->getFirstMedia('photos');
if ($material->rawMaterialPrice) {
$material->rawMaterialPrice->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->file_name)
: null;
}
});
});
return $paginator;
}
public function getForCreate(): array
{
return [
'rawMaterials' => $this->rawMaterialService->getVariantsForCutting(),
];
}
public function getForEdit(Cutting $cutting): array
{
$cutting->load([
'cuttingResults',
'cuttingMaterials.rawMaterialPrice.rawMaterial',
'cuttingMaterialCombinations',
]);
$result = $cutting->cuttingResults->first();
$materials = $cutting->cuttingMaterials->map(function (CuttingMaterial $material) {
$media = $material->rawMaterialPrice?->getFirstMedia('photos');
return [
'id' => $material->id,
'raw_material_price_id' => $material->raw_material_price_id,
'material_usage' => $material->material_usage,
'material_result' => $material->material_result,
'combination_id' => $material->combination_id,
'variant' => $material->rawMaterialPrice?->variant,
'photo_url' => $media
? $this->s3Service->getTemporaryUrl($media->file_name)
: null,
];
});
$combinations = $cutting->cuttingMaterialCombinations->map(function (CuttingMaterialCombination $combination) use ($materials) {
$materialIndices = $materials
->filter(fn ($m) => $m['combination_id'] === $combination->id)
->keys()
->values();
return [
'id' => $combination->id,
'material_result' => $combination->material_result,
'material_indices' => $materialIndices,
];
});
$cuttingMedia = $cutting->getFirstMedia('photos');
$photoKey = $cuttingMedia?->file_name;
$photoUrl = $cuttingMedia
? $this->s3Service->getTemporaryUrl($cuttingMedia->file_name)
: null;
return [
'id' => $cutting->id,
'status' => $cutting->status->value,
'description' => $cutting->description,
'product_name' => $result?->product_name ?? '',
'sample' => $result?->sample ?? 0,
'original_outside_sample' => $result?->original_outside_sample ?? 0,
'cutting_result' => $result?->cutting_result ?? 0,
'materials' => $materials,
'combinations' => $combinations,
'photo_key' => $photoKey,
'photo_url' => $photoUrl,
];
}
public function create(array $data): Cutting
{
return DB::transaction(function () use ($data) {
foreach ($data['materials'] as $materialData) {
$usage = (int) ($materialData['material_usage'] ?? 0);
if ($usage <= 0) {
continue;
}
$price = RawMaterialPrice::find($materialData['raw_material_price_id']);
if (! $price) {
continue;
}
if ($usage > $price->stock) {
throw ValidationException::withMessages([
'materials' => "Stok {$price->variant} tidak mencukupi. Tersedia: {$price->stock}, dibutuhkan: {$usage}.",
]);
}
}
$cutting = Cutting::create([
'created_by_id' => auth()->id(),
'status' => 'in_progress',
'description' => $data['description'] ?? null,
]);
$totalMaterialCost = 0;
$now = now();
$combinations = $data['combinations'] ?? [];
$combinationMap = [];
foreach ($combinations as $index => $combo) {
$combination = CuttingMaterialCombination::create([
'cutting_id' => $cutting->id,
'user_id' => auth()->id(),
'material_result' => $combo['material_result'] ?? null,
'created_at' => $now,
'updated_at' => $now,
]);
$combinationMap[$index] = $combination->id;
}
foreach ($data['materials'] as $materialData) {
$price = RawMaterialPrice::find($materialData['raw_material_price_id']);
$materialCost = $price ? $price->price * ($materialData['material_usage'] ?? 0) : 0;
$totalMaterialCost += $materialCost;
$combinationId = null;
if (isset($materialData['combination_index']) && isset($combinationMap[$materialData['combination_index']])) {
$combinationId = $combinationMap[$materialData['combination_index']];
}
CuttingMaterial::create([
'cutting_id' => $cutting->id,
'user_id' => auth()->id(),
'raw_material_price_id' => $materialData['raw_material_price_id'],
'material_usage' => $materialData['material_usage'] ?? 0,
'material_result' => $materialData['material_result'] ?? null,
'combination_id' => $combinationId,
'created_at' => $now,
'updated_at' => $now,
]);
if ($price && ($materialData['material_usage'] ?? 0) > 0) {
$price->decrement('stock', (int) $materialData['material_usage']);
}
}
$costPerUnit = 0;
if (($data['cutting_result'] ?? 0) > 0) {
$costPerUnit = (int) ($totalMaterialCost / $data['cutting_result']);
}
$cutting->update([
'total_material_cost' => $totalMaterialCost,
'cost_per_unit' => $costPerUnit,
]);
CuttingResult::create([
'cutting_id' => $cutting->id,
'user_id' => auth()->id(),
'product_name' => $data['product_name'] ?? null,
'sample' => $data['sample'] ?? null,
'original_outside_sample' => $data['original_outside_sample'] ?? null,
'cutting_result' => $data['cutting_result'] ?? null,
'created_at' => $now,
'updated_at' => $now,
]);
if (! empty($data['photo_key'])) {
$this->registerMedia(
model: $cutting,
s3Key: $data['photo_key'],
collectionName: 'photos',
orderColumn: 1,
);
}
return $cutting;
});
}
public function update(Cutting $cutting, array $data): Cutting
{
return DB::transaction(function () use ($cutting, $data) {
$cutting->load(['cuttingMaterials.rawMaterialPrice']);
foreach ($cutting->cuttingMaterials as $oldMaterial) {
if ($oldMaterial->material_usage > 0 && $oldMaterial->rawMaterialPrice) {
$oldMaterial->rawMaterialPrice->increment('stock', (int) $oldMaterial->material_usage);
}
}
foreach ($data['materials'] as $materialData) {
$usage = (int) ($materialData['material_usage'] ?? 0);
if ($usage <= 0) {
continue;
}
$price = RawMaterialPrice::find($materialData['raw_material_price_id']);
if (! $price) {
continue;
}
if ($usage > $price->stock) {
throw ValidationException::withMessages([
'materials' => "Stok {$price->variant} tidak mencukupi. Tersedia: {$price->stock}, dibutuhkan: {$usage}.",
]);
}
}
$cutting->load(['cuttingMaterials', 'cuttingMaterialCombinations', 'cuttingResults']);
$cutting->cuttingResults()->delete();
$cutting->cuttingMaterials()->delete();
$cutting->cuttingMaterialCombinations()->delete();
$totalMaterialCost = 0;
$now = now();
$combinations = $data['combinations'] ?? [];
$combinationMap = [];
foreach ($combinations as $index => $combo) {
$combination = CuttingMaterialCombination::create([
'cutting_id' => $cutting->id,
'user_id' => auth()->id(),
'material_result' => $combo['material_result'] ?? null,
'created_at' => $now,
'updated_at' => $now,
]);
$combinationMap[$index] = $combination->id;
}
foreach ($data['materials'] as $materialData) {
$price = RawMaterialPrice::find($materialData['raw_material_price_id']);
$materialCost = $price ? $price->price * ($materialData['material_usage'] ?? 0) : 0;
$totalMaterialCost += $materialCost;
$combinationId = null;
if (isset($materialData['combination_index']) && isset($combinationMap[$materialData['combination_index']])) {
$combinationId = $combinationMap[$materialData['combination_index']];
}
CuttingMaterial::create([
'cutting_id' => $cutting->id,
'user_id' => auth()->id(),
'raw_material_price_id' => $materialData['raw_material_price_id'],
'material_usage' => $materialData['material_usage'] ?? 0,
'material_result' => $materialData['material_result'] ?? null,
'combination_id' => $combinationId,
'created_at' => $now,
'updated_at' => $now,
]);
if ($price && ($materialData['material_usage'] ?? 0) > 0) {
$price->decrement('stock', (int) $materialData['material_usage']);
}
}
$costPerUnit = 0;
if (($data['cutting_result'] ?? 0) > 0) {
$costPerUnit = (int) ($totalMaterialCost / $data['cutting_result']);
}
$cutting->update([
'description' => $data['description'] ?? null,
'total_material_cost' => $totalMaterialCost,
'cost_per_unit' => $costPerUnit,
]);
CuttingResult::create([
'cutting_id' => $cutting->id,
'user_id' => auth()->id(),
'product_name' => $data['product_name'] ?? null,
'sample' => $data['sample'] ?? null,
'original_outside_sample' => $data['original_outside_sample'] ?? null,
'cutting_result' => $data['cutting_result'] ?? null,
'created_at' => $now,
'updated_at' => $now,
]);
$this->syncPhoto($cutting, $data);
return $cutting;
});
}
public function delete(Cutting $cutting): bool
{
return DB::transaction(function () use ($cutting) {
$cutting->load('cuttingMaterials.rawMaterialPrice');
foreach ($cutting->cuttingMaterials as $material) {
if ($material->material_usage > 0 && $material->rawMaterialPrice) {
$material->rawMaterialPrice->increment('stock', (int) $material->material_usage);
}
}
$cutting->clearMediaCollection('photos');
$cutting->cuttingResults()->delete();
$cutting->cuttingMaterials()->delete();
$cutting->cuttingMaterialCombinations()->delete();
$cutting->delete();
return true;
});
}
}