partial update create mutations workflow

This commit is contained in:
2025-06-12 15:10:51 +07:00
parent a5e1348436
commit 1a01efb1b5
33 changed files with 560 additions and 12994 deletions

View File

@@ -19,7 +19,7 @@ class MutationsController extends Controller
$menu = Menu::where('link','mutations.index')->first();
if ($request->ajax()) {
$data = Mutation::with(['fromDealer', 'toDealer', 'requestedBy', 'approvedBy', 'receivedBy'])
$data = Mutation::with(['fromDealer', 'toDealer', 'requestedBy.role', 'approvedBy.role', 'receivedBy.role'])
->select('mutations.*');
// Filter berdasarkan dealer jika user bukan admin
@@ -47,7 +47,19 @@ class MutationsController extends Controller
->addColumn('status', function($row) {
$statusColor = $row->status_color;
$statusLabel = $row->status_label;
return "<span class=\"kt-badge kt-badge--{$statusColor} kt-badge--dot\"></span>&nbsp;<span class=\"kt-font-bold kt-font-{$statusColor}\">{$statusLabel}</span>";
$textColorClass = match($statusColor) {
'success' => 'text-success',
'warning' => 'text-warning',
'danger' => 'text-danger',
'info' => 'text-info',
'primary' => 'text-primary',
'brand' => 'text-primary',
'secondary' => 'text-muted',
default => 'text-dark'
};
return "<span class=\"font-weight-bold {$textColorClass}\">{$statusLabel}</span>";
})
->addColumn('total_items', function($row) {
return number_format($row->total_items, 0);
@@ -79,7 +91,6 @@ class MutationsController extends Controller
$request->validate([
'from_dealer_id' => 'required|exists:dealers,id',
'to_dealer_id' => 'required|exists:dealers,id|different:from_dealer_id',
'notes' => 'nullable|string',
'products' => 'required|array|min:1',
'products.*.product_id' => 'required|exists:products,id',
'products.*.quantity_requested' => 'required|numeric|min:0.01'
@@ -92,8 +103,7 @@ class MutationsController extends Controller
'from_dealer_id' => $request->from_dealer_id,
'to_dealer_id' => $request->to_dealer_id,
'status' => 'sent',
'requested_by' => auth()->id(),
'notes' => $request->notes
'requested_by' => auth()->id()
]);
// Buat mutation details
@@ -101,8 +111,7 @@ class MutationsController extends Controller
MutationDetail::create([
'mutation_id' => $mutation->id,
'product_id' => $productData['product_id'],
'quantity_requested' => $productData['quantity_requested'],
'notes' => $productData['notes'] ?? null
'quantity_requested' => $productData['quantity_requested']
]);
}
@@ -118,24 +127,63 @@ class MutationsController extends Controller
public function show(Mutation $mutation)
{
$mutation->load(['fromDealer', 'toDealer', 'requestedBy', 'approvedBy', 'receivedBy', 'mutationDetails.product']);
$mutation->load(['fromDealer', 'toDealer', 'requestedBy.role', 'approvedBy.role', 'receivedBy.role', 'mutationDetails.product']);
return view('warehouse_management.mutations.show', compact('mutation'));
}
public function receive(Mutation $mutation)
public function receive(Request $request, Mutation $mutation)
{
$request->validate([
'notes' => 'nullable|string',
'products' => 'required|array',
'products.*.quantity_approved' => 'required|numeric|min:0',
'products.*.notes' => 'nullable|string'
]);
if (!$mutation->canBeReceived()) {
return back()->withErrors(['error' => 'Mutasi tidak dapat diterima dalam status saat ini']);
}
DB::beginTransaction();
try {
// Update mutation notes jika ada
if ($request->notes) {
$mutation->update(['notes' => $request->notes]);
}
// Update product details dengan quantity_approved dan notes
if ($request->products) {
foreach ($request->products as $detailId => $productData) {
$updateData = [];
// Set quantity_approved
if (isset($productData['quantity_approved'])) {
$updateData['quantity_approved'] = $productData['quantity_approved'];
}
// Set notes jika ada
if (isset($productData['notes']) && !empty($productData['notes'])) {
$updateData['notes'] = $productData['notes'];
}
if (!empty($updateData)) {
MutationDetail::where('id', $detailId)
->where('mutation_id', $mutation->id)
->update($updateData);
}
}
}
// Receive mutation
$mutation->receive(auth()->id());
DB::commit();
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil diterima dan menunggu persetujuan pengirim');
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Gagal menerima mutasi: ' . $e->getMessage()]);
}
}
@@ -143,34 +191,21 @@ class MutationsController extends Controller
public function approve(Request $request, Mutation $mutation)
{
$request->validate([
'notes' => 'nullable|string',
'details' => 'required|array',
'details.*.quantity_approved' => 'required|numeric|min:0'
'notes' => 'nullable|string'
]);
if (!$mutation->canBeApproved()) {
return back()->withErrors(['error' => 'Mutasi tidak dapat disetujui dalam status saat ini']);
}
DB::beginTransaction();
try {
// Update mutation details dengan quantity approved
foreach ($request->details as $detailId => $detailData) {
$mutationDetail = MutationDetail::findOrFail($detailId);
$mutationDetail->update([
'quantity_approved' => $detailData['quantity_approved']
]);
}
// Approve mutation
// Approve mutation (quantity_approved sudah diisi saat receive)
$mutation->approve(auth()->id(), $request->notes);
DB::commit();
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil disetujui');
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Gagal menyetujui mutasi: ' . $e->getMessage()]);
}
}
@@ -230,36 +265,7 @@ class MutationsController extends Controller
}
}
// API untuk mendapatkan detail mutasi untuk approval
public function getDetails(Mutation $mutation)
{
$mutation->load(['mutationDetails.product', 'fromDealer']);
$details = $mutation->mutationDetails->map(function($detail) use ($mutation) {
$availableStock = $detail->product->getStockByDealer($mutation->from_dealer_id);
return [
'id' => $detail->id,
'product' => [
'id' => $detail->product->id,
'name' => $detail->product->name
],
'quantity_requested' => $detail->quantity_requested,
'quantity_approved' => $detail->quantity_approved,
'available_stock' => $availableStock
];
});
return response()->json([
'mutation' => [
'id' => $mutation->id,
'mutation_number' => $mutation->mutation_number,
'from_dealer' => $mutation->fromDealer->name,
'to_dealer' => $mutation->toDealer->name
],
'details' => $details
]);
}
// API untuk mendapatkan stock produk di dealer tertentu
public function getProductStock(Request $request)

View File

