fix handle error and add note for shippings receive approve and reject mutations

This commit is contained in:
2025-06-13 14:19:12 +07:00
parent b2bfd666a7
commit 2f5eff9e63
28 changed files with 13055 additions and 154 deletions

View File

@@ -37,4 +37,31 @@ enum MutationStatus: string
self::CANCELLED => 'secondary', self::CANCELLED => 'secondary',
}; };
} }
public function textColorClass(): string
{
return match($this->color()) {
'success' => 'text-success',
'warning' => 'text-warning',
'danger' => 'text-danger',
'info' => 'text-info',
'primary' => 'text-primary',
'brand' => 'text-primary',
'secondary' => 'text-muted',
default => 'text-dark'
};
}
public static function getOptions(): array
{
return [
self::PENDING->value => self::PENDING->label(),
self::SENT->value => self::SENT->label(),
self::RECEIVED->value => self::RECEIVED->label(),
self::APPROVED->value => self::APPROVED->label(),
self::REJECTED->value => self::REJECTED->label(),
self::COMPLETED->value => self::COMPLETED->label(),
self::CANCELLED->value => self::CANCELLED->label(),
];
}
} }

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Enums;
enum OpnameStatus: string
{
case DRAFT = 'draft';
case PENDING = 'pending';
case APPROVED = 'approved';
case REJECTED = 'rejected';
public function label(): string
{
return match($this) {
self::DRAFT => 'Draft',
self::PENDING => 'Menunggu Persetujuan',
self::APPROVED => 'Disetujui',
self::REJECTED => 'Ditolak',
};
}
public function color(): string
{
return match($this) {
self::DRAFT => 'warning',
self::PENDING => 'info',
self::APPROVED => 'success',
self::REJECTED => 'danger',
};
}
public function textColorClass(): string
{
return match($this->color()) {
'success' => 'text-success',
'warning' => 'text-warning',
'danger' => 'text-danger',
'info' => 'text-info',
'primary' => 'text-primary',
'brand' => 'text-primary',
'secondary' => 'text-muted',
default => 'text-dark'
};
}
public static function getOptions(): array
{
return [
self::DRAFT->value => self::DRAFT->label(),
self::PENDING->value => self::PENDING->label(),
self::APPROVED->value => self::APPROVED->label(),
self::REJECTED->value => self::REJECTED->label(),
];
}
}

View File

