fix handle error and add note for shippings receive approve and reject mutations
This commit is contained in:
@@ -37,4 +37,31 @@ enum MutationStatus: string
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
55
app/Enums/OpnameStatus.php
Normal file
55
app/Enums/OpnameStatus.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -45,21 +45,11 @@ class MutationsController extends Controller
|
||||
return $row->requestedBy->name ?? '-';
|
||||
})
|
||||
->addColumn('status', function($row) {
|
||||
$statusColor = $row->status_color;
|
||||
$statusLabel = $row->status_label;
|
||||
$status = $row->status instanceof MutationStatus ? $row->status : MutationStatus::from($row->status);
|
||||
$textColorClass = $status->textColorClass();
|
||||
$label = $status->label();
|
||||
|
||||
$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>";
|
||||
return "<span class=\"font-weight-bold {$textColorClass}\">{$label}</span>";
|
||||
})
|
||||
->addColumn('total_items', function($row) {
|
||||
return number_format($row->total_items, 0);
|
||||
@@ -91,6 +81,7 @@ class MutationsController extends Controller
|
||||
$request->validate([
|
||||
'from_dealer_id' => 'required|exists:dealers,id',
|
||||
'to_dealer_id' => 'required|exists:dealers,id|different:from_dealer_id',
|
||||
'shipping_notes' => 'nullable|string',
|
||||
'products' => 'required|array|min:1',
|
||||
'products.*.product_id' => 'required|exists:products,id',
|
||||
'products.*.quantity_requested' => 'required|numeric|min:0.01'
|
||||
@@ -102,8 +93,9 @@ class MutationsController extends Controller
|
||||
$mutation = Mutation::create([
|
||||
'from_dealer_id' => $request->from_dealer_id,
|
||||
'to_dealer_id' => $request->to_dealer_id,
|
||||
'status' => 'sent',
|
||||
'requested_by' => auth()->id()
|
||||
'status' => MutationStatus::SENT,
|
||||
'requested_by' => auth()->id(),
|
||||
'shipping_notes' => $request->shipping_notes
|
||||
]);
|
||||
|
||||
// Buat mutation details
|
||||
@@ -134,7 +126,16 @@ class MutationsController extends Controller
|
||||
|
||||
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'));
|
||||
}
|
||||
@@ -142,7 +143,7 @@ class MutationsController extends Controller
|
||||
public function receive(Request $request, Mutation $mutation)
|
||||
{
|
||||
$request->validate([
|
||||
'notes' => 'nullable|string',
|
||||
'reception_notes' => 'nullable|string',
|
||||
'products' => 'required|array',
|
||||
'products.*.quantity_approved' => 'required|numeric|min:0',
|
||||
'products.*.notes' => 'nullable|string'
|
||||
@@ -154,11 +155,6 @@ class MutationsController extends Controller
|
||||
|
||||
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) {
|
||||
@@ -182,8 +178,8 @@ class MutationsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// Receive mutation
|
||||
$mutation->receive(auth()->id());
|
||||
// Receive mutation with reception notes
|
||||
$mutation->receive(auth()->id(), $request->reception_notes);
|
||||
|
||||
DB::commit();
|
||||
return redirect()->route('mutations.index')
|
||||
@@ -198,7 +194,7 @@ class MutationsController extends Controller
|
||||
public function approve(Request $request, Mutation $mutation)
|
||||
{
|
||||
$request->validate([
|
||||
'notes' => 'nullable|string'
|
||||
'approval_notes' => 'nullable|string'
|
||||
]);
|
||||
|
||||
if (!$mutation->canBeApproved()) {
|
||||
@@ -207,7 +203,7 @@ class MutationsController extends Controller
|
||||
|
||||
try {
|
||||
// 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')
|
||||
->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()) {
|
||||
return back()->withErrors(['error' => 'Mutasi tidak dapat dibatalkan dalam status saat ini']);
|
||||
}
|
||||
|
||||
try {
|
||||
$mutation->update(['status' => MutationStatus::CANCELLED]);
|
||||
$mutation->cancel(auth()->id(), $request->cancellation_reason);
|
||||
|
||||
return redirect()->route('mutations.index')
|
||||
->with('success', 'Mutasi berhasil dibatalkan');
|
||||
@@ -349,6 +349,8 @@ class MutationsController extends Controller
|
||||
'requestedBy.role',
|
||||
'approvedBy.role',
|
||||
'receivedBy.role',
|
||||
'rejectedBy.role',
|
||||
'cancelledBy.role',
|
||||
'mutationDetails.product'
|
||||
]);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\WarehouseManagement;
|
||||
|
||||
use App\Enums\OpnameStatus;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Dealer;
|
||||
use App\Models\Menu;
|
||||
@@ -39,30 +40,16 @@ class OpnamesController extends Controller
|
||||
return Carbon::parse($row->created_at)->format('d M Y H:i');
|
||||
})
|
||||
->editColumn('status', function ($row) {
|
||||
$statusColor = [
|
||||
'draft' => 'warning',
|
||||
'pending' => 'info',
|
||||
'approved' => 'success',
|
||||
'rejected' => 'danger'
|
||||
][$row->status] ?? 'secondary';
|
||||
$status = $row->status instanceof OpnameStatus ? $row->status : OpnameStatus::from($row->status);
|
||||
$textColorClass = $status->textColorClass();
|
||||
$label = $status->label();
|
||||
|
||||
$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>";
|
||||
return "<span class=\"font-weight-bold {$textColorClass}\">{$label}</span>";
|
||||
})
|
||||
->addColumn('action', function ($row) use ($menu) {
|
||||
$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>';
|
||||
|
||||
@@ -253,7 +240,7 @@ class OpnamesController extends Controller
|
||||
'opname_date' => $opnameDate,
|
||||
'user_id' => $userId,
|
||||
'note' => $note,
|
||||
'status' => 'approved', // Set status langsung approved
|
||||
'status' => OpnameStatus::APPROVED, // Set status langsung approved
|
||||
'approved_by' => $userId, // Set current user sebagai approver
|
||||
'approved_at' => now() // Set waktu approval
|
||||
]);
|
||||
|
||||
@@ -20,16 +20,25 @@ class Mutation extends Model
|
||||
'requested_by',
|
||||
'approved_by',
|
||||
'approved_at',
|
||||
'approval_notes',
|
||||
'received_by',
|
||||
'received_at',
|
||||
'notes',
|
||||
'rejection_reason'
|
||||
'reception_notes',
|
||||
'shipping_notes',
|
||||
'rejection_reason',
|
||||
'rejected_by',
|
||||
'rejected_at',
|
||||
'cancelled_by',
|
||||
'cancelled_at',
|
||||
'cancellation_reason'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'status' => MutationStatus::class,
|
||||
'approved_at' => 'datetime',
|
||||
'received_at' => 'datetime'
|
||||
'received_at' => 'datetime',
|
||||
'rejected_at' => 'datetime',
|
||||
'cancelled_at' => 'datetime'
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
@@ -66,6 +75,16 @@ class Mutation extends Model
|
||||
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()
|
||||
{
|
||||
return $this->hasMany(MutationDetail::class);
|
||||
@@ -137,7 +156,7 @@ class Mutation extends Model
|
||||
}
|
||||
|
||||
// Receive mutation by destination dealer
|
||||
public function receive($userId)
|
||||
public function receive($userId, $receptionNotes = null)
|
||||
{
|
||||
if (!$this->canBeReceived()) {
|
||||
throw new \Exception('Mutasi tidak dapat diterima dalam status saat ini');
|
||||
@@ -146,14 +165,15 @@ class Mutation extends Model
|
||||
$this->update([
|
||||
'status' => MutationStatus::RECEIVED,
|
||||
'received_by' => $userId,
|
||||
'received_at' => now()
|
||||
'received_at' => now(),
|
||||
'reception_notes' => $receptionNotes
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Approve mutation
|
||||
public function approve($userId, $notes = null)
|
||||
public function approve($userId, $approvalNotes = null)
|
||||
{
|
||||
if (!$this->canBeApproved()) {
|
||||
throw new \Exception('Mutasi tidak dapat disetujui dalam status saat ini');
|
||||
@@ -163,7 +183,7 @@ class Mutation extends Model
|
||||
'status' => MutationStatus::APPROVED,
|
||||
'approved_by' => $userId,
|
||||
'approved_at' => now(),
|
||||
'notes' => $notes
|
||||
'approval_notes' => $approvalNotes
|
||||
]);
|
||||
|
||||
return $this;
|
||||
@@ -178,14 +198,31 @@ class Mutation extends Model
|
||||
|
||||
$this->update([
|
||||
'status' => MutationStatus::REJECTED,
|
||||
'approved_by' => $userId,
|
||||
'approved_at' => now(),
|
||||
'rejected_by' => $userId,
|
||||
'rejected_at' => now(),
|
||||
'rejection_reason' => $rejectionReason
|
||||
]);
|
||||
|
||||
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)
|
||||
public function complete()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\OpnameStatus;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@@ -22,14 +23,15 @@ class Opname extends Model
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'approved_at' => 'datetime'
|
||||
'approved_at' => 'datetime',
|
||||
'status' => OpnameStatus::class
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::updated(function ($opname) {
|
||||
// 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
|
||||
foreach ($opname->details as $detail) {
|
||||
$stock = Stock::firstOrCreate(
|
||||
@@ -74,11 +76,11 @@ class Opname extends Model
|
||||
// Method untuk approve opname
|
||||
public function approve(User $approver)
|
||||
{
|
||||
if ($this->status !== 'pending') {
|
||||
if ($this->status !== OpnameStatus::PENDING) {
|
||||
throw new \Exception('Only pending opnames can be approved');
|
||||
}
|
||||
|
||||
$this->status = 'approved';
|
||||
$this->status = OpnameStatus::APPROVED;
|
||||
$this->approved_by = $approver->id;
|
||||
$this->approved_at = now();
|
||||
$this->save();
|
||||
@@ -89,11 +91,11 @@ class Opname extends Model
|
||||
// Method untuk reject opname
|
||||
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');
|
||||
}
|
||||
|
||||
$this->status = 'rejected';
|
||||
$this->status = OpnameStatus::REJECTED;
|
||||
$this->approved_by = $rejector->id;
|
||||
$this->approved_at = now();
|
||||
$this->rejection_note = $note;
|
||||
@@ -105,11 +107,11 @@ class Opname extends Model
|
||||
// Method untuk submit opname untuk approval
|
||||
public function submit()
|
||||
{
|
||||
if ($this->status !== 'draft') {
|
||||
if ($this->status !== OpnameStatus::DRAFT) {
|
||||
throw new \Exception('Only draft opnames can be submitted');
|
||||
}
|
||||
|
||||
$this->status = 'pending';
|
||||
$this->status = OpnameStatus::PENDING;
|
||||
$this->save();
|
||||
|
||||
return $this;
|
||||
|
||||
Binary file not shown.
@@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
11167
public/css/app.css
11167
public/css/app.css
File diff suppressed because one or more lines are too long
1211
public/js/app.js
1211
public/js/app.js
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
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
@@ -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"]();
|
||||
/******/
|
||||
/******/ })()
|
||||
;
|
||||
@@ -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
@@ -21,7 +21,12 @@ $(document).ready(function () {
|
||||
|
||||
if (fromDealerId && toDealerId && fromDealerId === toDealerId) {
|
||||
$(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
|
||||
@@ -216,24 +221,40 @@ $(document).ready(function () {
|
||||
|
||||
// Check dealers
|
||||
if (!fromDealerId) {
|
||||
alert("Pilih dealer asal");
|
||||
Swal.fire({
|
||||
type: "error",
|
||||
title: "Oops...",
|
||||
text: "Pilih dealer asal",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!toDealerId) {
|
||||
alert("Pilih dealer tujuan");
|
||||
Swal.fire({
|
||||
type: "error",
|
||||
title: "Oops...",
|
||||
text: "Pilih dealer tujuan",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Check products
|
||||
const productRows = $(".product-row");
|
||||
if (productRows.length === 0) {
|
||||
alert("Tambahkan minimal satu produk");
|
||||
Swal.fire({
|
||||
type: "error",
|
||||
title: "Oops...",
|
||||
text: "Tambahkan minimal satu produk",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -248,7 +269,11 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
$(document).ready(function () {
|
||||
console.log("Opnames create.js loaded - SweetAlert version");
|
||||
|
||||
$(".select2").select2({
|
||||
placeholder: "Pilih...",
|
||||
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
|
||||
function fetchStockData() {
|
||||
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
|
||||
$("#btn-add-row").click(function () {
|
||||
const template = document.getElementById("product-row-template");
|
||||
@@ -122,7 +145,16 @@ $(document).ready(function () {
|
||||
$(newRow).find("select").val("");
|
||||
$(newRow).find("input:not(.system-quantity)").val("");
|
||||
|
||||
// Append to DOM first
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -143,30 +175,50 @@ $(document).ready(function () {
|
||||
// Fungsi untuk reindex semua baris
|
||||
function reindexRows() {
|
||||
$(".product-row").each(function (index) {
|
||||
$(this)
|
||||
.find('select[name^="product"]')
|
||||
.attr("name", `product[${index}]`);
|
||||
$(this)
|
||||
.find('input[name^="system_quantity"]')
|
||||
.attr("name", `system_quantity[${index}]`);
|
||||
$(this)
|
||||
.find('input[name^="physical_quantity"]')
|
||||
.attr("name", `physical_quantity[${index}]`);
|
||||
$(this)
|
||||
.find('input[name^="item_notes"]')
|
||||
.attr("name", `item_notes[${index}]`);
|
||||
const $row = $(this);
|
||||
const $select = $row.find('select[name^="product"]');
|
||||
|
||||
// Destroy select2 before changing attributes
|
||||
if ($select.data("select2")) {
|
||||
$select.select2("destroy");
|
||||
}
|
||||
|
||||
$select.attr("name", `product[${index}]`);
|
||||
$row.find('input[name^="system_quantity"]').attr(
|
||||
"name",
|
||||
`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
|
||||
function calculateDifference(input) {
|
||||
// Update calculateDifference function - make it globally accessible
|
||||
window.calculateDifference = function (input) {
|
||||
const row = $(input).closest("tr");
|
||||
const systemQty = parseFloat(row.find(".system-quantity").val()) || 0;
|
||||
const physicalQty =
|
||||
parseFloat(row.find('input[name^="physical_quantity"]').val()) || 0;
|
||||
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.attr("required", true);
|
||||
noteInput.attr(
|
||||
@@ -180,7 +232,7 @@ $(document).ready(function () {
|
||||
noteInput.attr("placeholder", "Catatan item");
|
||||
row.removeClass("table-warning");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent manual editing of system quantity
|
||||
$(document).on("keydown", ".system-quantity", function (e) {
|
||||
@@ -198,7 +250,11 @@ $(document).ready(function () {
|
||||
const dealerId = $("#dealer").val();
|
||||
if (!dealerId) {
|
||||
e.preventDefault();
|
||||
alert("Silakan pilih dealer terlebih dahulu!");
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Oops...",
|
||||
text: "Silakan pilih dealer terlebih dahulu!",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -212,14 +268,22 @@ $(document).ready(function () {
|
||||
const uniqueProducts = [...new Set(products)];
|
||||
if (products.length !== uniqueProducts.length) {
|
||||
e.preventDefault();
|
||||
alert("Produk tidak boleh duplikat!");
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Oops...",
|
||||
text: "Produk tidak boleh duplikat!",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cek produk kosong
|
||||
if (products.includes("")) {
|
||||
e.preventDefault();
|
||||
alert("Semua produk harus dipilih!");
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Oops...",
|
||||
text: "Semua produk harus dipilih!",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -236,7 +300,11 @@ $(document).ready(function () {
|
||||
) || 0;
|
||||
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;
|
||||
$(this).addClass("table-danger");
|
||||
}
|
||||
@@ -244,9 +312,11 @@ $(document).ready(function () {
|
||||
|
||||
if (hasInvalidNotes) {
|
||||
e.preventDefault();
|
||||
alert(
|
||||
"Catatan wajib diisi untuk produk yang memiliki perbedaan stock!"
|
||||
);
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Oops...",
|
||||
text: "Catatan wajib diisi untuk produk yang memiliki perbedaan stock!",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1728,7 +1728,7 @@ use Illuminate\Support\Facades\Auth;
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<strong>Diminta oleh:</strong><br>
|
||||
<strong>Dikirim oleh:</strong><br>
|
||||
${mutation.requested_by ? mutation.requested_by.name : '-'}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1738,7 +1738,7 @@ use Illuminate\Support\Facades\Auth;
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<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>
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<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>
|
||||
|
||||
<h6>Detail Produk yang Diterima:</h6>
|
||||
@@ -182,7 +182,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<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>
|
||||
|
||||
<h6>Detail Produk yang Diterima:</h6>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="kt-portlet__head-toolbar">
|
||||
<div class="kt-portlet__head-wrapper">
|
||||
<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>
|
||||
Kembali
|
||||
</a>
|
||||
@@ -65,6 +65,17 @@
|
||||
</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="form-group">
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
<div class="kt-portlet__head-toolbar">
|
||||
<div class="kt-portlet__head-wrapper">
|
||||
<div class="kt-portlet__head-actions">
|
||||
<a href="{{ route('mutations.index') }}" class="btn btn-secondary btn-sm">
|
||||
<i class="la la-arrow-left"></i>
|
||||
Kembali
|
||||
<a href="{{ route('mutations.index') }}" class="btn btn-secondary">
|
||||
<i class="la la-arrow-left"></i> Kembali
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,12 +73,45 @@
|
||||
<p>{{ $mutation->approved_at->format('d/m/Y H:i:s') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if($mutation->notes)
|
||||
@if($mutation->rejectedBy)
|
||||
<div class="form-group">
|
||||
<label><strong>Catatan:</strong></label>
|
||||
<p>{{ $mutation->notes }}</p>
|
||||
<label><strong>Ditolak Oleh:</strong></label>
|
||||
<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>
|
||||
@endif
|
||||
@if($mutation->rejection_reason)
|
||||
@@ -88,6 +120,12 @@
|
||||
<div class="alert alert-danger">{{ $mutation->rejection_reason }}</div>
|
||||
</div>
|
||||
@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>
|
||||
|
||||
@@ -229,7 +267,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<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>
|
||||
|
||||
<h6>Detail Produk yang Diterima:</h6>
|
||||
@@ -302,7 +340,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<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>
|
||||
|
||||
<h6>Detail Produk yang Diterima:</h6>
|
||||
@@ -428,6 +466,10 @@
|
||||
<div class="alert alert-warning">
|
||||
<strong>Peringatan!</strong> Mutasi yang dibatalkan tidak dapat diubah lagi.
|
||||
</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>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -457,8 +499,6 @@
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Ensure proper alignment in approval modal */
|
||||
.approve-form .form-control.text-center {
|
||||
text-align: center;
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
@endphp
|
||||
<tr class="product-row">
|
||||
<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>
|
||||
@foreach($products as $product)
|
||||
<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"
|
||||
step="0.01" min="0"
|
||||
value="{{ $oldPhysicalQuantities[0] ?? '' }}"
|
||||
required
|
||||
onchange="calculateDifference(this)">
|
||||
required>
|
||||
</div>
|
||||
@error('physical_quantity.0')
|
||||
<div class="invalid-feedback d-block">{{ $message }}</div>
|
||||
@@ -168,7 +167,7 @@
|
||||
<template id="product-row-template">
|
||||
<tr class="product-row">
|
||||
<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>
|
||||
@foreach($products as $product)
|
||||
<option value="{{ $product->id }}">{{ $product->name }}</option>
|
||||
@@ -184,8 +183,7 @@
|
||||
<td>
|
||||
<div class="input-group">
|
||||
<input type="number" name="physical_quantity[]" class="form-control"
|
||||
step="0.01" min="0" value="" required
|
||||
onchange="calculateDifference(this)">
|
||||
step="0.01" min="0" value="" required>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -44,5 +44,5 @@
|
||||
@endsection
|
||||
|
||||
@section('javascripts')
|
||||
<script src="{{ asset('js/warehouse_management/opnames/index.js') }}"></script>
|
||||
<script src="{{ mix('js/warehouse_management/opnames/index.js') }}"></script>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user