Compare commits

...

2 Commits

4 changed files with 109 additions and 91 deletions

View File

@ -254,12 +254,15 @@ public function getForEdit(Cutting $cutting): array
public function store(array $data): Cutting public function store(array $data): Cutting
{ {
$cutting = DB::transaction(function () use ($data) { $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) { foreach ($data['materials'] as $materialData) {
$usage = (int) ($materialData['material_usage'] ?? 0); $usage = (int) ($materialData['material_usage'] ?? 0);
if ($usage <= 0) { if ($usage <= 0) {
continue; continue;
} }
$price = RawMaterialPrice::find($materialData['raw_material_price_id']); $price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
if (! $price) { if (! $price) {
continue; continue;
} }
@ -293,8 +296,10 @@ public function store(array $data): Cutting
$combinationMap[$index] = $combination->id; $combinationMap[$index] = $combination->id;
} }
$stockDecrementMap = [];
foreach ($data['materials'] as $materialData) { foreach ($data['materials'] as $materialData) {
$price = RawMaterialPrice::find($materialData['raw_material_price_id']); $price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
$materialCost = $price ? $price->price * ($materialData['material_usage'] ?? 0) : 0; $materialCost = $price ? $price->price * ($materialData['material_usage'] ?? 0) : 0;
$totalMaterialCost += $materialCost; $totalMaterialCost += $materialCost;
@ -314,11 +319,16 @@ public function store(array $data): Cutting
'updated_at' => $now, 'updated_at' => $now,
]); ]);
if ($price && ($materialData['material_usage'] ?? 0) > 0) { $usage = (int) ($materialData['material_usage'] ?? 0);
$price->decrement('stock', (int) $materialData['material_usage']); 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; $costPerUnit = 0;
if (($data['cutting_result'] ?? 0) > 0) { if (($data['cutting_result'] ?? 0) > 0) {
$costPerUnit = (int) ($totalMaterialCost / $data['cutting_result']); $costPerUnit = (int) ($totalMaterialCost / $data['cutting_result']);
@ -364,20 +374,29 @@ public function store(array $data): Cutting
public function update(Cutting $cutting, array $data): Cutting public function update(Cutting $cutting, array $data): Cutting
{ {
$cutting = DB::transaction(function () use ($cutting, $data) { $cutting = DB::transaction(function () use ($cutting, $data) {
$cutting->load(['cuttingMaterials.rawMaterialPrice']); $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'));
foreach ($cutting->cuttingMaterials as $oldMaterial) { if ($oldMaterialUsages->isNotEmpty()) {
if ($oldMaterial->material_usage > 0 && $oldMaterial->rawMaterialPrice) { foreach ($oldMaterialUsages as $priceId => $totalUsage) {
$oldMaterial->rawMaterialPrice->increment('stock', (int) $oldMaterial->material_usage); 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) { foreach ($data['materials'] as $materialData) {
$usage = (int) ($materialData['material_usage'] ?? 0); $usage = (int) ($materialData['material_usage'] ?? 0);
if ($usage <= 0) { if ($usage <= 0) {
continue; continue;
} }
$price = RawMaterialPrice::find($materialData['raw_material_price_id']); $price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
if (! $price) { if (! $price) {
continue; continue;
} }
@ -388,8 +407,6 @@ public function update(Cutting $cutting, array $data): Cutting
} }
} }
$cutting->load(['cuttingMaterials', 'cuttingMaterialCombinations', 'cuttingResults']);
$cutting->cuttingResults()->delete(); $cutting->cuttingResults()->delete();
$cutting->cuttingMaterials()->delete(); $cutting->cuttingMaterials()->delete();
$cutting->cuttingMaterialCombinations()->delete(); $cutting->cuttingMaterialCombinations()->delete();
@ -411,8 +428,10 @@ public function update(Cutting $cutting, array $data): Cutting
$combinationMap[$index] = $combination->id; $combinationMap[$index] = $combination->id;
} }
$stockDecrementMap = [];
foreach ($data['materials'] as $materialData) { foreach ($data['materials'] as $materialData) {
$price = RawMaterialPrice::find($materialData['raw_material_price_id']); $price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
$materialCost = $price ? $price->price * ($materialData['material_usage'] ?? 0) : 0; $materialCost = $price ? $price->price * ($materialData['material_usage'] ?? 0) : 0;
$totalMaterialCost += $materialCost; $totalMaterialCost += $materialCost;
@ -432,11 +451,16 @@ public function update(Cutting $cutting, array $data): Cutting
'updated_at' => $now, 'updated_at' => $now,
]); ]);
if ($price && ($materialData['material_usage'] ?? 0) > 0) { $usage = (int) ($materialData['material_usage'] ?? 0);
$price->decrement('stock', (int) $materialData['material_usage']); 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; $costPerUnit = 0;
if (($data['cutting_result'] ?? 0) > 0) { if (($data['cutting_result'] ?? 0) > 0) {
$costPerUnit = (int) ($totalMaterialCost / $data['cutting_result']); $costPerUnit = (int) ($totalMaterialCost / $data['cutting_result']);
@ -477,12 +501,15 @@ public function update(Cutting $cutting, array $data): Cutting
public function destroy(Cutting $cutting): bool public function destroy(Cutting $cutting): bool
{ {
$result = DB::transaction(function () use ($cutting) { $result = DB::transaction(function () use ($cutting) {
$cutting->load('cuttingMaterials.rawMaterialPrice'); $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 ($cutting->cuttingMaterials as $material) { foreach ($stockRestoreMap as $priceId => $totalUsage) {
if ($material->material_usage > 0 && $material->rawMaterialPrice) { RawMaterialPrice::where('id', $priceId)->increment('stock', $totalUsage);
$material->rawMaterialPrice->increment('stock', (int) $material->material_usage);
}
} }
$cutting->cuttingResults()->delete(); $cutting->cuttingResults()->delete();

View File

@ -94,15 +94,7 @@ public function getForCreate(): array
'rawMaterialPrices:id,raw_material_id,variant,price,stock', 'rawMaterialPrices:id,raw_material_id,variant,price,stock',
]) ])
->orderBy('name') ->orderBy('name')
->get() ->get(),
->each(function (RawMaterial $rawMaterial) {
$rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
$media = $price->getFirstMedia('images');
$price->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
});
}),
]; ];
} }
@ -226,9 +218,12 @@ private function storeFromExisting(array $data): Purchase
} }
DB::table('purchase_items')->insert($itemRows); DB::table('purchase_items')->insert($itemRows);
foreach ($data['existing_items'] as $item) { $stockIncrementMap = collect($data['existing_items'])
RawMaterialPrice::whereKey($item['raw_material_price_id']) ->groupBy('raw_material_price_id')
->increment('stock', (int) $item['quantity']); ->map(fn ($group) => $group->sum('quantity'));
foreach ($stockIncrementMap as $priceId => $totalQty) {
RawMaterialPrice::where('id', $priceId)->increment('stock', $totalQty);
} }
if (! empty($data['photo_keys']) && is_array($data['photo_keys'])) { if (! empty($data['photo_keys']) && is_array($data['photo_keys'])) {
@ -277,19 +272,23 @@ private function storeNew(array $data): Purchase
DB::table('raw_material_prices')->insert($priceRows); DB::table('raw_material_prices')->insert($priceRows);
$insertedPrices = RawMaterialPrice::where('raw_material_id', $rawMaterial->id)->get(); $priceIdMap = DB::table('raw_material_prices')
$variantMap = $insertedPrices->mapWithKeys(fn ($p) => [$p->variant => $p->id]); ->where('raw_material_id', $rawMaterial->id)
->pluck('id', 'variant')
->toArray();
foreach ($data['variants'] as $variantData) { foreach ($data['variants'] as $variantData) {
if (! empty($variantData['photo_key'])) { if (! empty($variantData['photo_key'])) {
$priceId = $variantMap[$variantData['variant']]; $priceId = $priceIdMap[$variantData['variant']] ?? null;
$priceModel = RawMaterialPrice::find($priceId); if ($priceId) {
$this->registerMedia( $priceModel = RawMaterialPrice::find($priceId);
model: $priceModel, $this->registerMedia(
s3Key: $variantData['photo_key'], model: $priceModel,
collectionName: 'images', s3Key: $variantData['photo_key'],
orderColumn: 1, collectionName: 'images',
); orderColumn: 1,
);
}
} }
} }
@ -307,16 +306,20 @@ private function storeNew(array $data): Purchase
'notes' => $data['notes'] ?? null, 'notes' => $data['notes'] ?? null,
]); ]);
$purchaseItems = $insertedPrices->map(fn ($price) => [ $purchaseItems = collect($data['variants'])->map(function ($v) use ($purchase, $priceIdMap, $now) {
'purchase_id' => $purchase->id, $priceId = $priceIdMap[$v['variant']] ?? null;
'raw_material_price_id' => $price->id,
'user_id' => auth()->id(), return [
'quantity' => $price->stock, 'purchase_id' => $purchase->id,
'unit_price' => $price->price, 'raw_material_price_id' => $priceId,
'subtotal' => (int) ($price->price * $price->stock), 'user_id' => auth()->id(),
'created_at' => $now, 'quantity' => $v['stock'],
'updated_at' => $now, 'unit_price' => $v['price'],
])->toArray(); 'subtotal' => (int) ($v['price'] * $v['stock']),
'created_at' => $now,
'updated_at' => $now,
];
})->toArray();
DB::table('purchase_items')->insert($purchaseItems); DB::table('purchase_items')->insert($purchaseItems);
@ -342,31 +345,28 @@ private function storeNew(array $data): Purchase
public function update(Purchase $purchase, array $data): Purchase public function update(Purchase $purchase, array $data): Purchase
{ {
$purchase = DB::transaction(function () use ($purchase, $data) { $purchase = DB::transaction(function () use ($purchase, $data) {
$purchase->load('purchaseItems.rawMaterialPrice.rawMaterial'); $oldStockMap = $purchase->purchaseItems()
->select('raw_material_price_id', 'quantity')
->get()
->groupBy('raw_material_price_id')
->map(fn ($group) => $group->sum('quantity'));
$oldItems = $purchase->purchaseItems; foreach ($oldStockMap as $priceId => $totalQty) {
RawMaterialPrice::where('id', $priceId)->decrement('stock', $totalQty);
}
// 1. Reverse the stock increments of the old items, so prices $oldMaterial = $purchase->purchaseItems()
// get adjusted by the difference instead of being reset. ->join('raw_material_prices', 'raw_material_prices.id', '=', 'purchase_items.raw_material_price_id')
$oldItems->each(function (PurchaseItem $item) { ->join('raw_materials', 'raw_materials.id', '=', 'raw_material_prices.raw_material_id')
if ($item->rawMaterialPrice) { ->select('raw_materials.*')
$item->rawMaterialPrice->decrement('stock', (int) $item->quantity);
}
});
$oldMaterial = $oldItems
->map(fn (PurchaseItem $item) => $item->rawMaterialPrice?->rawMaterial)
->filter()
->unique(fn (RawMaterial $material) => $material->id)
->first(); ->first();
$oldItems->each->delete(); $purchase->purchaseItems()->delete();
$now = now(); $now = now();
$subtotal = 0; $subtotal = 0;
if (($data['mode'] ?? 'new') === 'existing') { if (($data['mode'] ?? 'new') === 'existing') {
// 2a. Reference existing prices and add their new stock.
$itemRows = collect($data['existing_items'])->map(function ($item) use ($now, &$subtotal) { $itemRows = collect($data['existing_items'])->map(function ($item) use ($now, &$subtotal) {
$itemSubtotal = (int) ($item['unit_price'] * $item['quantity']); $itemSubtotal = (int) ($item['unit_price'] * $item['quantity']);
$subtotal += $itemSubtotal; $subtotal += $itemSubtotal;
@ -383,14 +383,14 @@ public function update(Purchase $purchase, array $data): Purchase
]; ];
})->toArray(); })->toArray();
foreach ($data['existing_items'] as $item) { $stockIncrementMap = collect($data['existing_items'])
RawMaterialPrice::whereKey($item['raw_material_price_id']) ->groupBy('raw_material_price_id')
->increment('stock', (int) $item['quantity']); ->map(fn ($group) => $group->sum('quantity'));
foreach ($stockIncrementMap as $priceId => $totalQty) {
RawMaterialPrice::where('id', $priceId)->increment('stock', $totalQty);
} }
} else { } else {
// 2a. Always reuse the purchase's existing material in place;
// a fresh material is only created when the purchase has
// no items yet.
if ($oldMaterial) { if ($oldMaterial) {
$rawMaterial = $oldMaterial; $rawMaterial = $oldMaterial;
$rawMaterial->update([ $rawMaterial->update([
@ -405,8 +405,6 @@ public function update(Purchase $purchase, array $data): Purchase
]); ]);
} }
// 2b. Adjust the stock of existing variant prices, create
// prices for new variants, but never delete variants.
$itemRows = collect($data['variants'])->map(function ($v) use ($purchase, $rawMaterial, $now, &$subtotal) { $itemRows = collect($data['variants'])->map(function ($v) use ($purchase, $rawMaterial, $now, &$subtotal) {
$price = null; $price = null;
@ -493,14 +491,15 @@ public function update(Purchase $purchase, array $data): Purchase
public function destroy(Purchase $purchase): bool public function destroy(Purchase $purchase): bool
{ {
$result = DB::transaction(function () use ($purchase) { $result = DB::transaction(function () use ($purchase) {
$purchase->load('purchaseItems.rawMaterialPrice'); $stockDecrementMap = $purchase->purchaseItems()
->select('raw_material_price_id', 'quantity')
->get()
->groupBy('raw_material_price_id')
->map(fn ($group) => $group->sum('quantity'));
// Remove the stock the purchase added, keep the variants. foreach ($stockDecrementMap as $priceId => $totalQty) {
$purchase->purchaseItems->each(function (PurchaseItem $item) { RawMaterialPrice::where('id', $priceId)->decrement('stock', $totalQty);
if ($item->rawMaterialPrice) { }
$item->rawMaterialPrice->decrement('stock', (int) $item->quantity);
}
});
$purchase->purchaseItems()->delete(); $purchase->purchaseItems()->delete();
$purchase->delete(); $purchase->delete();

View File

@ -28,14 +28,6 @@ public function getForCutting(): array
->active() ->active()
->orderBy('name') ->orderBy('name')
->get() ->get()
->each(function (RawMaterial $rawMaterial) {
$rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
$media = $price->getFirstMedia('images');
$price->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->getPath())
: null;
});
})
->toArray(); ->toArray();
} }

View File

@ -104,7 +104,7 @@ const hrItems: NavMenuItem[] = [
const sistemItems: NavMenuItem[] = [ const sistemItems: NavMenuItem[] = [
{ title: 'Pengaturan', href: '/admin/settings', icon: Settings, permission: ['settings.view_system', 'settings.view_homepage', 'settings.view_social_media', 'settings.view_hr'] }, { title: 'Pengaturan', href: '/admin/settings', icon: Settings, permission: ['settings.view_system', 'settings.view_homepage', 'settings.view_social_media', 'settings.view_hr'] },
{ title: 'Role & Permission', href: rolesIndex.url(), icon: Shield, permission: 'roles.view' }, // { title: 'Role & Permission', href: rolesIndex.url(), icon: Shield, permission: 'roles.view' },
// { title: 'Log Aktivitas', href: '#', icon: Activity, permission: 'activity_logs.view' }, // { title: 'Log Aktivitas', href: '#', icon: Activity, permission: 'activity_logs.view' },
]; ];