partial update create mutations

This commit is contained in:
2025-06-12 00:33:59 +07:00
parent 0b211915f1
commit a5e1348436
32 changed files with 2883 additions and 548 deletions

View File

@@ -0,0 +1,56 @@
APP_NAME="CKB Application"
APP_ENV=local
APP_KEY=base64:3XK9+3S0lhXOPnlBjPxik2Ru1oARb8REF5b/pfaHLKM=
APP_DEBUG=true
APP_URL=http://localhost:8000
LOG_CHANNEL=stack
LOG_LEVEL=debug
# Database Configuration for Docker
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=ckb_db
DB_USERNAME=laravel
DB_PASSWORD=password
DB_ROOT_PASSWORD=root
# Redis Configuration for Docker
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
# Cache Configuration
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
# Mail Configuration (using MailHog for development)
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=noreply@ckb.local
MAIL_FROM_NAME="${APP_NAME}"
# Broadcasting
BROADCAST_DRIVER=log
# AWS (if needed)
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
# Pusher (if needed)
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

View File

@@ -0,0 +1,112 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use App\Models\Mutation;
use App\Models\MutationDetail;
class ClearMutationsCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mutations:clear {--force : Force the operation without confirmation}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clear all mutations and mutation details, then reset auto increment IDs';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*/
public function handle()
{
// Show warning
$this->warn('⚠️ WARNING: This will permanently delete ALL mutations and mutation details!');
$this->warn('⚠️ This action cannot be undone!');
// Check for force flag
if (!$this->option('force')) {
if (!$this->confirm('Are you sure you want to continue?')) {
$this->info('Operation cancelled.');
return 0;
}
}
// Show current counts
$mutationCount = Mutation::count();
$detailCount = MutationDetail::count();
$trashedMutationCount = Mutation::onlyTrashed()->count();
$this->info("Current data:");
$this->info("- Mutations: {$mutationCount}");
$this->info("- Mutation Details: {$detailCount}");
if ($trashedMutationCount > 0) {
$this->info("- Soft Deleted Mutations: {$trashedMutationCount}");
}
if ($mutationCount === 0 && $detailCount === 0 && $trashedMutationCount === 0) {
$this->info('No data to clear.');
return 0;
}
$this->info('Starting cleanup process...');
try {
// Delete data within transaction
DB::beginTransaction();
// Delete mutation details first (foreign key constraint)
$this->info('🗑️ Deleting mutation details...');
MutationDetail::query()->delete();
// Delete mutations (including soft deleted ones)
$this->info('🗑️ Deleting mutations...');
Mutation::query()->delete();
// Force delete soft deleted mutations
$this->info('🗑️ Force deleting soft deleted mutations...');
Mutation::onlyTrashed()->forceDelete();
DB::commit();
// Reset auto increment outside transaction (DDL operations auto-commit)
$this->info('🔄 Resetting mutation_details auto increment...');
DB::statement('ALTER TABLE mutation_details AUTO_INCREMENT = 1');
$this->info('🔄 Resetting mutations auto increment...');
DB::statement('ALTER TABLE mutations AUTO_INCREMENT = 1');
$this->info('✅ Successfully cleared all mutations and reset auto increment!');
$this->info('📊 Final counts:');
$this->info('- Mutations: ' . Mutation::count());
$this->info('- Mutation Details: ' . MutationDetail::count());
return 0;
} catch (\Exception $e) {
if (DB::transactionLevel() > 0) {
DB::rollback();
}
$this->error('❌ Failed to clear mutations: ' . $e->getMessage());
return 1;
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Enums;
enum MutationStatus: string
{
case PENDING = 'pending';
case SENT = 'sent';
case RECEIVED = 'received';
case APPROVED = 'approved';
case REJECTED = 'rejected';
case COMPLETED = 'completed';
case CANCELLED = 'cancelled';
public function label(): string
{
return match($this) {
self::PENDING => 'Menunggu Konfirmasi',
self::SENT => 'Terkirim ke Dealer',
self::RECEIVED => 'Diterima Dealer',
self::APPROVED => 'Disetujui',
self::REJECTED => 'Ditolak',
self::COMPLETED => 'Selesai',
self::CANCELLED => 'Dibatalkan',
};
}
public function color(): string
{
return match($this) {
self::PENDING => 'warning',
self::SENT => 'primary',
self::RECEIVED => 'info',
self::APPROVED => 'brand',
self::REJECTED => 'danger',
self::COMPLETED => 'success',
self::CANCELLED => 'secondary',
};
}
}

View File

@@ -0,0 +1,278 @@
<?php
namespace App\Http\Controllers\WarehouseManagement;
use App\Http\Controllers\Controller;
use App\Models\Mutation;
use App\Models\MutationDetail;
use App\Models\Product;
use App\Models\Dealer;
use App\Enums\MutationStatus;
use App\Models\Menu;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\DataTables;
class MutationsController extends Controller
{
public function index(Request $request)
{
$menu = Menu::where('link','mutations.index')->first();
if ($request->ajax()) {
$data = Mutation::with(['fromDealer', 'toDealer', 'requestedBy', 'approvedBy', 'receivedBy'])
->select('mutations.*');
// Filter berdasarkan dealer jika user bukan admin
if (auth()->user()->dealer_id) {
$data->where(function($query) {
$query->where('from_dealer_id', auth()->user()->dealer_id)
->orWhere('to_dealer_id', auth()->user()->dealer_id);
});
}
return DataTables::of($data)
->addIndexColumn()
->addColumn('mutation_number', function($row) {
return $row->mutation_number;
})
->addColumn('from_dealer', function($row) {
return $row->fromDealer->name ?? '-';
})
->addColumn('to_dealer', function($row) {
return $row->toDealer->name ?? '-';
})
->addColumn('requested_by', function($row) {
return $row->requestedBy->name ?? '-';
})
->addColumn('status', function($row) {
$statusColor = $row->status_color;
$statusLabel = $row->status_label;
return "<span class=\"kt-badge kt-badge--{$statusColor} kt-badge--dot\"></span>&nbsp;<span class=\"kt-font-bold kt-font-{$statusColor}\">{$statusLabel}</span>";
})
->addColumn('total_items', function($row) {
return number_format($row->total_items, 0);
})
->addColumn('created_at', function($row) {
return $row->created_at->format('d/m/Y H:i');
})
->addColumn('action', function($row) {
return view('warehouse_management.mutations._action', compact('row'))->render();
})
->rawColumns(['status', 'action'])
->make(true);
}
return view('warehouse_management.mutations.index', compact('menu'));
}
public function create()
{
$menu = Menu::where('link','mutations.create')->first();
$dealers = Dealer::all();
$products = Product::with('stocks')->get();
return view('warehouse_management.mutations.create', compact('menu', 'dealers', 'products'));
}
public function store(Request $request)
{
$request->validate([
'from_dealer_id' => 'required|exists:dealers,id',
'to_dealer_id' => 'required|exists:dealers,id|different:from_dealer_id',
'notes' => 'nullable|string',
'products' => 'required|array|min:1',
'products.*.product_id' => 'required|exists:products,id',
'products.*.quantity_requested' => 'required|numeric|min:0.01'
]);
DB::beginTransaction();
try {
// Buat mutation record dengan status SENT (langsung terkirim ke dealer tujuan)
$mutation = Mutation::create([
'from_dealer_id' => $request->from_dealer_id,
'to_dealer_id' => $request->to_dealer_id,
'status' => 'sent',
'requested_by' => auth()->id(),
'notes' => $request->notes
]);
// Buat mutation details
foreach ($request->products as $productData) {
MutationDetail::create([
'mutation_id' => $mutation->id,
'product_id' => $productData['product_id'],
'quantity_requested' => $productData['quantity_requested'],
'notes' => $productData['notes'] ?? null
]);
}
DB::commit();
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil dibuat dan terkirim ke dealer tujuan');
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Gagal membuat mutasi: ' . $e->getMessage()]);
}
}
public function show(Mutation $mutation)
{
$mutation->load(['fromDealer', 'toDealer', 'requestedBy', 'approvedBy', 'receivedBy', 'mutationDetails.product']);
return view('warehouse_management.mutations.show', compact('mutation'));
}
public function receive(Mutation $mutation)
{
if (!$mutation->canBeReceived()) {
return back()->withErrors(['error' => 'Mutasi tidak dapat diterima dalam status saat ini']);
}
try {
$mutation->receive(auth()->id());
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil diterima dan menunggu persetujuan pengirim');
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Gagal menerima mutasi: ' . $e->getMessage()]);
}
}
public function approve(Request $request, Mutation $mutation)
{
$request->validate([
'notes' => 'nullable|string',
'details' => 'required|array',
'details.*.quantity_approved' => 'required|numeric|min:0'
]);
if (!$mutation->canBeApproved()) {
return back()->withErrors(['error' => 'Mutasi tidak dapat disetujui dalam status saat ini']);
}
DB::beginTransaction();
try {
// Update mutation details dengan quantity approved
foreach ($request->details as $detailId => $detailData) {
$mutationDetail = MutationDetail::findOrFail($detailId);
$mutationDetail->update([
'quantity_approved' => $detailData['quantity_approved']
]);
}
// Approve mutation
$mutation->approve(auth()->id(), $request->notes);
DB::commit();
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil disetujui');
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors(['error' => 'Gagal menyetujui mutasi: ' . $e->getMessage()]);
}
}
public function reject(Request $request, Mutation $mutation)
{
$request->validate([
'rejection_reason' => 'required|string'
]);
if (!$mutation->canBeApproved()) {
return back()->withErrors(['error' => 'Mutasi tidak dapat ditolak dalam status saat ini']);
}
try {
$mutation->reject(auth()->id(), $request->rejection_reason);
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil ditolak');
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Gagal menolak mutasi: ' . $e->getMessage()]);
}
}
public function complete(Mutation $mutation)
{
if (!$mutation->canBeCompleted()) {
return back()->withErrors(['error' => 'Mutasi tidak dapat diselesaikan dalam status saat ini']);
}
try {
$mutation->complete();
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil diselesaikan dan stock telah dipindahkan');
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Gagal menyelesaikan mutasi: ' . $e->getMessage()]);
}
}
public function cancel(Mutation $mutation)
{
if (!$mutation->canBeCancelled()) {
return back()->withErrors(['error' => 'Mutasi tidak dapat dibatalkan dalam status saat ini']);
}
try {
$mutation->update(['status' => MutationStatus::CANCELLED]);
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil dibatalkan');
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Gagal membatalkan mutasi: ' . $e->getMessage()]);
}
}
// API untuk mendapatkan detail mutasi untuk approval
public function getDetails(Mutation $mutation)
{
$mutation->load(['mutationDetails.product', 'fromDealer']);
$details = $mutation->mutationDetails->map(function($detail) use ($mutation) {
$availableStock = $detail->product->getStockByDealer($mutation->from_dealer_id);
return [
'id' => $detail->id,
'product' => [
'id' => $detail->product->id,
'name' => $detail->product->name
],
'quantity_requested' => $detail->quantity_requested,
'quantity_approved' => $detail->quantity_approved,
'available_stock' => $availableStock
];
});
return response()->json([
'mutation' => [
'id' => $mutation->id,
'mutation_number' => $mutation->mutation_number,
'from_dealer' => $mutation->fromDealer->name,
'to_dealer' => $mutation->toDealer->name
],
'details' => $details
]);
}
// API untuk mendapatkan stock produk di dealer tertentu
public function getProductStock(Request $request)
{
$dealerId = $request->dealer_id;
$productId = $request->product_id;
$product = Product::findOrFail($productId);
$stock = $product->getStockByDealer($dealerId);
return response()->json([
'product_name' => $product->name,
'current_stock' => $stock
]);
}
}

View File

@@ -41,13 +41,11 @@ class ProductsController extends Controller
if (Gate::allows('update', $menu)) { if (Gate::allows('update', $menu)) {
$btn .= '<a href="' . route('products.edit', $row->id) . '" class="btn btn-warning btn-sm" style="margin-right: 8px;">Edit</a>'; $btn .= '<a href="' . route('products.edit', $row->id) . '" class="btn btn-warning btn-sm" style="margin-right: 8px;">Edit</a>';
$btn .= '<button class="btn btn-sm btn-toggle-active '
. ($row->active ? 'btn-danger' : 'btn-success') . '"
data-url="' . route('products.toggleActive', $row->id) . '" data-active="'.$row->active.'" style="margin-right: 8px;">'
. ($row->active ? 'Nonaktifkan' : 'Aktifkan') . '</button>';
} }
$btn .= '<button class="btn btn-sm btn-toggle-active '
. ($row->active ? 'btn-danger' : 'btn-success') . '"
data-url="' . route('products.toggleActive', $row->id) . '" data-active="'.$row->active.'" style="margin-right: 8px;">'
. ($row->active ? 'Nonaktifkan' : 'Aktifkan') . '</button>';
$btn .= '<button class="btn btn-sm btn-secondary btn-product-stock-dealers" $btn .= '<button class="btn btn-sm btn-secondary btn-product-stock-dealers"
data-id="'.$row->id.'" data-id="'.$row->id.'"
data-url="'.route('products.dealers_stock').'" data-url="'.route('products.dealers_stock').'"

View File

@@ -1,117 +0,0 @@
<?php
namespace App\Http\Controllers\WarehouseManagement;
use App\Http\Controllers\Controller;
use App\Models\Dealer;
use App\Models\Product;
use App\Models\Stock;
use App\Models\StockLog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class StocksController extends Controller
{
public function index(Request $request)
{
if ($request->ajax()) {
$query = Stock::with(['dealer', 'product'])
->when($request->dealer_id, function($q) use ($request) {
return $q->where('dealer_id', $request->dealer_id);
})
->when($request->product_id, function($q) use ($request) {
return $q->where('product_id', $request->product_id);
});
return datatables()->of($query)
->addColumn('dealer_name', function($stock) {
return $stock->dealer->name;
})
->addColumn('product_name', function($stock) {
return $stock->product->name;
})
->addColumn('action', function($stock) {
return view('warehouse_management.stocks._action', compact('stock'));
})
->toJson();
}
$dealers = Dealer::all();
$products = Product::where('active', true)->get();
return view('warehouse_management.stocks.index', compact('dealers', 'products'));
}
public function adjust(Request $request)
{
$request->validate([
'stock_id' => 'required|exists:stocks,id',
'type' => 'required|in:add,reduce',
'quantity' => 'required|numeric|min:0.01',
'note' => 'required|string|max:255'
]);
try {
DB::beginTransaction();
$stock = Stock::findOrFail($request->stock_id);
$oldQuantity = $stock->quantity;
// Calculate new quantity
$change = $request->type === 'add' ? $request->quantity : -$request->quantity;
$newQuantity = $oldQuantity + $change;
// Update stock
$stock->update(['quantity' => $newQuantity]);
// Log the change
StockLog::create([
'stock_id' => $stock->id,
'user_id' => Auth::id(),
'old_quantity' => $oldQuantity,
'new_quantity' => $newQuantity,
'change' => $change,
'note' => $request->note,
'reference_type' => 'manual_adjustment'
]);
DB::commit();
return response()->json([
'success' => true,
'message' => 'Stok berhasil diadjust'
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'success' => false,
'message' => 'Gagal mengadjust stok: ' . $e->getMessage()
], 500);
}
}
public function history(Request $request)
{
$request->validate([
'stock_id' => 'required|exists:stocks,id'
]);
$logs = StockLog::with('user')
->where('stock_id', $request->stock_id)
->orderBy('created_at', 'desc')
->get()
->map(function($log) {
return [
'date' => $log->created_at->format('d/m/Y H:i'),
'user' => $log->user->name,
'change' => $log->change > 0 ? '+' . $log->change : $log->change,
'old_quantity' => $log->old_quantity,
'new_quantity' => $log->new_quantity,
'note' => $log->note
];
});
return response()->json(['logs' => $logs]);
}
}

View File

@@ -26,4 +26,19 @@ class Dealer extends Model
public function opnames(){ public function opnames(){
return $this->hasMany(Opname::class); return $this->hasMany(Opname::class);
} }
public function outgoingMutations()
{
return $this->hasMany(Mutation::class, 'from_dealer_id');
}
public function incomingMutations()
{
return $this->hasMany(Mutation::class, 'to_dealer_id');
}
public function stocks()
{
return $this->hasMany(Stock::class);
}
} }

