remove status pending and complete

This commit is contained in:
2025-06-15 02:29:26 +07:00
parent 3fb598ae4d
commit 9cfb566aee
13 changed files with 666 additions and 236 deletions

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class CleanMutationsData extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mutations:clean {--force : Force cleanup without confirmation}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clean mutations data to allow migration rollback';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*/
public function handle()
{
if (!$this->option('force')) {
if (!$this->confirm('This will delete ALL mutations data. Are you sure?')) {
$this->info('Operation cancelled.');
return 0;
}
}
try {
DB::beginTransaction();
// Delete mutations data in proper order (foreign key constraints)
$this->info('Cleaning mutations data...');
// 1. Delete stock logs related to mutations
if (Schema::hasTable('stock_logs')) {
$deleted = DB::table('stock_logs')
->where('source_type', 'App\\Models\\Mutation')
->delete();
$this->info("Deleted {$deleted} stock logs related to mutations");
}
// 2. Delete mutation details
if (Schema::hasTable('mutation_details')) {
$deleted = DB::table('mutation_details')->delete();
$this->info("Deleted {$deleted} mutation details");
}
// 3. Delete mutations
if (Schema::hasTable('mutations')) {
$deleted = DB::table('mutations')->delete();
$this->info("Deleted {$deleted} mutations");
}
// 4. Reset auto increment
if (Schema::hasTable('mutations')) {
DB::statement('ALTER TABLE mutations AUTO_INCREMENT = 1');
$this->info('Reset mutations auto increment');
}
if (Schema::hasTable('mutation_details')) {
DB::statement('ALTER TABLE mutation_details AUTO_INCREMENT = 1');
$this->info('Reset mutation_details auto increment');
}
DB::commit();
$this->info('✅ Mutations data cleaned successfully!');
$this->info('You can now rollback and re-run migrations.');
return 0;
} catch (\Exception $e) {
DB::rollback();
$this->error('❌ Error cleaning mutations data: ' . $e->getMessage());
return 1;
}
}
}

View File

@@ -4,23 +4,19 @@ 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::APPROVED => 'Disetujui & Stock Dipindahkan',
self::REJECTED => 'Ditolak',
self::COMPLETED => 'Selesai',
self::CANCELLED => 'Dibatalkan',
};
}
@@ -28,12 +24,10 @@ enum MutationStatus: string
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',
};
}
@@ -55,12 +49,10 @@ enum MutationStatus: string
public static function getOptions(): array
{
return [
self::PENDING->value => self::PENDING->label(),
self::SENT->value => self::SENT->label(),
self::RECEIVED->value => self::RECEIVED->label(),
self::APPROVED->value => self::APPROVED->label(),
self::REJECTED->value => self::REJECTED->label(),
self::COMPLETED->value => self::COMPLETED->label(),
self::CANCELLED->value => self::CANCELLED->label(),
];
}

View File

