- Added existing_material_ids to PurchaseForEdit and RestockForEdit types. - Introduced RawMaterialVariant and ProductVariantForRestock types for better variant handling. - Updated PurchaseCreate and PurchaseEdit components to fetch and display raw material variants. - Enhanced RestockCreate and RestockEdit components to manage product variants dynamically. - Modified TransactionCreate and TransactionEdit components to support product variants. - Added routes for fetching active products and raw materials.
562 lines
24 KiB
PHP
562 lines
24 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Admin\Manage;
|
|
|
|
use App\Enums\CuttingStatus;
|
|
use App\Enums\Role;
|
|
use App\Models\Cutting;
|
|
use App\Models\CuttingMaterial;
|
|
use App\Models\CuttingMaterialCombination;
|
|
use App\Models\CuttingResult;
|
|
use App\Models\RawMaterialPrice;
|
|
use App\Services\Concerns\RegistersMedia;
|
|
use App\Services\NotificationService;
|
|
use App\Services\S3PresignedService;
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class CuttingService
|
|
{
|
|
use RegistersMedia;
|
|
|
|
public function __construct(
|
|
private S3PresignedService $s3Service,
|
|
) {}
|
|
|
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = [], ?int $highlight = null): LengthAwarePaginator
|
|
{
|
|
$materialsCountQuery = '(SELECT COUNT(*) FROM cutting_materials WHERE cutting_materials.cutting_id = cuttings.id AND cutting_materials.deleted_at IS NULL)';
|
|
$totalUsageQuery = '(SELECT IFNULL(SUM(material_usage), 0) FROM cutting_materials WHERE cutting_materials.cutting_id = cuttings.id AND cutting_materials.deleted_at IS NULL)';
|
|
$productNameQuery = '(SELECT product_name FROM cutting_results WHERE cutting_results.cutting_id = cuttings.id AND cutting_results.deleted_at IS NULL LIMIT 1)';
|
|
$cuttingResultQuery = '(SELECT cutting_result FROM cutting_results WHERE cutting_results.cutting_id = cuttings.id AND cutting_results.deleted_at IS NULL LIMIT 1)';
|
|
|
|
$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',
|
|
])
|
|
->selectRaw("{$materialsCountQuery} as materials_count")
|
|
->selectRaw("{$totalUsageQuery} as total_usage")
|
|
->selectRaw("{$productNameQuery} as product_name")
|
|
->selectRaw("{$cuttingResultQuery} as cutting_result")
|
|
->when($highlight, fn ($q) => $q->where('id', $highlight))
|
|
->when($search, function ($q) use ($search) {
|
|
$q->whereHas('cuttingResults', fn ($rq) => $rq->where('product_name', 'like', "%{$search}%"))
|
|
->orWhere('description', 'like', "%{$search}%");
|
|
})
|
|
->when($filters['product_name'] ?? null, fn ($q, $productName) => $q->whereHas('cuttingResults', fn ($rq) => $rq->where('product_name', $productName)))
|
|
->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
|
|
->orderBy($sort, $direction)
|
|
->paginate($perPage);
|
|
|
|
$paginator->getCollection()->each(function (Cutting $cutting) {
|
|
$cuttingMedia = $cutting->getMedia('images');
|
|
$cutting->photo_urls = $cuttingMedia->map(
|
|
fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath())
|
|
)->toArray();
|
|
$cutting->photo_conversion_urls = $cuttingMedia->map(
|
|
fn ($media) => $media->getGeneratedConversions()->contains('thumb')
|
|
? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
|
|
: $this->s3Service->getTemporaryUrl($media->getPath())
|
|
)->toArray();
|
|
});
|
|
|
|
return $paginator;
|
|
}
|
|
|
|
public function getMaterials(Cutting $cutting): Collection
|
|
{
|
|
return $cutting->cuttingMaterials()
|
|
->select(['id', 'cutting_id', 'raw_material_price_id', 'material_usage', 'material_result', 'combination_id'])
|
|
->with([
|
|
'rawMaterialPrice:id,raw_material_id,variant',
|
|
'rawMaterialPrice.rawMaterial:id,name,unit',
|
|
])
|
|
->get()
|
|
->each(function (CuttingMaterial $material) {
|
|
if (! $material->rawMaterialPrice) {
|
|
return;
|
|
}
|
|
|
|
$media = $material->rawMaterialPrice->getFirstMedia('images');
|
|
$material->rawMaterialPrice->photo_url = $media
|
|
? $this->s3Service->getTemporaryUrl($media->getPath())
|
|
: null;
|
|
$material->rawMaterialPrice->photo_conversion_url = $media
|
|
? ($media->getGeneratedConversions()->contains('thumb')
|
|
? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
|
|
: $this->s3Service->getTemporaryUrl($media->getPath()))
|
|
: null;
|
|
});
|
|
}
|
|
|
|
public function getCombinations(Cutting $cutting): Collection
|
|
{
|
|
return $cutting->cuttingMaterialCombinations()
|
|
->select(['id', 'cutting_id', 'material_result'])
|
|
->get();
|
|
}
|
|
|
|
public function getProductNames(): Collection
|
|
{
|
|
return CuttingResult::query()
|
|
->select('product_name')
|
|
->whereNotNull('product_name')
|
|
->where('product_name', '!=', '')
|
|
->distinct()
|
|
->orderBy('product_name')
|
|
->get();
|
|
}
|
|
|
|
public function getForShow(Cutting $cutting): array
|
|
{
|
|
$cutting->load([
|
|
'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',
|
|
'cuttingMaterials.rawMaterialPrice.rawMaterial:id,name,unit',
|
|
'cuttingMaterialCombinations:id,cutting_id,material_result',
|
|
]);
|
|
|
|
$cuttingMedia = $cutting->getMedia('images');
|
|
$cutting->photo_urls = $cuttingMedia->map(
|
|
fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath())
|
|
)->toArray();
|
|
$cutting->photo_conversion_urls = $cuttingMedia->map(
|
|
fn ($media) => $media->getGeneratedConversions()->contains('thumb')
|
|
? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
|
|
: $this->s3Service->getTemporaryUrl($media->getPath())
|
|
)->toArray();
|
|
|
|
$cutting->cuttingMaterials->each(function (CuttingMaterial $material) {
|
|
$media = $material->rawMaterialPrice?->getFirstMedia('images');
|
|
if ($material->rawMaterialPrice) {
|
|
$material->rawMaterialPrice->photo_url = $media
|
|
? $this->s3Service->getTemporaryUrl($media->getPath())
|
|
: null;
|
|
$material->rawMaterialPrice->photo_conversion_url = $media
|
|
? ($media->getGeneratedConversions()->contains('thumb')
|
|
? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
|
|
: $this->s3Service->getTemporaryUrl($media->getPath()))
|
|
: null;
|
|
}
|
|
});
|
|
|
|
return [
|
|
'id' => $cutting->id,
|
|
'status' => $cutting->status->value,
|
|
'status_label' => $cutting->status_label,
|
|
'description' => $cutting->description,
|
|
'total_material_cost' => $cutting->total_material_cost,
|
|
'formatted_total_material_cost' => $cutting->formatted_total_material_cost,
|
|
'cost_per_unit' => $cutting->cost_per_unit,
|
|
'formatted_cost_per_unit' => $cutting->formatted_cost_per_unit,
|
|
'created_at' => $cutting->created_at,
|
|
'formatted_created_at' => $cutting->formatted_created_at,
|
|
'photo_urls' => $cutting->photo_urls,
|
|
'photo_conversion_urls' => $cutting->photo_conversion_urls,
|
|
'created_by' => [
|
|
'id' => $cutting->createdBy->id,
|
|
'user_profile' => [
|
|
'full_name' => $cutting->createdBy->userProfile->full_name ?? '-',
|
|
],
|
|
],
|
|
'cutting_results' => $cutting->cuttingResults->map(fn ($r) => [
|
|
'id' => $r->id,
|
|
'product_name' => $r->product_name,
|
|
'cutting_result' => $r->cutting_result,
|
|
'sample' => $r->sample,
|
|
'original_outside_sample' => $r->original_outside_sample,
|
|
]),
|
|
'cutting_materials' => $cutting->cuttingMaterials->map(fn ($m) => [
|
|
'id' => $m->id,
|
|
'material_usage' => $m->material_usage,
|
|
'material_result' => $m->material_result,
|
|
'combination_id' => $m->combination_id,
|
|
'raw_material_price' => [
|
|
'id' => $m->rawMaterialPrice->id,
|
|
'variant' => $m->rawMaterialPrice->variant,
|
|
'price' => $m->rawMaterialPrice->price,
|
|
'photo_url' => $m->rawMaterialPrice->photo_url,
|
|
'photo_conversion_url' => $m->rawMaterialPrice->photo_conversion_url,
|
|
'raw_material' => [
|
|
'id' => $m->rawMaterialPrice->rawMaterial->id,
|
|
'name' => $m->rawMaterialPrice->rawMaterial->name,
|
|
'unit' => $m->rawMaterialPrice->rawMaterial->unit,
|
|
],
|
|
],
|
|
]),
|
|
'cutting_material_combinations' => $cutting->cuttingMaterialCombinations->map(fn ($c) => [
|
|
'id' => $c->id,
|
|
'material_result' => $c->material_result,
|
|
]),
|
|
];
|
|
}
|
|
|
|
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('images');
|
|
|
|
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->getPath())
|
|
: 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->getMedia('images');
|
|
$photoKeys = $cuttingMedia->map(
|
|
fn ($media) => $media->getCustomProperty('s3_key') ?? $media->file_name
|
|
)->toArray();
|
|
$photoUrls = $cuttingMedia->map(
|
|
fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath())
|
|
)->toArray();
|
|
|
|
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_keys' => $photoKeys,
|
|
'photo_urls' => $photoUrls,
|
|
'existing_material_ids' => $cutting->cuttingMaterials
|
|
->map(fn (CuttingMaterial $m) => $m->rawMaterialPrice?->raw_material_id)
|
|
->filter()
|
|
->unique()
|
|
->values()
|
|
->all(),
|
|
];
|
|
}
|
|
|
|
public function store(array $data): Cutting
|
|
{
|
|
$cutting = DB::transaction(function () use ($data) {
|
|
$priceIds = collect($data['materials'])->pluck('raw_material_price_id')->unique()->values();
|
|
$pricesMap = RawMaterialPrice::whereIn('id', $priceIds)->get()->keyBy('id');
|
|
|
|
foreach ($data['materials'] as $materialData) {
|
|
$usage = (float) ($materialData['material_usage'] ?? 0);
|
|
if ($usage <= 0) {
|
|
continue;
|
|
}
|
|
$price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
|
|
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;
|
|
}
|
|
|
|
$stockDecrementMap = [];
|
|
|
|
foreach ($data['materials'] as $materialData) {
|
|
$price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
|
|
$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,
|
|
]);
|
|
|
|
$usage = (float) ($materialData['material_usage'] ?? 0);
|
|
if ($price && $usage > 0) {
|
|
$stockDecrementMap[$materialData['raw_material_price_id']] = ($stockDecrementMap[$materialData['raw_material_price_id']] ?? 0) + $usage;
|
|
}
|
|
}
|
|
|
|
foreach ($stockDecrementMap as $priceId => $totalUsage) {
|
|
RawMaterialPrice::where('id', $priceId)->decrement('stock', $totalUsage);
|
|
}
|
|
|
|
$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_keys']) && is_array($data['photo_keys'])) {
|
|
$this->registerPhotos(
|
|
model: $cutting,
|
|
photoKeys: $data['photo_keys'],
|
|
collectionName: 'images',
|
|
);
|
|
}
|
|
|
|
return $cutting;
|
|
});
|
|
|
|
NotificationService::notify(
|
|
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
|
title: 'Cutting Baru',
|
|
body: 'Cutting berhasil ditambahkan oleh '.auth()->user()->full_name.'.',
|
|
url: route('admin.manage.cuttings.index', ['highlight' => $cutting->id]),
|
|
);
|
|
|
|
return $cutting;
|
|
}
|
|
|
|
public function update(Cutting $cutting, array $data): Cutting
|
|
{
|
|
$cutting = DB::transaction(function () use ($cutting, $data) {
|
|
$oldMaterialUsages = $cutting->cuttingMaterials()
|
|
->select('raw_material_price_id', 'material_usage')
|
|
->where('material_usage', '>', 0)
|
|
->get()
|
|
->filter(fn ($m) => $m->raw_material_price_id)
|
|
->groupBy('raw_material_price_id')
|
|
->map(fn ($group) => $group->sum('material_usage'));
|
|
|
|
if ($oldMaterialUsages->isNotEmpty()) {
|
|
foreach ($oldMaterialUsages as $priceId => $totalUsage) {
|
|
RawMaterialPrice::where('id', $priceId)->increment('stock', $totalUsage);
|
|
}
|
|
}
|
|
|
|
$priceIds = collect($data['materials'])->pluck('raw_material_price_id')->unique()->values();
|
|
$pricesMap = RawMaterialPrice::whereIn('id', $priceIds)->get()->keyBy('id');
|
|
|
|
foreach ($data['materials'] as $materialData) {
|
|
$usage = (float) ($materialData['material_usage'] ?? 0);
|
|
if ($usage <= 0) {
|
|
continue;
|
|
}
|
|
$price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
|
|
if (! $price) {
|
|
continue;
|
|
}
|
|
if ($usage > $price->stock) {
|
|
throw ValidationException::withMessages([
|
|
'materials' => "Stok {$price->variant} tidak mencukupi. Tersedia: {$price->stock}, dibutuhkan: {$usage}.",
|
|
]);
|
|
}
|
|
}
|
|
|
|
$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;
|
|
}
|
|
|
|
$stockDecrementMap = [];
|
|
|
|
foreach ($data['materials'] as $materialData) {
|
|
$price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
|
|
$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,
|
|
]);
|
|
|
|
$usage = (float) ($materialData['material_usage'] ?? 0);
|
|
if ($price && $usage > 0) {
|
|
$stockDecrementMap[$materialData['raw_material_price_id']] = ($stockDecrementMap[$materialData['raw_material_price_id']] ?? 0) + $usage;
|
|
}
|
|
}
|
|
|
|
foreach ($stockDecrementMap as $priceId => $totalUsage) {
|
|
RawMaterialPrice::where('id', $priceId)->decrement('stock', $totalUsage);
|
|
}
|
|
|
|
$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->syncPhotos($cutting, $data['photo_keys'] ?? [], 'images');
|
|
|
|
return $cutting;
|
|
});
|
|
|
|
NotificationService::notify(
|
|
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
|
title: 'Cutting Diperbarui',
|
|
body: 'Cutting berhasil diperbarui oleh '.auth()->user()->full_name.'.',
|
|
url: route('admin.manage.cuttings.index', ['highlight' => $cutting->id]),
|
|
);
|
|
|
|
return $cutting;
|
|
}
|
|
|
|
public function destroy(Cutting $cutting): bool
|
|
{
|
|
$result = DB::transaction(function () use ($cutting) {
|
|
$stockRestoreMap = $cutting->cuttingMaterials()
|
|
->select('raw_material_price_id', 'material_usage')
|
|
->where('material_usage', '>', 0)
|
|
->get()
|
|
->groupBy('raw_material_price_id')
|
|
->map(fn ($group) => $group->sum('material_usage'));
|
|
|
|
foreach ($stockRestoreMap as $priceId => $totalUsage) {
|
|
RawMaterialPrice::where('id', $priceId)->increment('stock', $totalUsage);
|
|
}
|
|
|
|
$cutting->cuttingResults()->delete();
|
|
$cutting->cuttingMaterials()->delete();
|
|
$cutting->cuttingMaterialCombinations()->delete();
|
|
$cutting->delete();
|
|
|
|
return true;
|
|
});
|
|
|
|
NotificationService::notify(
|
|
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
|
title: 'Cutting Dihapus',
|
|
body: 'Cutting berhasil dihapus oleh '.auth()->user()->full_name.'.',
|
|
url: route('admin.manage.cuttings.index'),
|
|
);
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function complete(Cutting $cutting): Cutting
|
|
{
|
|
$cutting->update(['status' => CuttingStatus::COMPLETED]);
|
|
|
|
NotificationService::notify(
|
|
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
|
title: 'Cutting Selesai',
|
|
body: 'Cutting berhasil diselesaikan oleh '.auth()->user()->full_name.'.',
|
|
url: route('admin.manage.cuttings.index', ['highlight' => $cutting->id]),
|
|
);
|
|
|
|
return $cutting;
|
|
}
|
|
}
|