262
app/Models/Mutation.php Normal file
View File

@@ -0,0 +1,262 @@
<?php
namespace App\Models;
use App\Enums\MutationStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Mutation extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'mutation_number',
'from_dealer_id',
'to_dealer_id',
'status',
'requested_by',
'approved_by',
'approved_at',
'received_by',
'received_at',
'notes',
'rejection_reason'
];
protected $casts = [
'status' => MutationStatus::class,
'approved_at' => 'datetime',
'received_at' => 'datetime'
];
protected static function booted()
{
static::creating(function ($mutation) {
if (empty($mutation->mutation_number)) {
$mutation->mutation_number = $mutation->generateMutationNumber();
}
});
}
public function fromDealer()
{
return $this->belongsTo(Dealer::class, 'from_dealer_id');
}
public function toDealer()
{
return $this->belongsTo(Dealer::class, 'to_dealer_id');
}
public function requestedBy()
{
return $this->belongsTo(User::class, 'requested_by');
}
public function approvedBy()
{
return $this->belongsTo(User::class, 'approved_by');
}
public function receivedBy()
{
return $this->belongsTo(User::class, 'received_by');
}
public function mutationDetails()
{
return $this->hasMany(MutationDetail::class);
}
public function stockLogs()
{
return $this->morphMany(StockLog::class, 'source');
}
// Helper methods
public function getStatusLabelAttribute()
{
return $this->status->label();
}
public function getStatusColorAttribute()
{
return $this->status->color();
}
public function getTotalItemsAttribute()
{
return $this->mutationDetails()->sum('quantity_requested');
}
public function getTotalApprovedItemsAttribute()
{
return $this->mutationDetails()->sum('quantity_approved');
}
public function canBeSent()
{
return $this->status === MutationStatus::PENDING;
}
public function canBeReceived()
{
return $this->status === MutationStatus::SENT;
}
public function canBeApproved()
{
return $this->status === MutationStatus::RECEIVED;
}
public function canBeCompleted()
{
return $this->status === MutationStatus::APPROVED;
}
public function canBeCancelled()
{
return in_array($this->status, [MutationStatus::PENDING, MutationStatus::SENT]);
}
// Send mutation to destination dealer
public function send($userId)
{
if (!$this->canBeSent()) {
throw new \Exception('Mutasi tidak dapat dikirim dalam status saat ini');
}
$this->update([
'status' => MutationStatus::SENT
]);
return $this;
}
// Receive mutation by destination dealer
public function receive($userId)
{
if (!$this->canBeReceived()) {
throw new \Exception('Mutasi tidak dapat diterima dalam status saat ini');
}
$this->update([
'status' => MutationStatus::RECEIVED,
'received_by' => $userId,
'received_at' => now()
]);
return $this;
}
// Approve mutation
public function approve($userId, $notes = null)
{
if (!$this->canBeApproved()) {
throw new \Exception('Mutasi tidak dapat disetujui dalam status saat ini');
}
$this->update([
'status' => MutationStatus::APPROVED,
'approved_by' => $userId,
'approved_at' => now(),
'notes' => $notes
]);
return $this;
}
// Reject mutation
public function reject($userId, $rejectionReason)
{
if (!$this->canBeApproved()) {
throw new \Exception('Mutasi tidak dapat ditolak dalam status saat ini');
}
$this->update([
'status' => MutationStatus::REJECTED,
'approved_by' => $userId,
'approved_at' => now(),
'rejection_reason' => $rejectionReason
]);
return $this;
}
// Complete mutation (actually move the stock)
public function complete()
{
if (!$this->canBeCompleted()) {
throw new \Exception('Mutasi tidak dapat diselesaikan dalam status saat ini');
}
\DB::beginTransaction();
try {
foreach ($this->mutationDetails as $detail) {
if ($detail->quantity_approved > 0) {
$this->processStockMovement($detail);
}
}
$this->update(['status' => MutationStatus::COMPLETED]);
\DB::commit();
} catch (\Exception $e) {
\DB::rollback();
throw $e;
}
return $this;
}
private function processStockMovement(MutationDetail $detail)
{
// Kurangi stock dari dealer asal
$fromStock = Stock::firstOrCreate([
'product_id' => $detail->product_id,
'dealer_id' => $this->from_dealer_id
], ['quantity' => 0]);
if ($fromStock->quantity < $detail->quantity_approved) {
throw new \Exception("Stock tidak mencukupi untuk produk {$detail->product->name} di {$this->fromDealer->name}");
}
$fromStock->updateStock(
$fromStock->quantity - $detail->quantity_approved,
$this,
"Mutasi keluar ke {$this->toDealer->name} - {$this->mutation_number}"
);
// Tambah stock ke dealer tujuan
$toStock = Stock::firstOrCreate([
'product_id' => $detail->product_id,
'dealer_id' => $this->to_dealer_id
], ['quantity' => 0]);
$toStock->updateStock(
$toStock->quantity + $detail->quantity_approved,
$this,
"Mutasi masuk dari {$this->fromDealer->name} - {$this->mutation_number}"
);
}
private function generateMutationNumber()
{
$prefix = 'MUT';
$date = now()->format('Ymd');
$lastNumber = static::whereDate('created_at', today())
->whereNotNull('mutation_number')
->latest('id')
->first();
if ($lastNumber) {
$lastSequence = (int) substr($lastNumber->mutation_number, -4);
$sequence = str_pad($lastSequence + 1, 4, '0', STR_PAD_LEFT);
} else {
$sequence = '0001';
}
return "{$prefix}{$date}{$sequence}";
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class MutationDetail extends Model
{
use HasFactory;
protected $fillable = [
'mutation_id',
'product_id',
'quantity_requested',
'quantity_approved',
'notes'
];
protected $casts = [
'quantity_requested' => 'decimal:2',
'quantity_approved' => 'decimal:2'
];
public function mutation()
{
return $this->belongsTo(Mutation::class);
}
public function product()
{
return $this->belongsTo(Product::class);
}
// Helper methods
public function getQuantityDifferenceAttribute()
{
return $this->quantity_approved - $this->quantity_requested;
}
public function isFullyApproved()
{
return $this->quantity_approved == $this->quantity_requested;
}
public function isPartiallyApproved()
{
return $this->quantity_approved > 0 && $this->quantity_approved < $this->quantity_requested;
}
public function isRejected()
{
return $this->quantity_approved == 0;
}
public function getApprovalStatusAttribute()
{
if ($this->isFullyApproved()) {
return 'Disetujui Penuh';
} elseif ($this->isPartiallyApproved()) {
return 'Disetujui Sebagian';
} elseif ($this->isRejected()) {
return 'Ditolak';
} else {
return 'Menunggu';
}
}
public function getApprovalStatusColorAttribute()
{
if ($this->isFullyApproved()) {
return 'success';
} elseif ($this->isPartiallyApproved()) {
return 'warning';
} elseif ($this->isRejected()) {
return 'danger';
} else {
return 'info';
}
}
// Scope untuk filter berdasarkan status approval
public function scopeFullyApproved($query)
{
return $query->whereColumn('quantity_approved', '=', 'quantity_requested');
}
public function scopePartiallyApproved($query)
{
return $query->where('quantity_approved', '>', 0)
->whereColumn('quantity_approved', '<', 'quantity_requested');
}
public function scopeRejected($query)
{
return $query->where('quantity_approved', '=', 0);
}
}

View File

@@ -24,9 +24,20 @@ class Product extends Model
return $this->hasMany(Stock::class); return $this->hasMany(Stock::class);
} }
public function mutationDetails()
{
return $this->hasMany(MutationDetail::class);
}
// Helper method untuk mendapatkan total stock saat ini // Helper method untuk mendapatkan total stock saat ini
public function getCurrentTotalStockAttribute() public function getCurrentTotalStockAttribute()
{ {
return $this->stocks()->sum('quantity'); return $this->stocks()->sum('quantity');
} }
// Helper method untuk mendapatkan stock di dealer tertentu
public function getStockByDealer($dealerId)
{
return $this->stocks()->where('dealer_id', $dealerId)->first()?->quantity ?? 0;
}
} }

343
ckb.sql
View File