@@ -45,21 +45,11 @@ class MutationsController extends Controller
return $row->requestedBy->name ?? '-'; return $row->requestedBy->name ?? '-';
}) })
->addColumn('status', function($row) { ->addColumn('status', function($row) {
$statusColor = $row->status_color; $status = $row->status instanceof MutationStatus ? $row->status : MutationStatus::from($row->status);
$statusLabel = $row->status_label; $textColorClass = $status->textColorClass();
$label = $status->label();
$textColorClass = match($statusColor) { return "<span class=\"font-weight-bold {$textColorClass}\">{$label}</span>";
'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) { ->addColumn('total_items', function($row) {
return number_format($row->total_items, 0); return number_format($row->total_items, 0);
@@ -91,6 +81,7 @@ class MutationsController extends Controller
$request->validate([ $request->validate([
'from_dealer_id' => 'required|exists:dealers,id', 'from_dealer_id' => 'required|exists:dealers,id',
'to_dealer_id' => 'required|exists:dealers,id|different:from_dealer_id', 'to_dealer_id' => 'required|exists:dealers,id|different:from_dealer_id',
'shipping_notes' => 'nullable|string',
'products' => 'required|array|min:1', 'products' => 'required|array|min:1',
'products.*.product_id' => 'required|exists:products,id', 'products.*.product_id' => 'required|exists:products,id',
'products.*.quantity_requested' => 'required|numeric|min:0.01' 'products.*.quantity_requested' => 'required|numeric|min:0.01'
@@ -102,8 +93,9 @@ class MutationsController extends Controller
$mutation = Mutation::create([ $mutation = Mutation::create([
'from_dealer_id' => $request->from_dealer_id, 'from_dealer_id' => $request->from_dealer_id,
'to_dealer_id' => $request->to_dealer_id, 'to_dealer_id' => $request->to_dealer_id,
'status' => 'sent', 'status' => MutationStatus::SENT,
'requested_by' => auth()->id() 'requested_by' => auth()->id(),
'shipping_notes' => $request->shipping_notes
]); ]);
// Buat mutation details // Buat mutation details
@@ -134,7 +126,16 @@ class MutationsController extends Controller
public function show(Mutation $mutation) public function show(Mutation $mutation)
{ {
$mutation->load(['fromDealer', 'toDealer', 'requestedBy.role', 'approvedBy.role', 'receivedBy.role', 'mutationDetails.product']); $mutation->load([
'fromDealer',
'toDealer',
'requestedBy.role',
'approvedBy.role',
'receivedBy.role',
'rejectedBy.role',
'cancelledBy.role',
'mutationDetails.product'
]);
return view('warehouse_management.mutations.show', compact('mutation')); return view('warehouse_management.mutations.show', compact('mutation'));
} }
@@ -142,7 +143,7 @@ class MutationsController extends Controller
public function receive(Request $request, Mutation $mutation) public function receive(Request $request, Mutation $mutation)
{ {
$request->validate([ $request->validate([
'notes' => 'nullable|string', 'reception_notes' => 'nullable|string',
'products' => 'required|array', 'products' => 'required|array',
'products.*.quantity_approved' => 'required|numeric|min:0', 'products.*.quantity_approved' => 'required|numeric|min:0',
'products.*.notes' => 'nullable|string' 'products.*.notes' => 'nullable|string'
@@ -154,11 +155,6 @@ class MutationsController extends Controller
DB::beginTransaction(); DB::beginTransaction();
try { try {
// Update mutation notes jika ada
if ($request->notes) {
$mutation->update(['notes' => $request->notes]);
}
// Update product details dengan quantity_approved dan notes // Update product details dengan quantity_approved dan notes
if ($request->products) { if ($request->products) {
foreach ($request->products as $detailId => $productData) { foreach ($request->products as $detailId => $productData) {
@@ -182,8 +178,8 @@ class MutationsController extends Controller
} }
} }
// Receive mutation // Receive mutation with reception notes
$mutation->receive(auth()->id()); $mutation->receive(auth()->id(), $request->reception_notes);
DB::commit(); DB::commit();
return redirect()->route('mutations.index') return redirect()->route('mutations.index')
@@ -198,7 +194,7 @@ class MutationsController extends Controller
public function approve(Request $request, Mutation $mutation) public function approve(Request $request, Mutation $mutation)
{ {
$request->validate([ $request->validate([
'notes' => 'nullable|string' 'approval_notes' => 'nullable|string'
]); ]);
if (!$mutation->canBeApproved()) { if (!$mutation->canBeApproved()) {
@@ -207,7 +203,7 @@ class MutationsController extends Controller
try { try {
// Approve mutation (quantity_approved sudah diisi saat receive) // Approve mutation (quantity_approved sudah diisi saat receive)
$mutation->approve(auth()->id(), $request->notes); $mutation->approve(auth()->id(), $request->approval_notes);
return redirect()->route('mutations.index') return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil disetujui'); ->with('success', 'Mutasi berhasil disetujui');
@@ -255,14 +251,18 @@ class MutationsController extends Controller
} }
} }
public function cancel(Mutation $mutation) public function cancel(Request $request, Mutation $mutation)
{ {
$request->validate([
'cancellation_reason' => 'nullable|string'
]);
if (!$mutation->canBeCancelled()) { if (!$mutation->canBeCancelled()) {
return back()->withErrors(['error' => 'Mutasi tidak dapat dibatalkan dalam status saat ini']); return back()->withErrors(['error' => 'Mutasi tidak dapat dibatalkan dalam status saat ini']);
} }
try { try {
$mutation->update(['status' => MutationStatus::CANCELLED]); $mutation->cancel(auth()->id(), $request->cancellation_reason);
return redirect()->route('mutations.index') return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil dibatalkan'); ->with('success', 'Mutasi berhasil dibatalkan');
@@ -349,6 +349,8 @@ class MutationsController extends Controller
'requestedBy.role', 'requestedBy.role',
'approvedBy.role', 'approvedBy.role',
'receivedBy.role', 'receivedBy.role',
'rejectedBy.role',
'cancelledBy.role',
'mutationDetails.product' 'mutationDetails.product'
]); ]);

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers\WarehouseManagement; namespace App\Http\Controllers\WarehouseManagement;
use App\Enums\OpnameStatus;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Dealer; use App\Models\Dealer;
use App\Models\Menu; use App\Models\Menu;
@@ -39,30 +40,16 @@ class OpnamesController extends Controller
return Carbon::parse($row->created_at)->format('d M Y H:i'); return Carbon::parse($row->created_at)->format('d M Y H:i');
}) })
->editColumn('status', function ($row) { ->editColumn('status', function ($row) {
$statusColor = [ $status = $row->status instanceof OpnameStatus ? $row->status : OpnameStatus::from($row->status);
'draft' => 'warning', $textColorClass = $status->textColorClass();
'pending' => 'info', $label = $status->label();
'approved' => 'success',
'rejected' => 'danger'
][$row->status] ?? 'secondary';
$textColorClass = match($statusColor) { return "<span class=\"font-weight-bold {$textColorClass}\">{$label}</span>";
'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) { ->addColumn('action', function ($row) use ($menu) {
$btn = '<div class="d-flex">'; $btn = '<div class="d-flex">';
$btn .= '<a href="'.route('opnames.show', $row->id).'" class="btn btn-secondary btn-sm">Detail</a>'; $btn .= '<a href="'.route('opnames.show', $row->id).'" class="btn btn-primary btn-sm">Detail</a>';
$btn .= '</div>'; $btn .= '</div>';
@@ -253,7 +240,7 @@ class OpnamesController extends Controller
'opname_date' => $opnameDate, 'opname_date' => $opnameDate,
'user_id' => $userId, 'user_id' => $userId,
'note' => $note, 'note' => $note,
'status' => 'approved', // Set status langsung approved 'status' => OpnameStatus::APPROVED, // Set status langsung approved
'approved_by' => $userId, // Set current user sebagai approver 'approved_by' => $userId, // Set current user sebagai approver
'approved_at' => now() // Set waktu approval 'approved_at' => now() // Set waktu approval
]); ]);

View File

@@ -20,16 +20,25 @@ class Mutation extends Model
'requested_by', 'requested_by',
'approved_by', 'approved_by',
'approved_at', 'approved_at',
'approval_notes',
'received_by', 'received_by',
'received_at', 'received_at',
'notes', 'reception_notes',
'rejection_reason' 'shipping_notes',
'rejection_reason',
'rejected_by',
'rejected_at',
'cancelled_by',
'cancelled_at',
'cancellation_reason'
]; ];
protected $casts = [ protected $casts = [
'status' => MutationStatus::class, 'status' => MutationStatus::class,
'approved_at' => 'datetime', 'approved_at' => 'datetime',
'received_at' => 'datetime' 'received_at' => 'datetime',
'rejected_at' => 'datetime',
'cancelled_at' => 'datetime'
]; ];
protected static function booted() protected static function booted()
@@ -66,6 +75,16 @@ class Mutation extends Model
return $this->belongsTo(User::class, 'received_by'); return $this->belongsTo(User::class, 'received_by');
} }
public function rejectedBy()
{
return $this->belongsTo(User::class, 'rejected_by');
}
public function cancelledBy()
{
return $this->belongsTo(User::class, 'cancelled_by');
}
public function mutationDetails() public function mutationDetails()
{ {
return $this->hasMany(MutationDetail::class); return $this->hasMany(MutationDetail::class);
@@ -137,7 +156,7 @@ class Mutation extends Model
} }
// Receive mutation by destination dealer // Receive mutation by destination dealer
public function receive($userId) public function receive($userId, $receptionNotes = null)
{ {
if (!$this->canBeReceived()) { if (!$this->canBeReceived()) {
throw new \Exception('Mutasi tidak dapat diterima dalam status saat ini'); throw new \Exception('Mutasi tidak dapat diterima dalam status saat ini');
@@ -146,14 +165,15 @@ class Mutation extends Model
$this->update([ $this->update([
'status' => MutationStatus::RECEIVED, 'status' => MutationStatus::RECEIVED,
'received_by' => $userId, 'received_by' => $userId,
'received_at' => now() 'received_at' => now(),
'reception_notes' => $receptionNotes
]); ]);
return $this; return $this;
} }
// Approve mutation // Approve mutation
public function approve($userId, $notes = null) public function approve($userId, $approvalNotes = null)
{ {
if (!$this->canBeApproved()) { if (!$this->canBeApproved()) {
throw new \Exception('Mutasi tidak dapat disetujui dalam status saat ini'); throw new \Exception('Mutasi tidak dapat disetujui dalam status saat ini');
@@ -163,7 +183,7 @@ class Mutation extends Model
'status' => MutationStatus::APPROVED, 'status' => MutationStatus::APPROVED,
'approved_by' => $userId, 'approved_by' => $userId,
'approved_at' => now(), 'approved_at' => now(),
'notes' => $notes 'approval_notes' => $approvalNotes
]); ]);
return $this; return $this;
@@ -178,14 +198,31 @@ class Mutation extends Model
$this->update([ $this->update([
'status' => MutationStatus::REJECTED, 'status' => MutationStatus::REJECTED,
'approved_by' => $userId, 'rejected_by' => $userId,
'approved_at' => now(), 'rejected_at' => now(),
'rejection_reason' => $rejectionReason 'rejection_reason' => $rejectionReason
]); ]);
return $this; return $this;
} }
// Cancel mutation
public function cancel($userId, $cancellationReason = null)
{
if (!$this->canBeCancelled()) {
throw new \Exception('Mutasi tidak dapat dibatalkan dalam status saat ini');
}
$this->update([
'status' => MutationStatus::CANCELLED,
'cancelled_by' => $userId,
'cancelled_at' => now(),
'cancellation_reason' => $cancellationReason
]);
return $this;
}
// Complete mutation (actually move the stock) // Complete mutation (actually move the stock)
public function complete() public function complete()
{ {

View File

@@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use App\Enums\OpnameStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@@ -22,14 +23,15 @@ class Opname extends Model
]; ];
protected $casts = [ protected $casts = [
'approved_at' => 'datetime' 'approved_at' => 'datetime',
'status' => OpnameStatus::class
]; ];
protected static function booted() protected static function booted()
{ {
static::updated(function ($opname) { static::updated(function ($opname) {
// Jika status berubah menjadi approved // Jika status berubah menjadi approved
if ($opname->isDirty('status') && $opname->status === 'approved') { if ($opname->isDirty('status') && $opname->status === OpnameStatus::APPROVED) {
// Update stock untuk setiap detail opname // Update stock untuk setiap detail opname
foreach ($opname->details as $detail) { foreach ($opname->details as $detail) {
$stock = Stock::firstOrCreate( $stock = Stock::firstOrCreate(
@@ -74,11 +76,11 @@ class Opname extends Model
// Method untuk approve opname // Method untuk approve opname
public function approve(User $approver) public function approve(User $approver)
{ {
if ($this->status !== 'pending') { if ($this->status !== OpnameStatus::PENDING) {
throw new \Exception('Only pending opnames can be approved'); throw new \Exception('Only pending opnames can be approved');
} }
$this->status = 'approved'; $this->status = OpnameStatus::APPROVED;
$this->approved_by = $approver->id; $this->approved_by = $approver->id;
$this->approved_at = now(); $this->approved_at = now();
$this->save(); $this->save();
@@ -89,11 +91,11 @@ class Opname extends Model
// Method untuk reject opname // Method untuk reject opname
public function reject(User $rejector, string $note) public function reject(User $rejector, string $note)
{ {
if ($this->status !== 'pending') { if ($this->status !== OpnameStatus::PENDING) {
throw new \Exception('Only pending opnames can be rejected'); throw new \Exception('Only pending opnames can be rejected');
} }
$this->status = 'rejected'; $this->status = OpnameStatus::REJECTED;
$this->approved_by = $rejector->id; $this->approved_by = $rejector->id;
$this->approved_at = now(); $this->approved_at = now();
$this->rejection_note = $note; $this->rejection_note = $note;
@@ -105,11 +107,11 @@ class Opname extends Model
// Method untuk submit opname untuk approval // Method untuk submit opname untuk approval
public function submit() public function submit()
{ {
if ($this->status !== 'draft') { if ($this->status !== OpnameStatus::DRAFT) {
throw new \Exception('Only draft opnames can be submitted'); throw new \Exception('Only draft opnames can be submitted');
} }
$this->status = 'pending'; $this->status = OpnameStatus::PENDING;
$this->save(); $this->save();
return $this; return $this;

43
ckb.sql

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,58 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddAdditionalNotesAndRejectionFieldsToMutationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('mutations', function (Blueprint $table) {
// Rename existing notes to shipping_notes for clarity
$table->renameColumn('notes', 'shipping_notes');
// Add new note fields
$table->text('reception_notes')->nullable()->after('received_at')->comment('Catatan saat penerimaan mutasi');
$table->text('approval_notes')->nullable()->after('approved_at')->comment('Catatan saat persetujuan mutasi');
// Add rejection tracking fields
$table->foreignId('rejected_by')->nullable()->constrained('users')->after('rejection_reason');
$table->timestamp('rejected_at')->nullable()->after('rejected_by');
// Add cancellation tracking fields
$table->foreignId('cancelled_by')->nullable()->constrained('users')->after('rejected_at');
$table->timestamp('cancelled_at')->nullable()->after('cancelled_by');
$table->text('cancellation_reason')->nullable()->after('cancelled_at')->comment('Alasan pembatalan mutasi');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('mutations', function (Blueprint $table) {
// Remove new fields
$table->dropColumn([
'reception_notes',
'approval_notes',
'rejected_by',
'rejected_at',
'cancelled_by',
'cancelled_at',
'cancellation_reason'
]);
// Rename back to original
$table->renameColumn('shipping_notes', 'notes');
});
}
}

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

34
public/js/warehouse_management/opnames/create.js Executable file → 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,2 +1,32 @@
(()=>{$.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 * 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"]();
/******/
/******/ })()
;

View File

@@ -1,2 +1,32 @@
(()=>{$.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 * 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"]();
/******/
/******/ })()
;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -21,7 +21,12 @@ $(document).ready(function () {
if (fromDealerId && toDealerId && fromDealerId === toDealerId) { if (fromDealerId && toDealerId && fromDealerId === toDealerId) {
$(this).val("").trigger("change"); $(this).val("").trigger("change");
alert("Dealer asal dan tujuan tidak boleh sama!"); Swal.fire({
type: "error",
title: "Oops...",
text: "Dealer asal dan tujuan tidak boleh sama",
});
return false;
} }
// Update available stock when dealer changes // Update available stock when dealer changes
@@ -216,24 +221,40 @@ $(document).ready(function () {
// Check dealers // Check dealers
if (!fromDealerId) { if (!fromDealerId) {
alert("Pilih dealer asal"); Swal.fire({
type: "error",
title: "Oops...",
text: "Pilih dealer asal",
});
return false; return false;
} }
if (!toDealerId) { if (!toDealerId) {
alert("Pilih dealer tujuan"); Swal.fire({
type: "error",
title: "Oops...",
text: "Pilih dealer tujuan",
});
return false; return false;
} }
if (fromDealerId === toDealerId) { if (fromDealerId === toDealerId) {
alert("Dealer asal dan tujuan tidak boleh sama"); Swal.fire({
type: "error",
title: "Oops...",
text: "Dealer asal dan tujuan tidak boleh sama",
});
return false; return false;
} }
// Check products // Check products
const productRows = $(".product-row"); const productRows = $(".product-row");
if (productRows.length === 0) { if (productRows.length === 0) {
alert("Tambahkan minimal satu produk"); Swal.fire({
type: "error",
title: "Oops...",
text: "Tambahkan minimal satu produk",
});
return false; return false;
} }
@@ -248,7 +269,11 @@ $(document).ready(function () {
}); });
if (!hasValidProduct) { if (!hasValidProduct) {
alert("Pilih minimal satu produk dengan quantity yang valid"); Swal.fire({
type: "error",
title: "Oops...",
text: "Pilih minimal satu produk dengan quantity yang valid",
});
return false; return false;
} }

View File

@@ -1,9 +1,23 @@
$(document).ready(function () { $(document).ready(function () {
console.log("Opnames create.js loaded - SweetAlert version");
$(".select2").select2({ $(".select2").select2({
placeholder: "Pilih...", placeholder: "Pilih...",
allowClear: true, allowClear: true,
}); });
// Initialize select2 for all product selects
function initializeProductSelects() {
$(".product-select").select2({
placeholder: "Pilih Produk...",
allowClear: true,
width: "100%",
});
}
// Initial initialization
initializeProductSelects();
// Fungsi untuk mengambil data stok // Fungsi untuk mengambil data stok
function fetchStockData() { function fetchStockData() {
const dealerId = $("#dealer").val(); const dealerId = $("#dealer").val();
@@ -88,6 +102,15 @@ $(document).ready(function () {
} }
}); });
// Handle physical quantity changes using event delegation
$(document).on(
"change input",
'input[name^="physical_quantity"]',
function () {
calculateDifference(this);
}
);
// Fungsi untuk menambah baris produk // Fungsi untuk menambah baris produk
$("#btn-add-row").click(function () { $("#btn-add-row").click(function () {
const template = document.getElementById("product-row-template"); const template = document.getElementById("product-row-template");
@@ -122,7 +145,16 @@ $(document).ready(function () {
$(newRow).find("select").val(""); $(newRow).find("select").val("");
$(newRow).find("input:not(.system-quantity)").val(""); $(newRow).find("input:not(.system-quantity)").val("");
// Append to DOM first
tbody.append(newRow); tbody.append(newRow);
// Initialize select2 for the new row AFTER it's added to DOM
tbody.find("tr:last-child .product-select").select2({
placeholder: "Pilih Produk...",
allowClear: true,
width: "100%",
});
updateRemoveButtons(); updateRemoveButtons();
}); });
@@ -143,30 +175,50 @@ $(document).ready(function () {
// Fungsi untuk reindex semua baris // Fungsi untuk reindex semua baris
function reindexRows() { function reindexRows() {
$(".product-row").each(function (index) { $(".product-row").each(function (index) {
$(this) const $row = $(this);
.find('select[name^="product"]') const $select = $row.find('select[name^="product"]');
.attr("name", `product[${index}]`);
$(this) // Destroy select2 before changing attributes
.find('input[name^="system_quantity"]') if ($select.data("select2")) {
.attr("name", `system_quantity[${index}]`); $select.select2("destroy");
$(this) }
.find('input[name^="physical_quantity"]')
.attr("name", `physical_quantity[${index}]`); $select.attr("name", `product[${index}]`);
$(this) $row.find('input[name^="system_quantity"]').attr(
.find('input[name^="item_notes"]') "name",
.attr("name", `item_notes[${index}]`); `system_quantity[${index}]`
);
$row.find('input[name^="physical_quantity"]').attr(
"name",
`physical_quantity[${index}]`
);
$row.find('input[name^="item_notes"]').attr(
"name",
`item_notes[${index}]`
);
// Reinitialize select2
$select.select2({
placeholder: "Pilih Produk...",
allowClear: true,
width: "100%",
});
}); });
} }
// Update calculateDifference function // Update calculateDifference function - make it globally accessible
function calculateDifference(input) { window.calculateDifference = function (input) {
const row = $(input).closest("tr"); const row = $(input).closest("tr");
const systemQty = parseFloat(row.find(".system-quantity").val()) || 0; const systemQty = parseFloat(row.find(".system-quantity").val()) || 0;
const physicalQty = const physicalQty =
parseFloat(row.find('input[name^="physical_quantity"]').val()) || 0; parseFloat(row.find('input[name^="physical_quantity"]').val()) || 0;
const noteInput = row.find('input[name^="item_notes"]'); const noteInput = row.find('input[name^="item_notes"]');
if (Math.abs(systemQty - physicalQty) > 0.01) { // Round both values to 2 decimal places for comparison
const roundedSystemQty = Math.round(systemQty * 100) / 100;
const roundedPhysicalQty = Math.round(physicalQty * 100) / 100;
if (roundedSystemQty !== roundedPhysicalQty) {
noteInput.addClass("is-invalid"); noteInput.addClass("is-invalid");
noteInput.attr("required", true); noteInput.attr("required", true);
noteInput.attr( noteInput.attr(
@@ -180,7 +232,7 @@ $(document).ready(function () {
noteInput.attr("placeholder", "Catatan item"); noteInput.attr("placeholder", "Catatan item");
row.removeClass("table-warning"); row.removeClass("table-warning");
} }
} };
// Prevent manual editing of system quantity // Prevent manual editing of system quantity
$(document).on("keydown", ".system-quantity", function (e) { $(document).on("keydown", ".system-quantity", function (e) {
@@ -198,7 +250,11 @@ $(document).ready(function () {
const dealerId = $("#dealer").val(); const dealerId = $("#dealer").val();
if (!dealerId) { if (!dealerId) {
e.preventDefault(); e.preventDefault();
alert("Silakan pilih dealer terlebih dahulu!"); Swal.fire({
icon: "error",
title: "Oops...",
text: "Silakan pilih dealer terlebih dahulu!",
});
return false; return false;
} }
@@ -212,14 +268,22 @@ $(document).ready(function () {
const uniqueProducts = [...new Set(products)]; const uniqueProducts = [...new Set(products)];
if (products.length !== uniqueProducts.length) { if (products.length !== uniqueProducts.length) {
e.preventDefault(); e.preventDefault();
alert("Produk tidak boleh duplikat!"); Swal.fire({
icon: "error",
title: "Oops...",
text: "Produk tidak boleh duplikat!",
});
return false; return false;
} }
// Cek produk kosong // Cek produk kosong
if (products.includes("")) { if (products.includes("")) {
e.preventDefault(); e.preventDefault();
alert("Semua produk harus dipilih!"); Swal.fire({
icon: "error",
title: "Oops...",
text: "Semua produk harus dipilih!",
});
return false; return false;
} }
@@ -236,7 +300,11 @@ $(document).ready(function () {
) || 0; ) || 0;
const note = $(this).find('input[name^="item_notes"]').val(); const note = $(this).find('input[name^="item_notes"]').val();
if (Math.abs(systemQty - physicalQty) > 0.01 && !note) { // Round both values to 2 decimal places for comparison
const roundedSystemQty = Math.round(systemQty * 100) / 100;
const roundedPhysicalQty = Math.round(physicalQty * 100) / 100;
if (roundedSystemQty !== roundedPhysicalQty && !note) {
hasInvalidNotes = true; hasInvalidNotes = true;
$(this).addClass("table-danger"); $(this).addClass("table-danger");
} }
@@ -244,9 +312,11 @@ $(document).ready(function () {
if (hasInvalidNotes) { if (hasInvalidNotes) {
e.preventDefault(); e.preventDefault();
alert( Swal.fire({
"Catatan wajib diisi untuk produk yang memiliki perbedaan stock!" icon: "error",
); title: "Oops...",
text: "Catatan wajib diisi untuk produk yang memiliki perbedaan stock!",
});
return false; return false;
} }
}); });

View File

@@ -1728,7 +1728,7 @@ use Illuminate\Support\Facades\Auth;
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-12"> <div class="col-md-12">
<strong>Diminta oleh:</strong><br> <strong>Dikirim oleh:</strong><br>
${mutation.requested_by ? mutation.requested_by.name : '-'} ${mutation.requested_by ? mutation.requested_by.name : '-'}
</div> </div>
</div> </div>
@@ -1738,7 +1738,7 @@ use Illuminate\Support\Facades\Auth;
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-12"> <div class="col-md-12">
<label for="mutationNotes"><strong>Catatan Penerimaan:</strong></label> <label for="mutationNotes"><strong>Catatan Penerimaan:</strong></label>
<textarea name="notes" id="mutationNotes" class="form-control" rows="3" placeholder="Masukkan catatan jika diperlukan..."></textarea> <textarea name="reception_notes" id="mutationNotes" class="form-control" rows="3" placeholder="Masukkan catatan jika diperlukan..."></textarea>
</div> </div>
</div> </div>

View File

@@ -90,7 +90,7 @@
<div class="form-group"> <div class="form-group">
<label>Catatan Persetujuan</label> <label>Catatan Persetujuan</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Opsional: tambahkan catatan..."></textarea> <textarea name="approval_notes" class="form-control" rows="3" placeholder="Opsional: tambahkan catatan..."></textarea>
</div> </div>
<h6>Detail Produk yang Diterima:</h6> <h6>Detail Produk yang Diterima:</h6>
@@ -182,7 +182,7 @@
<div class="form-group"> <div class="form-group">
<label>Catatan Penerimaan</label> <label>Catatan Penerimaan</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Catatan kondisi barang saat diterima (opsional)"></textarea> <textarea name="reception_notes" class="form-control" rows="3" placeholder="Catatan kondisi barang saat diterima (opsional)"></textarea>
</div> </div>
<h6>Detail Produk yang Diterima:</h6> <h6>Detail Produk yang Diterima:</h6>

View File

@@ -13,7 +13,7 @@
<div class="kt-portlet__head-toolbar"> <div class="kt-portlet__head-toolbar">
<div class="kt-portlet__head-wrapper"> <div class="kt-portlet__head-wrapper">
<div class="kt-portlet__head-actions"> <div class="kt-portlet__head-actions">
<a href="{{ route('mutations.index') }}" class="btn btn-clean btn-sm"> <a href="{{ route('mutations.index') }}" class="btn btn-secondary">
<i class="la la-arrow-left"></i> <i class="la la-arrow-left"></i>
Kembali Kembali
</a> </a>
@@ -65,6 +65,17 @@
</div> </div>
</div> </div>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label for="shipping_notes">Catatan Pengiriman</label>
<textarea name="shipping_notes" id="shipping_notes" class="form-control" rows="3"
placeholder="Catatan khusus untuk pengiriman mutasi ini (opsional)">{{ old('shipping_notes') }}</textarea>
<small class="form-text text-muted">Catatan ini akan dilihat oleh dealer penerima</small>
</div>
</div>
</div>
<div class="kt-separator kt-separator--border-dashed kt-separator--space-lg"></div> <div class="kt-separator kt-separator--border-dashed kt-separator--space-lg"></div>
<div class="form-group"> <div class="form-group">

View File

@@ -14,9 +14,8 @@
<div class="kt-portlet__head-toolbar"> <div class="kt-portlet__head-toolbar">
<div class="kt-portlet__head-wrapper"> <div class="kt-portlet__head-wrapper">
<div class="kt-portlet__head-actions"> <div class="kt-portlet__head-actions">
<a href="{{ route('mutations.index') }}" class="btn btn-secondary btn-sm"> <a href="{{ route('mutations.index') }}" class="btn btn-secondary">
<i class="la la-arrow-left"></i> <i class="la la-arrow-left"></i> Kembali
Kembali
</a> </a>
</div> </div>
</div> </div>
@@ -74,12 +73,45 @@
<p>{{ $mutation->approved_at->format('d/m/Y H:i:s') }}</p> <p>{{ $mutation->approved_at->format('d/m/Y H:i:s') }}</p>
</div> </div>
@endif @endif
</div> @if($mutation->rejectedBy)
</div>
@if($mutation->notes)
<div class="form-group"> <div class="form-group">
<label><strong>Catatan:</strong></label> <label><strong>Ditolak Oleh:</strong></label>
<p>{{ $mutation->notes }}</p> <p>{{ $mutation->rejectedBy->name }}</p>
</div>
<div class="form-group">
<label><strong>Tanggal Ditolak:</strong></label>
<p>{{ $mutation->rejected_at->format('d/m/Y H:i:s') }}</p>
</div>
@endif
@if($mutation->cancelledBy)
<div class="form-group">
<label><strong>Dibatalkan Oleh:</strong></label>
<p>{{ $mutation->cancelledBy->name }}</p>
</div>
<div class="form-group">
<label><strong>Tanggal Dibatalkan:</strong></label>
<p>{{ $mutation->cancelled_at->format('d/m/Y H:i:s') }}</p>
</div>
@endif
</div>
</div>
<!-- Notes Section -->
@if($mutation->shipping_notes)
<div class="form-group">
<label><strong>Catatan Pengiriman:</strong></label>
<div class="alert alert-info">{{ $mutation->shipping_notes }}</div>
</div>
@endif
@if($mutation->reception_notes)
<div class="form-group">
<label><strong>Catatan Penerimaan:</strong></label>
<div class="alert alert-primary">{{ $mutation->reception_notes }}</div>
</div>
@endif
@if($mutation->approval_notes)
<div class="form-group">
<label><strong>Catatan Persetujuan:</strong></label>
<div class="alert alert-success">{{ $mutation->approval_notes }}</div>
</div> </div>
@endif @endif
@if($mutation->rejection_reason) @if($mutation->rejection_reason)
@@ -88,6 +120,12 @@
<div class="alert alert-danger">{{ $mutation->rejection_reason }}</div> <div class="alert alert-danger">{{ $mutation->rejection_reason }}</div>
</div> </div>
@endif @endif
@if($mutation->cancellation_reason)
<div class="form-group">
<label><strong>Alasan Pembatalan:</strong></label>
<div class="alert alert-warning">{{ $mutation->cancellation_reason }}</div>
</div>
@endif
</div> </div>
</div> </div>
@@ -229,7 +267,7 @@
<div class="form-group"> <div class="form-group">
<label>Catatan Penerimaan</label> <label>Catatan Penerimaan</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Catatan kondisi barang saat diterima (opsional)"></textarea> <textarea name="reception_notes" class="form-control" rows="3" placeholder="Catatan kondisi barang saat diterima (opsional)"></textarea>
</div> </div>
<h6>Detail Produk yang Diterima:</h6> <h6>Detail Produk yang Diterima:</h6>
@@ -302,7 +340,7 @@
<div class="form-group"> <div class="form-group">
<label>Catatan Persetujuan</label> <label>Catatan Persetujuan</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Opsional: tambahkan catatan..."></textarea> <textarea name="approval_notes" class="form-control" rows="3" placeholder="Opsional: tambahkan catatan..."></textarea>
</div> </div>
<h6>Detail Produk yang Diterima:</h6> <h6>Detail Produk yang Diterima:</h6>
@@ -428,6 +466,10 @@
<div class="alert alert-warning"> <div class="alert alert-warning">
<strong>Peringatan!</strong> Mutasi yang dibatalkan tidak dapat diubah lagi. <strong>Peringatan!</strong> Mutasi yang dibatalkan tidak dapat diubah lagi.
</div> </div>
<div class="form-group">
<label>Alasan Pembatalan</label>
<textarea name="cancellation_reason" class="form-control" rows="3" placeholder="Masukkan alasan pembatalan (opsional)"></textarea>
</div>
<p>Apakah Anda yakin ingin membatalkan mutasi ini?</p> <p>Apakah Anda yakin ingin membatalkan mutasi ini?</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@@ -457,8 +499,6 @@
text-align: center !important; text-align: center !important;
} }
/* Ensure proper alignment in approval modal */ /* Ensure proper alignment in approval modal */
.approve-form .form-control.text-center { .approve-form .form-control.text-center {
text-align: center; text-align: center;

View File

@@ -94,7 +94,7 @@
@endphp @endphp
<tr class="product-row"> <tr class="product-row">
<td> <td>
<select name="product[0]" class="form-control product-select @error('product.0') is-invalid @enderror" required> <select name="product[0]" class="form-control product-select select2 @error('product.0') is-invalid @enderror" required>
<option value="">Pilih Produk</option> <option value="">Pilih Produk</option>
@foreach($products as $product) @foreach($products as $product)
<option value="{{ $product->id }}" {{ (isset($oldProducts[0]) && $oldProducts[0] == $product->id) ? 'selected' : '' }}> <option value="{{ $product->id }}" {{ (isset($oldProducts[0]) && $oldProducts[0] == $product->id) ? 'selected' : '' }}>
@@ -124,8 +124,7 @@
class="form-control @error('physical_quantity.0') is-invalid @enderror" class="form-control @error('physical_quantity.0') is-invalid @enderror"
step="0.01" min="0" step="0.01" min="0"
value="{{ $oldPhysicalQuantities[0] ?? '' }}" value="{{ $oldPhysicalQuantities[0] ?? '' }}"
required required>
onchange="calculateDifference(this)">
</div> </div>
@error('physical_quantity.0') @error('physical_quantity.0')
<div class="invalid-feedback d-block">{{ $message }}</div> <div class="invalid-feedback d-block">{{ $message }}</div>
@@ -168,7 +167,7 @@
<template id="product-row-template"> <template id="product-row-template">
<tr class="product-row"> <tr class="product-row">
<td> <td>
<select name="product[]" class="form-control product-select" required> <select name="product[]" class="form-control product-select select2" required>
<option value="">Pilih Produk</option> <option value="">Pilih Produk</option>
@foreach($products as $product) @foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option> <option value="{{ $product->id }}">{{ $product->name }}</option>
@@ -184,8 +183,7 @@
<td> <td>
<div class="input-group"> <div class="input-group">
<input type="number" name="physical_quantity[]" class="form-control" <input type="number" name="physical_quantity[]" class="form-control"
step="0.01" min="0" value="" required step="0.01" min="0" value="" required>
onchange="calculateDifference(this)">
</div> </div>
</td> </td>
<td> <td>

View File

@@ -44,5 +44,5 @@
@endsection @endsection
@section('javascripts') @section('javascripts')
<script src="{{ asset('js/warehouse_management/opnames/index.js') }}"></script> <script src="{{ mix('js/warehouse_management/opnames/index.js') }}"></script>
@endsection @endsection