refactor: implement error handling and logging in various service methods to enhance transaction safety and improve code robustness

This commit is contained in:
Yoga Pangestu 2026-06-25 21:40:21 +07:00
parent d19bd8000a
commit 58ba7512a5
16 changed files with 1510 additions and 900 deletions

View File

@ -5,6 +5,8 @@
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class ProfileService class ProfileService
{ {
@ -13,23 +15,35 @@ class ProfileService
*/ */
public function update(array $validated, User $user): void public function update(array $validated, User $user): void
{ {
DB::transaction(function () use ($user, $validated): void { try {
$user->update([ DB::transaction(function () use ($user, $validated): void {
'email' => $validated['email'], $user->update([
'username' => $validated['username'], 'email' => $validated['email'],
'username' => $validated['username'],
]);
$user->profile()->updateOrCreate(
['user_id' => $user->id],
[
'full_name' => $validated['full_name'],
'phone_number' => $validated['phone_number'] ?? null,
'gender' => $validated['gender'] ?? null,
'birth_date' => $validated['birth_date'] ?? null,
'address' => $validated['address'] ?? null,
],
);
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui profil: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$user->profile()->updateOrCreate( throw ValidationException::withMessages([
['user_id' => $user->id], 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
[ ]);
'full_name' => $validated['full_name'], }
'phone_number' => $validated['phone_number'] ?? null,
'gender' => $validated['gender'] ?? null,
'birth_date' => $validated['birth_date'] ?? null,
'address' => $validated['address'] ?? null,
],
);
});
} }
/** /**

View File

@ -13,6 +13,7 @@
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class CashService class CashService
@ -71,27 +72,39 @@ public function paginateForIndex(CashAccount $cashAccount, array $tableQuery, st
*/ */
public function deposit(CashAccount $cashAccount, array $validated, User $user): CashTransaction public function deposit(CashAccount $cashAccount, array $validated, User $user): CashTransaction
{ {
$transaction = DB::transaction(function () use ($cashAccount, $validated, $user): CashTransaction { try {
$account = CashAccount::query()->lockForUpdate()->findOrFail($cashAccount->id); $transaction = DB::transaction(function () use ($cashAccount, $validated, $user): CashTransaction {
$amount = (int) $validated['amount']; $account = CashAccount::query()->lockForUpdate()->findOrFail($cashAccount->id);
$newBalance = $account->balance + $amount; $amount = (int) $validated['amount'];
$newBalance = $account->balance + $amount;
$account->balance = $newBalance; $account->balance = $newBalance;
$account->save(); $account->save();
$transaction = CashTransaction::create([ $transaction = CashTransaction::create([
'cash_account_id' => $account->id, 'cash_account_id' => $account->id,
'type' => CashTransactionType::DEPOSIT, 'type' => CashTransactionType::DEPOSIT,
'amount' => $amount, 'amount' => $amount,
'balance_after' => $newBalance, 'balance_after' => $newBalance,
'description' => $validated['description'], 'description' => $validated['description'],
'created_by_id' => $user->id, 'created_by_id' => $user->id,
]);
$this->syncPhotos($transaction, $validated);
return $transaction;
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal melakukan setoran kas: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$this->syncPhotos($transaction, $validated); throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
return $transaction; ]);
}); }
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'💰 Setoran Kas', '💰 Setoran Kas',
@ -108,34 +121,46 @@ public function deposit(CashAccount $cashAccount, array $validated, User $user):
*/ */
public function withdraw(CashAccount $cashAccount, array $validated, User $user): CashTransaction public function withdraw(CashAccount $cashAccount, array $validated, User $user): CashTransaction
{ {
$transaction = DB::transaction(function () use ($cashAccount, $validated, $user): CashTransaction { try {
$account = CashAccount::query()->lockForUpdate()->findOrFail($cashAccount->id); $transaction = DB::transaction(function () use ($cashAccount, $validated, $user): CashTransaction {
$amount = (int) $validated['amount']; $account = CashAccount::query()->lockForUpdate()->findOrFail($cashAccount->id);
$amount = (int) $validated['amount'];
if ($account->balance < $amount) { if ($account->balance < $amount) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'amount' => 'Saldo kas tidak mencukupi.', 'amount' => 'Saldo kas tidak mencukupi.',
]);
}
$newBalance = $account->balance - $amount;
$account->balance = $newBalance;
$account->save();
$transaction = CashTransaction::create([
'cash_account_id' => $account->id,
'type' => CashTransactionType::WITHDRAWAL,
'amount' => $amount,
'balance_after' => $newBalance,
'description' => $validated['description'],
'created_by_id' => $user->id,
]); ]);
}
$newBalance = $account->balance - $amount; $this->syncPhotos($transaction, $validated);
$account->balance = $newBalance; return $transaction;
$account->save(); });
} catch (ValidationException $e) {
$transaction = CashTransaction::create([ throw $e;
'cash_account_id' => $account->id, } catch (\Throwable $e) {
'type' => CashTransactionType::WITHDRAWAL, Log::error('Gagal melakukan tarik kas: '.$e->getMessage(), [
'amount' => $amount, 'trace' => $e->getTraceAsString(),
'balance_after' => $newBalance,
'description' => $validated['description'],
'created_by_id' => $user->id,
]); ]);
$this->syncPhotos($transaction, $validated); throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
return $transaction; ]);
}); }
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'🏦 Tarik Kas', '🏦 Tarik Kas',
@ -154,36 +179,48 @@ public function recordOutgoing(
User $user, User $user,
?CashAccount $cashAccount = null, ?CashAccount $cashAccount = null,
): CashTransaction { ): CashTransaction {
return DB::transaction(function () use ($reference, $amount, $description, $user, $cashAccount): CashTransaction { try {
$account = CashAccount::query()->lockForUpdate()->findOrFail( return DB::transaction(function () use ($reference, $amount, $description, $user, $cashAccount): CashTransaction {
($cashAccount ?? $this->getDefaultAccount())->id, $account = CashAccount::query()->lockForUpdate()->findOrFail(
); ($cashAccount ?? $this->getDefaultAccount())->id,
);
if ($account->balance < $amount) { if ($account->balance < $amount) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'amount' => 'Saldo kas tidak mencukupi.', 'amount' => 'Saldo kas tidak mencukupi.',
]);
}
$newBalance = $account->balance - $amount;
$account->balance = $newBalance;
$account->save();
$transaction = new CashTransaction([
'cash_account_id' => $account->id,
'type' => CashTransactionType::WITHDRAWAL,
'amount' => $amount,
'balance_after' => $newBalance,
'description' => $description,
'created_by_id' => $user->id,
]); ]);
}
$newBalance = $account->balance - $amount; $transaction->reference()->associate($reference);
$transaction->save();
$account->balance = $newBalance; return $transaction;
$account->save(); });
} catch (ValidationException $e) {
$transaction = new CashTransaction([ throw $e;
'cash_account_id' => $account->id, } catch (\Throwable $e) {
'type' => CashTransactionType::WITHDRAWAL, Log::error('Gagal mencatat transaksi keluar kas: '.$e->getMessage(), [
'amount' => $amount, 'trace' => $e->getTraceAsString(),
'balance_after' => $newBalance,
'description' => $description,
'created_by_id' => $user->id,
]); ]);
$transaction->reference()->associate($reference); throw ValidationException::withMessages([
$transaction->save(); 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
return $transaction; }
});
} }
public function recordIncoming( public function recordIncoming(
@ -193,30 +230,42 @@ public function recordIncoming(
User $user, User $user,
?CashAccount $cashAccount = null, ?CashAccount $cashAccount = null,
): CashTransaction { ): CashTransaction {
return DB::transaction(function () use ($reference, $amount, $description, $user, $cashAccount): CashTransaction { try {
$account = CashAccount::query()->lockForUpdate()->findOrFail( return DB::transaction(function () use ($reference, $amount, $description, $user, $cashAccount): CashTransaction {
($cashAccount ?? $this->getDefaultAccount())->id, $account = CashAccount::query()->lockForUpdate()->findOrFail(
); ($cashAccount ?? $this->getDefaultAccount())->id,
);
$newBalance = $account->balance + $amount; $newBalance = $account->balance + $amount;
$account->balance = $newBalance; $account->balance = $newBalance;
$account->save(); $account->save();
$transaction = new CashTransaction([ $transaction = new CashTransaction([
'cash_account_id' => $account->id, 'cash_account_id' => $account->id,
'type' => CashTransactionType::DEPOSIT, 'type' => CashTransactionType::DEPOSIT,
'amount' => $amount, 'amount' => $amount,
'balance_after' => $newBalance, 'balance_after' => $newBalance,
'description' => $description, 'description' => $description,
'created_by_id' => $user->id, 'created_by_id' => $user->id,
]);
$transaction->reference()->associate($reference);
$transaction->save();
return $transaction;
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal mencatat transaksi masuk kas: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$transaction->reference()->associate($reference); throw ValidationException::withMessages([
$transaction->save(); 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
return $transaction; }
});
} }
/** /**
@ -226,25 +275,37 @@ public function updateDeposit(CashTransaction $transaction, array $validated): v
{ {
$this->ensureEditable($transaction); $this->ensureEditable($transaction);
DB::transaction(function () use ($transaction, $validated): void { try {
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); DB::transaction(function () use ($transaction, $validated): void {
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id);
$transaction->amount = (int) $validated['amount']; $transaction->amount = (int) $validated['amount'];
$transaction->description = $validated['description']; $transaction->description = $validated['description'];
$transaction->save(); $transaction->save();
$this->syncPhotos($transaction, $validated); $this->syncPhotos($transaction, $validated);
$this->recalculateBalances($transaction->cashAccount); $this->recalculateBalances($transaction->cashAccount);
$account = $transaction->cashAccount->fresh(); $account = $transaction->cashAccount->fresh();
if ($account->balance < 0) { if ($account->balance < 0) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'amount' => 'Saldo kas tidak mencukupi.', 'amount' => 'Saldo kas tidak mencukupi.',
]); ]);
} }
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui setoran kas: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'✏️ Transaksi Kas Diperbarui', '✏️ Transaksi Kas Diperbarui',
@ -258,22 +319,34 @@ public function deleteTransaction(CashTransaction $transaction): void
{ {
$this->ensureEditable($transaction); $this->ensureEditable($transaction);
DB::transaction(function () use ($transaction): void { try {
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); DB::transaction(function () use ($transaction): void {
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id);
$account = $transaction->cashAccount; $account = $transaction->cashAccount;
$transaction->clearMediaCollection('photos'); $transaction->clearMediaCollection('photos');
$transaction->delete(); $transaction->delete();
$this->recalculateBalances($account); $this->recalculateBalances($account);
if ($account->fresh()->balance < 0) { if ($account->fresh()->balance < 0) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'transaction' => 'Saldo kas tidak mencukupi jika transaksi ini dihapus.', 'transaction' => 'Saldo kas tidak mencukupi jika transaksi ini dihapus.',
]); ]);
} }
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menghapus transaksi kas: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'🗑️ Transaksi Kas Dihapus', '🗑️ Transaksi Kas Dihapus',
@ -288,41 +361,65 @@ public function updateReferencedTransaction(
int $amount, int $amount,
string $description, string $description,
): void { ): void {
DB::transaction(function () use ($transaction, $amount, $description): void { try {
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); DB::transaction(function () use ($transaction, $amount, $description): void {
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id);
$transaction->amount = $amount; $transaction->amount = $amount;
$transaction->description = $description; $transaction->description = $description;
$transaction->save(); $transaction->save();
$this->recalculateBalances($transaction->cashAccount); $this->recalculateBalances($transaction->cashAccount);
$account = $transaction->cashAccount->fresh(); $account = $transaction->cashAccount->fresh();
if ($account->balance < 0) { if ($account->balance < 0) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'amount' => 'Saldo kas tidak mencukupi.', 'amount' => 'Saldo kas tidak mencukupi.',
]); ]);
} }
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui transaksi kas referensi: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
} }
public function deleteReferencedTransaction(CashTransaction $transaction): void public function deleteReferencedTransaction(CashTransaction $transaction): void
{ {
DB::transaction(function () use ($transaction): void { try {
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); DB::transaction(function () use ($transaction): void {
$account = $transaction->cashAccount; CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id);
$account = $transaction->cashAccount;
$transaction->delete(); $transaction->delete();
$this->recalculateBalances($account); $this->recalculateBalances($account);
if ($account->fresh()->balance < 0) { if ($account->fresh()->balance < 0) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'transaction' => 'Saldo kas tidak mencukupi jika transaksi ini dihapus.', 'transaction' => 'Saldo kas tidak mencukupi jika transaksi ini dihapus.',
]); ]);
} }
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menghapus transaksi kas referensi: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
} }
/** /**

View File

@ -11,6 +11,8 @@
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class EmployeeAdvanceService class EmployeeAdvanceService
{ {
@ -86,8 +88,16 @@ public function create(array $validated, User $user): void
'status' => EmployeeAdvanceStatus::PENDING, 'status' => EmployeeAdvanceStatus::PENDING,
]); ]);
}); });
} catch (\Throwable $e) { } catch (ValidationException $e) {
throw $e; throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membuat pengajuan kasbon: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
} }
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
@ -111,8 +121,16 @@ public function update(EmployeeAdvance $employeeAdvance, array $validated, User
'due_date' => $validated['due_date'], 'due_date' => $validated['due_date'],
]); ]);
}); });
} catch (\Throwable $e) { } catch (ValidationException $e) {
throw $e; throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui kasbon: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
} }
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
@ -163,8 +181,16 @@ public function approve(EmployeeAdvance $employeeAdvance, User $user): void
'verified_by_id' => $user->id, 'verified_by_id' => $user->id,
]); ]);
}); });
} catch (\Throwable $e) { } catch (ValidationException $e) {
throw $e; throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menyetujui kasbon: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
} }
$employeeAdvance->loadMissing('employee.user'); $employeeAdvance->loadMissing('employee.user');
@ -193,8 +219,16 @@ public function reject(EmployeeAdvance $employeeAdvance, string $reason, User $u
'rejected_by_id' => $user->id, 'rejected_by_id' => $user->id,
]); ]);
}); });
} catch (\Throwable $e) { } catch (ValidationException $e) {
throw $e; throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menolak kasbon: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
} }
$employeeAdvance->loadMissing('employee.user'); $employeeAdvance->loadMissing('employee.user');
@ -233,8 +267,16 @@ public function pay(EmployeeAdvance $employeeAdvance, User $user): void
'status' => EmployeeAdvanceStatus::PAID, 'status' => EmployeeAdvanceStatus::PAID,
]); ]);
}); });
} catch (\Throwable $e) { } catch (ValidationException $e) {
throw $e; throw $e;
} catch (\Throwable $e) {
Log::error('Gagal melunasi kasbon: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
} }
} }

View File

@ -10,6 +10,8 @@
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class ExpenseService class ExpenseService
{ {
@ -55,31 +57,43 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
*/ */
public function create(array $validated, User $user): void public function create(array $validated, User $user): void
{ {
$expense = DB::transaction(function () use ($validated, $user): Expense { try {
$amount = (int) $validated['amount']; $expense = DB::transaction(function () use ($validated, $user): Expense {
$description = $validated['description']; $amount = (int) $validated['amount'];
$description = $validated['description'];
$expense = Expense::create([ $expense = Expense::create([
'amount' => $amount, 'amount' => $amount,
'description' => $description, 'description' => $description,
'created_by_id' => $user->id, 'created_by_id' => $user->id,
]);
$cashTransaction = $this->cashService->recordOutgoing(
$expense,
$amount,
$description,
$user,
);
$expense->update([
'cash_transaction_id' => $cashTransaction->id,
]);
$this->syncPhotos($expense, $validated);
return $expense;
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membuat pengeluaran: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$cashTransaction = $this->cashService->recordOutgoing( throw ValidationException::withMessages([
$expense, 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
$amount,
$description,
$user,
);
$expense->update([
'cash_transaction_id' => $cashTransaction->id,
]); ]);
}
$this->syncPhotos($expense, $validated);
return $expense;
});
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'💸 Pengeluaran Baru', '💸 Pengeluaran Baru',
@ -94,25 +108,37 @@ public function create(array $validated, User $user): void
*/ */
public function update(Expense $expense, array $validated): void public function update(Expense $expense, array $validated): void
{ {
DB::transaction(function () use ($expense, $validated): void { try {
$amount = (int) $validated['amount']; DB::transaction(function () use ($expense, $validated): void {
$description = $validated['description']; $amount = (int) $validated['amount'];
$description = $validated['description'];
$expense->update([ $expense->update([
'amount' => $amount, 'amount' => $amount,
'description' => $description, 'description' => $description,
]);
if ($expense->cashTransaction) {
$this->cashService->updateReferencedTransaction(
$expense->cashTransaction,
$amount,
$description,
);
}
$this->syncPhotos($expense, $validated);
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui pengeluaran: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
if ($expense->cashTransaction) { throw ValidationException::withMessages([
$this->cashService->updateReferencedTransaction( 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
$expense->cashTransaction, ]);
$amount, }
$description,
);
}
$this->syncPhotos($expense, $validated);
});
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'✏️ Pengeluaran Diperbarui', '✏️ Pengeluaran Diperbarui',
@ -124,14 +150,26 @@ public function update(Expense $expense, array $validated): void
public function delete(Expense $expense): void public function delete(Expense $expense): void
{ {
DB::transaction(function () use ($expense): void { try {
if ($expense->cashTransaction) { DB::transaction(function () use ($expense): void {
$this->cashService->deleteReferencedTransaction($expense->cashTransaction); if ($expense->cashTransaction) {
} $this->cashService->deleteReferencedTransaction($expense->cashTransaction);
}
$expense->clearMediaCollection('photos'); $expense->clearMediaCollection('photos');
$expense->delete(); $expense->delete();
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menghapus pengeluaran: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'🗑️ Pengeluaran Dihapus', '🗑️ Pengeluaran Dihapus',

View File

@ -19,6 +19,8 @@
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class PayrollService class PayrollService
{ {
@ -115,55 +117,67 @@ public function openCurrentPeriod(?User $closedBy = null): PayrollPeriod
?? User::query()->whereHas('roles', fn (Builder $roleQuery) => $roleQuery->whereIn('name', [Role::DEVELOPER->value, Role::OWNER->value]))->first() ?? User::query()->whereHas('roles', fn (Builder $roleQuery) => $roleQuery->whereIn('name', [Role::DEVELOPER->value, Role::OWNER->value]))->first()
?? User::query()->first(); ?? User::query()->first();
$period = DB::transaction(function () use ($user): PayrollPeriod { try {
$now = now(); $period = DB::transaction(function () use ($user): PayrollPeriod {
$year = $now->year; $now = now();
$month = $now->month; $year = $now->year;
$month = $now->month;
$openPeriods = PayrollPeriod::query() $openPeriods = PayrollPeriod::query()
->where('status', PayrollPeriodStatus::OPEN) ->where('status', PayrollPeriodStatus::OPEN)
->get();
foreach ($openPeriods as $oldPeriod) {
$unpaidPayrolls = Payroll::query()
->where('payroll_period_id', $oldPeriod->id)
->where('status', PayrollStatus::UNPAID)
->get(); ->get();
foreach ($unpaidPayrolls as $payroll) { foreach ($openPeriods as $oldPeriod) {
if ($user) { $unpaidPayrolls = Payroll::query()
$this->pay($payroll, $user); ->where('payroll_period_id', $oldPeriod->id)
->where('status', PayrollStatus::UNPAID)
->get();
foreach ($unpaidPayrolls as $payroll) {
if ($user) {
$this->pay($payroll, $user);
}
} }
$oldPeriod->status = PayrollPeriodStatus::CLOSED;
$oldPeriod->closed_at = now();
$oldPeriod->closed_by_id = $user?->id;
$oldPeriod->save();
} }
$oldPeriod->status = PayrollPeriodStatus::CLOSED; $period = PayrollPeriod::query()
$oldPeriod->closed_at = now(); ->where('year', $year)
$oldPeriod->closed_by_id = $user?->id; ->where('month', $month)
$oldPeriod->save(); ->first();
}
$period = PayrollPeriod::query() if ($period === null) {
->where('year', $year) $period = PayrollPeriod::create([
->where('month', $month) 'year' => $year,
->first(); 'month' => $month,
'status' => PayrollPeriodStatus::OPEN,
]);
} elseif ($period->status === PayrollPeriodStatus::CLOSED) {
$period->status = PayrollPeriodStatus::OPEN;
$period->closed_at = null;
$period->closed_by_id = null;
$period->save();
}
if ($period === null) { $this->generatePayrollsForPeriod($period);
$period = PayrollPeriod::create([
'year' => $year,
'month' => $month,
'status' => PayrollPeriodStatus::OPEN,
]);
} elseif ($period->status === PayrollPeriodStatus::CLOSED) {
$period->status = PayrollPeriodStatus::OPEN;
$period->closed_at = null;
$period->closed_by_id = null;
$period->save();
}
$this->generatePayrollsForPeriod($period); return $period->fresh();
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membuka periode payroll: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
return $period->fresh(); throw ValidationException::withMessages([
}); 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
return $period; return $period;
} }
@ -204,18 +218,30 @@ public function generatePayrollsForPeriod(PayrollPeriod $period): void
public function addAdjustment(Payroll $payroll, array $validated, User $user): void public function addAdjustment(Payroll $payroll, array $validated, User $user): void
{ {
DB::transaction(function () use ($payroll, $validated, $user): void { try {
$payroll->adjustments()->create([ DB::transaction(function () use ($payroll, $validated, $user): void {
'type' => PayrollAdjustmentType::from($validated['type']), $payroll->adjustments()->create([
'amount' => (int) $validated['amount'], 'type' => PayrollAdjustmentType::from($validated['type']),
'description' => $validated['description'], 'amount' => (int) $validated['amount'],
'created_by_id' => $user->id, 'description' => $validated['description'],
'created_by_id' => $user->id,
]);
$payroll->load('adjustments');
$payroll->recalculateAmounts();
$payroll->save();
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menambahkan penyesuaian gaji: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$payroll->load('adjustments'); throw ValidationException::withMessages([
$payroll->recalculateAmounts(); 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
$payroll->save(); ]);
}); }
if ($payroll->employee?->user_id) { if ($payroll->employee?->user_id) {
$typeLabel = PayrollAdjustmentType::from($validated['type'])->label(); $typeLabel = PayrollAdjustmentType::from($validated['type'])->label();
@ -234,16 +260,28 @@ public function updateAdjustment(PayrollAdjustment $adjustment, array $validated
$payroll = $adjustment->payroll; $payroll = $adjustment->payroll;
$payroll->loadMissing(['payrollPeriod', 'employee.user']); $payroll->loadMissing(['payrollPeriod', 'employee.user']);
DB::transaction(function () use ($payroll, $adjustment, $validated): void { try {
$adjustment->type = PayrollAdjustmentType::from($validated['type']); DB::transaction(function () use ($payroll, $adjustment, $validated): void {
$adjustment->amount = (int) $validated['amount']; $adjustment->type = PayrollAdjustmentType::from($validated['type']);
$adjustment->description = $validated['description']; $adjustment->amount = (int) $validated['amount'];
$adjustment->save(); $adjustment->description = $validated['description'];
$adjustment->save();
$payroll->load('adjustments'); $payroll->load('adjustments');
$payroll->recalculateAmounts(); $payroll->recalculateAmounts();
$payroll->save(); $payroll->save();
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui penyesuaian gaji: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
if ($payroll->employee?->user_id) { if ($payroll->employee?->user_id) {
$typeLabel = PayrollAdjustmentType::from($validated['type'])->label(); $typeLabel = PayrollAdjustmentType::from($validated['type'])->label();
@ -262,13 +300,25 @@ public function deleteAdjustment(PayrollAdjustment $adjustment): void
$payroll = $adjustment->payroll; $payroll = $adjustment->payroll;
$payroll->loadMissing(['payrollPeriod', 'employee.user']); $payroll->loadMissing(['payrollPeriod', 'employee.user']);
DB::transaction(function () use ($payroll, $adjustment): void { try {
$adjustment->delete(); DB::transaction(function () use ($payroll, $adjustment): void {
$adjustment->delete();
$payroll->load('adjustments'); $payroll->load('adjustments');
$payroll->recalculateAmounts(); $payroll->recalculateAmounts();
$payroll->save(); $payroll->save();
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menghapus penyesuaian gaji: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
if ($payroll->employee?->user_id) { if ($payroll->employee?->user_id) {
$this->pushNotificationService->sendToUser( $this->pushNotificationService->sendToUser(
@ -285,7 +335,46 @@ public function pay(Payroll $payroll, User $user): void
$payroll->loadMissing(['payrollPeriod', 'employee.user.profile']); $payroll->loadMissing(['payrollPeriod', 'employee.user.profile']);
if ($payroll->total_amount <= 0) { if ($payroll->total_amount <= 0) {
try {
DB::transaction(function () use ($payroll, $user): void {
$payroll->status = PayrollStatus::PAID;
$payroll->paid_at = now();
$payroll->paid_by_id = $user->id;
$payroll->save();
$this->settleKasbonFromPayroll($payroll, $user);
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membayar gaji (total 0): '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
return;
}
try {
DB::transaction(function () use ($payroll, $user): void { DB::transaction(function () use ($payroll, $user): void {
$description = sprintf(
'Pembayaran gaji: %s (%s)',
$payroll->employeeName,
$payroll->payrollPeriod->period_label,
);
$cashTransaction = $this->cashService->recordOutgoing(
$payroll,
$payroll->total_amount,
$description,
$user,
);
$payroll->cash_transaction_id = $cashTransaction->id;
$payroll->status = PayrollStatus::PAID; $payroll->status = PayrollStatus::PAID;
$payroll->paid_at = now(); $payroll->paid_at = now();
$payroll->paid_by_id = $user->id; $payroll->paid_by_id = $user->id;
@ -293,33 +382,18 @@ public function pay(Payroll $payroll, User $user): void
$this->settleKasbonFromPayroll($payroll, $user); $this->settleKasbonFromPayroll($payroll, $user);
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membayar gaji: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
return; throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
} }
DB::transaction(function () use ($payroll, $user): void {
$description = sprintf(
'Pembayaran gaji: %s (%s)',
$payroll->employeeName,
$payroll->payrollPeriod->period_label,
);
$cashTransaction = $this->cashService->recordOutgoing(
$payroll,
$payroll->total_amount,
$description,
$user,
);
$payroll->cash_transaction_id = $cashTransaction->id;
$payroll->status = PayrollStatus::PAID;
$payroll->paid_at = now();
$payroll->paid_by_id = $user->id;
$payroll->save();
$this->settleKasbonFromPayroll($payroll, $user);
});
if ($payroll->employee?->user_id) { if ($payroll->employee?->user_id) {
$this->pushNotificationService->sendToUser( $this->pushNotificationService->sendToUser(
'💸 Gaji Dibayarkan', '💸 Gaji Dibayarkan',

View File

@ -10,6 +10,8 @@
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class EmployeeService class EmployeeService
{ {
@ -56,33 +58,45 @@ public function paginateForIndex(
*/ */
public function create(array $validated): void public function create(array $validated): void
{ {
DB::transaction(function () use ($validated): void { try {
$user = User::create([ DB::transaction(function () use ($validated): void {
'email' => $validated['email'], $user = User::create([
'username' => $validated['username'], 'email' => $validated['email'],
'password' => Hash::make(config('auth.password_default')), 'username' => $validated['username'],
]); 'password' => Hash::make(config('auth.password_default')),
UserProfile::create([
'user_id' => $user->id,
'full_name' => $validated['full_name'],
'phone_number' => $validated['phone_number'],
'gender' => $validated['gender'],
'birth_date' => $validated['birth_date'],
'address' => $validated['address'],
]);
if ($validated['role'] !== Role::OWNER->value) {
Employee::create([
'user_id' => $user->id,
'join_date' => $validated['join_date'],
'employment_status' => $validated['employment_status'],
'base_salary' => $validated['base_salary'],
]); ]);
}
$user->syncRoles([$validated['role']]); UserProfile::create([
}); 'user_id' => $user->id,
'full_name' => $validated['full_name'],
'phone_number' => $validated['phone_number'],
'gender' => $validated['gender'],
'birth_date' => $validated['birth_date'],
'address' => $validated['address'],
]);
if ($validated['role'] !== Role::OWNER->value) {
Employee::create([
'user_id' => $user->id,
'join_date' => $validated['join_date'],
'employment_status' => $validated['employment_status'],
'base_salary' => $validated['base_salary'],
]);
}
$user->syncRoles([$validated['role']]);
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membuat karyawan: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
} }
/** /**
@ -92,48 +106,60 @@ public function update(User $user, array $validated): void
{ {
$employee = $user->employee; $employee = $user->employee;
DB::transaction(function () use ($validated, $user, $employee): void { try {
$user->update([ DB::transaction(function () use ($validated, $user, $employee): void {
'email' => $validated['email'], $user->update([
'username' => $validated['username'], 'email' => $validated['email'],
'username' => $validated['username'],
]);
$user->profile()->updateOrCreate(
['user_id' => $user->id],
[
'full_name' => $validated['full_name'],
'phone_number' => $validated['phone_number'],
'gender' => $validated['gender'],
'birth_date' => $validated['birth_date'],
'address' => $validated['address'],
],
);
$hasEmployee = ! empty($validated['join_date']) && ! empty($validated['employment_status']) && ! empty($validated['base_salary']);
if ($hasEmployee) {
if ($employee) {
$employee->update([
'join_date' => $validated['join_date'],
'employment_status' => $validated['employment_status'],
'base_salary' => $validated['base_salary'],
]);
} else {
Employee::create([
'user_id' => $user->id,
'join_date' => $validated['join_date'],
'employment_status' => $validated['employment_status'],
'base_salary' => $validated['base_salary'],
]);
}
} else {
if ($employee) {
$employee->delete();
}
}
$user->syncRoles([$validated['role']]);
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui karyawan: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$user->profile()->updateOrCreate( throw ValidationException::withMessages([
['user_id' => $user->id], 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
[ ]);
'full_name' => $validated['full_name'], }
'phone_number' => $validated['phone_number'],
'gender' => $validated['gender'],
'birth_date' => $validated['birth_date'],
'address' => $validated['address'],
],
);
$hasEmployee = ! empty($validated['join_date']) && ! empty($validated['employment_status']) && ! empty($validated['base_salary']);
if ($hasEmployee) {
if ($employee) {
$employee->update([
'join_date' => $validated['join_date'],
'employment_status' => $validated['employment_status'],
'base_salary' => $validated['base_salary'],
]);
} else {
Employee::create([
'user_id' => $user->id,
'join_date' => $validated['join_date'],
'employment_status' => $validated['employment_status'],
'base_salary' => $validated['base_salary'],
]);
}
} else {
if ($employee) {
$employee->delete();
}
}
$user->syncRoles([$validated['role']]);
});
} }
public function toggleStatus(User $user, array $validated): void public function toggleStatus(User $user, array $validated): void
@ -158,11 +184,23 @@ public function resetPassword(User $user): void
public function delete(User $user): void public function delete(User $user): void
{ {
DB::transaction(function () use ($user): void { try {
$user->employee?->delete(); DB::transaction(function () use ($user): void {
$user->profile?->delete(); $user->employee?->delete();
$user->delete(); $user->profile?->delete();
}); $user->delete();
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menghapus karyawan: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
} }
private function applySorting(Builder $query, string $sort, string $direction): void private function applySorting(Builder $query, string $sort, string $direction): void

View File

@ -11,6 +11,7 @@
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class LeaveRequestService class LeaveRequestService
@ -128,13 +129,25 @@ public function delete(LeaveRequest $leaveRequest): void
public function approve(LeaveRequest $leaveRequest, User $user): void public function approve(LeaveRequest $leaveRequest, User $user): void
{ {
DB::transaction(function () use ($leaveRequest, $user): void { try {
$leaveRequest->update([ DB::transaction(function () use ($leaveRequest, $user): void {
'status' => LeaveRequestStatus::APPROVED, $leaveRequest->update([
'verified_at' => Carbon::now(), 'status' => LeaveRequestStatus::APPROVED,
'verified_by_id' => $user->id, 'verified_at' => Carbon::now(),
'verified_by_id' => $user->id,
]);
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menyetujui pengajuan cuti: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
});
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$leaveRequest->loadMissing('employee.user'); $leaveRequest->loadMissing('employee.user');
if ($leaveRequest->employee?->user_id) { if ($leaveRequest->employee?->user_id) {
@ -149,18 +162,30 @@ public function approve(LeaveRequest $leaveRequest, User $user): void
public function reject(LeaveRequest $leaveRequest, string $reason, User $user): void public function reject(LeaveRequest $leaveRequest, string $reason, User $user): void
{ {
DB::transaction(function () use ($leaveRequest, $user, $reason): void { try {
$leaveRequest->update([ DB::transaction(function () use ($leaveRequest, $user, $reason): void {
'status' => LeaveRequestStatus::REJECTED, $leaveRequest->update([
'verified_at' => Carbon::now(), 'status' => LeaveRequestStatus::REJECTED,
'verified_by_id' => $user->id, 'verified_at' => Carbon::now(),
'verified_by_id' => $user->id,
]);
$leaveRequest->rejection()->create([
'reason' => $reason,
'rejected_by_id' => $user->id,
]);
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menolak pengajuan cuti: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$leaveRequest->rejection()->create([ throw ValidationException::withMessages([
'reason' => $reason, 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
'rejected_by_id' => $user->id,
]); ]);
}); }
$leaveRequest->loadMissing('employee.user'); $leaveRequest->loadMissing('employee.user');
if ($leaveRequest->employee?->user_id) { if ($leaveRequest->employee?->user_id) {

View File

@ -20,6 +20,7 @@
use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class CuttingService class CuttingService
@ -422,55 +423,67 @@ public function removeDraftResult(User $user, ProductVariant $productVariant): v
*/ */
public function create(array $validated, User $user): Cutting public function create(array $validated, User $user): Cutting
{ {
$cutting = DB::transaction(function () use ($validated, $user): Cutting { try {
/** @var EloquentCollection<int, CuttingMaterial> $draftMaterials */ $cutting = DB::transaction(function () use ($validated, $user): Cutting {
$draftMaterials = $this->draftMaterialsQuery($user) /** @var EloquentCollection<int, CuttingMaterial> $draftMaterials */
->with('rawMaterialPrice.rawMaterial') $draftMaterials = $this->draftMaterialsQuery($user)
->lockForUpdate() ->with('rawMaterialPrice.rawMaterial')
->get(); ->lockForUpdate()
->get();
/** @var EloquentCollection<int, CuttingResult> $draftResults */ /** @var EloquentCollection<int, CuttingResult> $draftResults */
$draftResults = $this->draftResultsQuery($user) $draftResults = $this->draftResultsQuery($user)
->lockForUpdate() ->lockForUpdate()
->get(); ->get();
if ($draftMaterials->isEmpty()) { if ($draftMaterials->isEmpty()) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'materials' => 'Tambahkan minimal satu bahan baku.', 'materials' => 'Tambahkan minimal satu bahan baku.',
]);
}
if ($draftResults->isEmpty()) {
throw ValidationException::withMessages([
'results' => 'Tambahkan minimal satu hasil produk.',
]);
}
$cutting = Cutting::create([
'status' => CuttingStatus::IN_PROGRESS,
'description' => $validated['description'] ?? null,
'sewing_cost' => (int) ($validated['sewing_cost'] ?? 0),
'other_cost' => (int) ($validated['other_cost'] ?? 0),
'created_by_id' => $user->id,
]); ]);
}
if ($draftResults->isEmpty()) { foreach ($draftMaterials as $material) {
throw ValidationException::withMessages([ $material->cutting_id = $cutting->id;
'results' => 'Tambahkan minimal satu hasil produk.', $material->user_id = null;
]); $material->save();
} }
$cutting = Cutting::create([ foreach ($draftResults as $result) {
'status' => CuttingStatus::IN_PROGRESS, $result->cutting_id = $cutting->id;
'description' => $validated['description'] ?? null, $result->user_id = null;
'sewing_cost' => (int) ($validated['sewing_cost'] ?? 0), $result->save();
'other_cost' => (int) ($validated['other_cost'] ?? 0), }
'created_by_id' => $user->id,
$cutting->load('materials.rawMaterialPrice.rawMaterial');
$this->deductMaterialStock($cutting);
return $cutting;
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membuat proses cutting: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
foreach ($draftMaterials as $material) { throw ValidationException::withMessages([
$material->cutting_id = $cutting->id; 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
$material->user_id = null; ]);
$material->save(); }
}
foreach ($draftResults as $result) {
$result->cutting_id = $cutting->id;
$result->user_id = null;
$result->save();
}
$cutting->load('materials.rawMaterialPrice.rawMaterial');
$this->deductMaterialStock($cutting);
return $cutting;
});
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'✂️ Proses Cutting Baru', '✂️ Proses Cutting Baru',
@ -493,43 +506,55 @@ public function update(Cutting $cutting, array $validated): void
]); ]);
} }
DB::transaction(function () use ($cutting, $validated): void { try {
$cutting->load(['materials.rawMaterialPrice.rawMaterial', 'results']); DB::transaction(function () use ($cutting, $validated): void {
$cutting->load(['materials.rawMaterialPrice.rawMaterial', 'results']);
if ($cutting->status === CuttingStatus::IN_PROGRESS) { if ($cutting->status === CuttingStatus::IN_PROGRESS) {
$this->reverseTotalMaterialStock($cutting); $this->reverseTotalMaterialStock($cutting);
} elseif ($cutting->status === CuttingStatus::REJECTED) { } elseif ($cutting->status === CuttingStatus::REJECTED) {
$this->reverseMaterialStock($cutting); $this->reverseMaterialStock($cutting);
} }
$cutting->materials()->delete(); $cutting->materials()->delete();
$cutting->results()->delete(); $cutting->results()->delete();
$materials = $this->buildMaterials($validated['materials']); $materials = $this->buildMaterials($validated['materials']);
$results = $this->buildResults($validated['results']); $results = $this->buildResults($validated['results']);
$cutting->description = $validated['description'] ?? null; $cutting->description = $validated['description'] ?? null;
$cutting->sewing_cost = (int) ($validated['sewing_cost'] ?? 0); $cutting->sewing_cost = (int) ($validated['sewing_cost'] ?? 0);
$cutting->other_cost = (int) ($validated['other_cost'] ?? 0); $cutting->other_cost = (int) ($validated['other_cost'] ?? 0);
if ($cutting->status === CuttingStatus::REJECTED) { if ($cutting->status === CuttingStatus::REJECTED) {
$cutting->status = CuttingStatus::IN_PROGRESS; $cutting->status = CuttingStatus::IN_PROGRESS;
$cutting->rejection()?->delete(); $cutting->rejection()?->delete();
} }
$cutting->save(); $cutting->save();
foreach ($materials as $materialData) { foreach ($materials as $materialData) {
$cutting->materials()->create($materialData); $cutting->materials()->create($materialData);
} }
foreach ($results as $resultData) { foreach ($results as $resultData) {
$cutting->results()->create($resultData); $cutting->results()->create($resultData);
} }
if ($cutting->status === CuttingStatus::IN_PROGRESS) { if ($cutting->status === CuttingStatus::IN_PROGRESS) {
$cutting->load('materials.rawMaterialPrice.rawMaterial'); $cutting->load('materials.rawMaterialPrice.rawMaterial');
$this->deductMaterialStock($cutting); $this->deductMaterialStock($cutting);
} }
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui proses cutting: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$description = $cutting->description ?? '-'; $description = $cutting->description ?? '-';
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
@ -550,19 +575,31 @@ public function delete(Cutting $cutting): void
$description = $cutting->description ?? '-'; $description = $cutting->description ?? '-';
DB::transaction(function () use ($cutting): void { try {
$cutting->load(['materials.rawMaterialPrice.rawMaterial']); DB::transaction(function () use ($cutting): void {
$cutting->load(['materials.rawMaterialPrice.rawMaterial']);
if ($cutting->status === CuttingStatus::IN_PROGRESS) { if ($cutting->status === CuttingStatus::IN_PROGRESS) {
$this->reverseTotalMaterialStock($cutting); $this->reverseTotalMaterialStock($cutting);
} elseif ($cutting->status === CuttingStatus::REJECTED) { } elseif ($cutting->status === CuttingStatus::REJECTED) {
$this->reverseMaterialStock($cutting); $this->reverseMaterialStock($cutting);
} }
$cutting->materials()->delete(); $cutting->materials()->delete();
$cutting->results()->delete(); $cutting->results()->delete();
$cutting->delete(); $cutting->delete();
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menghapus proses cutting: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'🗑️ Proses Cutting Dihapus', '🗑️ Proses Cutting Dihapus',
@ -587,48 +624,60 @@ public function transitionStatus(
]); ]);
} }
DB::transaction(function () use ($cutting, $status, $verificationNote, $results, $resultPrices, $user, $reason): void { try {
$cutting->load(['materials.rawMaterialPrice', 'results']); DB::transaction(function () use ($cutting, $status, $verificationNote, $results, $resultPrices, $user, $reason): void {
$cutting->load(['materials.rawMaterialPrice', 'results']);
if ($status === CuttingStatus::COMPLETED) { if ($status === CuttingStatus::COMPLETED) {
$cutting->total_material_cost = $this->calculateTotalMaterialCost($cutting); $cutting->total_material_cost = $this->calculateTotalMaterialCost($cutting);
$cutting->cost_per_unit = $this->calculateCostPerUnit($cutting); $cutting->cost_per_unit = $this->calculateCostPerUnit($cutting);
} }
if ($status === CuttingStatus::IN_PROGRESS) { if ($status === CuttingStatus::IN_PROGRESS) {
$cutting->rejection()?->delete(); $cutting->rejection()?->delete();
} }
if ($status === CuttingStatus::REJECTED) { if ($status === CuttingStatus::REJECTED) {
$this->storeRejection($cutting, $reason, $user); $this->storeRejection($cutting, $reason, $user);
} }
if ($status === CuttingStatus::VERIFIED) { if ($status === CuttingStatus::VERIFIED) {
if ($results !== null) { if ($results !== null) {
foreach ($results as $item) { foreach ($results as $item) {
$cutting->results() $cutting->results()
->where('product_variant_id', $item['product_variant_id']) ->where('product_variant_id', $item['product_variant_id'])
->update([ ->update([
'warehouse_stock' => $item['warehouse_stock'], 'warehouse_stock' => $item['warehouse_stock'],
'cutting_reject' => $item['cutting_reject'], 'cutting_reject' => $item['cutting_reject'],
]); ]);
}
$cutting->load('results');
} }
$cutting->load('results'); $this->applyProductStockOnVerify($cutting);
} $this->storeResultPrices($cutting, $resultPrices ?? []);
$this->applyProductStockOnVerify($cutting);
$this->storeResultPrices($cutting, $resultPrices ?? []);
if ($verificationNote !== null && trim($verificationNote) !== '') { if ($verificationNote !== null && trim($verificationNote) !== '') {
$cutting->rejection()->create([ $cutting->rejection()->create([
'reason' => trim($verificationNote), 'reason' => trim($verificationNote),
'rejected_by_id' => $user->id, 'rejected_by_id' => $user->id,
]); ]);
}
} }
}
$cutting->status = $status; $cutting->status = $status;
$cutting->save(); $cutting->save();
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal mengubah status proses cutting: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$description = $cutting->description ?? '-'; $description = $cutting->description ?? '-';
$message = match ($status) { $message = match ($status) {

View File

@ -23,6 +23,7 @@
use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class OrderService class OrderService
@ -397,87 +398,99 @@ public function resyncDraftPrices(User $user, string $priceTypeValue): array
*/ */
public function create(array $validated, User $user): Order public function create(array $validated, User $user): Order
{ {
$order = DB::transaction(function () use ($validated, $user): Order { try {
$priceType = $this->resolvePriceType($validated['channel'], $validated['price_type']); $order = DB::transaction(function () use ($validated, $user): Order {
$priceType = $this->resolvePriceType($validated['channel'], $validated['price_type']);
/** @var EloquentCollection<int, OrderItem> $draftItems */ /** @var EloquentCollection<int, OrderItem> $draftItems */
$draftItems = $this->draftItemsQuery($user) $draftItems = $this->draftItemsQuery($user)
->lockForUpdate() ->lockForUpdate()
->get(); ->get();
if ($draftItems->isEmpty()) { if ($draftItems->isEmpty()) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'items' => 'Tambahkan minimal satu produk ke keranjang.', 'items' => 'Tambahkan minimal satu produk ke keranjang.',
]);
}
$this->applyDraftPrices($draftItems, $priceType);
$subtotal = $draftItems->sum('subtotal');
$discount = (int) ($validated['discount'] ?? 0);
$shippingCost = (int) ($validated['shipping_cost'] ?? 0);
$totalAmount = max($subtotal - $discount + $shippingCost, 0);
$channel = OrderChannel::from($validated['channel']);
$order = Order::create([
'customer_id' => $validated['customer_id'] ?? null,
'marketing_id' => $validated['marketing_id'] ?? null,
'channel' => $channel,
'price_type' => $priceType,
'payment_type' => PaymentType::from($validated['payment_type']),
'is_affiliate' => $validated['is_affiliate'] ?? false,
'status' => OrderStatus::PENDING,
'tiktok_order_id' => $validated['tiktok_order_id'] ?? null,
'shopee_order_id' => $validated['shopee_order_id'] ?? null,
'created_by_id' => $user->id,
'subtotal' => $subtotal,
'discount' => $discount,
'shipping_cost' => $shippingCost,
'marketplace_settings_snapshot' => $this->marketplaceService->buildOrderSnapshot(
$channel,
$totalAmount,
$this->lineItemsForSnapshot($draftItems),
$validated['is_affiliate'] ?? false,
),
'total_amount' => $totalAmount,
'notes' => $validated['notes'] ?? null,
]); ]);
}
$this->applyDraftPrices($draftItems, $priceType); if ($order->payment_type === PaymentType::CASH) {
$cashTransaction = $this->cashService->recordIncoming(
$order,
$totalAmount,
"Pembayaran pesanan {$order->order_number}",
$user,
);
$subtotal = $draftItems->sum('subtotal'); $order->cash_transaction_id = $cashTransaction->id;
$discount = (int) ($validated['discount'] ?? 0); $order->save();
$shippingCost = (int) ($validated['shipping_cost'] ?? 0); }
$totalAmount = max($subtotal - $discount + $shippingCost, 0);
$channel = OrderChannel::from($validated['channel']);
$order = Order::create([ foreach ($draftItems as $item) {
'customer_id' => $validated['customer_id'] ?? null, $variant = ProductVariant::query()->with('product')->lockForUpdate()->find($item->product_variant_id);
'marketing_id' => $validated['marketing_id'] ?? null, if ($variant === null) {
'channel' => $channel, throw ValidationException::withMessages([
'price_type' => $priceType, 'items' => 'Varian produk tidak ditemukan.',
'payment_type' => PaymentType::from($validated['payment_type']), ]);
'is_affiliate' => $validated['is_affiliate'] ?? false, }
'status' => OrderStatus::PENDING,
'tiktok_order_id' => $validated['tiktok_order_id'] ?? null, $stockQuality = $item->stock_quality ?? ProductStockQuality::GOOD;
'shopee_order_id' => $validated['shopee_order_id'] ?? null, $availableStock = $this->availableStock($variant, $stockQuality);
'created_by_id' => $user->id,
'subtotal' => $subtotal, if ($availableStock < $item->quantity) {
'discount' => $discount, throw ValidationException::withMessages([
'shipping_cost' => $shippingCost, 'items' => "Stok {$stockQuality->label()} produk {$variant->product->name} ({$variant->name}) tidak mencukupi. Stok saat ini: {$availableStock} pcs.",
'marketplace_settings_snapshot' => $this->marketplaceService->buildOrderSnapshot( ]);
$channel, }
$totalAmount, $item->order_id = $order->id;
$this->lineItemsForSnapshot($draftItems), $item->save();
$validated['is_affiliate'] ?? false, $this->decrementStock($item);
), }
'total_amount' => $totalAmount,
'notes' => $validated['notes'] ?? null, return $order;
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membuat pesanan: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
if ($order->payment_type === PaymentType::CASH) { throw ValidationException::withMessages([
$cashTransaction = $this->cashService->recordIncoming( 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
$order, ]);
$totalAmount, }
"Pembayaran pesanan {$order->order_number}",
$user,
);
$order->cash_transaction_id = $cashTransaction->id;
$order->save();
}
foreach ($draftItems as $item) {
$variant = ProductVariant::query()->with('product')->lockForUpdate()->find($item->product_variant_id);
if ($variant === null) {
throw ValidationException::withMessages([
'items' => 'Varian produk tidak ditemukan.',
]);
}
$stockQuality = $item->stock_quality ?? ProductStockQuality::GOOD;
$availableStock = $this->availableStock($variant, $stockQuality);
if ($availableStock < $item->quantity) {
throw ValidationException::withMessages([
'items' => "Stok {$stockQuality->label()} produk {$variant->product->name} ({$variant->name}) tidak mencukupi. Stok saat ini: {$availableStock} pcs.",
]);
}
$item->order_id = $order->id;
$item->save();
$this->decrementStock($item);
}
return $order;
});
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'📦 Pesanan Baru', '📦 Pesanan Baru',
@ -500,64 +513,76 @@ public function update(Order $order, array $validated): void
]); ]);
} }
DB::transaction(function () use ($order, $validated): void { try {
$order->load('items'); DB::transaction(function () use ($order, $validated): void {
$order->load('items');
foreach ($order->items as $item) { foreach ($order->items as $item) {
$this->incrementStock($item); $this->incrementStock($item);
}
$order->items()->delete();
$priceType = $this->resolvePriceType($validated['channel'], $validated['price_type']);
$lineItems = $this->buildLineItems($validated['items'], $priceType);
$subtotal = array_sum(array_column($lineItems, 'subtotal'));
$discount = (int) ($validated['discount'] ?? 0);
$shippingCost = (int) ($validated['shipping_cost'] ?? 0);
$totalAmount = max($subtotal - $discount + $shippingCost, 0);
$channel = OrderChannel::from($validated['channel']);
$order->customer_id = $validated['customer_id'] ?? null;
$order->marketing_id = $validated['marketing_id'] ?? null;
$order->channel = $channel;
$order->price_type = $priceType;
$order->payment_type = PaymentType::from($validated['payment_type']);
$order->is_affiliate = $validated['is_affiliate'] ?? false;
$order->tiktok_order_id = $validated['tiktok_order_id'] ?? null;
$order->shopee_order_id = $validated['shopee_order_id'] ?? null;
$order->subtotal = $subtotal;
$order->discount = $discount;
$order->shipping_cost = $shippingCost;
$order->marketplace_settings_snapshot = $this->marketplaceService->buildOrderSnapshot(
$channel,
$totalAmount,
$this->lineItemsForSnapshot($lineItems),
$validated['is_affiliate'] ?? false,
);
$order->total_amount = $totalAmount;
$order->notes = $validated['notes'] ?? null;
$order->save();
foreach ($lineItems as $itemData) {
$variant = ProductVariant::query()->with('product')->lockForUpdate()->find($itemData['product_variant_id']);
if ($variant === null) {
throw ValidationException::withMessages([
'items' => 'Varian produk tidak ditemukan.',
]);
} }
$stockQuality = ProductStockQuality::from($itemData['stock_quality']); $order->items()->delete();
$availableStock = $this->availableStock($variant, $stockQuality);
if ($availableStock < $itemData['quantity']) { $priceType = $this->resolvePriceType($validated['channel'], $validated['price_type']);
throw ValidationException::withMessages([ $lineItems = $this->buildLineItems($validated['items'], $priceType);
'items' => "Stok {$stockQuality->label()} produk {$variant->product->name} ({$variant->name}) tidak mencukupi. Stok saat ini: {$availableStock} pcs.", $subtotal = array_sum(array_column($lineItems, 'subtotal'));
]); $discount = (int) ($validated['discount'] ?? 0);
$shippingCost = (int) ($validated['shipping_cost'] ?? 0);
$totalAmount = max($subtotal - $discount + $shippingCost, 0);
$channel = OrderChannel::from($validated['channel']);
$order->customer_id = $validated['customer_id'] ?? null;
$order->marketing_id = $validated['marketing_id'] ?? null;
$order->channel = $channel;
$order->price_type = $priceType;
$order->payment_type = PaymentType::from($validated['payment_type']);
$order->is_affiliate = $validated['is_affiliate'] ?? false;
$order->tiktok_order_id = $validated['tiktok_order_id'] ?? null;
$order->shopee_order_id = $validated['shopee_order_id'] ?? null;
$order->subtotal = $subtotal;
$order->discount = $discount;
$order->shipping_cost = $shippingCost;
$order->marketplace_settings_snapshot = $this->marketplaceService->buildOrderSnapshot(
$channel,
$totalAmount,
$this->lineItemsForSnapshot($lineItems),
$validated['is_affiliate'] ?? false,
);
$order->total_amount = $totalAmount;
$order->notes = $validated['notes'] ?? null;
$order->save();
foreach ($lineItems as $itemData) {
$variant = ProductVariant::query()->with('product')->lockForUpdate()->find($itemData['product_variant_id']);
if ($variant === null) {
throw ValidationException::withMessages([
'items' => 'Varian produk tidak ditemukan.',
]);
}
$stockQuality = ProductStockQuality::from($itemData['stock_quality']);
$availableStock = $this->availableStock($variant, $stockQuality);
if ($availableStock < $itemData['quantity']) {
throw ValidationException::withMessages([
'items' => "Stok {$stockQuality->label()} produk {$variant->product->name} ({$variant->name}) tidak mencukupi. Stok saat ini: {$availableStock} pcs.",
]);
}
$orderItem = $order->items()->create($itemData);
$this->decrementStock($orderItem);
} }
$orderItem = $order->items()->create($itemData); });
$this->decrementStock($orderItem); } catch (ValidationException $e) {
} throw $e;
}); } catch (\Throwable $e) {
Log::error('Gagal memperbarui pesanan: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'✏️ Pesanan Diperbarui', '✏️ Pesanan Diperbarui',
@ -572,22 +597,34 @@ public function delete(Order $order): void
$orderNumber = $order->order_number; $orderNumber = $order->order_number;
$totalAmount = $order->total_amount; $totalAmount = $order->total_amount;
DB::transaction(function () use ($order): void { try {
$order->load('items'); DB::transaction(function () use ($order): void {
$order->load('items');
if ($this->isEditable($order->status)) { if ($this->isEditable($order->status)) {
foreach ($order->items as $item) { foreach ($order->items as $item) {
$this->incrementStock($item); $this->incrementStock($item);
}
} }
}
if ($order->cashTransaction) { if ($order->cashTransaction) {
$this->cashService->deleteReferencedTransaction($order->cashTransaction); $this->cashService->deleteReferencedTransaction($order->cashTransaction);
} }
$order->items()->delete(); $order->items()->delete();
$order->delete(); $order->delete();
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menghapus pesanan: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'🗑️ Pesanan Dihapus', '🗑️ Pesanan Dihapus',
@ -605,23 +642,35 @@ public function transitionStatus(Order $order, OrderStatus $status): void
]); ]);
} }
DB::transaction(function () use ($order, $status): void { try {
if ($status === OrderStatus::CANCELLED) { DB::transaction(function () use ($order, $status): void {
$order->load('items'); if ($status === OrderStatus::CANCELLED) {
$order->load('items');
foreach ($order->items as $item) { foreach ($order->items as $item) {
$this->incrementStock($item); $this->incrementStock($item);
}
if ($order->cashTransaction) {
$this->cashService->deleteReferencedTransaction($order->cashTransaction);
$order->cash_transaction_id = null;
}
} }
if ($order->cashTransaction) { $order->status = $status;
$this->cashService->deleteReferencedTransaction($order->cashTransaction); $order->save();
$order->cash_transaction_id = null; });
} } catch (ValidationException $e) {
} throw $e;
} catch (\Throwable $e) {
Log::error('Gagal mengubah status pesanan: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
$order->status = $status; throw ValidationException::withMessages([
$order->save(); 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
}); ]);
}
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'📦 Status Pesanan Diubah', '📦 Status Pesanan Diubah',

View File

@ -16,6 +16,7 @@
use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class PurchaseService class PurchaseService
@ -209,44 +210,56 @@ public function removeDraftItem(User $user, RawMaterialPrice $rawMaterialPrice):
*/ */
public function create(array $validated, User $user): Purchase public function create(array $validated, User $user): Purchase
{ {
$purchase = DB::transaction(function () use ($validated, $user): Purchase { try {
/** @var EloquentCollection<int, PurchaseItem> $draftItems */ $purchase = DB::transaction(function () use ($validated, $user): Purchase {
$draftItems = $this->draftItemsQuery($user) /** @var EloquentCollection<int, PurchaseItem> $draftItems */
->lockForUpdate() $draftItems = $this->draftItemsQuery($user)
->get(); ->lockForUpdate()
->get();
if ($draftItems->isEmpty()) { if ($draftItems->isEmpty()) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'items' => 'Tambahkan minimal satu bahan baku ke keranjang.', 'items' => 'Tambahkan minimal satu bahan baku ke keranjang.',
]);
}
$subtotal = $draftItems->sum('subtotal');
$discount = (int) ($validated['discount'] ?? 0);
$shippingCost = (int) ($validated['shipping_cost'] ?? 0);
$total = max($subtotal - $discount + $shippingCost, 0);
$purchase = Purchase::create([
'supplier_id' => $validated['supplier_id'],
'created_by_id' => $user->id,
'subtotal' => $subtotal,
'discount' => $discount,
'shipping_cost' => $shippingCost,
'total' => $total,
'notes' => $validated['notes'] ?? null,
]); ]);
}
$subtotal = $draftItems->sum('subtotal'); foreach ($draftItems as $item) {
$discount = (int) ($validated['discount'] ?? 0); $item->update([
$shippingCost = (int) ($validated['shipping_cost'] ?? 0); 'purchase_id' => $purchase->id,
$total = max($subtotal - $discount + $shippingCost, 0); ]);
$this->incrementStock($item);
}
$purchase = Purchase::create([ $this->syncPhotos($purchase, $validated);
'supplier_id' => $validated['supplier_id'],
'created_by_id' => $user->id, return $purchase;
'subtotal' => $subtotal, });
'discount' => $discount, } catch (ValidationException $e) {
'shipping_cost' => $shippingCost, throw $e;
'total' => $total, } catch (\Throwable $e) {
'notes' => $validated['notes'] ?? null, Log::error('Gagal membuat pembelian: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
foreach ($draftItems as $item) { throw ValidationException::withMessages([
$item->update([ 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
'purchase_id' => $purchase->id, ]);
]); }
$this->incrementStock($item);
}
$this->syncPhotos($purchase, $validated);
return $purchase;
});
$purchase->load('supplier'); $purchase->load('supplier');
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
@ -264,37 +277,49 @@ public function create(array $validated, User $user): Purchase
*/ */
public function update(Purchase $purchase, array $validated): void public function update(Purchase $purchase, array $validated): void
{ {
DB::transaction(function () use ($purchase, $validated): void { try {
$purchase->load('items'); DB::transaction(function () use ($purchase, $validated): void {
$purchase->load('items');
foreach ($purchase->items as $item) { foreach ($purchase->items as $item) {
$this->decrementStock($item); $this->decrementStock($item);
} }
$purchase->items()->delete(); $purchase->items()->delete();
$lineItems = $this->buildLineItems($validated['items']); $lineItems = $this->buildLineItems($validated['items']);
$subtotal = array_sum(array_column($lineItems, 'subtotal')); $subtotal = array_sum(array_column($lineItems, 'subtotal'));
$discount = (int) ($validated['discount'] ?? 0); $discount = (int) ($validated['discount'] ?? 0);
$shippingCost = (int) ($validated['shipping_cost'] ?? 0); $shippingCost = (int) ($validated['shipping_cost'] ?? 0);
$total = max($subtotal - $discount + $shippingCost, 0); $total = max($subtotal - $discount + $shippingCost, 0);
$purchase->update([ $purchase->update([
'supplier_id' => $validated['supplier_id'], 'supplier_id' => $validated['supplier_id'],
'subtotal' => $subtotal, 'subtotal' => $subtotal,
'discount' => $discount, 'discount' => $discount,
'shipping_cost' => $shippingCost, 'shipping_cost' => $shippingCost,
'total' => $total, 'total' => $total,
'notes' => $validated['notes'] ?? null, 'notes' => $validated['notes'] ?? null,
]);
foreach ($lineItems as $itemData) {
$purchaseItem = $purchase->items()->create($itemData);
$this->incrementStock($purchaseItem);
}
$this->syncPhotos($purchase, $validated);
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui pembelian: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
foreach ($lineItems as $itemData) { throw ValidationException::withMessages([
$purchaseItem = $purchase->items()->create($itemData); 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
$this->incrementStock($purchaseItem); ]);
} }
$this->syncPhotos($purchase, $validated);
});
$purchase->load('supplier'); $purchase->load('supplier');
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
@ -309,17 +334,29 @@ public function delete(Purchase $purchase): void
{ {
$supplierName = $purchase->supplier->name; $supplierName = $purchase->supplier->name;
DB::transaction(function () use ($purchase): void { try {
$purchase->load('items'); DB::transaction(function () use ($purchase): void {
$purchase->load('items');
foreach ($purchase->items as $item) { foreach ($purchase->items as $item) {
$this->decrementStock($item); $this->decrementStock($item);
} }
$purchase->clearMediaCollection('photos'); $purchase->clearMediaCollection('photos');
$purchase->items()->delete(); $purchase->items()->delete();
$purchase->delete(); $purchase->delete();
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menghapus pembelian: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
'🗑️ Belanja Dihapus', '🗑️ Belanja Dihapus',

View File

@ -11,6 +11,7 @@
use App\Services\System\PushNotificationService; use App\Services\System\PushNotificationService;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class StockService class StockService
@ -85,34 +86,46 @@ public function submitVerification(
]); ]);
} }
DB::transaction(function () use ($cutting, $user, $verificationNote, $results, $resultPrices): void { try {
$cutting->load(['materials.rawMaterialPrice', 'results']); DB::transaction(function () use ($cutting, $user, $verificationNote, $results, $resultPrices): void {
$cutting->load(['materials.rawMaterialPrice', 'results']);
if ($results !== null) { if ($results !== null) {
foreach ($results as $item) { foreach ($results as $item) {
$cutting->results() $cutting->results()
->where('product_variant_id', $item['product_variant_id']) ->where('product_variant_id', $item['product_variant_id'])
->update([ ->update([
'warehouse_stock' => $item['warehouse_stock'], 'warehouse_stock' => $item['warehouse_stock'],
'cutting_reject' => $item['cutting_reject'], 'cutting_reject' => $item['cutting_reject'],
]); ]);
}
$cutting->load('results');
} }
$cutting->load('results');
}
$this->storeResultPrices($cutting, $resultPrices ?? []); $this->storeResultPrices($cutting, $resultPrices ?? []);
if ($verificationNote !== null && trim($verificationNote) !== '') { if ($verificationNote !== null && trim($verificationNote) !== '') {
$cutting->rejection()->create([ $cutting->rejection()->create([
'reason' => trim($verificationNote), 'reason' => trim($verificationNote),
'rejected_by_id' => $user->id, 'rejected_by_id' => $user->id,
]); ]);
} }
$cutting->submitted_by_id = $user->id; $cutting->submitted_by_id = $user->id;
$cutting->status = CuttingStatus::PENDING_VERIFICATION; $cutting->status = CuttingStatus::PENDING_VERIFICATION;
$cutting->save(); $cutting->save();
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal mengajukan verifikasi stok: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$description = $cutting->description ?? '-'; $description = $cutting->description ?? '-';
@ -138,22 +151,34 @@ public function approveVerification(
]); ]);
} }
DB::transaction(function () use ($cutting, $user, $approvalNote): void { try {
$cutting->load(['materials.rawMaterialPrice', 'results', 'resultPrices']); DB::transaction(function () use ($cutting, $user, $approvalNote): void {
$cutting->load(['materials.rawMaterialPrice', 'results', 'resultPrices']);
$this->applyProductStockOnVerify($cutting); $this->applyProductStockOnVerify($cutting);
$this->applyResultPricesToProducts($cutting); $this->applyResultPricesToProducts($cutting);
if ($approvalNote !== null && trim($approvalNote) !== '') { if ($approvalNote !== null && trim($approvalNote) !== '') {
$cutting->rejection()->create([ $cutting->rejection()->create([
'reason' => trim($approvalNote), 'reason' => trim($approvalNote),
'rejected_by_id' => $user->id, 'rejected_by_id' => $user->id,
]); ]);
} }
$cutting->status = CuttingStatus::VERIFIED; $cutting->status = CuttingStatus::VERIFIED;
$cutting->save(); $cutting->save();
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menyetujui verifikasi stok: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
$description = $cutting->description ?? '-'; $description = $cutting->description ?? '-';
@ -179,15 +204,27 @@ public function rejectVerification(
]); ]);
} }
DB::transaction(function () use ($cutting, $user, $reason): void { try {
$cutting->rejection()->create([ DB::transaction(function () use ($cutting, $user, $reason): void {
'reason' => trim($reason), $cutting->rejection()->create([
'rejected_by_id' => $user->id, 'reason' => trim($reason),
'rejected_by_id' => $user->id,
]);
$cutting->status = CuttingStatus::COMPLETED;
$cutting->save();
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal menolak verifikasi stok: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$cutting->status = CuttingStatus::COMPLETED; throw ValidationException::withMessages([
$cutting->save(); 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
}); ]);
}
$description = $cutting->description ?? '-'; $description = $cutting->description ?? '-';
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(

View File

@ -9,6 +9,8 @@
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class ProductService class ProductService
{ {
@ -73,19 +75,31 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $ca
*/ */
public function create(array $validated): void public function create(array $validated): void
{ {
DB::transaction(function () use ($validated): void { try {
$product = Product::create([ DB::transaction(function () use ($validated): void {
'name' => $validated['name'], $product = Product::create([
'description' => $validated['description'] ?? null, 'name' => $validated['name'],
'is_active' => true, 'description' => $validated['description'] ?? null,
'is_active' => true,
]);
$product->categories()->sync($validated['category_ids']);
foreach ($validated['variants'] as $index => $variantData) {
$this->createVariant($product, $variantData, $index);
}
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membuat produk: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$product->categories()->sync($validated['category_ids']); throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
foreach ($validated['variants'] as $index => $variantData) { ]);
$this->createVariant($product, $variantData, $index); }
}
});
} }
/** /**
@ -93,43 +107,55 @@ public function create(array $validated): void
*/ */
public function update(Product $product, array $validated): void public function update(Product $product, array $validated): void
{ {
DB::transaction(function () use ($validated, $product): void { try {
$product->update([ DB::transaction(function () use ($validated, $product): void {
'name' => $validated['name'], $product->update([
'description' => $validated['description'] ?? null, 'name' => $validated['name'],
'description' => $validated['description'] ?? null,
]);
$product->categories()->sync($validated['category_ids']);
$submittedVariantIds = collect($validated['variants'])
->pluck('id')
->filter()
->map(fn ($id) => (int) $id)
->all();
$product->variants()
->whereNotIn('id', $submittedVariantIds)
->get()
->each(function (ProductVariant $variant): void {
$variant->clearMediaCollection('images');
$variant->delete();
});
foreach ($validated['variants'] as $index => $variantData) {
if (! empty($variantData['id'])) {
$variant = $product->variants()->findOrFail($variantData['id']);
$variant->update([
'name' => $variantData['name'],
'stock' => $variantData['stock'],
]);
$this->syncVariantImages($variant, $variantData, $index);
continue;
}
$this->createVariant($product, $variantData, $index);
}
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui produk: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$product->categories()->sync($validated['category_ids']); throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
$submittedVariantIds = collect($validated['variants']) ]);
->pluck('id') }
->filter()
->map(fn ($id) => (int) $id)
->all();
$product->variants()
->whereNotIn('id', $submittedVariantIds)
->get()
->each(function (ProductVariant $variant): void {
$variant->clearMediaCollection('images');
$variant->delete();
});
foreach ($validated['variants'] as $index => $variantData) {
if (! empty($variantData['id'])) {
$variant = $product->variants()->findOrFail($variantData['id']);
$variant->update([
'name' => $variantData['name'],
'stock' => $variantData['stock'],
]);
$this->syncVariantImages($variant, $variantData, $index);
continue;
}
$this->createVariant($product, $variantData, $index);
}
});
} }
public function toggleStatus(Product $product, array $validated): void public function toggleStatus(Product $product, array $validated): void
@ -141,14 +167,26 @@ public function toggleStatus(Product $product, array $validated): void
public function delete(Product $product): void public function delete(Product $product): void
{ {
DB::transaction(function () use ($product): void { try {
$product->variants()->each(function (ProductVariant $variant): void { DB::transaction(function () use ($product): void {
$variant->clearMediaCollection('images'); $product->variants()->each(function (ProductVariant $variant): void {
$variant->clearMediaCollection('images');
});
$product->variants()->delete();
$product->categories()->detach();
$product->delete();
}); });
$product->variants()->delete(); } catch (ValidationException $e) {
$product->categories()->detach(); throw $e;
$product->delete(); } catch (\Throwable $e) {
}); Log::error('Gagal menghapus produk: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
} }
private function applySorting(Builder $query, string $sort, string $direction): void private function applySorting(Builder $query, string $sort, string $direction): void

View File

@ -10,6 +10,8 @@
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class RawMaterialService class RawMaterialService
{ {
@ -82,16 +84,28 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $st
*/ */
public function create(array $validated): void public function create(array $validated): void
{ {
DB::transaction(function () use ($validated): void { try {
$rawMaterial = RawMaterial::create([ DB::transaction(function () use ($validated): void {
'name' => $validated['name'], $rawMaterial = RawMaterial::create([
'unit' => $validated['unit'], 'name' => $validated['name'],
'unit' => $validated['unit'],
]);
foreach ($validated['prices'] as $index => $priceData) {
$this->createPrice($rawMaterial, $priceData, $index);
}
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membuat bahan baku: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
foreach ($validated['prices'] as $index => $priceData) { throw ValidationException::withMessages([
$this->createPrice($rawMaterial, $priceData, $index); 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
} ]);
}); }
} }
/** /**
@ -99,41 +113,53 @@ public function create(array $validated): void
*/ */
public function update(RawMaterial $rawMaterial, array $validated): void public function update(RawMaterial $rawMaterial, array $validated): void
{ {
DB::transaction(function () use ($validated, $rawMaterial): void { try {
$rawMaterial->update([ DB::transaction(function () use ($validated, $rawMaterial): void {
'name' => $validated['name'], $rawMaterial->update([
'name' => $validated['name'],
]);
$submittedPriceIds = collect($validated['prices'])
->pluck('id')
->filter()
->map(fn ($id) => (int) $id)
->all();
$rawMaterial->prices()
->whereNotIn('id', $submittedPriceIds)
->get()
->each(function (RawMaterialPrice $price): void {
$price->clearMediaCollection('images');
$price->delete();
});
foreach ($validated['prices'] as $index => $priceData) {
if (! empty($priceData['id'])) {
$price = $rawMaterial->prices()->findOrFail($priceData['id']);
$price->update([
'variant' => $priceData['variant'],
'price' => $priceData['price'],
'stock' => $priceData['stock'],
]);
$this->syncPriceImages($price, $priceData, $index);
continue;
}
$this->createPrice($rawMaterial, $priceData, $index);
}
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui bahan baku: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
$submittedPriceIds = collect($validated['prices']) throw ValidationException::withMessages([
->pluck('id') 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
->filter() ]);
->map(fn ($id) => (int) $id) }
->all();
$rawMaterial->prices()
->whereNotIn('id', $submittedPriceIds)
->get()
->each(function (RawMaterialPrice $price): void {
$price->clearMediaCollection('images');
$price->delete();
});
foreach ($validated['prices'] as $index => $priceData) {
if (! empty($priceData['id'])) {
$price = $rawMaterial->prices()->findOrFail($priceData['id']);
$price->update([
'variant' => $priceData['variant'],
'price' => $priceData['price'],
'stock' => $priceData['stock'],
]);
$this->syncPriceImages($price, $priceData, $index);
continue;
}
$this->createPrice($rawMaterial, $priceData, $index);
}
});
} }
public function toggleStatus(RawMaterial $rawMaterial, array $validated): void public function toggleStatus(RawMaterial $rawMaterial, array $validated): void
@ -145,13 +171,25 @@ public function toggleStatus(RawMaterial $rawMaterial, array $validated): void
public function delete(RawMaterial $rawMaterial): void public function delete(RawMaterial $rawMaterial): void
{ {
DB::transaction(function () use ($rawMaterial): void { try {
$rawMaterial->prices()->each(function (RawMaterialPrice $price): void { DB::transaction(function () use ($rawMaterial): void {
$price->clearMediaCollection('images'); $rawMaterial->prices()->each(function (RawMaterialPrice $price): void {
$price->clearMediaCollection('images');
});
$rawMaterial->prices()->delete();
$rawMaterial->delete();
}); });
$rawMaterial->prices()->delete(); } catch (ValidationException $e) {
$rawMaterial->delete(); throw $e;
}); } catch (\Throwable $e) {
Log::error('Gagal menghapus bahan baku: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
} }
private function applySorting(Builder $query, string $sort, string $direction): void private function applySorting(Builder $query, string $sort, string $direction): void

View File

@ -6,7 +6,9 @@
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Role;
class RoleService class RoleService
@ -31,29 +33,53 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
public function create(array $validated): void public function create(array $validated): void
{ {
DB::transaction(function () use ($validated): void { try {
$role = Role::create([ DB::transaction(function () use ($validated): void {
'name' => Str::slug($validated['name']), $role = Role::create([
'guard_name' => 'web', 'name' => Str::slug($validated['name']),
'guard_name' => 'web',
]);
if (! empty($validated['permissions'])) {
$role->syncPermissions($validated['permissions']);
}
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal membuat role: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]); ]);
if (! empty($validated['permissions'])) { throw ValidationException::withMessages([
$role->syncPermissions($validated['permissions']); 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
} ]);
}); }
} }
public function update(Role $role, array $validated): void public function update(Role $role, array $validated): void
{ {
DB::transaction(function () use ($role, $validated): void { try {
$updateData = []; DB::transaction(function () use ($role, $validated): void {
if (! in_array($role->name, [EnumsRole::DEVELOPER->value, EnumsRole::OWNER->value], true)) { $updateData = [];
$updateData['name'] = Str::slug($validated['name']); if (! in_array($role->name, [EnumsRole::DEVELOPER->value, EnumsRole::OWNER->value], true)) {
} $updateData['name'] = Str::slug($validated['name']);
$role->update($updateData); }
$role->update($updateData);
$role->syncPermissions($validated['permissions'] ?? []); $role->syncPermissions($validated['permissions'] ?? []);
}); });
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
Log::error('Gagal memperbarui role: '.$e->getMessage(), [
'trace' => $e->getTraceAsString(),
]);
throw ValidationException::withMessages([
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
]);
}
} }
public function delete(Role $role): void public function delete(Role $role): void

View File

@ -70,29 +70,33 @@ public function run(): void
], ],
]; ];
DB::transaction(function () use ($products, $categories): void { try {
foreach ($products as $productData) { DB::transaction(function () use ($products, $categories): void {
$product = Product::factory()->create([ foreach ($products as $productData) {
'name' => $productData['name'], $product = Product::factory()->create([
'slug' => str()->slug($productData['name']), 'name' => $productData['name'],
'description' => $productData['description'], 'slug' => str()->slug($productData['name']),
'is_active' => true, 'description' => $productData['description'],
]); 'is_active' => true,
$product->categories()->sync(
collect($productData['category_slugs'])
->map(fn (string $slug) => $categories[$slug])
->all()
);
foreach ($productData['variants'] as $variantData) {
ProductVariant::factory()->create([
'product_id' => $product->id,
'name' => $variantData['name'],
'stock' => $variantData['stock'],
]); ]);
$product->categories()->sync(
collect($productData['category_slugs'])
->map(fn (string $slug) => $categories[$slug])
->all()
);
foreach ($productData['variants'] as $variantData) {
ProductVariant::factory()->create([
'product_id' => $product->id,
'name' => $variantData['name'],
'stock' => $variantData['stock'],
]);
}
} }
} });
}); } catch (\Throwable $e) {
throw $e;
}
} }
} }

View File

@ -42,23 +42,27 @@ public function run(): void
], ],
]; ];
DB::transaction(function () use ($rawMaterials): void { try {
foreach ($rawMaterials as $rawMaterialData) { DB::transaction(function () use ($rawMaterials): void {
$rawMaterial = RawMaterial::factory()->create([ foreach ($rawMaterials as $rawMaterialData) {
'name' => $rawMaterialData['name'], $rawMaterial = RawMaterial::factory()->create([
'unit' => $rawMaterialData['unit'], 'name' => $rawMaterialData['name'],
'is_active' => true, 'unit' => $rawMaterialData['unit'],
]); 'is_active' => true,
foreach ($rawMaterialData['prices'] as $priceData) {
RawMaterialPrice::factory()->create([
'raw_material_id' => $rawMaterial->id,
'variant' => $priceData['variant'],
'price' => $priceData['price'],
'stock' => $priceData['stock'],
]); ]);
foreach ($rawMaterialData['prices'] as $priceData) {
RawMaterialPrice::factory()->create([
'raw_material_id' => $rawMaterial->id,
'variant' => $priceData['variant'],
'price' => $priceData['price'],
'stock' => $priceData['stock'],
]);
}
} }
} });
}); } catch (\Throwable $e) {
throw $e;
}
} }
} }