@@ -1,13 +1,14 @@
-- MySQL dump 10.13 Distrib 8.0.42, for Linux (x86_64) /*M!999999\- enable the sandbox mode */
-- MariaDB dump 10.19 Distrib 10.6.22-MariaDB, for debian-linux-gnu (x86_64)
-- --
-- Host: localhost Database: ckb -- Host: localhost Database: ckb_db
-- ------------------------------------------------------ -- ------------------------------------------------------
-- Server version 8.0.42-0ubuntu0.22.04.1 -- Server version 10.6.22-MariaDB-ubu2004-log
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */; /*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */; /*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
@@ -21,13 +22,13 @@
DROP TABLE IF EXISTS `categories`; DROP TABLE IF EXISTS `categories`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `categories` ( CREATE TABLE `categories` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
`form` enum('work','wash') COLLATE utf8mb4_unicode_ci NOT NULL, `form` enum('work','wash') NOT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -49,15 +50,15 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `dealers`; DROP TABLE IF EXISTS `dealers`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `dealers` ( CREATE TABLE `dealers` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`dealer_code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `dealer_code` varchar(255) NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) NOT NULL,
`address` text COLLATE utf8mb4_unicode_ci NOT NULL, `address` text NOT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
`pic` bigint unsigned DEFAULT NULL, `pic` bigint(20) unsigned DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `dealers_dealer_code_unique` (`dealer_code`), UNIQUE KEY `dealers_dealer_code_unique` (`dealer_code`),
@@ -82,15 +83,15 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `failed_jobs`; DROP TABLE IF EXISTS `failed_jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `failed_jobs` ( CREATE TABLE `failed_jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `uuid` varchar(255) NOT NULL,
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL, `connection` text NOT NULL,
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL, `queue` text NOT NULL,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL, `payload` longtext NOT NULL,
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL, `exception` longtext NOT NULL,
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `failed_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`) UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -111,11 +112,11 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `menus`; DROP TABLE IF EXISTS `menus`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `menus` ( CREATE TABLE `menus` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) NOT NULL,
`link` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `link` varchar(255) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
@@ -138,13 +139,13 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `migrations`; DROP TABLE IF EXISTS `migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `migrations` ( CREATE TABLE `migrations` (
`id` int unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `migration` varchar(255) NOT NULL,
`batch` int NOT NULL, `batch` int(11) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=70 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=78 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -153,25 +154,107 @@ CREATE TABLE `migrations` (
LOCK TABLES `migrations` WRITE; LOCK TABLES `migrations` WRITE;
/*!40000 ALTER TABLE `migrations` DISABLE KEYS */; /*!40000 ALTER TABLE `migrations` DISABLE KEYS */;
INSERT INTO `migrations` VALUES (1,'2014_10_12_100000_create_password_resets_table',1),(2,'2019_08_19_000000_create_failed_jobs_table',1),(3,'2019_12_14_000001_create_personal_access_tokens_table',1),(4,'2022_05_20_100209_create_dealers_table',1),(5,'2022_05_20_100326_create_categories_table',1),(6,'2022_05_20_100335_create_works_table',1),(7,'2022_05_20_100340_create_users_table',1),(8,'2022_05_20_100410_create_transactions_table',1),(9,'2022_05_25_024641_update_works_table',2),(10,'2022_05_25_103839_update_categories_table',3),(13,'2022_05_25_144502_update_transaction_table',4),(18,'2022_05_29_211410_update_table_works_add_shortname_field',5),(20,'2022_05_29_211531_update_dealers_table_add_pic_field',6),(21,'2022_05_29_220642_update_transactions_table_add_status_column',7),(22,'2022_05_31_003725_update_transaction_table_add_dealer_id',8),(23,'2022_06_23_215115_add_deleted_at_at_users_table',9),(24,'2022_06_23_215243_add_deleted_at_at_dealers_table',9),(25,'2022_06_23_215321_add_deleted_at_at_categories_table',9),(26,'2022_06_23_215341_add_deleted_at_at_works_table',9),(27,'2022_06_23_215404_add_deleted_at_at_transactions_table',9),(28,'2023_08_11_140743_create_roles_table',10),(29,'2023_08_11_140957_create_privileges_table',10),(30,'2023_08_11_141029_add_role_id_into_users_table',10),(31,'2023_08_11_144823_create_menus_table',10),(32,'2023_08_11_144857_add_menu_id_in_privileges_table',10),(33,'2023_08_11_145537_remove_name_in_privileges_table',10),(55,'2025_05_28_113228_create_product_categories_table',11),(56,'2025_05_28_113324_create_products_table',11),(59,'2025_06_04_101915_create_opnames_table',12),(60,'2025_06_04_103359_create_opname_details_table',12),(66,'2025_06_10_135321_create_stocks_table',13),(67,'2025_06_10_135341_create_stock_logs_table',14),(68,'2025_06_10_135417_add_approval_columns_to_opnames_table',14),(69,'2025_06_10_140540_change_stock_columns_to_decimal_in_opname_details',14); INSERT INTO `migrations` VALUES (1,'2014_10_12_100000_create_password_resets_table',1),(2,'2019_08_19_000000_create_failed_jobs_table',1),(3,'2019_12_14_000001_create_personal_access_tokens_table',1),(4,'2022_05_20_100209_create_dealers_table',1),(5,'2022_05_20_100326_create_categories_table',1),(6,'2022_05_20_100335_create_works_table',1),(7,'2022_05_20_100340_create_users_table',1),(8,'2022_05_20_100410_create_transactions_table',1),(9,'2022_05_25_024641_update_works_table',2),(10,'2022_05_25_103839_update_categories_table',3),(13,'2022_05_25_144502_update_transaction_table',4),(18,'2022_05_29_211410_update_table_works_add_shortname_field',5),(20,'2022_05_29_211531_update_dealers_table_add_pic_field',6),(21,'2022_05_29_220642_update_transactions_table_add_status_column',7),(22,'2022_05_31_003725_update_transaction_table_add_dealer_id',8),(23,'2022_06_23_215115_add_deleted_at_at_users_table',9),(24,'2022_06_23_215243_add_deleted_at_at_dealers_table',9),(25,'2022_06_23_215321_add_deleted_at_at_categories_table',9),(26,'2022_06_23_215341_add_deleted_at_at_works_table',9),(27,'2022_06_23_215404_add_deleted_at_at_transactions_table',9),(28,'2023_08_11_140743_create_roles_table',10),(29,'2023_08_11_140957_create_privileges_table',10),(30,'2023_08_11_141029_add_role_id_into_users_table',10),(31,'2023_08_11_144823_create_menus_table',10),(32,'2023_08_11_144857_add_menu_id_in_privileges_table',10),(33,'2023_08_11_145537_remove_name_in_privileges_table',10),(55,'2025_05_28_113228_create_product_categories_table',11),(56,'2025_05_28_113324_create_products_table',11),(59,'2025_06_04_101915_create_opnames_table',12),(60,'2025_06_04_103359_create_opname_details_table',12),(66,'2025_06_10_135321_create_stocks_table',13),(67,'2025_06_10_135341_create_stock_logs_table',14),(68,'2025_06_10_135417_add_approval_columns_to_opnames_table',14),(69,'2025_06_10_140540_change_stock_columns_to_decimal_in_opname_details',14),(76,'2025_06_12_002513_create_mutations_table',15),(77,'2025_06_12_002623_create_mutation_details_table',15);
/*!40000 ALTER TABLE `migrations` ENABLE KEYS */; /*!40000 ALTER TABLE `migrations` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
--
-- Table structure for table `mutation_details`
--
DROP TABLE IF EXISTS `mutation_details`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `mutation_details` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`mutation_id` bigint(20) unsigned NOT NULL,
`product_id` bigint(20) unsigned NOT NULL,
`quantity_requested` decimal(15,2) NOT NULL,
`quantity_approved` decimal(15,2) NOT NULL DEFAULT 0.00,
`notes` text DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `mutation_details_product_id_foreign` (`product_id`),
KEY `mutation_details_mutation_id_product_id_index` (`mutation_id`,`product_id`),
CONSTRAINT `mutation_details_mutation_id_foreign` FOREIGN KEY (`mutation_id`) REFERENCES `mutations` (`id`) ON DELETE CASCADE,
CONSTRAINT `mutation_details_product_id_foreign` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `mutation_details`
--
LOCK TABLES `mutation_details` WRITE;
/*!40000 ALTER TABLE `mutation_details` DISABLE KEYS */;
/*!40000 ALTER TABLE `mutation_details` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `mutations`
--
DROP TABLE IF EXISTS `mutations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `mutations` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`mutation_number` varchar(255) NOT NULL,
`from_dealer_id` bigint(20) unsigned NOT NULL,
`to_dealer_id` bigint(20) unsigned NOT NULL,
`status` enum('pending','sent','received','approved','rejected','completed','cancelled') NOT NULL DEFAULT 'sent',
`requested_by` bigint(20) unsigned NOT NULL,
`approved_by` bigint(20) unsigned DEFAULT NULL,
`approved_at` timestamp NULL DEFAULT NULL,
`received_by` bigint(20) unsigned DEFAULT NULL,
`received_at` timestamp NULL DEFAULT NULL,
`notes` text DEFAULT NULL,
`rejection_reason` text DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `mutations_mutation_number_unique` (`mutation_number`),
KEY `mutations_requested_by_foreign` (`requested_by`),
KEY `mutations_approved_by_foreign` (`approved_by`),
KEY `mutations_received_by_foreign` (`received_by`),
KEY `mutations_from_dealer_id_status_index` (`from_dealer_id`,`status`),
KEY `mutations_to_dealer_id_status_index` (`to_dealer_id`,`status`),
KEY `mutations_status_created_at_index` (`status`,`created_at`),
KEY `mutations_mutation_number_index` (`mutation_number`),
CONSTRAINT `mutations_approved_by_foreign` FOREIGN KEY (`approved_by`) REFERENCES `users` (`id`),
CONSTRAINT `mutations_from_dealer_id_foreign` FOREIGN KEY (`from_dealer_id`) REFERENCES `dealers` (`id`),
CONSTRAINT `mutations_received_by_foreign` FOREIGN KEY (`received_by`) REFERENCES `users` (`id`),
CONSTRAINT `mutations_requested_by_foreign` FOREIGN KEY (`requested_by`) REFERENCES `users` (`id`),
CONSTRAINT `mutations_to_dealer_id_foreign` FOREIGN KEY (`to_dealer_id`) REFERENCES `dealers` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `mutations`
--
LOCK TABLES `mutations` WRITE;
/*!40000 ALTER TABLE `mutations` DISABLE KEYS */;
/*!40000 ALTER TABLE `mutations` ENABLE KEYS */;
UNLOCK TABLES;
-- --
-- Table structure for table `opname_details` -- Table structure for table `opname_details`
-- --
DROP TABLE IF EXISTS `opname_details`; DROP TABLE IF EXISTS `opname_details`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `opname_details` ( CREATE TABLE `opname_details` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`opname_id` bigint unsigned NOT NULL, `opname_id` bigint(20) unsigned NOT NULL,
`product_id` bigint unsigned NOT NULL, `product_id` bigint(20) unsigned NOT NULL,
`system_stock` decimal(10,2) NOT NULL, `system_stock` decimal(10,2) NOT NULL,
`physical_stock` decimal(10,2) NOT NULL, `physical_stock` decimal(10,2) NOT NULL,
`difference` decimal(10,2) NOT NULL DEFAULT '0.00', `difference` decimal(10,2) NOT NULL DEFAULT 0.00,
`note` text COLLATE utf8mb4_unicode_ci, `note` text DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
@@ -180,7 +263,7 @@ CREATE TABLE `opname_details` (
KEY `opname_details_product_id_foreign` (`product_id`), KEY `opname_details_product_id_foreign` (`product_id`),
CONSTRAINT `opname_details_opname_id_foreign` FOREIGN KEY (`opname_id`) REFERENCES `opnames` (`id`) ON DELETE CASCADE, CONSTRAINT `opname_details_opname_id_foreign` FOREIGN KEY (`opname_id`) REFERENCES `opnames` (`id`) ON DELETE CASCADE,
CONSTRAINT `opname_details_product_id_foreign` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE CONSTRAINT `opname_details_product_id_foreign` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -189,7 +272,7 @@ CREATE TABLE `opname_details` (
LOCK TABLES `opname_details` WRITE; LOCK TABLES `opname_details` WRITE;
/*!40000 ALTER TABLE `opname_details` DISABLE KEYS */; /*!40000 ALTER TABLE `opname_details` DISABLE KEYS */;
INSERT INTO `opname_details` VALUES (1,1,1,0.00,10.00,10.00,'tambah produk baru',NULL,'2025-06-10 10:26:24','2025-06-10 10:26:24'),(2,2,1,0.00,30.00,30.00,'tambah baru',NULL,'2025-06-10 10:27:30','2025-06-10 10:27:30'); INSERT INTO `opname_details` VALUES (1,1,1,0.00,10.00,10.00,'tambah produk baru',NULL,'2025-06-10 10:26:24','2025-06-10 10:26:24'),(2,2,1,0.00,30.00,30.00,'tambah baru',NULL,'2025-06-10 10:27:30','2025-06-10 10:27:30'),(3,3,1,10.00,0.00,-10.00,NULL,NULL,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(4,3,2,0.00,0.00,0.00,NULL,NULL,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(5,3,3,0.00,0.00,0.00,NULL,NULL,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(6,3,4,0.00,0.00,0.00,NULL,NULL,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(7,3,5,0.00,0.00,0.00,NULL,NULL,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(8,3,6,0.00,0.00,0.00,NULL,NULL,'2025-06-11 22:21:32','2025-06-11 22:21:32');
/*!40000 ALTER TABLE `opname_details` ENABLE KEYS */; /*!40000 ALTER TABLE `opname_details` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
@@ -199,20 +282,20 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `opnames`; DROP TABLE IF EXISTS `opnames`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `opnames` ( CREATE TABLE `opnames` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`dealer_id` bigint unsigned NOT NULL, `dealer_id` bigint(20) unsigned NOT NULL,
`opname_date` date NOT NULL, `opname_date` date NOT NULL,
`user_id` bigint unsigned NOT NULL, `user_id` bigint(20) unsigned NOT NULL,
`note` text COLLATE utf8mb4_unicode_ci, `note` text DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
`status` enum('draft','pending','approved','rejected') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'draft', `status` enum('draft','pending','approved','rejected') NOT NULL DEFAULT 'draft',
`approved_by` bigint unsigned DEFAULT NULL, `approved_by` bigint(20) unsigned DEFAULT NULL,
`approved_at` timestamp NULL DEFAULT NULL, `approved_at` timestamp NULL DEFAULT NULL,
`rejection_note` text COLLATE utf8mb4_unicode_ci, `rejection_note` text DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `opnames_dealer_id_foreign` (`dealer_id`), KEY `opnames_dealer_id_foreign` (`dealer_id`),
KEY `opnames_user_id_foreign` (`user_id`), KEY `opnames_user_id_foreign` (`user_id`),
@@ -220,7 +303,7 @@ CREATE TABLE `opnames` (
CONSTRAINT `opnames_approved_by_foreign` FOREIGN KEY (`approved_by`) REFERENCES `users` (`id`), CONSTRAINT `opnames_approved_by_foreign` FOREIGN KEY (`approved_by`) REFERENCES `users` (`id`),
CONSTRAINT `opnames_dealer_id_foreign` FOREIGN KEY (`dealer_id`) REFERENCES `dealers` (`id`) ON DELETE CASCADE, CONSTRAINT `opnames_dealer_id_foreign` FOREIGN KEY (`dealer_id`) REFERENCES `dealers` (`id`) ON DELETE CASCADE,
CONSTRAINT `opnames_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE CONSTRAINT `opnames_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -229,7 +312,7 @@ CREATE TABLE `opnames` (
LOCK TABLES `opnames` WRITE; LOCK TABLES `opnames` WRITE;
/*!40000 ALTER TABLE `opnames` DISABLE KEYS */; /*!40000 ALTER TABLE `opnames` DISABLE KEYS */;
INSERT INTO `opnames` VALUES (1,20,'2025-06-10',8,'test tambah product by opname',NULL,'2025-06-10 10:26:24','2025-06-10 10:26:24','approved',8,'2025-06-10 10:26:24',NULL),(2,24,'2025-06-10',8,'test tambah produk by opname',NULL,'2025-06-10 10:27:30','2025-06-10 10:27:30','approved',8,'2025-06-10 10:27:30',NULL); INSERT INTO `opnames` VALUES (1,20,'2025-06-10',8,'test tambah product by opname',NULL,'2025-06-10 10:26:24','2025-06-10 10:26:24','approved',8,'2025-06-10 10:26:24',NULL),(2,24,'2025-06-10',8,'test tambah produk by opname',NULL,'2025-06-10 10:27:30','2025-06-10 10:27:30','approved',8,'2025-06-10 10:27:30',NULL),(3,20,'2025-06-11',8,NULL,NULL,'2025-06-11 22:21:32','2025-06-11 22:21:32','approved',8,'2025-06-11 22:21:32',NULL);
/*!40000 ALTER TABLE `opnames` ENABLE KEYS */; /*!40000 ALTER TABLE `opnames` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
@@ -239,10 +322,10 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `password_resets`; DROP TABLE IF EXISTS `password_resets`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `password_resets` ( CREATE TABLE `password_resets` (
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `email` varchar(255) NOT NULL,
`token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `token` varchar(255) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
KEY `password_resets_email_index` (`email`) KEY `password_resets_email_index` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -263,14 +346,14 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `personal_access_tokens`; DROP TABLE IF EXISTS `personal_access_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `personal_access_tokens` ( CREATE TABLE `personal_access_tokens` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`tokenable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `tokenable_type` varchar(255) NOT NULL,
`tokenable_id` bigint unsigned NOT NULL, `tokenable_id` bigint(20) unsigned NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) NOT NULL,
`token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, `token` varchar(64) NOT NULL,
`abilities` text COLLATE utf8mb4_unicode_ci, `abilities` text DEFAULT NULL,
`last_used_at` timestamp NULL DEFAULT NULL, `last_used_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
@@ -296,18 +379,18 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `privileges`; DROP TABLE IF EXISTS `privileges`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `privileges` ( CREATE TABLE `privileges` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`role_id` bigint unsigned NOT NULL, `role_id` bigint(20) unsigned NOT NULL,
`create` tinyint NOT NULL, `create` tinyint(4) NOT NULL,
`update` tinyint NOT NULL, `update` tinyint(4) NOT NULL,
`delete` tinyint NOT NULL, `delete` tinyint(4) NOT NULL,
`view` tinyint NOT NULL, `view` tinyint(4) NOT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
`menu_id` bigint unsigned NOT NULL, `menu_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `privileges_role_id_foreign` (`role_id`), KEY `privileges_role_id_foreign` (`role_id`),
KEY `privileges_menu_id_foreign` (`menu_id`), KEY `privileges_menu_id_foreign` (`menu_id`),
@@ -332,11 +415,11 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `product_categories`; DROP TABLE IF EXISTS `product_categories`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `product_categories` ( CREATE TABLE `product_categories` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) NOT NULL,
`parent_id` bigint unsigned DEFAULT NULL, `parent_id` bigint(20) unsigned DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
@@ -362,15 +445,15 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `products`; DROP TABLE IF EXISTS `products`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `products` ( CREATE TABLE `products` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `code` varchar(255) NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1', `active` tinyint(1) NOT NULL DEFAULT 1,
`unit` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `unit` varchar(255) DEFAULT NULL,
`description` text COLLATE utf8mb4_unicode_ci, `description` text DEFAULT NULL,
`product_category_id` bigint unsigned NOT NULL, `product_category_id` bigint(20) unsigned NOT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
@@ -397,10 +480,10 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `roles`; DROP TABLE IF EXISTS `roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `roles` ( CREATE TABLE `roles` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) NOT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
@@ -424,18 +507,18 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `stock_logs`; DROP TABLE IF EXISTS `stock_logs`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `stock_logs` ( CREATE TABLE `stock_logs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`stock_id` bigint unsigned NOT NULL, `stock_id` bigint(20) unsigned NOT NULL,
`source_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `source_type` varchar(255) NOT NULL,
`source_id` bigint unsigned NOT NULL, `source_id` bigint(20) unsigned NOT NULL,
`previous_quantity` decimal(10,2) NOT NULL, `previous_quantity` decimal(10,2) NOT NULL,
`new_quantity` decimal(10,2) NOT NULL, `new_quantity` decimal(10,2) NOT NULL,
`quantity_change` decimal(10,2) NOT NULL, `quantity_change` decimal(10,2) NOT NULL,
`change_type` enum('increase','decrease','adjustment','no_change') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'no_change', `change_type` enum('increase','decrease','adjustment','no_change') NOT NULL DEFAULT 'no_change',
`description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `description` varchar(255) DEFAULT NULL,
`user_id` bigint unsigned NOT NULL, `user_id` bigint(20) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@@ -444,7 +527,7 @@ CREATE TABLE `stock_logs` (
KEY `stock_logs_source_type_source_id_index` (`source_type`,`source_id`), KEY `stock_logs_source_type_source_id_index` (`source_type`,`source_id`),
CONSTRAINT `stock_logs_stock_id_foreign` FOREIGN KEY (`stock_id`) REFERENCES `stocks` (`id`) ON DELETE CASCADE, CONSTRAINT `stock_logs_stock_id_foreign` FOREIGN KEY (`stock_id`) REFERENCES `stocks` (`id`) ON DELETE CASCADE,
CONSTRAINT `stock_logs_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) CONSTRAINT `stock_logs_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -453,7 +536,7 @@ CREATE TABLE `stock_logs` (
LOCK TABLES `stock_logs` WRITE; LOCK TABLES `stock_logs` WRITE;
/*!40000 ALTER TABLE `stock_logs` DISABLE KEYS */; /*!40000 ALTER TABLE `stock_logs` DISABLE KEYS */;
INSERT INTO `stock_logs` VALUES (1,1,'App\\Models\\Opname',1,0.00,10.00,10.00,'increase','Stock adjustment from auto-approved opname #1',8,'2025-06-10 10:26:24','2025-06-10 10:26:24'),(2,2,'App\\Models\\Opname',2,0.00,30.00,30.00,'increase','Stock adjustment from auto-approved opname #2',8,'2025-06-10 10:27:30','2025-06-10 10:27:30'); INSERT INTO `stock_logs` VALUES (1,1,'App\\Models\\Opname',1,0.00,10.00,10.00,'increase','Stock adjustment from auto-approved opname #1',8,'2025-06-10 10:26:24','2025-06-10 10:26:24'),(2,2,'App\\Models\\Opname',2,0.00,30.00,30.00,'increase','Stock adjustment from auto-approved opname #2',8,'2025-06-10 10:27:30','2025-06-10 10:27:30'),(3,1,'App\\Models\\Opname',3,10.00,0.00,-10.00,'decrease','Stock adjustment from auto-approved opname #3',8,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(4,3,'App\\Models\\Opname',3,0.00,0.00,0.00,'no_change','Stock adjustment from auto-approved opname #3',8,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(5,4,'App\\Models\\Opname',3,0.00,0.00,0.00,'no_change','Stock adjustment from auto-approved opname #3',8,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(6,5,'App\\Models\\Opname',3,0.00,0.00,0.00,'no_change','Stock adjustment from auto-approved opname #3',8,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(7,6,'App\\Models\\Opname',3,0.00,0.00,0.00,'no_change','Stock adjustment from auto-approved opname #3',8,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(8,7,'App\\Models\\Opname',3,0.00,0.00,0.00,'no_change','Stock adjustment from auto-approved opname #3',8,'2025-06-11 22:21:32','2025-06-11 22:21:32');
/*!40000 ALTER TABLE `stock_logs` ENABLE KEYS */; /*!40000 ALTER TABLE `stock_logs` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
@@ -463,12 +546,12 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `stocks`; DROP TABLE IF EXISTS `stocks`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `stocks` ( CREATE TABLE `stocks` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`product_id` bigint unsigned NOT NULL, `product_id` bigint(20) unsigned NOT NULL,
`dealer_id` bigint unsigned NOT NULL, `dealer_id` bigint(20) unsigned NOT NULL,
`quantity` decimal(10,2) NOT NULL DEFAULT '0.00', `quantity` decimal(10,2) NOT NULL DEFAULT 0.00,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@@ -476,7 +559,7 @@ CREATE TABLE `stocks` (
KEY `stocks_dealer_id_foreign` (`dealer_id`), KEY `stocks_dealer_id_foreign` (`dealer_id`),
CONSTRAINT `stocks_dealer_id_foreign` FOREIGN KEY (`dealer_id`) REFERENCES `dealers` (`id`) ON DELETE CASCADE, CONSTRAINT `stocks_dealer_id_foreign` FOREIGN KEY (`dealer_id`) REFERENCES `dealers` (`id`) ON DELETE CASCADE,
CONSTRAINT `stocks_product_id_foreign` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE CONSTRAINT `stocks_product_id_foreign` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -485,7 +568,7 @@ CREATE TABLE `stocks` (
LOCK TABLES `stocks` WRITE; LOCK TABLES `stocks` WRITE;
/*!40000 ALTER TABLE `stocks` DISABLE KEYS */; /*!40000 ALTER TABLE `stocks` DISABLE KEYS */;
INSERT INTO `stocks` VALUES (1,1,20,10.00,'2025-06-10 10:26:24','2025-06-10 10:26:24'),(2,1,24,30.00,'2025-06-10 10:27:30','2025-06-10 10:27:30'); INSERT INTO `stocks` VALUES (1,1,20,0.00,'2025-06-10 10:26:24','2025-06-11 22:21:32'),(2,1,24,30.00,'2025-06-10 10:27:30','2025-06-10 10:27:30'),(3,2,20,0.00,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(4,3,20,0.00,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(5,4,20,0.00,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(6,5,20,0.00,'2025-06-11 22:21:32','2025-06-11 22:21:32'),(7,6,20,0.00,'2025-06-11 22:21:32','2025-06-11 22:21:32');
/*!40000 ALTER TABLE `stocks` ENABLE KEYS */; /*!40000 ALTER TABLE `stocks` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
@@ -495,22 +578,22 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `transactions`; DROP TABLE IF EXISTS `transactions`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `transactions` ( CREATE TABLE `transactions` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint unsigned NOT NULL, `user_id` bigint(20) unsigned NOT NULL,
`user_sa_id` bigint unsigned NOT NULL, `user_sa_id` bigint(20) unsigned NOT NULL,
`work_id` bigint unsigned NOT NULL, `work_id` bigint(20) unsigned NOT NULL,
`spk` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `spk` varchar(255) NOT NULL,
`police_number` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `police_number` varchar(255) NOT NULL,
`warranty` tinyint NOT NULL, `warranty` tinyint(4) NOT NULL,
`date` datetime NOT NULL, `date` datetime NOT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
`form` enum('work','wash') COLLATE utf8mb4_unicode_ci NOT NULL, `form` enum('work','wash') NOT NULL,
`qty` bigint unsigned NOT NULL, `qty` bigint(20) unsigned NOT NULL,
`status` tinyint NOT NULL DEFAULT '0', `status` tinyint(4) NOT NULL DEFAULT 0,
`dealer_id` bigint unsigned NOT NULL, `dealer_id` bigint(20) unsigned NOT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `transactions_user_id_foreign` (`user_id`), KEY `transactions_user_id_foreign` (`user_id`),
@@ -574,20 +657,20 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `users`; DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `users` ( CREATE TABLE `users` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) NOT NULL,
`dealer_id` bigint unsigned NOT NULL, `dealer_id` bigint(20) unsigned NOT NULL,
`role` enum('admin','sa','mechanic') COLLATE utf8mb4_unicode_ci NOT NULL, `role` enum('admin','sa','mechanic') NOT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `email` varchar(255) NOT NULL,
`email_verified_at` timestamp NULL DEFAULT NULL, `email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `password` varchar(255) NOT NULL,
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `remember_token` varchar(100) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
`role_id` bigint unsigned NOT NULL, `role_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`), UNIQUE KEY `users_email_unique` (`email`),
KEY `users_dealer_id_foreign` (`dealer_id`), KEY `users_dealer_id_foreign` (`dealer_id`),
@@ -611,15 +694,15 @@ UNLOCK TABLES;
DROP TABLE IF EXISTS `works`; DROP TABLE IF EXISTS `works`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */; /*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `works` ( CREATE TABLE `works` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) NOT NULL,
`desc` text COLLATE utf8mb4_unicode_ci NOT NULL, `desc` text NOT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
`category_id` bigint unsigned NOT NULL, `category_id` bigint(20) unsigned NOT NULL,
`shortname` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `shortname` varchar(255) NOT NULL,
`deleted_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `works_category_id_foreign` (`category_id`), KEY `works_category_id_foreign` (`category_id`),
@@ -636,6 +719,10 @@ LOCK TABLES `works` WRITE;
INSERT INTO `works` VALUES (1,'CUCI','-',NULL,'2022-06-09 08:06:59',1,'Cuci',NULL),(6,'SPOORING','-',NULL,'2022-06-09 08:34:27',3,'SP',NULL),(7,'SPOORING TRUK','-',NULL,'2022-06-09 08:34:38',3,'SP TRUCK',NULL),(8,'SPOORING + BAUT CAMBER','-',NULL,'2022-06-09 08:34:56',3,'SP + BC',NULL),(9,'BALANCING','-',NULL,'2022-06-09 08:35:08',3,'BL',NULL),(10,'BALANCING R < 15','-',NULL,'2022-06-09 08:35:26',3,'BL R<15',NULL),(11,'BALANCING R > 15','-',NULL,'2022-06-09 08:35:43',3,'BL R>15',NULL),(12,'NITROGEN FULL','-',NULL,'2022-06-09 08:36:32',3,'N2',NULL),(13,'NITROGEN TAMBAH','-',NULL,'2022-06-09 08:41:19',3,'N+',NULL),(14,'BAUT CAMBER ','-',NULL,NULL,3,'BC',NULL),(15,'AC CLEANER SINGLE BLOWER','-',NULL,'2022-06-09 08:41:36',3,'SB',NULL),(16,'AC CLEANER DOUBLE BLOWER','-',NULL,'2022-06-09 08:41:45',3,'DB',NULL),(17,'FOGGING','-',NULL,'2022-06-09 08:42:29',3,'FOG',NULL),(18,'GLASS POLISH ','-',NULL,NULL,3,'GP',NULL),(19,'ENGINE DRESSING ','-',NULL,NULL,3,'ED',NULL),(20,'ENGINE CARE ','-',NULL,NULL,3,'EC',NULL),(21,'ENGINE CLEANING UAP ','-',NULL,NULL,3,'ECU',NULL),(22,'BUBUT DISC BRAKE','-',NULL,'2022-06-09 08:44:00',3,'DL',NULL),(23,'TIRE CHANGER ','-',NULL,NULL,3,'TC',NULL),(24,'CHAMBER CLEANER ','-',NULL,NULL,3,'CC',NULL),(25,'DIESEL PURGING ','-',NULL,NULL,3,'DP',NULL),(26,'FUEL INJECTOR CLEANER','-',NULL,'2022-06-09 08:48:35',3,'IC',NULL),(27,'TURBO CLEANER','-',NULL,'2022-06-09 08:45:19',3,'TCC',NULL),(28,'CATALYTIC CLEANER','-',NULL,NULL,3,'CTC',NULL),(29,'INTERIOR CLEANING UAP ','-',NULL,NULL,3,'ICU',NULL),(30,'HHO ','-',NULL,NULL,3,'HHO',NULL),(31,'engine flush','-','2023-06-02 03:35:58','2023-06-02 03:35:58',3,'EF',NULL),(32,'ISI FREON SINGLE BLOWER','-','2023-11-02 04:08:57','2023-11-02 04:08:57',3,'FR SB 1',NULL),(33,'ISI FREON DOUBLE BLOWER','-','2023-11-02 04:09:25','2023-11-02 04:09:25',3,'FR DB 2',NULL),(34,'Oil lining performance','jasa oil lining','2023-12-05 10:26:43','2023-12-05 10:28:49',3,'OLP',NULL),(35,'engine oil performance b1','jasa ganti oli','2023-12-05 10:27:36','2023-12-05 10:28:59',3,'B1',NULL),(36,'engine oil performance b2','jasa ganti oli b2','2023-12-05 10:28:03','2023-12-05 10:29:09',3,'B2',NULL),(37,'Oil lining performance non engine flush','jasa oil lining tanpa engine flush','2024-03-08 06:41:41','2024-03-08 06:42:53',3,'OLP NON EF',NULL),(38,'AC FRESH SERVICE','parfurmed ac','2024-03-13 05:08:15','2024-03-13 05:08:15',3,'AC FRESH',NULL),(39,'AC LIGHT SERVICE - SINGLE BLOWER','penggantian dryer, oli komprespor, dan refrigant','2024-03-13 05:12:47','2024-03-13 05:12:47',3,'AC LIGHT - SB',NULL),(40,'AC LIGHT SERVICE - DOUBLE BLOWER','penggantian dryer, oli komprespor, dan refrigant','2024-03-13 05:13:39','2024-03-13 05:14:18',3,'AC LIGHT - DB',NULL),(41,'AC HEAVY SERVICE - SINGLE BLOWER','penggantian dryer, oli komprespor, dan refrigant (cek kebocoran EVAP)','2024-03-13 05:15:29','2024-03-13 05:15:29',3,'AC HEAVY - SB',NULL),(42,'AC HEAVY SERVICE - DOUBLE BLOWER','penggantian dryer, oli komprespor, dan refrigant (cek kebocoran EVAP)','2024-03-13 05:16:05','2024-03-13 05:16:05',3,'AC HEAVY - DB',NULL),(43,'AC CLEAN SERVICE','pembersihan saluran ac dan evap','2024-04-24 09:49:02','2024-04-24 09:49:02',3,'ACCS',NULL),(44,'COOLANT CHANGER & COOLING SYSTEM CLEANER','kuras radiator dan penggantian coolant','2024-07-08 10:58:52','2024-07-08 10:58:52',3,'C3SC',NULL),(45,'AT CVT FLUSHING','pembersihan transmisi matic cvt','2024-09-18 09:33:09','2024-09-18 09:33:09',3,'AT CVT FLUSHING',NULL),(46,'CUCI SNOW','cuci snow','2024-09-19 08:20:30','2024-09-19 08:20:30',1,'CUCI SNOW',NULL); INSERT INTO `works` VALUES (1,'CUCI','-',NULL,'2022-06-09 08:06:59',1,'Cuci',NULL),(6,'SPOORING','-',NULL,'2022-06-09 08:34:27',3,'SP',NULL),(7,'SPOORING TRUK','-',NULL,'2022-06-09 08:34:38',3,'SP TRUCK',NULL),(8,'SPOORING + BAUT CAMBER','-',NULL,'2022-06-09 08:34:56',3,'SP + BC',NULL),(9,'BALANCING','-',NULL,'2022-06-09 08:35:08',3,'BL',NULL),(10,'BALANCING R < 15','-',NULL,'2022-06-09 08:35:26',3,'BL R<15',NULL),(11,'BALANCING R > 15','-',NULL,'2022-06-09 08:35:43',3,'BL R>15',NULL),(12,'NITROGEN FULL','-',NULL,'2022-06-09 08:36:32',3,'N2',NULL),(13,'NITROGEN TAMBAH','-',NULL,'2022-06-09 08:41:19',3,'N+',NULL),(14,'BAUT CAMBER ','-',NULL,NULL,3,'BC',NULL),(15,'AC CLEANER SINGLE BLOWER','-',NULL,'2022-06-09 08:41:36',3,'SB',NULL),(16,'AC CLEANER DOUBLE BLOWER','-',NULL,'2022-06-09 08:41:45',3,'DB',NULL),(17,'FOGGING','-',NULL,'2022-06-09 08:42:29',3,'FOG',NULL),(18,'GLASS POLISH ','-',NULL,NULL,3,'GP',NULL),(19,'ENGINE DRESSING ','-',NULL,NULL,3,'ED',NULL),(20,'ENGINE CARE ','-',NULL,NULL,3,'EC',NULL),(21,'ENGINE CLEANING UAP ','-',NULL,NULL,3,'ECU',NULL),(22,'BUBUT DISC BRAKE','-',NULL,'2022-06-09 08:44:00',3,'DL',NULL),(23,'TIRE CHANGER ','-',NULL,NULL,3,'TC',NULL),(24,'CHAMBER CLEANER ','-',NULL,NULL,3,'CC',NULL),(25,'DIESEL PURGING ','-',NULL,NULL,3,'DP',NULL),(26,'FUEL INJECTOR CLEANER','-',NULL,'2022-06-09 08:48:35',3,'IC',NULL),(27,'TURBO CLEANER','-',NULL,'2022-06-09 08:45:19',3,'TCC',NULL),(28,'CATALYTIC CLEANER','-',NULL,NULL,3,'CTC',NULL),(29,'INTERIOR CLEANING UAP ','-',NULL,NULL,3,'ICU',NULL),(30,'HHO ','-',NULL,NULL,3,'HHO',NULL),(31,'engine flush','-','2023-06-02 03:35:58','2023-06-02 03:35:58',3,'EF',NULL),(32,'ISI FREON SINGLE BLOWER','-','2023-11-02 04:08:57','2023-11-02 04:08:57',3,'FR SB 1',NULL),(33,'ISI FREON DOUBLE BLOWER','-','2023-11-02 04:09:25','2023-11-02 04:09:25',3,'FR DB 2',NULL),(34,'Oil lining performance','jasa oil lining','2023-12-05 10:26:43','2023-12-05 10:28:49',3,'OLP',NULL),(35,'engine oil performance b1','jasa ganti oli','2023-12-05 10:27:36','2023-12-05 10:28:59',3,'B1',NULL),(36,'engine oil performance b2','jasa ganti oli b2','2023-12-05 10:28:03','2023-12-05 10:29:09',3,'B2',NULL),(37,'Oil lining performance non engine flush','jasa oil lining tanpa engine flush','2024-03-08 06:41:41','2024-03-08 06:42:53',3,'OLP NON EF',NULL),(38,'AC FRESH SERVICE','parfurmed ac','2024-03-13 05:08:15','2024-03-13 05:08:15',3,'AC FRESH',NULL),(39,'AC LIGHT SERVICE - SINGLE BLOWER','penggantian dryer, oli komprespor, dan refrigant','2024-03-13 05:12:47','2024-03-13 05:12:47',3,'AC LIGHT - SB',NULL),(40,'AC LIGHT SERVICE - DOUBLE BLOWER','penggantian dryer, oli komprespor, dan refrigant','2024-03-13 05:13:39','2024-03-13 05:14:18',3,'AC LIGHT - DB',NULL),(41,'AC HEAVY SERVICE - SINGLE BLOWER','penggantian dryer, oli komprespor, dan refrigant (cek kebocoran EVAP)','2024-03-13 05:15:29','2024-03-13 05:15:29',3,'AC HEAVY - SB',NULL),(42,'AC HEAVY SERVICE - DOUBLE BLOWER','penggantian dryer, oli komprespor, dan refrigant (cek kebocoran EVAP)','2024-03-13 05:16:05','2024-03-13 05:16:05',3,'AC HEAVY - DB',NULL),(43,'AC CLEAN SERVICE','pembersihan saluran ac dan evap','2024-04-24 09:49:02','2024-04-24 09:49:02',3,'ACCS',NULL),(44,'COOLANT CHANGER & COOLING SYSTEM CLEANER','kuras radiator dan penggantian coolant','2024-07-08 10:58:52','2024-07-08 10:58:52',3,'C3SC',NULL),(45,'AT CVT FLUSHING','pembersihan transmisi matic cvt','2024-09-18 09:33:09','2024-09-18 09:33:09',3,'AT CVT FLUSHING',NULL),(46,'CUCI SNOW','cuci snow','2024-09-19 08:20:30','2024-09-19 08:20:30',1,'CUCI SNOW',NULL);
/*!40000 ALTER TABLE `works` ENABLE KEYS */; /*!40000 ALTER TABLE `works` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
--
-- Dumping routines for database 'ckb_db'
--
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
@@ -646,4 +733,4 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2025-06-10 18:16:53 -- Dump completed on 2025-06-11 17:30:35

View File

@@ -18,7 +18,7 @@
"laravel/ui": "^3.4", "laravel/ui": "^3.4",
"maatwebsite/excel": "^3.1", "maatwebsite/excel": "^3.1",
"nesbot/carbon": "^2.73", "nesbot/carbon": "^2.73",
"yajra/laravel-datatables-oracle": "^9.20" "yajra/laravel-datatables-oracle": "^9.21"
}, },
"require-dev": { "require-dev": {
"facade/ignition": "^2.5", "facade/ignition": "^2.5",

28
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "3e3b47389d4bce664f705134cae65b49", "content-hash": "a73100beed847d2c43aca4cca10a0d86",
"packages": [ "packages": [
{ {
"name": "asm89/stack-cors", "name": "asm89/stack-cors",
@@ -6450,16 +6450,16 @@
}, },
{ {
"name": "yajra/laravel-datatables-oracle", "name": "yajra/laravel-datatables-oracle",
"version": "v9.20.0", "version": "v9.21.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/yajra/laravel-datatables.git", "url": "https://github.com/yajra/laravel-datatables.git",
"reference": "4c22b09c8c664df5aad9f17d99c3823c0f2d84e2" "reference": "a7fd01f06282923e9c63fa27fe6b391e21dc321a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/4c22b09c8c664df5aad9f17d99c3823c0f2d84e2", "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/a7fd01f06282923e9c63fa27fe6b391e21dc321a",
"reference": "4c22b09c8c664df5aad9f17d99c3823c0f2d84e2", "reference": "a7fd01f06282923e9c63fa27fe6b391e21dc321a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -6481,16 +6481,16 @@
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": {
"dev-master": "9.0-dev"
},
"laravel": { "laravel": {
"providers": [
"Yajra\\DataTables\\DataTablesServiceProvider"
],
"aliases": { "aliases": {
"DataTables": "Yajra\\DataTables\\Facades\\DataTables" "DataTables": "Yajra\\DataTables\\Facades\\DataTables"
} },
"providers": [
"Yajra\\DataTables\\DataTablesServiceProvider"
]
},
"branch-alias": {
"dev-master": "9.0-dev"
} }
}, },
"autoload": { "autoload": {
@@ -6519,7 +6519,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/yajra/laravel-datatables/issues", "issues": "https://github.com/yajra/laravel-datatables/issues",
"source": "https://github.com/yajra/laravel-datatables/tree/v9.20.0" "source": "https://github.com/yajra/laravel-datatables/tree/v9.21.2"
}, },
"funding": [ "funding": [
{ {
@@ -6531,7 +6531,7 @@
"type": "patreon" "type": "patreon"
} }
], ],
"time": "2022-05-08T16:04:16+00:00" "time": "2022-07-12T04:48:03+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [

View File

@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('mutations', function (Blueprint $table) {
$table->id();
$table->string('mutation_number')->unique();
$table->foreignId('from_dealer_id')->constrained('dealers');
$table->foreignId('to_dealer_id')->constrained('dealers');
$table->enum('status', ['pending', 'sent', 'received', 'approved', 'rejected', 'completed', 'cancelled'])
->default('sent');
$table->foreignId('requested_by')->constrained('users');
$table->foreignId('approved_by')->nullable()->constrained('users');
$table->timestamp('approved_at')->nullable();
$table->foreignId('received_by')->nullable()->constrained('users');
$table->timestamp('received_at')->nullable();
$table->text('notes')->nullable();
$table->text('rejection_reason')->nullable();
$table->timestamps();
$table->softDeletes();
// Indexes untuk performa
$table->index(['from_dealer_id', 'status']);
$table->index(['to_dealer_id', 'status']);
$table->index(['status', 'created_at']);
$table->index('mutation_number');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('mutations');
}
};

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('mutation_details', function (Blueprint $table) {
$table->id();
$table->foreignId('mutation_id')->constrained('mutations')->onDelete('cascade');
$table->foreignId('product_id')->constrained('products');
$table->decimal('quantity_requested', 15, 2);
$table->decimal('quantity_approved', 15, 2)->default(0);
$table->text('notes')->nullable();
$table->timestamps();
// Indexes untuk performa
$table->index(['mutation_id', 'product_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('mutation_details');
}
};

View File

@@ -0,0 +1,159 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Mutation;
use App\Models\MutationDetail;
use App\Models\Dealer;
use App\Models\Product;
use App\Models\User;
use App\Enums\MutationStatus;
class MutationTestSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Pastikan ada dealer dan produk
$dealers = Dealer::take(3)->get();
$products = Product::take(5)->get();
$users = User::take(3)->get();
$this->command->info('Dealers found: ' . $dealers->count());
$this->command->info('Products found: ' . $products->count());
$this->command->info('Users found: ' . $users->count());
if ($dealers->count() < 2 || $products->count() < 2 || $users->count() < 1) {
$this->command->error('Tidak cukup data dealer, produk, atau user untuk membuat test mutation');
$this->command->error("Need at least 2 dealers, 2 products, 1 user");
return;
}
try {
// 1. Mutation dengan status SENT (baru terkirim, menunggu diterima dealer tujuan)
$this->command->info('Creating SENT mutation...');
$mutation1 = Mutation::create([
'from_dealer_id' => $dealers[0]->id,
'to_dealer_id' => $dealers[1]->id,
'status' => MutationStatus::SENT,
'requested_by' => $users[0]->id,
'notes' => 'Mutasi test - status SENT (menunggu diterima)'
]);
MutationDetail::create([
'mutation_id' => $mutation1->id,
'product_id' => $products[0]->id,
'quantity_requested' => 10.00,
'notes' => 'Produk test 1'
]);
MutationDetail::create([
'mutation_id' => $mutation1->id,
'product_id' => $products[1]->id,
'quantity_requested' => 5.00,
'notes' => 'Produk test 2'
]);
// 2. Mutation dengan status RECEIVED (sudah diterima, menunggu approval pengirim)
$this->command->info('Creating RECEIVED mutation...');
$mutation2 = Mutation::create([
'from_dealer_id' => $dealers[0]->id,
'to_dealer_id' => $dealers[1]->id,
'status' => MutationStatus::RECEIVED,
'requested_by' => $users[0]->id,
'received_by' => $users[1]->id ?? $users[0]->id,
'received_at' => now()->subHours(2),
'notes' => 'Mutasi test - status RECEIVED (menunggu approval)'
]);
MutationDetail::create([
'mutation_id' => $mutation2->id,
'product_id' => $products[0]->id,
'quantity_requested' => 8.00,
'notes' => 'Produk untuk approval'
]);
// 3. Mutation dengan status APPROVED (sudah diapprove, siap dikompletkan)
$this->command->info('Creating APPROVED mutation...');
$mutation3 = Mutation::create([
'from_dealer_id' => $dealers[0]->id,
'to_dealer_id' => $dealers[1]->id,
'status' => MutationStatus::APPROVED,
'requested_by' => $users[0]->id,
'received_by' => $users[1]->id ?? $users[0]->id,
'received_at' => now()->subHours(4),
'approved_by' => $users[0]->id,
'approved_at' => now()->subHours(1),
'notes' => 'Mutasi test - status APPROVED (siap dikompletkan)'
]);
$mutation3Detail = MutationDetail::create([
'mutation_id' => $mutation3->id,
'product_id' => $products[0]->id,
'quantity_requested' => 12.00,
'quantity_approved' => 10.00, // Approved dengan jumlah berbeda
'notes' => 'Produk approved parsial'
]);
// 4. Mutation dengan status COMPLETED (selesai)
$this->command->info('Creating COMPLETED mutation...');
$mutation4 = Mutation::create([
'from_dealer_id' => $dealers[0]->id,
'to_dealer_id' => $dealers[1]->id,
'status' => MutationStatus::COMPLETED,
'requested_by' => $users[0]->id,
'received_by' => $users[1]->id ?? $users[0]->id,
'received_at' => now()->subDay(),
'approved_by' => $users[0]->id,
'approved_at' => now()->subHours(6),
'notes' => 'Mutasi test - status COMPLETED'
]);
MutationDetail::create([
'mutation_id' => $mutation4->id,
'product_id' => $products[1]->id,
'quantity_requested' => 6.00,
'quantity_approved' => 6.00,
'notes' => 'Produk completed'
]);
// 5. Mutation dengan status REJECTED
$this->command->info('Creating REJECTED mutation...');
$mutation5 = Mutation::create([
'from_dealer_id' => $dealers[0]->id,
'to_dealer_id' => $dealers[1]->id,
'status' => MutationStatus::REJECTED,
'requested_by' => $users[0]->id,
'received_by' => $users[1]->id ?? $users[0]->id,
'received_at' => now()->subHours(8),
'approved_by' => $users[0]->id,
'approved_at' => now()->subHours(3),
'rejection_reason' => 'Stock tidak mencukupi untuk produk yang diminta',
'notes' => 'Mutasi test - status REJECTED'
]);
MutationDetail::create([
'mutation_id' => $mutation5->id,
'product_id' => $products[2]->id,
'quantity_requested' => 20.00,
'quantity_approved' => 0,
'notes' => 'Produk rejected'
]);
$this->command->info('Test mutations created successfully!');
$this->command->info('- Mutation SENT: ' . $mutation1->mutation_number);
$this->command->info('- Mutation RECEIVED: ' . $mutation2->mutation_number);
$this->command->info('- Mutation APPROVED: ' . $mutation3->mutation_number);
$this->command->info('- Mutation COMPLETED: ' . $mutation4->mutation_number);
$this->command->info('- Mutation REJECTED: ' . $mutation5->mutation_number);
} catch (\Exception $e) {
$this->command->error('Error creating test mutations: ' . $e->getMessage());
$this->command->error('Stack trace: ' . $e->getTraceAsString());
}
}
}

62
package-lock.json generated
View File

@@ -1,5 +1,5 @@
{ {
"name": "ckb", "name": "html",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
@@ -12664,19 +12664,22 @@
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz",
"integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==",
"dev": true "dev": true,
"requires": {}
}, },
"@webpack-cli/info": { "@webpack-cli/info": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz",
"integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==",
"dev": true "dev": true,
"requires": {}
}, },
"@webpack-cli/serve": { "@webpack-cli/serve": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz",
"integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==",
"dev": true "dev": true,
"requires": {}
}, },
"@xtuc/ieee754": { "@xtuc/ieee754": {
"version": "1.2.0", "version": "1.2.0",
@@ -12761,7 +12764,8 @@
"version": "3.5.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true "dev": true,
"requires": {}
}, },
"ansi-html-community": { "ansi-html-community": {
"version": "0.0.8", "version": "0.0.8",
@@ -13055,7 +13059,8 @@
"version": "5.1.3", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
"integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
"dev": true "dev": true,
"requires": {}
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
@@ -14026,7 +14031,8 @@
"version": "6.2.2", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz",
"integrity": "sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==", "integrity": "sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==",
"dev": true "dev": true,
"requires": {}
}, },
"css-loader": { "css-loader": {
"version": "5.2.7", "version": "5.2.7",
@@ -14157,7 +14163,8 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz",
"integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==",
"dev": true "dev": true,
"requires": {}
}, },
"csso": { "csso": {
"version": "4.2.0", "version": "4.2.0",
@@ -14469,7 +14476,8 @@
"version": "8.17.1", "version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true "dev": true,
"requires": {}
} }
} }
}, },
@@ -14490,7 +14498,8 @@
"version": "8.17.1", "version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true "dev": true,
"requires": {}
} }
} }
}, },
@@ -15337,7 +15346,8 @@
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true "dev": true,
"requires": {}
}, },
"ieee754": { "ieee754": {
"version": "1.2.1", "version": "1.2.1",
@@ -15719,7 +15729,8 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
"integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==",
"dev": true "dev": true,
"requires": {}
}, },
"@webpack-cli/info": { "@webpack-cli/info": {
"version": "1.5.0", "version": "1.5.0",
@@ -15734,7 +15745,8 @@
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz",
"integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==",
"dev": true "dev": true,
"requires": {}
}, },
"interpret": { "interpret": {
"version": "2.2.0", "version": "2.2.0",
@@ -16617,25 +16629,29 @@
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz",
"integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==", "integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-discard-duplicates": { "postcss-discard-duplicates": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz",
"integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-discard-empty": { "postcss-discard-empty": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz",
"integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-discard-overridden": { "postcss-discard-overridden": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz",
"integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-load-config": { "postcss-load-config": {
"version": "3.1.4", "version": "3.1.4",
@@ -16724,7 +16740,8 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-modules-local-by-default": { "postcss-modules-local-by-default": {
"version": "4.0.0", "version": "4.0.0",
@@ -16759,7 +16776,8 @@
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz",
"integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==",
"dev": true "dev": true,
"requires": {}
}, },
"postcss-normalize-display-values": { "postcss-normalize-display-values": {
"version": "5.1.0", "version": "5.1.0",
@@ -17674,7 +17692,8 @@
"version": "8.17.1", "version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true "dev": true,
"requires": {}
} }
} }
}, },
@@ -18630,7 +18649,8 @@
"version": "8.18.2", "version": "8.18.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
"dev": true "dev": true,
"requires": {}
}, },
"xmlhttprequest-ssl": { "xmlhttprequest-ssl": {
"version": "2.1.2", "version": "2.1.2",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,5 +5,7 @@
"/js/warehouse_management/opnames/index.js": "/js/warehouse_management/opnames/index.js", "/js/warehouse_management/opnames/index.js": "/js/warehouse_management/opnames/index.js",
"/js/warehouse_management/opnames/create.js": "/js/warehouse_management/opnames/create.js", "/js/warehouse_management/opnames/create.js": "/js/warehouse_management/opnames/create.js",
"/js/warehouse_management/opnames/detail.js": "/js/warehouse_management/opnames/detail.js", "/js/warehouse_management/opnames/detail.js": "/js/warehouse_management/opnames/detail.js",
"/js/warehouse_management/mutations/index.js": "/js/warehouse_management/mutations/index.js",
"/js/warehouse_management/mutations/create.js": "/js/warehouse_management/mutations/create.js",
"/css/app.css": "/css/app.css" "/css/app.css": "/css/app.css"
} }

View File

@@ -0,0 +1,262 @@
$(document).ready(function () {
let productIndex = 1;
// Initialize Select2
$(".select2").select2({
placeholder: "Pilih...",
allowClear: true,
});
// Prevent same dealer selection
$("#from_dealer_id, #to_dealer_id").on("change", function () {
const fromDealerId = $("#from_dealer_id").val();
const toDealerId = $("#to_dealer_id").val();
if (fromDealerId && toDealerId && fromDealerId === toDealerId) {
$(this).val("").trigger("change");
alert("Dealer asal dan tujuan tidak boleh sama!");
}
// Update available stock when dealer changes
updateAllAvailableStock();
});
// Add new product row
$("#add-product").on("click", function () {
const newRow = createProductRow(productIndex);
$("#products-tbody").append(newRow);
// Initialize Select2 for new row
const newSelect = $(
`#products-tbody tr[data-index="${productIndex}"] .product-select`
);
newSelect.select2({
placeholder: "Pilih Produk...",
allowClear: true,
});
productIndex++;
updateRemoveButtons();
});
// Remove product row
$(document).on("click", ".remove-product", function () {
$(this).closest("tr").remove();
updateRemoveButtons();
reindexRows();
});
// Handle product selection change
$(document).on("change", ".product-select", function () {
const row = $(this).closest("tr");
const productId = $(this).val();
const fromDealerId = $("#from_dealer_id").val();
if (productId && fromDealerId) {
getAvailableStock(productId, fromDealerId, row);
} else {
row.find(".available-stock").text("-");
row.find(".quantity-input").attr("max", "");
}
});
// Validate quantity input
$(document).on("input", ".quantity-input", function () {
const maxValue = parseFloat($(this).attr("max"));
const currentValue = parseFloat($(this).val());
if (maxValue && currentValue > maxValue) {
$(this).val(maxValue);
$(this).addClass("is-invalid");
if (!$(this).siblings(".invalid-feedback").length) {
$(this).after(
'<div class="invalid-feedback">Quantity melebihi stock yang tersedia</div>'
);
}
} else {
$(this).removeClass("is-invalid");
$(this).siblings(".invalid-feedback").remove();
}
});
// Form submission
$("#mutation-form").on("submit", function (e) {
e.preventDefault();
if (!validateForm()) {
return false;
}
const submitBtn = $("#submit-btn");
const originalText = submitBtn.html();
submitBtn
.prop("disabled", true)
.html('<i class="la la-spinner la-spin"></i> Menyimpan...');
// Submit form
this.submit();
});
function createProductRow(index) {
// Get product options from the existing select
const existingSelect = $(".product-select").first();
const productOptions = existingSelect.html();
return `
<tr class="product-row" data-index="${index}">
<td>
<select name="products[${index}][product_id]" class="form-control select2 product-select" required>
${productOptions}
</select>
</td>
<td>
<span class="available-stock text-muted">-</span>
</td>
<td>
<input type="number"
name="products[${index}][quantity_requested]"
class="form-control quantity-input"
min="0.01"
step="0.01"
placeholder="0"
required>
</td>
<td>
<input type="text"
name="products[${index}][notes]"
class="form-control"
placeholder="Catatan produk (opsional)">
</td>
<td>
<button type="button" class="btn btn-danger btn-sm remove-product">
<i class="la la-trash"></i>
</button>
</td>
</tr>
`;
}
function updateRemoveButtons() {
const rows = $(".product-row");
$(".remove-product").prop("disabled", rows.length <= 1);
}
function reindexRows() {
$(".product-row").each(function (index) {
$(this).attr("data-index", index);
$(this)
.find('select[name*="product_id"]')
.attr("name", `products[${index}][product_id]`);
$(this)
.find('input[name*="quantity_requested"]')
.attr("name", `products[${index}][quantity_requested]`);
$(this)
.find('input[name*="notes"]')
.attr("name", `products[${index}][notes]`);
});
productIndex = $(".product-row").length;
}
function getAvailableStock(productId, dealerId, row) {
$.ajax({
url: "/warehouse/mutations/get-product-stock",
method: "GET",
data: {
product_id: productId,
dealer_id: dealerId,
},
beforeSend: function () {
row.find(".available-stock").html(
'<i class="la la-spinner la-spin"></i>'
);
},
success: function (response) {
const stock = parseFloat(response.current_stock);
row.find(".available-stock").text(stock.toLocaleString());
row.find(".quantity-input").attr("max", stock);
// Set max value message
if (stock <= 0) {
row.find(".available-stock")
.addClass("text-danger")
.removeClass("text-muted");
row.find(".quantity-input").attr("readonly", true).val("");
} else {
row.find(".available-stock")
.removeClass("text-danger")
.addClass("text-muted");
row.find(".quantity-input").attr("readonly", false);
}
},
error: function () {
row.find(".available-stock")
.text("Error")
.addClass("text-danger");
},
});
}
function updateAllAvailableStock() {
const fromDealerId = $("#from_dealer_id").val();
$(".product-row").each(function () {
const row = $(this);
const productId = row.find(".product-select").val();
if (productId && fromDealerId) {
getAvailableStock(productId, fromDealerId, row);
} else {
row.find(".available-stock").text("-");
row.find(".quantity-input").attr("max", "");
}
});
}
function validateForm() {
let isValid = true;
const fromDealerId = $("#from_dealer_id").val();
const toDealerId = $("#to_dealer_id").val();
// Check dealers
if (!fromDealerId) {
alert("Pilih dealer asal");
return false;
}
if (!toDealerId) {
alert("Pilih dealer tujuan");
return false;
}
if (fromDealerId === toDealerId) {
alert("Dealer asal dan tujuan tidak boleh sama");
return false;
}
// Check products
const productRows = $(".product-row");
if (productRows.length === 0) {
alert("Tambahkan minimal satu produk");
return false;
}
let hasValidProduct = false;
productRows.each(function () {
const productId = $(this).find(".product-select").val();
const quantity = $(this).find(".quantity-input").val();
if (productId && quantity && parseFloat(quantity) > 0) {
hasValidProduct = true;
}
});
if (!hasValidProduct) {
alert("Pilih minimal satu produk dengan quantity yang valid");
return false;
}
return isValid;
}
});

View File

@@ -0,0 +1,261 @@
$(document).ready(function () {
// Initialize DataTable
var table = $("#mutations-table").DataTable({
processing: true,
serverSide: true,
ajax: {
url: $("#mutations-table").data("url"),
type: "GET",
},
columns: [
{
data: "DT_RowIndex",
name: "DT_RowIndex",
orderable: false,
searchable: false,
width: "5%",
},
{
data: "mutation_number",
name: "mutation_number",
width: "12%",
},
{
data: "created_at",
name: "created_at",
width: "12%",
},
{
data: "from_dealer",
name: "fromDealer.name",
width: "15%",
},
{
data: "to_dealer",
name: "toDealer.name",
width: "15%",
},
{
data: "requested_by",
name: "requestedBy.name",
width: "12%",
},
{
data: "total_items",
name: "total_items",
width: "8%",
className: "text-center",
},
{
data: "status",
name: "status",
width: "12%",
className: "text-center",
},
{
data: "action",
name: "action",
orderable: false,
searchable: false,
width: "15%",
className: "text-center",
},
],
order: [[2, "desc"]], // Order by created_at desc
pageLength: 10,
responsive: true,
language: {
processing: "Memuat data...",
lengthMenu: "Tampilkan _MENU_ data per halaman",
zeroRecords: "Data tidak ditemukan",
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ data",
infoEmpty: "Menampilkan 0 sampai 0 dari 0 data",
infoFiltered: "(difilter dari _MAX_ total data)",
},
});
// Handle Receive Button Click
$(document).on("click", ".btn-receive", function () {
var mutationId = $(this).data("id");
$("#receiveModal" + mutationId).modal("show");
});
// Handle Approve Button Click
$(document).on("click", ".btn-approve", function () {
var mutationId = $(this).data("id");
// Load mutation details via AJAX
$.ajax({
url: "/warehouse/mutations/" + mutationId + "/details",
type: "GET",
beforeSend: function () {
$("#mutation-details" + mutationId).html(
'<div class="text-center">' +
'<div class="spinner-border" role="status">' +
'<span class="sr-only">Loading...</span>' +
"</div>" +
"<p>Memuat detail produk...</p>" +
"</div>"
);
},
success: function (response) {
var detailsHtml = "<h6>Detail Produk:</h6>";
detailsHtml += '<div class="table-responsive">';
detailsHtml += '<table class="table table-sm">';
detailsHtml += "<thead>";
detailsHtml += "<tr>";
detailsHtml += "<th>Produk</th>";
detailsHtml += "<th>Diminta</th>";
detailsHtml += "<th>Disetujui</th>";
detailsHtml += "<th>Stock Tersedia</th>";
detailsHtml += "</tr>";
detailsHtml += "</thead>";
detailsHtml += "<tbody>";
response.details.forEach(function (detail, index) {
detailsHtml += "<tr>";
detailsHtml += "<td>" + detail.product.name + "</td>";
detailsHtml +=
"<td>" +
parseFloat(detail.quantity_requested).toLocaleString() +
"</td>";
detailsHtml += "<td>";
detailsHtml +=
'<input type="number" name="details[' +
detail.id +
'][quantity_approved]" ';
detailsHtml += 'class="form-control form-control-sm" ';
detailsHtml += 'value="' + detail.quantity_requested + '" ';
detailsHtml +=
'min="0" max="' +
Math.min(
detail.quantity_requested,
detail.available_stock
) +
'" ';
detailsHtml += 'step="0.01" required>';
detailsHtml += "</td>";
detailsHtml +=
"<td>" +
parseFloat(detail.available_stock).toLocaleString() +
"</td>";
detailsHtml += "</tr>";
});
detailsHtml += "</tbody>";
detailsHtml += "</table>";
detailsHtml += "</div>";
$("#mutation-details" + mutationId).html(detailsHtml);
},
error: function () {
$("#mutation-details" + mutationId).html(
'<div class="alert alert-danger">Gagal memuat detail produk</div>'
);
},
});
$("#approveModal" + mutationId).modal("show");
});
// Handle other button clicks
$(document).on("click", ".btn-reject", function () {
var mutationId = $(this).data("id");
$("#rejectModal" + mutationId).modal("show");
});
$(document).on("click", ".btn-complete", function () {
var mutationId = $(this).data("id");
$("#completeModal" + mutationId).modal("show");
});
// Handle Cancel Button Click with SweetAlert
$(document).on("click", ".btn-cancel", function () {
var mutationId = $(this).data("id");
if (typeof Swal !== "undefined") {
Swal.fire({
title: "Batalkan Mutasi?",
text: "Apakah Anda yakin ingin membatalkan mutasi ini?",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#d33",
cancelButtonColor: "#3085d6",
confirmButtonText: "Ya, Batalkan",
cancelButtonText: "Batal",
}).then((result) => {
if (result.isConfirmed) {
cancelMutation(mutationId);
}
});
} else {
if (confirm("Apakah Anda yakin ingin membatalkan mutasi ini?")) {
cancelMutation(mutationId);
}
}
});
function cancelMutation(mutationId) {
$.ajax({
url: "/warehouse/mutations/" + mutationId + "/cancel",
type: "POST",
data: {
_token: $('meta[name="csrf-token"]').attr("content"),
},
success: function (response) {
if (typeof Swal !== "undefined") {
Swal.fire({
title: "Berhasil!",
text: "Mutasi berhasil dibatalkan",
icon: "success",
timer: 2000,
showConfirmButton: false,
});
} else {
alert("Mutasi berhasil dibatalkan");
}
table.ajax.reload();
},
error: function (xhr) {
var errorMsg =
xhr.responseJSON?.message || "Gagal membatalkan mutasi";
if (typeof Swal !== "undefined") {
Swal.fire({
title: "Error!",
text: errorMsg,
icon: "error",
});
} else {
alert("Error: " + errorMsg);
}
},
});
}
// Handle form submissions with loading state
$(document).on("submit", ".approve-form", function () {
$(this)
.find('button[type="submit"]')
.prop("disabled", true)
.html("Memproses...");
});
// Auto-calculate approved quantity based on available stock
$(document).on("input", 'input[name*="quantity_approved"]', function () {
var maxValue = parseFloat($(this).attr("max"));
var currentValue = parseFloat($(this).val());
if (maxValue && currentValue > maxValue) {
$(this).val(maxValue);
$(this).addClass("is-invalid");
if (!$(this).siblings(".invalid-feedback").length) {
$(this).after(
'<div class="invalid-feedback">Jumlah melebihi stock yang tersedia</div>'
);
}
} else {
$(this).removeClass("is-invalid");
$(this).siblings(".invalid-feedback").remove();
}
});
});

View File

@@ -215,9 +215,9 @@
</li> </li>
@endcan @endcan
@can('view', $menus['opnames.index']) @can('view', $menus['mutations.index'])
<li class="kt-menu__item" aria-haspopup="true"> <li class="kt-menu__item" aria-haspopup="true">
<a href="{{ route('opnames.index') }}" class="kt-menu__link"> <a href="{{ route('mutations.index') }}" class="kt-menu__link">
<i class="fa fa-list" style="display: flex; align-items: center; margin-right: 10px;"></i> <i class="fa fa-list" style="display: flex; align-items: center; margin-right: 10px;"></i>
<span class="kt-menu__link-text">Mutasi Produk</span> <span class="kt-menu__link-text">Mutasi Produk</span>
</a> </a>

View File

@@ -0,0 +1,214 @@
<div class="btn-group btn-group-sm" role="group">
<!-- View Button -->
<a href="{{ route('mutations.show', $row->id) }}"
class="btn btn-sm btn-clean btn-icon btn-icon-md"
title="Lihat Detail">
<i class="la la-eye"></i>
</a>
@if($row->status->value === 'sent')
<!-- Receive Button (untuk dealer tujuan) -->
@if(auth()->user()->dealer_id == $row->to_dealer_id)
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-receive"
data-id="{{ $row->id }}"
title="Terima Mutasi">
<i class="la la-download text-primary"></i>
</button>
@endif
<!-- Cancel Button (untuk pengirim) -->
@if(auth()->user()->dealer_id == $row->from_dealer_id || auth()->user()->hasRole('admin'))
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-cancel"
data-id="{{ $row->id }}"
title="Batalkan Mutasi">
<i class="la la-ban text-warning"></i>
</button>
@endif
@endif
@if($row->status->value === 'received')
<!-- Approve Button (untuk pengirim atau admin) -->
@if(auth()->user()->dealer_id == $row->from_dealer_id || auth()->user()->hasRole('admin'))
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-approve"
data-id="{{ $row->id }}"
title="Setujui Mutasi">
<i class="la la-check text-success"></i>
</button>
@endif
<!-- Reject Button (untuk pengirim atau admin) -->
@if(auth()->user()->dealer_id == $row->from_dealer_id || auth()->user()->hasRole('admin'))
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-reject"
data-id="{{ $row->id }}"
title="Tolak Mutasi">
<i class="la la-times text-danger"></i>
</button>
@endif
@endif
@if($row->status->value === 'approved')
<!-- Complete/Receive Button -->
@can('complete-mutation')
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-complete"
data-id="{{ $row->id }}"
title="Terima & Selesaikan Mutasi">
<i class="la la-check-circle text-primary"></i>
</button>
@endcan
<!-- Cancel Button -->
@can('edit-mutation')
<button type="button"
class="btn btn-sm btn-clean btn-icon btn-icon-md btn-cancel"
data-id="{{ $row->id }}"
title="Batalkan Mutasi">
<i class="la la-ban text-warning"></i>
</button>
@endcan
@endif
@if(in_array($row->status->value, ['pending', 'approved']) && auth()->user()->id === $row->requested_by)
<!-- Edit Button (only for creator and if still pending/approved) -->
<a href="{{ route('mutations.edit', $row->id) }}"
class="btn btn-sm btn-clean btn-icon btn-icon-md"
title="Edit Mutasi">
<i class="la la-edit text-info"></i>
</a>
@endif
@if($row->status->value === 'completed')
<!-- Print Button -->
<a href="{{ route('mutations.print', $row->id) }}"
class="btn btn-sm btn-clean btn-icon btn-icon-md"
target="_blank"
title="Cetak Laporan">
<i class="la la-print text-info"></i>
</a>
@endif
</div>
<!-- Modal untuk Approve -->
<div class="modal fade" id="approveModal{{ $row->id }}" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Setujui Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="{{ route('mutations.approve', $row->id) }}" method="POST" class="approve-form">
@csrf
<div class="modal-body">
<div class="form-group">
<label>Catatan Persetujuan</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Opsional: tambahkan catatan..."></textarea>
</div>
<!-- Detail produk akan dimuat via AJAX -->
<div id="mutation-details{{ $row->id }}">
<div class="text-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
<p>Memuat detail produk...</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-success">Setujui Mutasi</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal untuk Reject -->
<div class="modal fade" id="rejectModal{{ $row->id }}" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Tolak Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="{{ route('mutations.reject', $row->id) }}" method="POST">
@csrf
<div class="modal-body">
<div class="alert alert-warning">
<strong>Peringatan!</strong> Mutasi yang ditolak tidak dapat diubah lagi.
</div>
<div class="form-group">
<label>Alasan Penolakan <span class="text-danger">*</span></label>
<textarea name="rejection_reason" class="form-control" rows="3" required placeholder="Masukkan alasan penolakan..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-danger">Tolak Mutasi</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal untuk Receive -->
<div class="modal fade" id="receiveModal{{ $row->id }}" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Terima Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="{{ route('mutations.receive', $row->id) }}" method="POST">
@csrf
<div class="modal-body">
<div class="alert alert-info">
<strong>Konfirmasi!</strong> Anda akan menerima mutasi dari <strong>{{ $row->fromDealer->name }}</strong>.
</div>
<p>Setelah menerima, mutasi akan menunggu persetujuan dari pengirim sebelum stock dipindahkan.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Ya, Terima</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal untuk Complete -->
<div class="modal fade" id="completeModal{{ $row->id }}" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Selesaikan Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="{{ route('mutations.complete', $row->id) }}" method="POST">
@csrf
<div class="modal-body">
<div class="alert alert-info">
<strong>Konfirmasi!</strong> Stock akan dipindahkan dari <strong>{{ $row->fromDealer->name }}</strong> ke <strong>{{ $row->toDealer->name }}</strong>.
</div>
<p>Apakah Anda yakin ingin menyelesaikan mutasi ini? Tindakan ini tidak dapat dibatalkan.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Ya, Selesaikan</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,159 @@
@extends('layouts.backapp')
@section('content')
<div class="row">
<div class="col-lg-12">
<div class="kt-portlet">
<div class="kt-portlet__head">
<div class="kt-portlet__head-label">
<h3 class="kt-portlet__head-title">
Tambah Mutasi Baru
</h3>
</div>
<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">
<i class="la la-arrow-left"></i>
Kembali
</a>
</div>
</div>
</div>
</div>
<form action="{{ route('mutations.store') }}" method="POST" id="mutation-form">
@csrf
<div class="kt-portlet__body">
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="from_dealer_id">Dealer Asal <span class="text-danger">*</span></label>
<select name="from_dealer_id" id="from_dealer_id" class="form-control select2" required>
<option value="">Pilih Dealer Asal</option>
@foreach($dealers as $dealer)
<option value="{{ $dealer->id }}" {{ old('from_dealer_id') == $dealer->id ? 'selected' : '' }}>
{{ $dealer->name }}
</option>
@endforeach
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="to_dealer_id">Dealer Tujuan <span class="text-danger">*</span></label>
<select name="to_dealer_id" id="to_dealer_id" class="form-control select2" required>
<option value="">Pilih Dealer Tujuan</option>
@foreach($dealers as $dealer)
<option value="{{ $dealer->id }}" {{ old('to_dealer_id') == $dealer->id ? 'selected' : '' }}>
{{ $dealer->name }}
</option>
@endforeach
</select>
</div>
</div>
</div>
<div class="form-group">
<label for="notes">Catatan</label>
<textarea name="notes" id="notes" class="form-control" rows="3" placeholder="Catatan untuk mutasi ini (opsional)">{{ old('notes') }}</textarea>
</div>
<div class="kt-separator kt-separator--border-dashed kt-separator--space-lg"></div>
<div class="form-group">
<div class="d-flex justify-content-between align-items-center mb-3">
<label class="form-label mb-0">Detail Produk <span class="text-danger">*</span></label>
<button type="button" class="btn btn-success btn-sm" id="add-product">
<i class="la la-plus"></i> Tambah Produk
</button>
</div>
<div class="table-responsive">
<table class="table table-bordered" id="products-table">
<thead>
<tr>
<th width="35%">Produk</th>
<th width="15%">Stock Tersedia</th>
<th width="15%">Quantity</th>
<th width="25%">Catatan</th>
<th width="10%">Aksi</th>
</tr>
</thead>
<tbody id="products-tbody">
<tr class="product-row" data-index="0">
<td>
<select name="products[0][product_id]" class="form-control select2 product-select" required>
<option value="">Pilih Produk</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
</td>
<td>
<span class="available-stock text-muted">-</span>
</td>
<td>
<input type="number"
name="products[0][quantity_requested]"
class="form-control quantity-input"
min="0.01"
step="0.01"
placeholder="0"
required>
</td>
<td>
<input type="text"
name="products[0][notes]"
class="form-control"
placeholder="Catatan produk (opsional)">
</td>
<td>
<button type="button" class="btn btn-danger btn-sm remove-product" disabled>
<i class="la la-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="alert alert-info">
<i class="la la-info-circle"></i>
<strong>Informasi:</strong>
Mutasi akan dibuat dengan status "Menunggu Persetujuan" dan memerlukan approval sebelum stock dipindahkan.
</div>
</div>
<div class="kt-portlet__foot">
<div class="kt-form__actions kt-form__actions--right">
<button type="button" class="btn btn-secondary" onclick="window.history.back()">
Batal
</button>
<button type="submit" class="btn btn-primary" id="submit-btn">
<i class="la la-save"></i>
Simpan Mutasi
</button>
</div>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('javascripts')
<script src="{{ mix('js/warehouse_management/mutations/create.js') }}"></script>
@endsection

View File

@@ -0,0 +1,52 @@
@extends('layouts.backapp')
@section('content')
<div class="kt-portlet kt-portlet--mobile" id="kt_blockui_datatable">
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-list-1"></i>
</span>
<h3 class="kt-portlet__head-title">
Tabel Mutasi
</h3>
</div>
<div class="kt-portlet__head-toolbar">
<div class="kt-portlet__head-wrapper">
<div class="kt-portlet__head-actions">
<a href="{{ route('mutations.create') }}" class="btn btn-bold btn-label-brand btn--sm">
<i class="la la-plus"></i>
Tambah Mutasi
</a>
</div>
</div>
</div>
</div>
<div class="kt-portlet__body">
<div class="table-responsive">
<!--begin: Datatable -->
<table class="table table-striped table-bordered table-hover" id="mutations-table" data-url="{{ route('mutations.index') }}">
<thead>
<tr>
<th>No.</th>
<th>No. Mutasi</th>
<th>Tanggal</th>
<th>Dari Dealer</th>
<th>Ke Dealer</th>
<th>Dibuat Oleh</th>
<th>Total Item</th>
<th>Status</th>
<th>Aksi</th>
</tr>
</thead>
</table>
<!--end: Datatable -->
</div>
</div>
</div>
@endsection
@section('javascripts')
<script src="{{ mix('js/warehouse_management/mutations/index.js') }}"></script>
@endsection

View File

@@ -0,0 +1,457 @@
@extends('layouts.backapp')
@section('content')
<div class="row">
<div class="col-lg-12">
<!-- Mutation Header -->
<div class="kt-portlet">
<div class="kt-portlet__head">
<div class="kt-portlet__head-label">
<h3 class="kt-portlet__head-title">
Detail Mutasi - {{ $mutation->mutation_number }}
</h3>
</div>
<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>
</div>
</div>
</div>
</div>
<div class="kt-portlet__body">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label><strong>No. Mutasi:</strong></label>
<p>{{ $mutation->mutation_number }}</p>
</div>
<div class="form-group">
<label><strong>Dari Dealer:</strong></label>
<p>{{ $mutation->fromDealer->name }}</p>
</div>
<div class="form-group">
<label><strong>Ke Dealer:</strong></label>
<p>{{ $mutation->toDealer->name }}</p>
</div>
<div class="form-group">
<label><strong>Status:</strong></label>
<p>
<span class="kt-badge kt-badge--{{ $mutation->status_color }} kt-badge--dot"></span>
<span class="kt-font-bold kt-font-{{ $mutation->status_color }}">{{ $mutation->status_label }}</span>
</p>
</div>
</div>
<div class="col-lg-6">
<div class="form-group">
<label><strong>Dibuat Oleh:</strong></label>
<p>{{ $mutation->requestedBy->name }}</p>
</div>
<div class="form-group">
<label><strong>Tanggal Dibuat:</strong></label>
<p>{{ $mutation->created_at->format('d/m/Y H:i:s') }}</p>
</div>
@if($mutation->receivedBy)
<div class="form-group">
<label><strong>Diterima Oleh:</strong></label>
<p>{{ $mutation->receivedBy->name }}</p>
</div>
<div class="form-group">
<label><strong>Tanggal Diterima:</strong></label>
<p>{{ $mutation->received_at->format('d/m/Y H:i:s') }}</p>
</div>
@endif
@if($mutation->approvedBy)
<div class="form-group">
<label><strong>Disetujui Oleh:</strong></label>
<p>{{ $mutation->approvedBy->name }}</p>
</div>
<div class="form-group">
<label><strong>Tanggal Disetujui:</strong></label>
<p>{{ $mutation->approved_at->format('d/m/Y H:i:s') }}</p>
</div>
@endif
</div>
</div>
@if($mutation->notes)
<div class="form-group">
<label><strong>Catatan:</strong></label>
<p>{{ $mutation->notes }}</p>
</div>
@endif
@if($mutation->rejection_reason)
<div class="form-group">
<label><strong>Alasan Penolakan:</strong></label>
<div class="alert alert-danger">{{ $mutation->rejection_reason }}</div>
</div>
@endif
</div>
</div>
<!-- Mutation Details -->
<div class="kt-portlet">
<div class="kt-portlet__head">
<div class="kt-portlet__head-label">
<h3 class="kt-portlet__head-title">
Detail Produk
</h3>
</div>
</div>
<div class="kt-portlet__body">
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>No.</th>
<th>Nama Produk</th>
<th>Jumlah Diminta</th>
<th>Jumlah Disetujui</th>
<th>Status Approval</th>
<th>Catatan</th>
</tr>
</thead>
<tbody>
@foreach($mutation->mutationDetails as $index => $detail)
<tr>
<td>{{ $index + 1 }}</td>
<td>{{ $detail->product->name }}</td>
<td>{{ number_format($detail->quantity_requested, 2) }}</td>
<td>
@if($mutation->status->value === 'received' || $mutation->status->value === 'approved' || $mutation->status->value === 'completed')
{{ number_format($detail->quantity_approved ?? 0, 2) }}
@else
<span class="text-muted">Belum ditentukan</span>
@endif
</td>
<td>
@if($mutation->status->value === 'received' || $mutation->status->value === 'approved' || $mutation->status->value === 'completed')
<span class="kt-badge kt-badge--{{ $detail->approval_status_color }} kt-badge--pill">
{{ $detail->approval_status }}
</span>
@else
<span class="kt-badge kt-badge--secondary kt-badge--pill">Menunggu</span>
@endif
</td>
<td>{{ $detail->notes ?? '-' }}</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr class="kt-font-bold">
<td colspan="2">Total</td>
<td>{{ number_format($mutation->mutationDetails->sum('quantity_requested'), 2) }}</td>
<td>
@if($mutation->status->value === 'received' || $mutation->status->value === 'approved' || $mutation->status->value === 'completed')
{{ number_format($mutation->mutationDetails->sum('quantity_approved'), 2) }}
@else
<span class="text-muted">-</span>
@endif
</td>
<td colspan="2"></td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="kt-portlet">
<div class="kt-portlet__body">
<div class="row">
<div class="col-lg-12">
@if($mutation->status->value === 'sent' && auth()->user()->dealer_id == $mutation->to_dealer_id)
<!-- Receive Button for destination dealer -->
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#receiveModal">
<i class="la la-download"></i>
Terima Mutasi
</button>
@endif
@if($mutation->status->value === 'received' && (auth()->user()->dealer_id == $mutation->from_dealer_id || auth()->user()->hasRole('admin')))
<!-- Approve Button for sender or admin -->
<button type="button" class="btn btn-success btn-approve" data-id="{{ $mutation->id }}">
<i class="la la-check"></i>
Setujui Mutasi
</button>
<!-- Reject Button for sender or admin -->
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#rejectModal">
<i class="la la-times"></i>
Tolak Mutasi
</button>
@endif
@if($mutation->status->value === 'approved')
<!-- Complete Button -->
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#completeModal">
<i class="la la-check-circle"></i>
Selesaikan Mutasi
</button>
@endif
@if($mutation->canBeCancelled() && (auth()->user()->dealer_id == $mutation->from_dealer_id || auth()->user()->hasRole('admin')))
<!-- Cancel Button -->
<button type="button" class="btn btn-warning" data-toggle="modal" data-target="#cancelModal">
<i class="la la-ban"></i>
Batalkan Mutasi
</button>
@endif
@if($mutation->status->value === 'completed')
<!-- Print Button -->
<a href="{{ route('mutations.print', $mutation->id) }}" class="btn btn-info" target="_blank">
<i class="la la-print"></i>
Cetak Laporan
</a>
@endif
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modals -->
@if($mutation->status->value === 'sent' && auth()->user()->dealer_id == $mutation->to_dealer_id)
<!-- Receive Modal -->
<div class="modal fade" id="receiveModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Terima Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="{{ route('mutations.receive', $mutation->id) }}" method="POST">
@csrf
<div class="modal-body">
<div class="alert alert-info">
<strong>Konfirmasi!</strong> Anda akan menerima mutasi dari <strong>{{ $mutation->fromDealer->name }}</strong>.
</div>
<p>Setelah menerima, mutasi akan menunggu persetujuan dari pengirim sebelum stock dipindahkan.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Ya, Terima</button>
</div>
</form>
</div>
</div>
</div>
@endif
@if($mutation->status->value === 'received' && (auth()->user()->dealer_id == $mutation->from_dealer_id || auth()->user()->hasRole('admin')))
<!-- Approve Modal -->
<div class="modal fade" id="approveModal{{ $mutation->id }}" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Setujui Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="{{ route('mutations.approve', $mutation->id) }}" method="POST" class="approve-form">
@csrf
<div class="modal-body">
<div class="form-group">
<label>Catatan Persetujuan</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Opsional: tambahkan catatan..."></textarea>
</div>
<!-- Detail produk akan dimuat via AJAX -->
<div id="mutation-details{{ $mutation->id }}">
<div class="text-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
<p>Memuat detail produk...</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-success">Setujui Mutasi</button>
</div>
</form>
</div>
</div>
</div>
<!-- Reject Modal -->
<div class="modal fade" id="rejectModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Tolak Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="{{ route('mutations.reject', $mutation->id) }}" method="POST">
@csrf
<div class="modal-body">
<div class="alert alert-warning">
<strong>Peringatan!</strong> Mutasi yang ditolak tidak dapat diubah lagi.
</div>
<div class="form-group">
<label>Alasan Penolakan <span class="text-danger">*</span></label>
<textarea name="rejection_reason" class="form-control" rows="3" required placeholder="Masukkan alasan penolakan..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-danger">Tolak Mutasi</button>
</div>
</form>
</div>
</div>
</div>
@endif
@if($mutation->status->value === 'approved')
<!-- Complete Modal -->
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Selesaikan Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="{{ route('mutations.complete', $mutation->id) }}" method="POST">
@csrf
<div class="modal-body">
<div class="alert alert-info">
<strong>Konfirmasi!</strong> Stock akan dipindahkan dari <strong>{{ $mutation->fromDealer->name }}</strong> ke <strong>{{ $mutation->toDealer->name }}</strong>.
</div>
<p>Apakah Anda yakin ingin menyelesaikan mutasi ini? Tindakan ini tidak dapat dibatalkan.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Ya, Selesaikan</button>
</div>
</form>
</div>
</div>
</div>
@endif
@if($mutation->canBeCancelled() && (auth()->user()->dealer_id == $mutation->from_dealer_id || auth()->user()->hasRole('admin')))
<!-- Cancel Modal -->
<div class="modal fade" id="cancelModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Batalkan Mutasi</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="{{ route('mutations.cancel', $mutation->id) }}" method="POST">
@csrf
<div class="modal-body">
<div class="alert alert-warning">
<strong>Peringatan!</strong> Mutasi yang dibatalkan tidak dapat diubah lagi.
</div>
<p>Apakah Anda yakin ingin membatalkan mutasi ini?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-warning">Ya, Batalkan</button>
</div>
</form>
</div>
</div>
</div>
@endif
@endsection
@section('javascripts')
<script>
$(document).ready(function() {
// Handle Approve Button Click
$(document).on('click', '.btn-approve', function() {
var mutationId = $(this).data('id');
// Load mutation details via AJAX
$.ajax({
url: '/warehouse/mutations/' + mutationId + '/details',
type: 'GET',
beforeSend: function() {
$('#mutation-details' + mutationId).html(
'<div class="text-center">' +
'<div class="spinner-border" role="status">' +
'<span class="sr-only">Loading...</span>' +
'</div>' +
'<p>Memuat detail produk...</p>' +
'</div>'
);
},
success: function(response) {
var detailsHtml = '<h6>Detail Produk:</h6>';
detailsHtml += '<div class="table-responsive">';
detailsHtml += '<table class="table table-sm">';
detailsHtml += '<thead>';
detailsHtml += '<tr>';
detailsHtml += '<th>Produk</th>';
detailsHtml += '<th>Diminta</th>';
detailsHtml += '<th>Disetujui</th>';
detailsHtml += '<th>Stock Tersedia</th>';
detailsHtml += '</tr>';
detailsHtml += '</thead>';
detailsHtml += '<tbody>';
response.details.forEach(function(detail, index) {
detailsHtml += '<tr>';
detailsHtml += '<td>' + detail.product.name + '</td>';
detailsHtml += '<td>' + parseFloat(detail.quantity_requested).toLocaleString() + '</td>';
detailsHtml += '<td>';
detailsHtml += '<input type="number" name="details[' + detail.id + '][quantity_approved]" ';
detailsHtml += 'class="form-control form-control-sm" ';
detailsHtml += 'value="' + detail.quantity_requested + '" ';
detailsHtml += 'min="0" max="' + Math.min(detail.quantity_requested, detail.available_stock) + '" ';
detailsHtml += 'step="0.01" required>';
detailsHtml += '</td>';
detailsHtml += '<td>' + parseFloat(detail.available_stock).toLocaleString() + '</td>';
detailsHtml += '</tr>';
});
detailsHtml += '</tbody>';
detailsHtml += '</table>';
detailsHtml += '</div>';
$('#mutation-details' + mutationId).html(detailsHtml);
},
error: function() {
$('#mutation-details' + mutationId).html('<div class="alert alert-danger">Gagal memuat detail produk</div>');
}
});
$('#approveModal' + mutationId).modal('show');
});
// Auto-calculate approved quantity based on available stock
$(document).on('input', 'input[name*="quantity_approved"]', function() {
var maxValue = parseFloat($(this).attr('max'));
var currentValue = parseFloat($(this).val());
if (maxValue && currentValue > maxValue) {
$(this).val(maxValue);
$(this).addClass('is-invalid');
if (!$(this).siblings('.invalid-feedback').length) {
$(this).after('<div class="invalid-feedback">Jumlah melebihi stock yang tersedia</div>');
}
} else {
$(this).removeClass('is-invalid');
$(this).siblings('.invalid-feedback').remove();
}
});
});
</script>
@endsection

View File

@@ -1,19 +0,0 @@
<div class="d-flex">
<button type="button" class="btn btn-success btn-sm mr-2"
onclick="showAdjustStockModal({{ $stock->id }}, 'add')"
title="Tambah Stok">
<i class="la la-plus"></i>
</button>
<button type="button" class="btn btn-warning btn-sm mr-2"
onclick="showAdjustStockModal({{ $stock->id }}, 'reduce')"
title="Kurangi Stok">
<i class="la la-minus"></i>
</button>
<button type="button" class="btn btn-info btn-sm"
onclick="showStockHistory({{ $stock->id }})"
title="Lihat Riwayat">
<i class="la la-history"></i>
</button>
</div>

View File

@@ -1,234 +0,0 @@
@extends('layouts.backapp')
@section('content')
<div class="kt-portlet kt-portlet--mobile">
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-box"></i>
</span>
<h3 class="kt-portlet__head-title">Manajemen Stok</h3>
</div>
<div class="kt-portlet__head-toolbar">
<div class="kt-portlet__head-actions">
<a href="{{ route('opnames.create') }}" class="btn btn-primary">
<i class="la la-plus"></i> Buat Opname
</a>
</div>
</div>
</div>
<div class="kt-portlet__body">
<!-- Filter -->
<div class="row mb-4">
<div class="col-md-4">
<div class="form-group">
<label for="dealer_filter">Filter Dealer</label>
<select class="form-control" id="dealer_filter">
<option value="">Semua Dealer</option>
@foreach($dealers as $dealer)
<option value="{{ $dealer->id }}">{{ $dealer->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="product_filter">Filter Produk</label>
<select class="form-control" id="product_filter">
<option value="">Semua Produk</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
</div>
</div>
</div>
<!-- Tabel Stok -->
<div class="table-responsive">
<table class="table table-bordered table-hover" id="stocks-table">
<thead class="thead-light">
<tr>
<th>Dealer</th>
<th>Produk</th>
<th>Stok</th>
<th>Aksi</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
<!-- Modal Adjust Stock -->
<div class="modal fade" id="adjustStockModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Adjust Stok</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="adjustStockForm">
<div class="modal-body">
<input type="hidden" id="stock_id" name="stock_id">
<input type="hidden" id="adjust_type" name="type">
<div class="form-group">
<label>Jumlah</label>
<div class="input-group">
<input type="number" class="form-control" name="quantity"
step="0.01" min="0.01" required>
<div class="input-group-append">
<span class="input-group-text">pcs</span>
</div>
</div>
</div>
<div class="form-group">
<label>Catatan</label>
<textarea class="form-control" name="note" rows="3" required></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Simpan</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal Stock History -->
<div class="modal fade" id="stockHistoryModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Riwayat Stok</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="table-responsive">
<table class="table table-bordered" id="history-table">
<thead class="thead-light">
<tr>
<th>Tanggal</th>
<th>User</th>
<th>Perubahan</th>
<th>Stok Lama</th>
<th>Stok Baru</th>
<th>Catatan</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('javascripts')
<script>
$(document).ready(function() {
// Inisialisasi DataTable
var table = $('#stocks-table').DataTable({
processing: true,
serverSide: true,
ajax: {
url: '{{ route("stocks.index") }}',
data: function(d) {
d.dealer_id = $('#dealer_filter').val();
d.product_id = $('#product_filter').val();
}
},
columns: [
{data: 'dealer_name', name: 'dealer_name'},
{data: 'product_name', name: 'product_name'},
{data: 'quantity', name: 'quantity'},
{data: 'action', name: 'action', orderable: false, searchable: false}
]
});
// Filter change handler
$('#dealer_filter, #product_filter').change(function() {
table.ajax.reload();
});
// Show adjust stock modal
window.showAdjustStockModal = function(stockId, type) {
$('#stock_id').val(stockId);
$('#adjust_type').val(type);
$('#adjustStockModal').modal('show');
};
// Handle adjust stock form submit
$('#adjustStockForm').submit(function(e) {
e.preventDefault();
$.ajax({
url: '{{ route("stocks.adjust") }}',
method: 'POST',
data: {
_token: '{{ csrf_token() }}',
stock_id: $('#stock_id').val(),
type: $('#adjust_type').val(),
quantity: $('input[name="quantity"]').val(),
note: $('textarea[name="note"]').val()
},
success: function(response) {
if (response.success) {
$('#adjustStockModal').modal('hide');
table.ajax.reload();
toastr.success(response.message);
} else {
toastr.error(response.message);
}
},
error: function(xhr) {
toastr.error(xhr.responseJSON?.message || 'Terjadi kesalahan');
}
});
});
// Show stock history
window.showStockHistory = function(stockId) {
$.get('{{ route("stocks.history") }}', {
stock_id: stockId
})
.done(function(response) {
var tbody = $('#history-table tbody');
tbody.empty();
response.logs.forEach(function(log) {
tbody.append(`
<tr>
<td>${log.date}</td>
<td>${log.user}</td>
<td>${log.change}</td>
<td>${log.old_quantity}</td>
<td>${log.new_quantity}</td>
<td>${log.note}</td>
</tr>
`);
});
$('#stockHistoryModal').modal('show');
})
.fail(function(xhr) {
toastr.error('Gagal memuat riwayat stok');
});
};
// Reset form when modal is closed
$('#adjustStockModal').on('hidden.bs.modal', function() {
$('#adjustStockForm')[0].reset();
});
});
</script>
@endsection

View File

@@ -11,7 +11,7 @@ use App\Http\Controllers\WarehouseManagement\OpnamesController;
use App\Http\Controllers\WarehouseManagement\ProductCategoriesController; use App\Http\Controllers\WarehouseManagement\ProductCategoriesController;
use App\Http\Controllers\WarehouseManagement\ProductsController; use App\Http\Controllers\WarehouseManagement\ProductsController;
use App\Http\Controllers\WorkController; use App\Http\Controllers\WorkController;
use App\Http\Controllers\WarehouseManagement\StocksController; use App\Http\Controllers\WarehouseManagement\MutationsController;
use App\Models\Menu; use App\Models\Menu;
use App\Models\Privilege; use App\Models\Privilege;
use App\Models\Role; use App\Models\Role;
@@ -241,10 +241,19 @@ Route::group(['middleware' => 'auth'], function() {
Route::post('get-stock-data', 'getStockData')->name('opnames.get-stock-data'); Route::post('get-stock-data', 'getStockData')->name('opnames.get-stock-data');
}); });
Route::prefix('stocks')->controller(StocksController::class)->group(function () { Route::prefix('mutations')->name('mutations.')->controller(MutationsController::class)->group(function () {
Route::get('/', 'index')->name('stocks.index'); Route::get('/', 'index')->name('index');
Route::post('adjust', 'adjust')->name('stocks.adjust'); Route::get('create', 'create')->name('create');
Route::get('history', 'history')->name('stocks.history'); Route::post('/', 'store')->name('store');
Route::get('get-product-stock', 'getProductStock')->name('get-product-stock');
Route::get('{mutation}', 'show')->name('show');
Route::get('{mutation}/edit', 'edit')->name('edit');
Route::get('{mutation}/details', 'getDetails')->name('details');
Route::post('{mutation}/receive', 'receive')->name('receive');
Route::post('{mutation}/approve', 'approve')->name('approve');
Route::post('{mutation}/reject', 'reject')->name('reject');
Route::post('{mutation}/complete', 'complete')->name('complete');
Route::post('{mutation}/cancel', 'cancel')->name('cancel');
}); });
}); });
}); });

View File

@@ -33,6 +33,14 @@ mix.js("resources/js/app.js", "public/js")
"resources/js/warehouse_management/opnames/detail.js", "resources/js/warehouse_management/opnames/detail.js",
"public/js/warehouse_management/opnames" "public/js/warehouse_management/opnames"
) )
.js(
"resources/js/warehouse_management/mutations/index.js",
"public/js/warehouse_management/mutations"
)
.js(
"resources/js/warehouse_management/mutations/create.js",
"public/js/warehouse_management/mutations"
)
.sourceMaps(); .sourceMaps();
mix.browserSync({ mix.browserSync({