@@ -39,14 +39,25 @@ class OpnamesController extends Controller
return Carbon::parse($row->created_at)->format('d M Y H:i');
})
->editColumn('status', function ($row) {
$statusClass = [
$statusColor = [
'draft' => 'warning',
'pending' => 'info',
'approved' => 'success',
'rejected' => 'danger'
][$row->status] ?? 'secondary';
return '<span class="badge badge-' . $statusClass . '">' . ucfirst($row->status) . '</span>';
$textColorClass = match($statusColor) {
'success' => 'text-success',
'warning' => 'text-warning',
'danger' => 'text-danger',
'info' => 'text-info',
'primary' => 'text-primary',
'brand' => 'text-primary',
'secondary' => 'text-muted',
default => 'text-dark'
};
return "<span class=\"font-weight-bold {$textColorClass}\">" . ucfirst($row->status) . "</span>";
})
->addColumn('action', function ($row) use ($menu) {
$btn = '<div class="d-flex">';

View File

@@ -6,6 +6,7 @@ use App\Enums\MutationStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;
class Mutation extends Model
{
@@ -192,18 +193,20 @@ class Mutation extends Model
throw new \Exception('Mutasi tidak dapat diselesaikan dalam status saat ini');
}
\DB::beginTransaction();
DB::beginTransaction();
try {
foreach ($this->mutationDetails as $detail) {
if ($detail->quantity_approved > 0) {
// Proses semua detail yang memiliki quantity_requested > 0
// karena barang sudah dikirim dari dealer asal
if ($detail->quantity_requested > 0) {
$this->processStockMovement($detail);
}
}
$this->update(['status' => MutationStatus::COMPLETED]);
\DB::commit();
DB::commit();
} catch (\Exception $e) {
\DB::rollback();
DB::rollback();
throw $e;
}
@@ -212,23 +215,23 @@ class Mutation extends Model
private function processStockMovement(MutationDetail $detail)
{
// Kurangi stock dari dealer asal
// Kurangi stock dari dealer asal berdasarkan quantity_requested (barang yang dikirim)
$fromStock = Stock::firstOrCreate([
'product_id' => $detail->product_id,
'dealer_id' => $this->from_dealer_id
], ['quantity' => 0]);
if ($fromStock->quantity < $detail->quantity_approved) {
if ($fromStock->quantity < $detail->quantity_requested) {
throw new \Exception("Stock tidak mencukupi untuk produk {$detail->product->name} di {$this->fromDealer->name}");
}
$fromStock->updateStock(
$fromStock->quantity - $detail->quantity_approved,
$fromStock->quantity - $detail->quantity_requested,
$this,
"Mutasi keluar ke {$this->toDealer->name} - {$this->mutation_number}"
"Mutasi keluar ke {$this->toDealer->name} - {$this->mutation_number} (Dikirim: {$detail->quantity_requested}, Diterima: {$detail->quantity_approved})"
);
// Tambah stock ke dealer tujuan
// Tambah stock ke dealer tujuan berdasarkan quantity_approved (barang yang diterima)
$toStock = Stock::firstOrCreate([
'product_id' => $detail->product_id,
'dealer_id' => $this->to_dealer_id
@@ -237,8 +240,23 @@ class Mutation extends Model
$toStock->updateStock(
$toStock->quantity + $detail->quantity_approved,
$this,
"Mutasi masuk dari {$this->fromDealer->name} - {$this->mutation_number}"
"Mutasi masuk dari {$this->fromDealer->name} - {$this->mutation_number} (Dikirim: {$detail->quantity_requested}, Diterima: {$detail->quantity_approved})"
);
// Jika ada selisih (kehilangan), catat sebagai stock log terpisah untuk audit
$lostQuantity = $detail->quantity_requested - $detail->quantity_approved;
if ($lostQuantity > 0) {
// Buat stock log untuk barang yang hilang/rusak
StockLog::create([
'stock_id' => $fromStock->id,
'previous_quantity' => $fromStock->quantity + $detail->quantity_requested, // Stock sebelum pengurangan
'new_quantity' => $fromStock->quantity, // Stock setelah pengurangan
'source_type' => get_class($this),
'source_id' => $this->id,
'description' => "Kehilangan/kerusakan saat mutasi ke {$this->toDealer->name} - {$this->mutation_number} (Hilang: {$lostQuantity})",
'user_id' => auth()->id()
]);
}
}
private function generateMutationNumber()

View File

@@ -40,21 +40,31 @@ class MutationDetail extends Model
public function isFullyApproved()
{
return $this->quantity_approved == $this->quantity_requested;
return $this->quantity_approved !== null && $this->quantity_approved == $this->quantity_requested;
}
public function isPartiallyApproved()
{
return $this->quantity_approved > 0 && $this->quantity_approved < $this->quantity_requested;
return $this->quantity_approved !== null && $this->quantity_approved > 0 && $this->quantity_approved < $this->quantity_requested;
}
public function isRejected()
{
return $this->quantity_approved == 0;
// Hanya dianggap ditolak jika mutasi sudah di-approve/reject dan quantity_approved = 0
$mutationStatus = $this->mutation->status->value ?? null;
return in_array($mutationStatus, ['approved', 'completed', 'rejected']) && $this->quantity_approved == 0;
}
public function getApprovalStatusAttribute()
{
$mutationStatus = $this->mutation->status->value ?? null;
// Jika mutasi belum di-approve, semua detail statusnya "Menunggu"
if (!in_array($mutationStatus, ['approved', 'completed', 'rejected'])) {
return 'Menunggu';
}
// Jika mutasi sudah di-approve/complete, baru cek quantity_approved
if ($this->isFullyApproved()) {
return 'Disetujui Penuh';
} elseif ($this->isPartiallyApproved()) {
@@ -68,6 +78,14 @@ class MutationDetail extends Model
public function getApprovalStatusColorAttribute()
{
$mutationStatus = $this->mutation->status->value ?? null;
// Jika mutasi belum di-approve, semua detail statusnya "info" (menunggu)
if (!in_array($mutationStatus, ['approved', 'completed', 'rejected'])) {
return 'info';
}
// Jika mutasi sudah di-approve/complete, baru cek quantity_approved
if ($this->isFullyApproved()) {
return 'success';
} elseif ($this->isPartiallyApproved()) {

View File

@@ -75,4 +75,61 @@ class User extends Authenticatable
{
return $this->hasOne(Dealer::class, 'id', 'dealer_id');
}
/**
* Get the role associated with the User
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function role()
{
return $this->belongsTo(Role::class, 'role_id');
}
/**
* Check if user has a specific role
*
* @param string $roleName
* @return bool
*/
public function hasRole($roleName)
{
// If role_id is 0 or null, user has no role
if (!$this->role_id) {
return false;
}
// For admin role, we can check if user has admin privileges
if (strtolower($roleName) === 'admin') {
return $this->isAdmin();
}
// Load role if not already loaded
if (!$this->relationLoaded('role')) {
$this->load('role');
}
return $this->role && strtolower($this->role->name) === strtolower($roleName);
}
/**
* Check if user is admin by checking admin privileges
*
* @return bool
*/
public function isAdmin()
{
// Check if user has admin privileges by checking if they can access admin area
try {
$adminPrivilege = \App\Models\Privilege::join('menus', 'menus.id', '=', 'privileges.menu_id')
->where('menus.link', 'adminarea')
->where('privileges.role_id', $this->role_id)
->where('privileges.view', 1)
->first();
return $adminPrivilege !== null;
} catch (\Exception $e) {
return false;
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Update existing records yang quantity_approved = 0 menjadi null untuk mutasi yang belum approved
DB::table('mutation_details')
->join('mutations', 'mutations.id', '=', 'mutation_details.mutation_id')
->where('mutation_details.quantity_approved', 0)
->whereNotIn('mutations.status', ['approved', 'completed', 'rejected'])
->update(['mutation_details.quantity_approved' => null]);
Schema::table('mutation_details', function (Blueprint $table) {
$table->decimal('quantity_approved', 15, 2)->nullable()->default(null)->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('mutation_details', function (Blueprint $table) {
$table->decimal('quantity_approved', 15, 2)->default(0)->change();
});
}
};

File diff suppressed because one or more lines are too long

1
public/css/app.css.map Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,9 @@
/*!
* Bootstrap v5.1.3 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/**
* @license
* Lodash <https://lodash.com/>

1
public/js/app.js.map Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,32 +1,2 @@
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./resources/js/warehouse_management/opnames/detail.js":
/*!*************************************************************!*\
!*** ./resources/js/warehouse_management/opnames/detail.js ***!
\*************************************************************/
/***/ (() => {
eval("$.ajaxSetup({\n headers: {\n \"X-CSRF-TOKEN\": $('meta[name=\"csrf-token\"]').attr(\"content\")\n }\n});\nvar tableContainer = $(\"#opname-detail-table\");\nvar url = tableContainer.data(\"url\");\nvar table = $(\"#opname-detail-table\").DataTable({\n processing: true,\n serverSide: true,\n ajax: url,\n columns: [{\n data: \"opname_date\",\n name: \"opname_date\"\n }, {\n data: \"user_name\",\n name: \"user.name\"\n }, {\n data: \"product_name\",\n name: \"product.name\"\n }, {\n data: \"system_stock\",\n name: \"system_stock\"\n }, {\n data: \"physical_stock\",\n name: \"physical_stock\"\n }, {\n data: \"difference\",\n name: \"difference\"\n }]\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyIkIiwiYWpheFNldHVwIiwiaGVhZGVycyIsImF0dHIiLCJ0YWJsZUNvbnRhaW5lciIsInVybCIsImRhdGEiLCJ0YWJsZSIsIkRhdGFUYWJsZSIsInByb2Nlc3NpbmciLCJzZXJ2ZXJTaWRlIiwiYWpheCIsImNvbHVtbnMiLCJuYW1lIl0sInNvdXJjZXMiOlsid2VicGFjazovLy8uL3Jlc291cmNlcy9qcy93YXJlaG91c2VfbWFuYWdlbWVudC9vcG5hbWVzL2RldGFpbC5qcz9mMzY3Il0sInNvdXJjZXNDb250ZW50IjpbIiQuYWpheFNldHVwKHtcbiAgICBoZWFkZXJzOiB7XG4gICAgICAgIFwiWC1DU1JGLVRPS0VOXCI6ICQoJ21ldGFbbmFtZT1cImNzcmYtdG9rZW5cIl0nKS5hdHRyKFwiY29udGVudFwiKSxcbiAgICB9LFxufSk7XG5sZXQgdGFibGVDb250YWluZXIgPSAkKFwiI29wbmFtZS1kZXRhaWwtdGFibGVcIik7XG5sZXQgdXJsID0gdGFibGVDb250YWluZXIuZGF0YShcInVybFwiKTtcbmxldCB0YWJsZSA9ICQoXCIjb3BuYW1lLWRldGFpbC10YWJsZVwiKS5EYXRhVGFibGUoe1xuICAgIHByb2Nlc3Npbmc6IHRydWUsXG4gICAgc2VydmVyU2lkZTogdHJ1ZSxcbiAgICBhamF4OiB1cmwsXG4gICAgY29sdW1uczogW1xuICAgICAgICB7IGRhdGE6IFwib3BuYW1lX2RhdGVcIiwgbmFtZTogXCJvcG5hbWVfZGF0ZVwiIH0sXG4gICAgICAgIHsgZGF0YTogXCJ1c2VyX25hbWVcIiwgbmFtZTogXCJ1c2VyLm5hbWVcIiB9LFxuICAgICAgICB7IGRhdGE6IFwicHJvZHVjdF9uYW1lXCIsIG5hbWU6IFwicHJvZHVjdC5uYW1lXCIgfSxcbiAgICAgICAgeyBkYXRhOiBcInN5c3RlbV9zdG9ja1wiLCBuYW1lOiBcInN5c3RlbV9zdG9ja1wiIH0sXG4gICAgICAgIHsgZGF0YTogXCJwaHlzaWNhbF9zdG9ja1wiLCBuYW1lOiBcInBoeXNpY2FsX3N0b2NrXCIgfSxcbiAgICAgICAgeyBkYXRhOiBcImRpZmZlcmVuY2VcIiwgbmFtZTogXCJkaWZmZXJlbmNlXCIgfSxcbiAgICBdLFxufSk7XG4iXSwibWFwcGluZ3MiOiJBQUFBQSxDQUFDLENBQUNDLFNBQUYsQ0FBWTtFQUNSQyxPQUFPLEVBQUU7SUFDTCxnQkFBZ0JGLENBQUMsQ0FBQyx5QkFBRCxDQUFELENBQTZCRyxJQUE3QixDQUFrQyxTQUFsQztFQURYO0FBREQsQ0FBWjtBQUtBLElBQUlDLGNBQWMsR0FBR0osQ0FBQyxDQUFDLHNCQUFELENBQXRCO0FBQ0EsSUFBSUssR0FBRyxHQUFHRCxjQUFjLENBQUNFLElBQWYsQ0FBb0IsS0FBcEIsQ0FBVjtBQUNBLElBQUlDLEtBQUssR0FBR1AsQ0FBQyxDQUFDLHNCQUFELENBQUQsQ0FBMEJRLFNBQTFCLENBQW9DO0VBQzVDQyxVQUFVLEVBQUUsSUFEZ0M7RUFFNUNDLFVBQVUsRUFBRSxJQUZnQztFQUc1Q0MsSUFBSSxFQUFFTixHQUhzQztFQUk1Q08sT0FBTyxFQUFFLENBQ0w7SUFBRU4sSUFBSSxFQUFFLGFBQVI7SUFBdUJPLElBQUksRUFBRTtFQUE3QixDQURLLEVBRUw7SUFBRVAsSUFBSSxFQUFFLFdBQVI7SUFBcUJPLElBQUksRUFBRTtFQUEzQixDQUZLLEVBR0w7SUFBRVAsSUFBSSxFQUFFLGNBQVI7SUFBd0JPLElBQUksRUFBRTtFQUE5QixDQUhLLEVBSUw7SUFBRVAsSUFBSSxFQUFFLGNBQVI7SUFBd0JPLElBQUksRUFBRTtFQUE5QixDQUpLLEVBS0w7SUFBRVAsSUFBSSxFQUFFLGdCQUFSO0lBQTBCTyxJQUFJLEVBQUU7RUFBaEMsQ0FMSyxFQU1MO0lBQUVQLElBQUksRUFBRSxZQUFSO0lBQXNCTyxJQUFJLEVBQUU7RUFBNUIsQ0FOSztBQUptQyxDQUFwQyxDQUFaIiwiZmlsZSI6Ii4vcmVzb3VyY2VzL2pzL3dhcmVob3VzZV9tYW5hZ2VtZW50L29wbmFtZXMvZGV0YWlsLmpzIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./resources/js/warehouse_management/opnames/detail.js\n");
/***/ })
/******/ });
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval-source-map devtool is used.
/******/ var __webpack_exports__ = {};
/******/ __webpack_modules__["./resources/js/warehouse_management/opnames/detail.js"]();
/******/
/******/ })()
;
(()=>{$.ajaxSetup({headers:{"X-CSRF-TOKEN":$('meta[name="csrf-token"]').attr("content")}});var a=$("#opname-detail-table").data("url");$("#opname-detail-table").DataTable({processing:!0,serverSide:!0,ajax:a,columns:[{data:"opname_date",name:"opname_date"},{data:"user_name",name:"user.name"},{data:"product_name",name:"product.name"},{data:"system_stock",name:"system_stock"},{data:"physical_stock",name:"physical_stock"},{data:"difference",name:"difference"}]})})();
//# sourceMappingURL=detail.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"/js/warehouse_management/opnames/detail.js","mappings":"MAAAA,EAAEC,UAAU,CACRC,QAAS,CACL,eAAgBF,EAAE,2BAA2BG,KAAK,cAG1D,IACIC,EADiBJ,EAAE,wBACEK,KAAK,OAClBL,EAAE,wBAAwBM,UAAU,CAC5CC,YAAY,EACZC,YAAY,EACZC,KAAML,EACNM,QAAS,CACL,CAAEL,KAAM,cAAeM,KAAM,eAC7B,CAAEN,KAAM,YAAaM,KAAM,aAC3B,CAAEN,KAAM,eAAgBM,KAAM,gBAC9B,CAAEN,KAAM,eAAgBM,KAAM,gBAC9B,CAAEN,KAAM,iBAAkBM,KAAM,kBAChC,CAAEN,KAAM,aAAcM,KAAM,gB","sources":["webpack:///./resources/js/warehouse_management/opnames/detail.js"],"sourcesContent":["$.ajaxSetup({\n headers: {\n \"X-CSRF-TOKEN\": $('meta[name=\"csrf-token\"]').attr(\"content\"),\n },\n});\nlet tableContainer = $(\"#opname-detail-table\");\nlet url = tableContainer.data(\"url\");\nlet table = $(\"#opname-detail-table\").DataTable({\n processing: true,\n serverSide: true,\n ajax: url,\n columns: [\n { data: \"opname_date\", name: \"opname_date\" },\n { data: \"user_name\", name: \"user.name\" },\n { data: \"product_name\", name: \"product.name\" },\n { data: \"system_stock\", name: \"system_stock\" },\n { data: \"physical_stock\", name: \"physical_stock\" },\n { data: \"difference\", name: \"difference\" },\n ],\n});\n"],"names":["$","ajaxSetup","headers","attr","url","data","DataTable","processing","serverSide","ajax","columns","name"],"sourceRoot":""}

View File

@@ -1,32 +1,2 @@
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./resources/js/warehouse_management/opnames/index.js":
/*!************************************************************!*\
!*** ./resources/js/warehouse_management/opnames/index.js ***!
\************************************************************/
/***/ (() => {
eval("$.ajaxSetup({\n headers: {\n \"X-CSRF-TOKEN\": $('meta[name=\"csrf-token\"]').attr(\"content\")\n }\n});\nvar tableContainer = $(\"#opnames-table\");\nvar url = tableContainer.data(\"url\");\nvar table = $(\"#opnames-table\").DataTable({\n processing: true,\n serverSide: true,\n ajax: url,\n order: [[0, \"desc\"]],\n columns: [{\n data: \"created_at\",\n name: \"created_at\",\n visible: false\n }, {\n data: \"opname_date\",\n name: \"opname_date\"\n }, {\n data: \"dealer_name\",\n name: \"dealer.name\"\n }, {\n data: \"user_name\",\n name: \"user.name\"\n }, {\n data: \"status\",\n name: \"status\"\n }, {\n data: \"action\",\n name: \"action\",\n orderable: false,\n searchable: false\n }]\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyIkIiwiYWpheFNldHVwIiwiaGVhZGVycyIsImF0dHIiLCJ0YWJsZUNvbnRhaW5lciIsInVybCIsImRhdGEiLCJ0YWJsZSIsIkRhdGFUYWJsZSIsInByb2Nlc3NpbmciLCJzZXJ2ZXJTaWRlIiwiYWpheCIsIm9yZGVyIiwiY29sdW1ucyIsIm5hbWUiLCJ2aXNpYmxlIiwib3JkZXJhYmxlIiwic2VhcmNoYWJsZSJdLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9yZXNvdXJjZXMvanMvd2FyZWhvdXNlX21hbmFnZW1lbnQvb3BuYW1lcy9pbmRleC5qcz9hNGM4Il0sInNvdXJjZXNDb250ZW50IjpbIiQuYWpheFNldHVwKHtcbiAgICBoZWFkZXJzOiB7XG4gICAgICAgIFwiWC1DU1JGLVRPS0VOXCI6ICQoJ21ldGFbbmFtZT1cImNzcmYtdG9rZW5cIl0nKS5hdHRyKFwiY29udGVudFwiKSxcbiAgICB9LFxufSk7XG5cbmxldCB0YWJsZUNvbnRhaW5lciA9ICQoXCIjb3BuYW1lcy10YWJsZVwiKTtcbmxldCB1cmwgPSB0YWJsZUNvbnRhaW5lci5kYXRhKFwidXJsXCIpO1xubGV0IHRhYmxlID0gJChcIiNvcG5hbWVzLXRhYmxlXCIpLkRhdGFUYWJsZSh7XG4gICAgcHJvY2Vzc2luZzogdHJ1ZSxcbiAgICBzZXJ2ZXJTaWRlOiB0cnVlLFxuICAgIGFqYXg6IHVybCxcbiAgICBvcmRlcjogW1swLCBcImRlc2NcIl1dLFxuICAgIGNvbHVtbnM6IFtcbiAgICAgICAgeyBkYXRhOiBcImNyZWF0ZWRfYXRcIiwgbmFtZTogXCJjcmVhdGVkX2F0XCIsIHZpc2libGU6IGZhbHNlIH0sXG4gICAgICAgIHsgZGF0YTogXCJvcG5hbWVfZGF0ZVwiLCBuYW1lOiBcIm9wbmFtZV9kYXRlXCIgfSxcbiAgICAgICAgeyBkYXRhOiBcImRlYWxlcl9uYW1lXCIsIG5hbWU6IFwiZGVhbGVyLm5hbWVcIiB9LFxuICAgICAgICB7IGRhdGE6IFwidXNlcl9uYW1lXCIsIG5hbWU6IFwidXNlci5uYW1lXCIgfSxcbiAgICAgICAgeyBkYXRhOiBcInN0YXR1c1wiLCBuYW1lOiBcInN0YXR1c1wiIH0sXG4gICAgICAgIHsgZGF0YTogXCJhY3Rpb25cIiwgbmFtZTogXCJhY3Rpb25cIiwgb3JkZXJhYmxlOiBmYWxzZSwgc2VhcmNoYWJsZTogZmFsc2UgfSxcbiAgICBdLFxufSk7XG4iXSwibWFwcGluZ3MiOiJBQUFBQSxDQUFDLENBQUNDLFNBQUYsQ0FBWTtFQUNSQyxPQUFPLEVBQUU7SUFDTCxnQkFBZ0JGLENBQUMsQ0FBQyx5QkFBRCxDQUFELENBQTZCRyxJQUE3QixDQUFrQyxTQUFsQztFQURYO0FBREQsQ0FBWjtBQU1BLElBQUlDLGNBQWMsR0FBR0osQ0FBQyxDQUFDLGdCQUFELENBQXRCO0FBQ0EsSUFBSUssR0FBRyxHQUFHRCxjQUFjLENBQUNFLElBQWYsQ0FBb0IsS0FBcEIsQ0FBVjtBQUNBLElBQUlDLEtBQUssR0FBR1AsQ0FBQyxDQUFDLGdCQUFELENBQUQsQ0FBb0JRLFNBQXBCLENBQThCO0VBQ3RDQyxVQUFVLEVBQUUsSUFEMEI7RUFFdENDLFVBQVUsRUFBRSxJQUYwQjtFQUd0Q0MsSUFBSSxFQUFFTixHQUhnQztFQUl0Q08sS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFELEVBQUksTUFBSixDQUFELENBSitCO0VBS3RDQyxPQUFPLEVBQUUsQ0FDTDtJQUFFUCxJQUFJLEVBQUUsWUFBUjtJQUFzQlEsSUFBSSxFQUFFLFlBQTVCO0lBQTBDQyxPQUFPLEVBQUU7RUFBbkQsQ0FESyxFQUVMO0lBQUVULElBQUksRUFBRSxhQUFSO0lBQXVCUSxJQUFJLEVBQUU7RUFBN0IsQ0FGSyxFQUdMO0lBQUVSLElBQUksRUFBRSxhQUFSO0lBQXVCUSxJQUFJLEVBQUU7RUFBN0IsQ0FISyxFQUlMO0lBQUVSLElBQUksRUFBRSxXQUFSO0lBQXFCUSxJQUFJLEVBQUU7RUFBM0IsQ0FKSyxFQUtMO0lBQUVSLElBQUksRUFBRSxRQUFSO0lBQWtCUSxJQUFJLEVBQUU7RUFBeEIsQ0FMSyxFQU1MO0lBQUVSLElBQUksRUFBRSxRQUFSO0lBQWtCUSxJQUFJLEVBQUUsUUFBeEI7SUFBa0NFLFNBQVMsRUFBRSxLQUE3QztJQUFvREMsVUFBVSxFQUFFO0VBQWhFLENBTks7QUFMNkIsQ0FBOUIsQ0FBWiIsImZpbGUiOiIuL3Jlc291cmNlcy9qcy93YXJlaG91c2VfbWFuYWdlbWVudC9vcG5hbWVzL2luZGV4LmpzIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./resources/js/warehouse_management/opnames/index.js\n");
/***/ })
/******/ });
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval-source-map devtool is used.
/******/ var __webpack_exports__ = {};
/******/ __webpack_modules__["./resources/js/warehouse_management/opnames/index.js"]();
/******/
/******/ })()
;
(()=>{$.ajaxSetup({headers:{"X-CSRF-TOKEN":$('meta[name="csrf-token"]').attr("content")}});var a=$("#opnames-table").data("url");$("#opnames-table").DataTable({processing:!0,serverSide:!0,ajax:a,order:[[0,"desc"]],columns:[{data:"created_at",name:"created_at",visible:!1},{data:"opname_date",name:"opname_date"},{data:"dealer_name",name:"dealer.name"},{data:"user_name",name:"user.name"},{data:"status",name:"status"},{data:"action",name:"action",orderable:!1,searchable:!1}]})})();
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"/js/warehouse_management/opnames/index.js","mappings":"MAAAA,EAAEC,UAAU,CACRC,QAAS,CACL,eAAgBF,EAAE,2BAA2BG,KAAK,cAI1D,IACIC,EADiBJ,EAAE,kBACEK,KAAK,OAClBL,EAAE,kBAAkBM,UAAU,CACtCC,YAAY,EACZC,YAAY,EACZC,KAAML,EACNM,MAAO,CAAC,CAAC,EAAG,SACZC,QAAS,CACL,CAAEN,KAAM,aAAcO,KAAM,aAAcC,SAAS,GACnD,CAAER,KAAM,cAAeO,KAAM,eAC7B,CAAEP,KAAM,cAAeO,KAAM,eAC7B,CAAEP,KAAM,YAAaO,KAAM,aAC3B,CAAEP,KAAM,SAAUO,KAAM,UACxB,CAAEP,KAAM,SAAUO,KAAM,SAAUE,WAAW,EAAOC,YAAY,K","sources":["webpack:///./resources/js/warehouse_management/opnames/index.js"],"sourcesContent":["$.ajaxSetup({\n headers: {\n \"X-CSRF-TOKEN\": $('meta[name=\"csrf-token\"]').attr(\"content\"),\n },\n});\n\nlet tableContainer = $(\"#opnames-table\");\nlet url = tableContainer.data(\"url\");\nlet table = $(\"#opnames-table\").DataTable({\n processing: true,\n serverSide: true,\n ajax: url,\n order: [[0, \"desc\"]],\n columns: [\n { data: \"created_at\", name: \"created_at\", visible: false },\n { data: \"opname_date\", name: \"opname_date\" },\n { data: \"dealer_name\", name: \"dealer.name\" },\n { data: \"user_name\", name: \"user.name\" },\n { data: \"status\", name: \"status\" },\n { data: \"action\", name: \"action\", orderable: false, searchable: false },\n ],\n});\n"],"names":["$","ajaxSetup","headers","attr","url","data","DataTable","processing","serverSide","ajax","order","columns","name","visible","orderable","searchable"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,6 @@
$(document).ready(function () {
let productIndex = 1;
let originalProductOptions = ""; // Store original product options
// Initialize Select2
$(".select2").select2({
@@ -7,6 +8,12 @@ $(document).ready(function () {
allowClear: true,
});
// Store original product options on page load
const firstSelect = $(".product-select").first();
if (firstSelect.length > 0) {
originalProductOptions = firstSelect.html();
}
// Prevent same dealer selection
$("#from_dealer_id, #to_dealer_id").on("change", function () {
const fromDealerId = $("#from_dealer_id").val();
@@ -26,12 +33,12 @@ $(document).ready(function () {
const newRow = createProductRow(productIndex);
$("#products-tbody").append(newRow);
// Initialize Select2 for new row
// Initialize Select2 for new row after it's added to DOM
const newSelect = $(
`#products-tbody tr[data-index="${productIndex}"] .product-select`
);
newSelect.select2({
placeholder: "Pilih Produk...",
placeholder: "Pilih...",
allowClear: true,
});
@@ -100,18 +107,14 @@ $(document).ready(function () {
});
function createProductRow(index) {
// Get product options from the existing select
const existingSelect = $(".product-select").first();
const productOptions = existingSelect.html();
return `
<tr class="product-row" data-index="${index}">
<td>
<select name="products[${index}][product_id]" class="form-control select2 product-select" required>
${productOptions}
<select name="products[${index}][product_id]" class="form-control product-select" required>
${originalProductOptions}
</select>
</td>
<td>
<td class="text-center">
<span class="available-stock text-muted">-</span>
</td>
<td>
@@ -123,12 +126,7 @@ $(document).ready(function () {
placeholder="0"
required>
</td>
<td>
<input type="text"
name="products[${index}][notes]"
class="form-control"
placeholder="Catatan produk (opsional)">
</td>
<td>
<button type="button" class="btn btn-danger btn-sm remove-product">
<i class="la la-trash"></i>
@@ -152,9 +150,6 @@ $(document).ready(function () {
$(this)
.find('input[name*="quantity_requested"]')
.attr("name", `products[${index}][quantity_requested]`);
$(this)
.find('input[name*="notes"]')
.attr("name", `products[${index}][notes]`);
});
productIndex = $(".product-row").length;
}

View File

@@ -28,12 +28,12 @@ $(document).ready(function () {
{
data: "from_dealer",
name: "fromDealer.name",
width: "15%",
width: "13%",
},
{
data: "to_dealer",
name: "toDealer.name",
width: "15%",
width: "13%",
},
{
data: "requested_by",
@@ -57,117 +57,17 @@ $(document).ready(function () {
name: "action",
orderable: false,
searchable: false,
width: "15%",
width: "20%",
className: "text-center",
},
],
order: [[2, "desc"]], // Order by created_at desc
pageLength: 10,
responsive: true,
language: {
processing: "Memuat data...",
lengthMenu: "Tampilkan _MENU_ data per halaman",
zeroRecords: "Data tidak ditemukan",
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ data",
infoEmpty: "Menampilkan 0 sampai 0 dari 0 data",
infoFiltered: "(difilter dari _MAX_ total data)",
},
});
// Handle Receive Button Click
$(document).on("click", ".btn-receive", function () {
var mutationId = $(this).data("id");
$("#receiveModal" + mutationId).modal("show");
});
// Handle Approve Button Click
$(document).on("click", ".btn-approve", function () {
var mutationId = $(this).data("id");
// Load mutation details via AJAX
$.ajax({
url: "/warehouse/mutations/" + mutationId + "/details",
type: "GET",
beforeSend: function () {
$("#mutation-details" + mutationId).html(
'<div class="text-center">' +
'<div class="spinner-border" role="status">' +
'<span class="sr-only">Loading...</span>' +
"</div>" +
"<p>Memuat detail produk...</p>" +
"</div>"
);
},
success: function (response) {
var detailsHtml = "<h6>Detail Produk:</h6>";
detailsHtml += '<div class="table-responsive">';
detailsHtml += '<table class="table table-sm">';
detailsHtml += "<thead>";
detailsHtml += "<tr>";
detailsHtml += "<th>Produk</th>";
detailsHtml += "<th>Diminta</th>";
detailsHtml += "<th>Disetujui</th>";
detailsHtml += "<th>Stock Tersedia</th>";
detailsHtml += "</tr>";
detailsHtml += "</thead>";
detailsHtml += "<tbody>";
response.details.forEach(function (detail, index) {
detailsHtml += "<tr>";
detailsHtml += "<td>" + detail.product.name + "</td>";
detailsHtml +=
"<td>" +
parseFloat(detail.quantity_requested).toLocaleString() +
"</td>";
detailsHtml += "<td>";
detailsHtml +=
'<input type="number" name="details[' +
detail.id +
'][quantity_approved]" ';
detailsHtml += 'class="form-control form-control-sm" ';
detailsHtml += 'value="' + detail.quantity_requested + '" ';
detailsHtml +=
'min="0" max="' +
Math.min(
detail.quantity_requested,
detail.available_stock
) +
'" ';
detailsHtml += 'step="0.01" required>';
detailsHtml += "</td>";
detailsHtml +=
"<td>" +
parseFloat(detail.available_stock).toLocaleString() +
"</td>";
detailsHtml += "</tr>";
});
detailsHtml += "</tbody>";
detailsHtml += "</table>";
detailsHtml += "</div>";
$("#mutation-details" + mutationId).html(detailsHtml);
},
error: function () {
$("#mutation-details" + mutationId).html(
'<div class="alert alert-danger">Gagal memuat detail produk</div>'
);
},
});
$("#approveModal" + mutationId).modal("show");
});
// Handle other button clicks
$(document).on("click", ".btn-reject", function () {
var mutationId = $(this).data("id");
$("#rejectModal" + mutationId).modal("show");
});
$(document).on("click", ".btn-complete", function () {
var mutationId = $(this).data("id");
$("#completeModal" + mutationId).modal("show");
});
// Modal event handlers are now handled by Bootstrap 5 data attributes
// No need for manual modal show/hide handlers
// Handle Cancel Button Click with SweetAlert
$(document).on("click", ".btn-cancel", function () {
@@ -240,7 +140,7 @@ $(document).ready(function () {
.html("Memproses...");
});
// Auto-calculate approved quantity based on available stock
// Validate quantity approved in receive modal
$(document).on("input", 'input[name*="quantity_approved"]', function () {
var maxValue = parseFloat($(this).attr("max"));
var currentValue = parseFloat($(this).val());
@@ -250,7 +150,7 @@ $(document).ready(function () {
$(this).addClass("is-invalid");
if (!$(this).siblings(".invalid-feedback").length) {
$(this).after(
'<div class="invalid-feedback">Jumlah melebihi stock yang tersedia</div>'
'<div class="invalid-feedback">Quantity tidak boleh melebihi yang diminta</div>'
);
}
} else {

View File

@@ -1,4 +1,9 @@
$(document).ready(function () {
$(".select2").select2({
placeholder: "Pilih...",
allowClear: true,
});
// Fungsi untuk mengambil data stok
function fetchStockData() {
const dealerId = $("#dealer").val();

View File

@@ -1,29 +1,28 @@
<div class="btn-group btn-group-sm" role="group">
<!-- View Button -->
<a href="{{ route('mutations.show', $row->id) }}"
class="btn btn-sm btn-clean btn-icon btn-icon-md"
title="Lihat Detail">
<i class="la la-eye"></i>
class="btn btn-sm btn-outline-info me-1">
Detail
</a>
@if($row->status->value === 'sent')
<!-- Receive Button (untuk dealer tujuan) -->
@if(auth()->user()->dealer_id == $row->to_dealer_id)
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-receive"
class="btn btn-sm btn-outline-primary btn-receive me-1"
data-id="{{ $row->id }}"
title="Terima Mutasi">
<i class="la la-download text-primary"></i>
data-bs-toggle="modal"
data-bs-target="#receiveModal{{ $row->id }}">
Terima
</button>
@endif
<!-- Cancel Button (untuk pengirim) -->
@if(auth()->user()->dealer_id == $row->from_dealer_id || auth()->user()->hasRole('admin'))
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-cancel"
data-id="{{ $row->id }}"
title="Batalkan Mutasi">
<i class="la la-ban text-warning"></i>
class="btn btn-sm btn-outline-warning btn-cancel me-1"
data-id="{{ $row->id }}">
Batal
</button>
@endif
@endif
@@ -32,20 +31,22 @@
<!-- Approve Button (untuk pengirim atau admin) -->
@if(auth()->user()->dealer_id == $row->from_dealer_id || auth()->user()->hasRole('admin'))
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-approve"
class="btn btn-sm btn-outline-success btn-approve me-1"
data-id="{{ $row->id }}"
title="Setujui Mutasi">
<i class="la la-check text-success"></i>
data-bs-toggle="modal"
data-bs-target="#approveModal{{ $row->id }}">
Setujui
</button>
@endif
<!-- Reject Button (untuk pengirim atau admin) -->
@if(auth()->user()->dealer_id == $row->from_dealer_id || auth()->user()->hasRole('admin'))
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-reject"
class="btn btn-sm btn-outline-danger btn-reject me-1"
data-id="{{ $row->id }}"
title="Tolak Mutasi">
<i class="la la-times text-danger"></i>
data-bs-toggle="modal"
data-bs-target="#rejectModal{{ $row->id }}">
Tolak
</button>
@endif
@endif
@@ -54,20 +55,20 @@
<!-- Complete/Receive Button -->
@can('complete-mutation')
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-complete"
class="btn btn-sm btn-outline-primary btn-complete me-1"
data-id="{{ $row->id }}"
title="Terima & Selesaikan Mutasi">
<i class="la la-check-circle text-primary"></i>
data-bs-toggle="modal"
data-bs-target="#completeModal{{ $row->id }}">
Selesaikan
</button>
@endcan
<!-- Cancel Button -->
@can('edit-mutation')
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-cancel"
data-id="{{ $row->id }}"
title="Batalkan Mutasi">
<i class="la la-ban text-warning"></i>
class="btn btn-sm btn-outline-warning btn-cancel me-1"
data-id="{{ $row->id }}">
Batal
</button>
@endcan
@endif
@@ -75,53 +76,71 @@
@if(in_array($row->status->value, ['pending', 'approved']) && auth()->user()->id === $row->requested_by)
<!-- Edit Button (only for creator and if still pending/approved) -->
<a href="{{ route('mutations.edit', $row->id) }}"
class="btn btn-sm btn-clean btn-icon btn-icon-md"
title="Edit Mutasi">
<i class="la la-edit text-info"></i>
</a>
@endif
@if($row->status->value === 'completed')
<!-- Print Button -->
<a href="{{ route('mutations.print', $row->id) }}"
class="btn btn-sm btn-clean btn-icon btn-icon-md"
target="_blank"
title="Cetak Laporan">
<i class="la la-print text-info"></i>
class="btn btn-sm btn-outline-secondary me-1">
Edit
</a>
@endif
</div>
<!-- Modal untuk Approve -->
<div class="modal fade" id="approveModal{{ $row->id }}" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal fade" id="approveModal{{ $row->id }}" tabindex="-1" aria-labelledby="approveModalLabel{{ $row->id }}" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Setujui Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title" id="approveModalLabel{{ $row->id }}">Setujui Mutasi</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ route('mutations.approve', $row->id) }}" method="POST" class="approve-form">
@csrf
<div class="modal-body">
<div class="alert alert-info">
<strong>Konfirmasi!</strong> Anda akan menyetujui mutasi yang telah diterima oleh <strong>{{ $row->toDealer->name }}</strong>.
</div>
<div class="form-group">
<label>Catatan Persetujuan</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Opsional: tambahkan catatan..."></textarea>
</div>
<!-- Detail produk akan dimuat via AJAX -->
<div id="mutation-details{{ $row->id }}">
<div class="text-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
<p>Memuat detail produk...</p>
</div>
<h6>Detail Produk yang Diterima:</h6>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Produk</th>
<th width="20%" class="text-center">Qty Diminta</th>
<th width="20%" class="text-center">Qty Diterima</th>
<th width="30%">Catatan</th>
</tr>
</thead>
<tbody>
@foreach($row->mutationDetails as $detail)
<tr>
<td>{{ $detail->product->name }}</td>
<td class="text-center">{{ number_format($detail->quantity_requested, 2) }}</td>
<td class="text-center">
<span class="font-weight-bold {{ $detail->quantity_approved < $detail->quantity_requested ? 'text-warning' : 'text-success' }}">
{{ number_format($detail->quantity_approved, 2) }}
</span>
@if($detail->quantity_approved < $detail->quantity_requested)
<small class="text-muted d-block">
(Kurang {{ number_format($detail->quantity_requested - $detail->quantity_approved, 2) }})
</small>
@endif
</td>
<td>
<small class="text-muted">{{ $detail->notes ?: '-' }}</small>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<p class="text-muted">Setelah disetujui, stock akan siap untuk dipindahkan.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-success">Setujui Mutasi</button>
</div>
</form>
@@ -130,14 +149,12 @@
</div>
<!-- Modal untuk Reject -->
<div class="modal fade" id="rejectModal{{ $row->id }}" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal fade" id="rejectModal{{ $row->id }}" tabindex="-1" aria-labelledby="rejectModalLabel{{ $row->id }}" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Tolak Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title" id="rejectModalLabel{{ $row->id }}">Tolak Mutasi</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ route('mutations.reject', $row->id) }}" method="POST">
@csrf
@@ -151,7 +168,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-danger">Tolak Mutasi</button>
</div>
</form>
@@ -160,14 +177,12 @@
</div>
<!-- Modal untuk Receive -->
<div class="modal fade" id="receiveModal{{ $row->id }}" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal fade" id="receiveModal{{ $row->id }}" tabindex="-1" aria-labelledby="receiveModalLabel{{ $row->id }}" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Terima Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title" id="receiveModalLabel{{ $row->id }}">Terima Mutasi</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ route('mutations.receive', $row->id) }}" method="POST">
@csrf
@@ -175,11 +190,55 @@
<div class="alert alert-info">
<strong>Konfirmasi!</strong> Anda akan menerima mutasi dari <strong>{{ $row->fromDealer->name }}</strong>.
</div>
<p>Setelah menerima, mutasi akan menunggu persetujuan dari pengirim sebelum stock dipindahkan.</p>
<div class="form-group">
<label>Catatan Penerimaan</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Catatan kondisi barang saat diterima (opsional)"></textarea>
</div>
<h6>Detail Produk yang Diterima:</h6>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Produk</th>
<th width="15%" class="text-center">Qty Diminta</th>
<th width="15%" class="text-center">Qty Diterima</th>
<th width="35%">Catatan Produk</th>
</tr>
</thead>
<tbody>
@foreach($row->mutationDetails as $index => $detail)
<tr>
<td>{{ $detail->product->name }}</td>
<td class="text-center">{{ number_format($detail->quantity_requested, 2) }}</td>
<td class="text-center">
<input type="number"
name="products[{{ $detail->id }}][quantity_approved]"
class="form-control form-control-sm text-center"
value="{{ $detail->quantity_requested }}"
min="0"
max="{{ $detail->quantity_requested }}"
step="0.01"
required>
</td>
<td>
<input type="text"
name="products[{{ $detail->id }}][notes]"
class="form-control form-control-sm"
placeholder="Catatan kondisi produk saat diterima">
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<p class="text-muted">Setelah menerima, mutasi akan menunggu persetujuan dari pengirim sebelum stock dipindahkan.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Ya, Terima</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Ya, Terima Mutasi</button>
</div>
</form>
</div>
@@ -187,14 +246,12 @@
</div>
<!-- Modal untuk Complete -->
<div class="modal fade" id="completeModal{{ $row->id }}" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal fade" id="completeModal{{ $row->id }}" tabindex="-1" aria-labelledby="completeModalLabel{{ $row->id }}" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Selesaikan Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title" id="completeModalLabel{{ $row->id }}">Selesaikan Mutasi</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ route('mutations.complete', $row->id) }}" method="POST">
@csrf
@@ -205,7 +262,7 @@
<p>Apakah Anda yakin ingin menyelesaikan mutasi ini? Tindakan ini tidak dapat dibatalkan.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Ya, Selesaikan</button>
</div>
</form>

View File

@@ -65,11 +65,6 @@
</div>
</div>
<div class="form-group">
<label for="notes">Catatan</label>
<textarea name="notes" id="notes" class="form-control" rows="3" placeholder="Catatan untuk mutasi ini (opsional)">{{ old('notes') }}</textarea>
</div>
<div class="kt-separator kt-separator--border-dashed kt-separator--space-lg"></div>
<div class="form-group">
@@ -84,24 +79,23 @@
<table class="table table-bordered" id="products-table">
<thead>
<tr>
<th width="35%">Produk</th>
<th width="15%">Stock Tersedia</th>
<th width="15%">Quantity</th>
<th width="25%">Catatan</th>
<th width="10%">Aksi</th>
<th width="40%">Produk</th>
<th width="20%">Stock Tersedia</th>
<th width="25%">Quantity</th>
<th width="15%">Aksi</th>
</tr>
</thead>
<tbody id="products-tbody">
<tr class="product-row" data-index="0">
<td>
<select name="products[0][product_id]" class="form-control select2 product-select" required>
<select name="products[0][product_id]" class="form-control product-select select2" required>
<option value="">Pilih Produk</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
</td>
<td>
<td class="text-center">
<span class="available-stock text-muted">-</span>
</td>
<td>
@@ -113,12 +107,7 @@
placeholder="0"
required>
</td>
<td>
<input type="text"
name="products[0][notes]"
class="form-control"
placeholder="Catatan produk (opsional)">
</td>
<td>
<button type="button" class="btn btn-danger btn-sm remove-product" disabled>
<i class="la la-trash"></i>
@@ -133,19 +122,18 @@
<div class="alert alert-info">
<i class="la la-info-circle"></i>
<strong>Informasi:</strong>
Mutasi akan dibuat dengan status "Menunggu Persetujuan" dan memerlukan approval sebelum stock dipindahkan.
Mutasi akan dikirim ke dealer tujuan. Catatan dapat ditambahkan saat dealer tujuan menerima mutasi.
</div>
</div>
<div class="kt-portlet__foot">
<div class="kt-form__actions kt-form__actions--right">
<button type="submit" class="btn btn-primary" id="submit-btn">
Simpan Mutasi
</button>
<button type="button" class="btn btn-secondary" onclick="window.history.back()">
Batal
</button>
<button type="submit" class="btn btn-primary" id="submit-btn">
<i class="la la-save"></i>
Simpan Mutasi
</button>
</div>
</div>
</form>

View File

@@ -15,8 +15,7 @@
<div class="kt-portlet__head-wrapper">
<div class="kt-portlet__head-actions">
<a href="{{ route('mutations.create') }}" class="btn btn-bold btn-label-brand btn--sm">
<i class="la la-plus"></i>
Tambah Mutasi
Tambah
</a>
</div>
</div>

View File

@@ -102,38 +102,43 @@
</div>
<div class="kt-portlet__body">
<div class="table-responsive">
<table class="table table-bordered table-hover">
<table class="table table-bordered table-hover mutation-detail-table">
<thead>
<tr>
<th>No.</th>
<th>Nama Produk</th>
<th>Jumlah Diminta</th>
<th>Jumlah Disetujui</th>
<th>Status Approval</th>
<th>Catatan</th>
<th width="5%">No.</th>
<th width="25%">Nama Produk</th>
<th width="15%" class="text-center">Jumlah Diminta</th>
<th width="15%" class="text-center">Jumlah Disetujui</th>
<th width="15%" class="text-center">Status Approval</th>
<th width="25%">Catatan</th>
</tr>
</thead>
<tbody>
@foreach($mutation->mutationDetails as $index => $detail)
<tr>
<td>{{ $index + 1 }}</td>
<td class="text-center">{{ $index + 1 }}</td>
<td>{{ $detail->product->name }}</td>
<td>{{ number_format($detail->quantity_requested, 2) }}</td>
<td>
<td class="text-center">{{ number_format($detail->quantity_requested, 2) }}</td>
<td class="text-center">
@if($mutation->status->value === 'received' || $mutation->status->value === 'approved' || $mutation->status->value === 'completed')
{{ number_format($detail->quantity_approved ?? 0, 2) }}
@else
<span class="text-muted">Belum ditentukan</span>
@endif
</td>
<td>
@if($mutation->status->value === 'received' || $mutation->status->value === 'approved' || $mutation->status->value === 'completed')
<span class="kt-badge kt-badge--{{ $detail->approval_status_color }} kt-badge--pill">
{{ $detail->approval_status }}
</span>
@else
<span class="kt-badge kt-badge--secondary kt-badge--pill">Menunggu</span>
@endif
<td class="text-center">
@php
$textColorClass = match($detail->approval_status_color) {
'success' => 'text-success',
'warning' => 'text-warning',
'danger' => 'text-danger',
'info' => 'text-info',
default => 'text-muted'
};
@endphp
<span class="font-weight-bold {{ $textColorClass }}">
{{ $detail->approval_status }}
</span>
</td>
<td>{{ $detail->notes ?? '-' }}</td>
</tr>
@@ -141,9 +146,9 @@
</tbody>
<tfoot>
<tr class="kt-font-bold">
<td colspan="2">Total</td>
<td>{{ number_format($mutation->mutationDetails->sum('quantity_requested'), 2) }}</td>
<td>
<td colspan="2" class="text-right">Total</td>
<td class="text-center">{{ number_format($mutation->mutationDetails->sum('quantity_requested'), 2) }}</td>
<td class="text-center">
@if($mutation->status->value === 'received' || $mutation->status->value === 'approved' || $mutation->status->value === 'completed')
{{ number_format($mutation->mutationDetails->sum('quantity_approved'), 2) }}
@else
@@ -165,8 +170,7 @@
<div class="col-lg-12">
@if($mutation->status->value === 'sent' && auth()->user()->dealer_id == $mutation->to_dealer_id)
<!-- Receive Button for destination dealer -->
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#receiveModal">
<i class="la la-download"></i>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#receiveModal{{ $mutation->id }}">
Terima Mutasi
</button>
@endif
@@ -174,12 +178,10 @@
@if($mutation->status->value === 'received' && (auth()->user()->dealer_id == $mutation->from_dealer_id || auth()->user()->hasRole('admin')))
<!-- Approve Button for sender or admin -->
<button type="button" class="btn btn-success btn-approve" data-id="{{ $mutation->id }}">
<i class="la la-check"></i>
Setujui Mutasi
</button>
<!-- Reject Button for sender or admin -->
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#rejectModal">
<i class="la la-times"></i>
Tolak Mutasi
</button>
@endif
@@ -187,7 +189,6 @@
@if($mutation->status->value === 'approved')
<!-- Complete Button -->
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#completeModal">
<i class="la la-check-circle"></i>
Selesaikan Mutasi
</button>
@endif
@@ -195,18 +196,11 @@
@if($mutation->canBeCancelled() && (auth()->user()->dealer_id == $mutation->from_dealer_id || auth()->user()->hasRole('admin')))
<!-- Cancel Button -->
<button type="button" class="btn btn-warning" data-toggle="modal" data-target="#cancelModal">
<i class="la la-ban"></i>
Batalkan Mutasi
</button>
@endif
@if($mutation->status->value === 'completed')
<!-- Print Button -->
<a href="{{ route('mutations.print', $mutation->id) }}" class="btn btn-info" target="_blank">
<i class="la la-print"></i>
Cetak Laporan
</a>
@endif
</div>
</div>
</div>
@@ -217,8 +211,8 @@
<!-- Modals -->
@if($mutation->status->value === 'sent' && auth()->user()->dealer_id == $mutation->to_dealer_id)
<!-- Receive Modal -->
<div class="modal fade" id="receiveModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal fade" id="receiveModal{{ $mutation->id }}" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Terima Mutasi</h5>
@@ -232,11 +226,55 @@
<div class="alert alert-info">
<strong>Konfirmasi!</strong> Anda akan menerima mutasi dari <strong>{{ $mutation->fromDealer->name }}</strong>.
</div>
<p>Setelah menerima, mutasi akan menunggu persetujuan dari pengirim sebelum stock dipindahkan.</p>
<div class="form-group">
<label>Catatan Penerimaan</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Catatan kondisi barang saat diterima (opsional)"></textarea>
</div>
<h6>Detail Produk yang Diterima:</h6>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Produk</th>
<th width="15%" class="text-center">Qty Diminta</th>
<th width="15%" class="text-center">Qty Diterima</th>
<th width="35%">Catatan Produk</th>
</tr>
</thead>
<tbody>
@foreach($mutation->mutationDetails as $index => $detail)
<tr>
<td>{{ $detail->product->name }}</td>
<td class="text-center">{{ number_format($detail->quantity_requested, 2) }}</td>
<td class="text-center">
<input type="number"
name="products[{{ $detail->id }}][quantity_approved]"
class="form-control form-control-sm text-center"
value="{{ $detail->quantity_requested }}"
min="0"
max="{{ $detail->quantity_requested }}"
step="0.01"
required>
</td>
<td>
<input type="text"
name="products[{{ $detail->id }}][notes]"
class="form-control form-control-sm"
placeholder="Catatan kondisi produk saat diterima">
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<p class="text-muted">Setelah menerima, mutasi akan menunggu persetujuan dari pengirim sebelum stock dipindahkan.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Ya, Terima</button>
<button type="submit" class="btn btn-primary">Ya, Terima Mutasi</button>
</div>
</form>
</div>
@@ -258,20 +296,51 @@
<form action="{{ route('mutations.approve', $mutation->id) }}" method="POST" class="approve-form">
@csrf
<div class="modal-body">
<div class="alert alert-info">
<strong>Konfirmasi!</strong> Anda akan menyetujui mutasi yang telah diterima oleh <strong>{{ $mutation->toDealer->name }}</strong>.
</div>
<div class="form-group">
<label>Catatan Persetujuan</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Opsional: tambahkan catatan..."></textarea>
</div>
<!-- Detail produk akan dimuat via AJAX -->
<div id="mutation-details{{ $mutation->id }}">
<div class="text-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
<p>Memuat detail produk...</p>
</div>
<h6>Detail Produk yang Diterima:</h6>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Produk</th>
<th width="20%" class="text-center">Qty Diminta</th>
<th width="20%" class="text-center">Qty Diterima</th>
<th width="30%">Catatan</th>
</tr>
</thead>
<tbody>
@foreach($mutation->mutationDetails as $detail)
<tr>
<td>{{ $detail->product->name }}</td>
<td class="text-center">{{ number_format($detail->quantity_requested, 2) }}</td>
<td class="text-center">
<span class="font-weight-bold {{ $detail->quantity_approved < $detail->quantity_requested ? 'text-warning' : 'text-success' }}">
{{ number_format($detail->quantity_approved, 2) }}
</span>
@if($detail->quantity_approved < $detail->quantity_requested)
<small class="text-muted d-block">
(Kurang {{ number_format($detail->quantity_requested - $detail->quantity_approved, 2) }})
</small>
@endif
</td>
<td>
<small class="text-muted">{{ $detail->notes ?: '-' }}</small>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<p class="text-muted">Setelah disetujui, stock akan siap untuk dipindahkan.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
@@ -372,71 +441,41 @@
@endif
@endsection
@section('styles')
<style>
/* Custom CSS for mutation detail table alignment */
.mutation-detail-table {
table-layout: fixed;
}
.mutation-detail-table th,
.mutation-detail-table td {
vertical-align: middle;
}
.mutation-detail-table .text-center {
text-align: center !important;
}
/* Ensure proper alignment in approval modal */
.approve-form .form-control.text-center {
text-align: center;
}
</style>
@endsection
@section('javascripts')
<script>
$(document).ready(function() {
// Handle Approve Button Click
$(document).on('click', '.btn-approve', function() {
var mutationId = $(this).data('id');
// Load mutation details via AJAX
$.ajax({
url: '/warehouse/mutations/' + mutationId + '/details',
type: 'GET',
beforeSend: function() {
$('#mutation-details' + mutationId).html(
'<div class="text-center">' +
'<div class="spinner-border" role="status">' +
'<span class="sr-only">Loading...</span>' +
'</div>' +
'<p>Memuat detail produk...</p>' +
'</div>'
);
},
success: function(response) {
var detailsHtml = '<h6>Detail Produk:</h6>';
detailsHtml += '<div class="table-responsive">';
detailsHtml += '<table class="table table-sm">';
detailsHtml += '<thead>';
detailsHtml += '<tr>';
detailsHtml += '<th>Produk</th>';
detailsHtml += '<th>Diminta</th>';
detailsHtml += '<th>Disetujui</th>';
detailsHtml += '<th>Stock Tersedia</th>';
detailsHtml += '</tr>';
detailsHtml += '</thead>';
detailsHtml += '<tbody>';
response.details.forEach(function(detail, index) {
detailsHtml += '<tr>';
detailsHtml += '<td>' + detail.product.name + '</td>';
detailsHtml += '<td>' + parseFloat(detail.quantity_requested).toLocaleString() + '</td>';
detailsHtml += '<td>';
detailsHtml += '<input type="number" name="details[' + detail.id + '][quantity_approved]" ';
detailsHtml += 'class="form-control form-control-sm" ';
detailsHtml += 'value="' + detail.quantity_requested + '" ';
detailsHtml += 'min="0" max="' + Math.min(detail.quantity_requested, detail.available_stock) + '" ';
detailsHtml += 'step="0.01" required>';
detailsHtml += '</td>';
detailsHtml += '<td>' + parseFloat(detail.available_stock).toLocaleString() + '</td>';
detailsHtml += '</tr>';
});
detailsHtml += '</tbody>';
detailsHtml += '</table>';
detailsHtml += '</div>';
$('#mutation-details' + mutationId).html(detailsHtml);
},
error: function() {
$('#mutation-details' + mutationId).html('<div class="alert alert-danger">Gagal memuat detail produk</div>');
}
});
$('#approveModal' + mutationId).modal('show');
});
// Auto-calculate approved quantity based on available stock
// Validate quantity approved in receive modal
$(document).on('input', 'input[name*="quantity_approved"]', function() {
var maxValue = parseFloat($(this).attr('max'));
var currentValue = parseFloat($(this).val());
@@ -445,7 +484,7 @@ $(document).ready(function() {
$(this).val(maxValue);
$(this).addClass('is-invalid');
if (!$(this).siblings('.invalid-feedback').length) {
$(this).after('<div class="invalid-feedback">Jumlah melebihi stock yang tersedia</div>');
$(this).after('<div class="invalid-feedback">Quantity tidak boleh melebihi yang diminta</div>');
}
} else {
$(this).removeClass('is-invalid');

View File

@@ -32,7 +32,7 @@
$oldDealer = old('dealer');
$dealerValue = is_array($oldDealer) ? '' : $oldDealer;
@endphp
<select name="dealer" id="dealer" class="form-control @error('dealer') is-invalid @enderror" required>
<select name="dealer" id="dealer" class="form-control select2 @error('dealer') is-invalid @enderror" required>
<option value="">Pilih Dealer</option>
@foreach($dealers as $dealer)
<option value="{{ $dealer->id }}" {{ $dealerValue == $dealer->id ? 'selected' : '' }}>
@@ -202,5 +202,5 @@
@endsection
@section('javascripts')
<script src="{{ asset('js/warehouse_management/opnames/create.js') }}"></script>
<script src="{{ mix('js/warehouse_management/opnames/create.js') }}"></script>
@endsection