@@ -182,8 +182,18 @@ class MutationsController extends Controller
$mutation->receive(auth()->id(), $request->reception_notes);
DB::commit();
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil diterima dan menunggu persetujuan pengirim');
// Check user role and redirect accordingly
if (!auth()->user()->dealer_id) {
// Users without dealer_id are likely admin, redirect to mutations index
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil diterima dan siap untuk disetujui. Stock akan dipindahkan setelah disetujui.');
} else {
// Dealer users redirect back to transaction page
return redirect()->route('transaction')
->with('success', 'Mutasi berhasil diterima. Silakan setujui mutasi ini untuk memindahkan stock.')
->with('active_tab', 'penerimaan');
}
} catch (\Exception $e) {
DB::rollback();
@@ -202,11 +212,25 @@ class MutationsController extends Controller
}
try {
// Approve mutation (quantity_approved sudah diisi saat receive)
// Approve mutation (stock will move automatically)
$mutation->approve(auth()->id(), $request->approval_notes);
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil disetujui');
// Check user role and redirect accordingly
if (!auth()->user()->dealer_id) {
// Admin users redirect to mutations index
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil disetujui dan stock telah dipindahkan');
} else {
// Dealer users
if ($request->has('from_transaction_page') || str_contains($request->header('referer', ''), '/transaction')) {
return redirect()->route('transaction')
->with('success', 'Mutasi berhasil disetujui dan stock telah dipindahkan')
->with('active_tab', 'penerimaan');
} else {
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil disetujui dan stock telah dipindahkan');
}
}
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Gagal menyetujui mutasi: ' . $e->getMessage()]);
@@ -226,30 +250,29 @@ class MutationsController extends Controller
try {
$mutation->reject(auth()->id(), $request->rejection_reason);
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil ditolak');
// Check user role and redirect accordingly
if (!auth()->user()->dealer_id) {
// Admin users redirect to mutations index
return redirect()->route('mutations.index')
->with('success', 'Mutasi berhasil ditolak');
} else {
// Dealer users
if ($request->has('from_transaction_page') || str_contains($request->header('referer', ''), '/transaction')) {
return redirect()->route('transaction')
->with('success', 'Mutasi berhasil ditolak')
->with('active_tab', 'penerimaan');
} else {
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()]);
}
}
// Complete method removed - Stock moves automatically after approval
public function cancel(Request $request, Mutation $mutation)
{
@@ -294,9 +317,20 @@ class MutationsController extends Controller
{
$dealerId = $request->dealer_id;
// Get mutations that need action from this dealer:
// 1. 'sent' status where this dealer is the recipient (need to receive)
// 2. 'received' status where this dealer is the recipient (need to approve) - CORRECTED LOGIC
$data = Mutation::with(['fromDealer', 'toDealer', 'requestedBy.role'])
->where('to_dealer_id', $dealerId)
->where('status', 'sent')
->where(function($query) use ($dealerId) {
// Mutations sent to this dealer that need to be received
$query->where('to_dealer_id', $dealerId)
->where('status', 'sent');
// OR mutations received by this dealer that need approval from recipient
$query->orWhere(function($subQuery) use ($dealerId) {
$subQuery->where('to_dealer_id', $dealerId)
->where('status', 'received');
});
})
->select('mutations.*');
return DataTables::of($data)
@@ -307,6 +341,9 @@ class MutationsController extends Controller
->addColumn('from_dealer', function($row) {
return $row->fromDealer->name ?? '-';
})
->addColumn('to_dealer', function($row) {
return $row->toDealer->name ?? '-';
})
->addColumn('status', function($row) {
$statusColor = $row->status_color;
$statusLabel = $row->status_label;
@@ -330,10 +367,28 @@ class MutationsController extends Controller
->addColumn('created_at', function($row) {
return $row->created_at->format('d/m/Y H:i');
})
->addColumn('action', function($row) {
return '<button type="button" class="btn btn-info btn-sm btn-detail" onclick="showMutationDetail('.$row->id.')">
Detail
</button>';
->addColumn('action', function($row) use ($dealerId) {
$buttons = '';
if ($row->status->value === 'sent' && $row->to_dealer_id == $dealerId) {
// For sent mutations where current dealer is recipient - show detail button for receiving
$buttons .= '<button type="button" class="btn btn-info btn-sm btn-detail" onclick="showMutationDetail('.$row->id.')">
Detail & Terima
</button>';
} elseif ($row->status->value === 'received' && $row->to_dealer_id == $dealerId) {
// For received mutations where current dealer is recipient - show approve/reject buttons
$buttons .= '<button type="button" class="btn btn-info btn-sm btn-detail mr-1" onclick="showMutationDetail('.$row->id.')">
Detail
</button>';
$buttons .= '<button type="button" class="btn btn-success btn-sm btn-approve-mutation mr-1" data-id="'.$row->id.'">
Setujui
</button>';
$buttons .= '<button type="button" class="btn btn-danger btn-sm btn-reject-mutation" data-id="'.$row->id.'">
Tolak
</button>';
}
return $buttons;
})
->rawColumns(['status', 'action'])
->make(true);

View File

@@ -116,11 +116,6 @@ class Mutation extends Model
return $this->mutationDetails()->sum('quantity_approved');
}
public function canBeSent()
{
return $this->status === MutationStatus::PENDING;
}
public function canBeReceived()
{
return $this->status === MutationStatus::SENT;
@@ -131,28 +126,9 @@ class Mutation extends Model
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;
return $this->status === MutationStatus::SENT;
}
// Receive mutation by destination dealer
@@ -172,19 +148,37 @@ class Mutation extends Model
return $this;
}
// Approve mutation
// Approve mutation and move stock immediately
public function approve($userId, $approvalNotes = 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(),
'approval_notes' => $approvalNotes
]);
DB::beginTransaction();
try {
// Update status to approved first
$this->update([
'status' => MutationStatus::APPROVED,
'approved_by' => $userId,
'approved_at' => now(),
'approval_notes' => $approvalNotes
]);
// Immediately move stock after approval
foreach ($this->mutationDetails as $detail) {
// Process all details that have quantity_requested > 0
// because goods have been sent from source dealer
if ($detail->quantity_requested > 0) {
$this->processStockMovement($detail);
}
}
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw $e;
}
return $this;
}
@@ -223,32 +217,7 @@ class Mutation extends Model
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) {
// Proses semua detail yang memiliki quantity_requested > 0
// karena barang sudah dikirim dari dealer asal
if ($detail->quantity_requested > 0) {
$this->processStockMovement($detail);
}
}
$this->update(['status' => MutationStatus::COMPLETED]);
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw $e;
}
return $this;
}
// Complete method removed - Stock moves automatically after approval
private function processStockMovement(MutationDetail $detail)
{

View File

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