feat: enhance service and controller logic for Cutting and Order management, implement best practices for method handling, and improve pagination defaults

This commit is contained in:
Yoga Pangestu 2026-06-28 16:23:57 +07:00
parent 7a1c4efe4a
commit b3790f2b2f
35 changed files with 317 additions and 684 deletions

View File

@ -284,6 +284,49 @@ ### Controller
} }
``` ```
### Service
- **Tugas Service:**
- Service menampung **semua logika bisnis** (query kompleks, transformasi data, transaksi, authorization bisnis).
- Controller hanya memanggil service dan mengembalikan response.
- **Urutan Method:**
1. `paginateForIndex` / method index/list
2. `findForEdit` / `findForShow` (jika ada)
3. `create` / `store`
4. `update`
5. `delete` / `destroy`
6. Method tambahan (custom action)
7. Method `private` di paling bawah
- **Docblock & Comment:**
- **Jangan** gunakan PHPDoc/docblock pada service.
- **Jangan** gunakan inline comment tipe `@var Model $variable`.
- **Pagination:**
- Default pagination untuk data table: **25** (`->paginate(25)`).
- **Update Model:**
- Gunakan `$model->update([...])`, **bukan** assign property lalu `$model->save()`.
- **Hindari:**
```php
$account->balance = $newBalance;
$account->save();
```
- **Gunakan:**
```php
$account->update(['balance' => $newBalance]);
```
- **Logic di Model/Enum, Bukan Wrapper di Service:**
- Method yang hanya meneruskan ke model/enum **wajib** ditempatkan di model/enum, bukan di service.
- **Contoh:** `isEditable()`, `transitionStatusMessage()`, `ensureEditable()` → enum/model.
- Service memanggil langsung: `$order->status->isEditable()`, `$status->transitionStatusMessage()`, `$order->ensureEditable()`.
- **Penamaan:**
- Variable, method, file **Bahasa Inggris**.
- Pesan error/flash ke user **Bahasa Indonesia**.
## Database & Migrasi ## Database & Migrasi
### Struktur File Migrasi ### Struktur File Migrasi

View File

@ -31,6 +31,17 @@ public function isEditable(): bool
return in_array($this, [self::IN_PROGRESS, self::REJECTED], true); return in_array($this, [self::IN_PROGRESS, self::REJECTED], true);
} }
public function transitionStatusMessage(): string
{
return match ($this) {
self::COMPLETED => 'Proses cutting berhasil diselesaikan. Menunggu verifikasi admin toko.',
self::VERIFIED => 'Proses cutting berhasil diverifikasi. Stok produk telah diperbarui.',
self::REJECTED => 'Proses cutting berhasil ditolak.',
self::IN_PROGRESS => 'Proses cutting dikembalikan ke proses.',
default => 'Status proses cutting berhasil diperbarui.',
};
}
public function canTransitionTo(self $status): bool public function canTransitionTo(self $status): bool
{ {
return match ($this) { return match ($this) {

View File

@ -29,6 +29,16 @@ public function isEditable(): bool
return in_array($this, [self::PENDING, self::PROCESSING], true); return in_array($this, [self::PENDING, self::PROCESSING], true);
} }
public function transitionStatusMessage(): string
{
return match ($this) {
self::PROCESSING => 'Pesanan berhasil dikirim.',
self::COMPLETED => 'Pesanan berhasil diselesaikan.',
self::CANCELLED => 'Pesanan berhasil dibatalkan.',
default => 'Status pesanan berhasil diperbarui.',
};
}
public function canTransitionTo(self $status): bool public function canTransitionTo(self $status): bool
{ {
return match ($this) { return match ($this) {

View File

@ -59,7 +59,7 @@ public function store(CuttingRequest $request): RedirectResponse
public function edit(Cutting $cutting): Response|RedirectResponse public function edit(Cutting $cutting): Response|RedirectResponse
{ {
try { try {
$this->cuttingService->ensureEditable($cutting); $cutting->ensureEditable();
} catch (ValidationException $exception) { } catch (ValidationException $exception) {
$this->flashError($exception->validator->errors()->first('status')); $this->flashError($exception->validator->errors()->first('status'));
@ -105,7 +105,7 @@ public function transitionStatus(CuttingStatusTransitionRequest $request, Cuttin
$request->validated('result_prices'), $request->validated('result_prices'),
); );
$this->flashSuccess($this->cuttingService->transitionStatusMessage($status)); $this->flashSuccess($status->transitionStatusMessage());
return redirect()->route('admin.manage.cuttings.index'); return redirect()->route('admin.manage.cuttings.index');
} }

View File

@ -77,7 +77,7 @@ public function show(Order $order): Response
public function edit(Order $order): Response|RedirectResponse public function edit(Order $order): Response|RedirectResponse
{ {
try { try {
$this->orderService->ensureEditable($order); $order->ensureEditable();
} catch (ValidationException $exception) { } catch (ValidationException $exception) {
$this->flashError($exception->validator->errors()->first('status')); $this->flashError($exception->validator->errors()->first('status'));
@ -119,7 +119,7 @@ public function transitionStatus(OrderStatusTransitionRequest $request, Order $o
$this->orderService->transitionStatus($order, $status); $this->orderService->transitionStatus($order, $status);
$this->flashSuccess($this->orderService->transitionStatusMessage($status)); $this->flashSuccess($status->transitionStatusMessage());
return redirect()->route('admin.manage.orders.index'); return redirect()->route('admin.manage.orders.index');
} }

View File

@ -15,6 +15,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Validation\ValidationException;
#[Guarded(['id'])] #[Guarded(['id'])]
#[Appends([ #[Appends([
@ -79,7 +80,17 @@ public function statusLabel(): Attribute
); );
} }
// 5. Relation // 5. Other Methods
public function ensureEditable(): void
{
if (! $this->status->isEditable()) {
throw ValidationException::withMessages([
'status' => 'Proses cutting tidak dapat diubah.',
]);
}
}
// 6. Relation
public function createdBy(): BelongsTo public function createdBy(): BelongsTo
{ {
return $this->belongsTo(User::class, 'created_by_id'); return $this->belongsTo(User::class, 'created_by_id');

View File

@ -18,6 +18,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Validation\ValidationException;
use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\HasMedia;
#[Guarded(['id'])] #[Guarded(['id'])]
@ -271,6 +272,15 @@ public static function mediaModuleName(): string
return 'order'; return 'order';
} }
public function ensureEditable(): void
{
if (! $this->status->isEditable()) {
throw ValidationException::withMessages([
'status' => 'Pesanan tidak dapat diubah.',
]);
}
}
public function registerMediaCollections(): void public function registerMediaCollections(): void
{ {
$this->addMediaCollection('photos'); $this->addMediaCollection('photos');

View File

@ -3,7 +3,6 @@
namespace App\Services\Account; namespace App\Services\Account;
use App\Models\User; use App\Models\User;
use App\Models\UserProfile;
use App\Services\Media\MediaService; use App\Services\Media\MediaService;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
@ -16,9 +15,6 @@ public function __construct(
private readonly MediaService $mediaService, private readonly MediaService $mediaService,
) {} ) {}
/**
* @param array<string, mixed> $validated
*/
public function update(array $validated, User $user): void public function update(array $validated, User $user): void
{ {
try { try {
@ -28,7 +24,6 @@ public function update(array $validated, User $user): void
'username' => $validated['username'], 'username' => $validated['username'],
]); ]);
/** @var UserProfile $profile */
$profile = $user->profile()->updateOrCreate( $profile = $user->profile()->updateOrCreate(
['user_id' => $user->id], ['user_id' => $user->id],
[ [
@ -58,9 +53,6 @@ public function update(array $validated, User $user): void
} }
} }
/**
* Update user password.
*/
public function updatePassword(User $user, string $password): void public function updatePassword(User $user, string $password): void
{ {
$user->update([ $user->update([

View File

@ -30,9 +30,7 @@ public function getDefaultAccount(): CashAccount
return CashAccount::query()->firstOrFail(); return CashAccount::query()->firstOrFail();
} }
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex(CashAccount $cashAccount, array $tableQuery, string $referenceType = ''): LengthAwarePaginator public function paginateForIndex(CashAccount $cashAccount, array $tableQuery, string $referenceType = ''): LengthAwarePaginator
{ {
$query = CashTransaction::query() $query = CashTransaction::query()
@ -55,7 +53,7 @@ public function paginateForIndex(CashAccount $cashAccount, array $tableQuery, st
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString() ->withQueryString()
->through(function (CashTransaction $transaction) { ->through(function (CashTransaction $transaction) {
$transaction->setAttribute( $transaction->setAttribute(
@ -67,9 +65,7 @@ public function paginateForIndex(CashAccount $cashAccount, array $tableQuery, st
}); });
} }
/**
* @param array{amount: int, description: string} $validated
*/
public function deposit(CashAccount $cashAccount, array $validated, User $user): CashTransaction public function deposit(CashAccount $cashAccount, array $validated, User $user): CashTransaction
{ {
try { try {
@ -78,8 +74,7 @@ public function deposit(CashAccount $cashAccount, array $validated, User $user):
$amount = (int) $validated['amount']; $amount = (int) $validated['amount'];
$newBalance = $account->balance + $amount; $newBalance = $account->balance + $amount;
$account->balance = $newBalance; $account->update(['balance' => $newBalance]);
$account->save();
$transaction = CashTransaction::create([ $transaction = CashTransaction::create([
'cash_account_id' => $account->id, 'cash_account_id' => $account->id,
@ -116,9 +111,7 @@ public function deposit(CashAccount $cashAccount, array $validated, User $user):
return $transaction; return $transaction;
} }
/**
* @param array{amount: int, description: string} $validated
*/
public function withdraw(CashAccount $cashAccount, array $validated, User $user): CashTransaction public function withdraw(CashAccount $cashAccount, array $validated, User $user): CashTransaction
{ {
try { try {
@ -134,8 +127,7 @@ public function withdraw(CashAccount $cashAccount, array $validated, User $user)
$newBalance = $account->balance - $amount; $newBalance = $account->balance - $amount;
$account->balance = $newBalance; $account->update(['balance' => $newBalance]);
$account->save();
$transaction = CashTransaction::create([ $transaction = CashTransaction::create([
'cash_account_id' => $account->id, 'cash_account_id' => $account->id,
@ -193,8 +185,7 @@ public function recordOutgoing(
$newBalance = $account->balance - $amount; $newBalance = $account->balance - $amount;
$account->balance = $newBalance; $account->update(['balance' => $newBalance]);
$account->save();
$transaction = new CashTransaction([ $transaction = new CashTransaction([
'cash_account_id' => $account->id, 'cash_account_id' => $account->id,
@ -238,8 +229,7 @@ public function recordIncoming(
$newBalance = $account->balance + $amount; $newBalance = $account->balance + $amount;
$account->balance = $newBalance; $account->update(['balance' => $newBalance]);
$account->save();
$transaction = new CashTransaction([ $transaction = new CashTransaction([
'cash_account_id' => $account->id, 'cash_account_id' => $account->id,
@ -268,9 +258,7 @@ public function recordIncoming(
} }
} }
/**
* @param array{amount: int, description: string} $validated
*/
public function updateDeposit(CashTransaction $transaction, array $validated): void public function updateDeposit(CashTransaction $transaction, array $validated): void
{ {
$this->ensureEditable($transaction); $this->ensureEditable($transaction);
@ -279,9 +267,10 @@ public function updateDeposit(CashTransaction $transaction, array $validated): v
DB::transaction(function () use ($transaction, $validated): void { DB::transaction(function () use ($transaction, $validated): void {
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id);
$transaction->amount = (int) $validated['amount']; $transaction->update([
$transaction->description = $validated['description']; 'amount' => (int) $validated['amount'],
$transaction->save(); 'description' => $validated['description'],
]);
$this->syncPhotos($transaction, $validated); $this->syncPhotos($transaction, $validated);
@ -365,9 +354,10 @@ public function updateReferencedTransaction(
DB::transaction(function () use ($transaction, $amount, $description): void { DB::transaction(function () use ($transaction, $amount, $description): void {
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id);
$transaction->amount = $amount; $transaction->update([
$transaction->description = $description; 'amount' => $amount,
$transaction->save(); 'description' => $description,
]);
$this->recalculateBalances($transaction->cashAccount); $this->recalculateBalances($transaction->cashAccount);
@ -422,9 +412,7 @@ public function deleteReferencedTransaction(CashTransaction $transaction): void
} }
} }
/**
* @param array<string, mixed> $validated
*/
private function syncPhotos(CashTransaction $transaction, array $validated): void private function syncPhotos(CashTransaction $transaction, array $validated): void
{ {
$this->mediaService->syncCollection( $this->mediaService->syncCollection(
@ -466,12 +454,10 @@ private function recalculateBalances(CashAccount $cashAccount): void
$runningBalance -= $transaction->amount; $runningBalance -= $transaction->amount;
} }
$transaction->balance_after = $runningBalance; $transaction->updateQuietly(['balance_after' => $runningBalance]);
$transaction->saveQuietly();
} }
$account->balance = $runningBalance; $account->update(['balance' => $runningBalance]);
$account->save();
} }
private function applySorting(Builder $query, string $sort, string $direction): void private function applySorting(Builder $query, string $sort, string $direction): void

View File

@ -25,9 +25,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* @return array{outstanding_amount: int, outstanding_amount_formatted: string, outstanding_count: int}
*/
public function outstandingSummary(?User $user = null): array public function outstandingSummary(?User $user = null): array
{ {
$query = EmployeeAdvance::query() $query = EmployeeAdvance::query()
@ -45,9 +43,7 @@ public function outstandingSummary(?User $user = null): array
]; ];
} }
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex(array $tableQuery, User $user, string $status = ''): LengthAwarePaginator public function paginateForIndex(array $tableQuery, User $user, string $status = ''): LengthAwarePaginator
{ {
$query = EmployeeAdvance::query() $query = EmployeeAdvance::query()
@ -70,13 +66,11 @@ public function paginateForIndex(array $tableQuery, User $user, string $status =
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString(); ->withQueryString();
} }
/**
* @param array{amount: int, description: string, due_date: string} $validated
*/
public function create(array $validated, User $user): void public function create(array $validated, User $user): void
{ {
$employee = $this->resolveAuthenticatedEmployee($user); $employee = $this->resolveAuthenticatedEmployee($user);
@ -111,9 +105,7 @@ public function create(array $validated, User $user): void
); );
} }
/**
* @param array{amount: int, description: string, due_date: string} $validated
*/
public function update(EmployeeAdvance $employeeAdvance, array $validated, User $user): void public function update(EmployeeAdvance $employeeAdvance, array $validated, User $user): void
{ {
try { try {

View File

@ -23,9 +23,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex(array $tableQuery): LengthAwarePaginator public function paginateForIndex(array $tableQuery): LengthAwarePaginator
{ {
$query = Expense::query() $query = Expense::query()
@ -40,7 +38,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString() ->withQueryString()
->through(function (Expense $expense) { ->through(function (Expense $expense) {
$expense->setAttribute( $expense->setAttribute(
@ -52,9 +50,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
}); });
} }
/**
* @param array{amount: int, description: string} $validated
*/
public function create(array $validated, User $user): void public function create(array $validated, User $user): void
{ {
try { try {
@ -103,9 +99,7 @@ public function create(array $validated, User $user): void
); );
} }
/**
* @param array{amount: int, description: string} $validated
*/
public function update(Expense $expense, array $validated): void public function update(Expense $expense, array $validated): void
{ {
try { try {
@ -179,9 +173,7 @@ public function delete(Expense $expense): void
); );
} }
/**
* @param array<string, mixed> $validated
*/
private function syncPhotos(Expense $expense, array $validated): void private function syncPhotos(Expense $expense, array $validated): void
{ {
$this->mediaService->syncCollection( $this->mediaService->syncCollection(

View File

@ -29,9 +29,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* @return Collection<int, PayrollPeriod>
*/
public function listPeriods(): Collection public function listPeriods(): Collection
{ {
return PayrollPeriod::query() return PayrollPeriod::query()
@ -57,9 +55,7 @@ public function resolvePeriod(?int $periodId): ?PayrollPeriod
->first(); ->first();
} }
/**
* @return array{total_amount: int, total_amount_formatted: string, total_count: int, status: string, status_label: string}
*/
public function periodSummary(PayrollPeriod $period, User $user): array public function periodSummary(PayrollPeriod $period, User $user): array
{ {
$query = Payroll::query() $query = Payroll::query()
@ -83,9 +79,7 @@ public function periodSummary(PayrollPeriod $period, User $user): array
]; ];
} }
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForPeriod(PayrollPeriod $period, array $tableQuery, User $user): LengthAwarePaginator public function paginateForPeriod(PayrollPeriod $period, array $tableQuery, User $user): LengthAwarePaginator
{ {
$query = Payroll::query() $query = Payroll::query()
@ -106,7 +100,7 @@ public function paginateForPeriod(PayrollPeriod $period, array $tableQuery, User
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString(); ->withQueryString();
} }
@ -212,9 +206,7 @@ public function generatePayrollsForPeriod(PayrollPeriod $period): void
} }
} }
/**
* @param array{type: string, amount: int, description: string} $validated
*/
public function addAdjustment(Payroll $payroll, array $validated, User $user): void public function addAdjustment(Payroll $payroll, array $validated, User $user): void
{ {
@ -404,9 +396,7 @@ public function pay(Payroll $payroll, User $user): void
} }
} }
/**
* @return Collection<int, Employee>
*/
private function payrollEligibleEmployees(PayrollPeriod $period): Collection private function payrollEligibleEmployees(PayrollPeriod $period): Collection
{ {
$periodStart = Carbon::create($period->year, $period->month, 1)->startOfMonth(); $periodStart = Carbon::create($period->year, $period->month, 1)->startOfMonth();

View File

@ -46,9 +46,7 @@ public function listForCalendar(
->get(); ->get();
} }
/**
* @return array<string, mixed>|null
*/
public function todayAttendanceForEmployee(Employee $employee): ?array public function todayAttendanceForEmployee(Employee $employee): ?array
{ {
$attendance = Attendance::query() $attendance = Attendance::query()
@ -70,9 +68,7 @@ public function isOnLeaveToday(Employee $employee): bool
->exists(); ->exists();
} }
/**
* @param array{photo: string, latitude: float, longitude: float} $validated
*/
public function checkIn(array $validated, User $user): void public function checkIn(array $validated, User $user): void
{ {
$employee = $this->resolveAuthenticatedEmployee($user); $employee = $this->resolveAuthenticatedEmployee($user);
@ -111,9 +107,7 @@ public function checkIn(array $validated, User $user): void
); );
} }
/**
* @param array{photo: string, latitude: float, longitude: float} $validated
*/
public function checkOut(array $validated, User $user): void public function checkOut(array $validated, User $user): void
{ {
$employee = $this->resolveAuthenticatedEmployee($user); $employee = $this->resolveAuthenticatedEmployee($user);

View File

@ -21,9 +21,7 @@ public function __construct(
private readonly MediaService $mediaService, private readonly MediaService $mediaService,
) {} ) {}
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex( public function paginateForIndex(
array $tableQuery, array $tableQuery,
string $role = '', string $role = '',
@ -55,7 +53,7 @@ public function paginateForIndex(
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString(); ->withQueryString();
} }
@ -76,9 +74,7 @@ public function findForEdit(User $user): array
]; ];
} }
/**
* @param array<string, mixed> $validated
*/
public function create(array $validated): void public function create(array $validated): void
{ {
try { try {
@ -124,9 +120,7 @@ public function create(array $validated): void
} }
} }
/**
* @param array<string, mixed> $validated
*/
public function update(User $user, array $validated): void public function update(User $user, array $validated): void
{ {
$employee = $user->employee; $employee = $user->employee;
@ -138,7 +132,7 @@ public function update(User $user, array $validated): void
'username' => $validated['username'], 'username' => $validated['username'],
]); ]);
/** @var UserProfile $profile */
$profile = $user->profile()->updateOrCreate( $profile = $user->profile()->updateOrCreate(
['user_id' => $user->id], ['user_id' => $user->id],
[ [
@ -231,9 +225,7 @@ public function delete(User $user): void
} }
} }
/**
* @param array<string, mixed> $validated
*/
private function syncProfilePhoto(UserProfile $profile, array $validated): void private function syncProfilePhoto(UserProfile $profile, array $validated): void
{ {
$newFiles = ! empty($validated['profile_photo']) ? [$validated['profile_photo']] : []; $newFiles = ! empty($validated['profile_photo']) ? [$validated['profile_photo']] : [];

View File

@ -23,9 +23,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex(array $tableQuery, User $user): LengthAwarePaginator public function paginateForIndex(array $tableQuery, User $user): LengthAwarePaginator
{ {
$query = LeaveRequest::query() $query = LeaveRequest::query()
@ -46,13 +44,11 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString(); ->withQueryString();
} }
/**
* @param array{start_date: string, end_date: string} $validated
*/
public function create(array $validated, User $user): void public function create(array $validated, User $user): void
{ {
$employee = $this->resolveAuthenticatedEmployee($user); $employee = $this->resolveAuthenticatedEmployee($user);
@ -89,9 +85,7 @@ public function create(array $validated, User $user): void
); );
} }
/**
* @param array{start_date: string, end_date: string} $validated
*/
public function update(LeaveRequest $leaveRequest, array $validated, User $user): void public function update(LeaveRequest $leaveRequest, array $validated, User $user): void
{ {
$startDate = Carbon::parse($validated['start_date'])->startOfDay(); $startDate = Carbon::parse($validated['start_date'])->startOfDay();

View File

@ -20,9 +20,7 @@ public function resolve(int $productVariantId, PriceType $priceType): ?CuttingRe
->first(); ->first();
} }
/**
* @return list<CuttingResultPrice>
*/
public function latestPricesForVariant(int $productVariantId): array public function latestPricesForVariant(int $productVariantId): array
{ {
$prices = []; $prices = [];
@ -38,14 +36,7 @@ public function latestPricesForVariant(int $productVariantId): array
return $prices; return $prices;
} }
/**
* Batch-fetch latest prices for multiple variant IDs in a single query.
* Returns a Collection keyed by product_variant_id, where each value is
* a Collection of CuttingResultPrice (one per price type, the latest).
*
* @param list<int> $variantIds
* @return Collection<int, Collection<int, CuttingResultPrice>>
*/
public function latestPricesForVariants(array $variantIds): Collection public function latestPricesForVariants(array $variantIds): Collection
{ {
if (empty($variantIds)) { if (empty($variantIds)) {

View File

@ -3,7 +3,6 @@
namespace App\Services\Manage; namespace App\Services\Manage;
use App\Enums\CuttingStatus; use App\Enums\CuttingStatus;
use App\Enums\Permission;
use App\Models\Cutting; use App\Models\Cutting;
use App\Models\CuttingMaterial; use App\Models\CuttingMaterial;
use App\Models\CuttingResult; use App\Models\CuttingResult;
@ -29,52 +28,6 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
public function isEditable(CuttingStatus $status): bool
{
return $status->isEditable();
}
public function ensureEditable(Cutting $cutting): void
{
if (! $this->isEditable($cutting->status)) {
throw ValidationException::withMessages([
'status' => 'Proses cutting tidak dapat diubah.',
]);
}
}
public function transitionStatusMessage(CuttingStatus $status): string
{
return match ($status) {
CuttingStatus::COMPLETED => 'Proses cutting berhasil diselesaikan. Menunggu verifikasi admin toko.',
CuttingStatus::VERIFIED => 'Proses cutting berhasil diverifikasi. Stok produk telah diperbarui.',
CuttingStatus::REJECTED => 'Proses cutting berhasil ditolak.',
CuttingStatus::IN_PROGRESS => 'Proses cutting dikembalikan ke proses.',
default => 'Status proses cutting berhasil diperbarui.',
};
}
public function canTransitionTo(CuttingStatus $from, CuttingStatus $to): bool
{
return $from->canTransitionTo($to);
}
public function transitionPermission(CuttingStatus $status): Permission
{
return $status->transitionPermission();
}
/**
* @return list<array{status: string, label: string, destructive: bool, permission: string, icon_only: bool}>
*/
public function availableActions(CuttingStatus $status): array
{
return $status->availableActions();
}
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex(array $tableQuery, User $user): LengthAwarePaginator public function paginateForIndex(array $tableQuery, User $user): LengthAwarePaginator
{ {
$query = Cutting::query() $query = Cutting::query()
@ -103,25 +56,23 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString() ->withQueryString()
->through(function (Cutting $cutting) use ($user) { ->through(function (Cutting $cutting) use ($user) {
$actions = collect($this->availableActions($cutting->status)) $actions = collect($cutting->status->availableActions())
->filter(fn (array $action) => $user->can($action['permission'])) ->filter(fn (array $action) => $user->can($action['permission']))
->values() ->values()
->all(); ->all();
$cutting->setAttribute('available_actions', $actions); $cutting->setAttribute('available_actions', $actions);
$cutting->setAttribute('is_editable', $this->isEditable($cutting->status)); $cutting->setAttribute('is_editable', $cutting->status->isEditable());
$this->appendCostPreview($cutting); $this->appendCostPreview($cutting);
return $cutting; return $cutting;
}); });
} }
/**
* @return Collection<int, Cutting>
*/
public function getInProgressCuttings(User $user): Collection public function getInProgressCuttings(User $user): Collection
{ {
return Cutting::query() return Cutting::query()
@ -136,20 +87,18 @@ public function getInProgressCuttings(User $user): Collection
->latest() ->latest()
->get() ->get()
->each(function (Cutting $cutting) use ($user): void { ->each(function (Cutting $cutting) use ($user): void {
$actions = collect($this->availableActions($cutting->status)) $actions = collect($cutting->status->availableActions())
->filter(fn (array $action) => $user->can($action['permission'])) ->filter(fn (array $action) => $user->can($action['permission']))
->values() ->values()
->all(); ->all();
$cutting->setAttribute('available_actions', $actions); $cutting->setAttribute('available_actions', $actions);
$cutting->setAttribute('is_editable', $this->isEditable($cutting->status)); $cutting->setAttribute('is_editable', $cutting->status->isEditable());
$this->appendCostPreview($cutting); $this->appendCostPreview($cutting);
}); });
} }
/**
* @return Collection<int, Cutting>
*/
public function getCompletedCuttings(User $user): Collection public function getCompletedCuttings(User $user): Collection
{ {
return Cutting::query() return Cutting::query()
@ -164,20 +113,18 @@ public function getCompletedCuttings(User $user): Collection
->latest() ->latest()
->get() ->get()
->each(function (Cutting $cutting) use ($user): void { ->each(function (Cutting $cutting) use ($user): void {
$actions = collect($this->availableActions($cutting->status)) $actions = collect($cutting->status->availableActions())
->filter(fn (array $action) => $user->can($action['permission'])) ->filter(fn (array $action) => $user->can($action['permission']))
->values() ->values()
->all(); ->all();
$cutting->setAttribute('available_actions', $actions); $cutting->setAttribute('available_actions', $actions);
$cutting->setAttribute('is_editable', $this->isEditable($cutting->status)); $cutting->setAttribute('is_editable', $cutting->status->isEditable());
$this->appendCostPreview($cutting); $this->appendCostPreview($cutting);
}); });
} }
/**
* @return Collection<int, RawMaterial>
*/
public function rawMaterialCatalog(?Cutting $cutting = null, ?User $user = null): Collection public function rawMaterialCatalog(?Cutting $cutting = null, ?User $user = null): Collection
{ {
$selectedPriceIds = $cutting $selectedPriceIds = $cutting
@ -212,9 +159,7 @@ public function rawMaterialCatalog(?Cutting $cutting = null, ?User $user = null)
}); });
} }
/**
* @return Collection<int, Product>
*/
public function productCatalog(?Cutting $cutting = null, ?User $user = null): Collection public function productCatalog(?Cutting $cutting = null, ?User $user = null): Collection
{ {
$selectedVariantIds = $cutting $selectedVariantIds = $cutting
@ -284,9 +229,7 @@ public function findForEdit(Cutting $cutting): Cutting
return $cutting; return $cutting;
} }
/**
* @return list<array<string, mixed>>
*/
public function draftMaterialsForUser(User $user): array public function draftMaterialsForUser(User $user): array
{ {
return $this->draftMaterialsQuery($user) return $this->draftMaterialsQuery($user)
@ -300,9 +243,7 @@ public function draftMaterialsForUser(User $user): array
->all(); ->all();
} }
/**
* @return list<array<string, mixed>>
*/
public function draftResultsForUser(User $user): array public function draftResultsForUser(User $user): array
{ {
return $this->draftResultsQuery($user) return $this->draftResultsQuery($user)
@ -316,10 +257,7 @@ public function draftResultsForUser(User $user): array
->all(); ->all();
} }
/**
* @param array<string, mixed> $validated
* @return array<string, mixed>
*/
public function syncDraftMaterial(array $validated, User $user): array public function syncDraftMaterial(array $validated, User $user): array
{ {
$price = RawMaterialPrice::query() $price = RawMaterialPrice::query()
@ -353,10 +291,7 @@ public function syncDraftMaterial(array $validated, User $user): array
return $this->presentDraftMaterial($item); return $this->presentDraftMaterial($item);
} }
/**
* @param array<string, mixed> $validated
* @return array<string, mixed>
*/
public function syncDraftResult(array $validated, User $user): array public function syncDraftResult(array $validated, User $user): array
{ {
$variant = ProductVariant::query() $variant = ProductVariant::query()
@ -430,20 +365,18 @@ public function removeDraftResult(User $user, ProductVariant $productVariant): v
->delete(); ->delete();
} }
/**
* @param array<string, mixed> $validated
*/
public function create(array $validated, User $user): Cutting public function create(array $validated, User $user): Cutting
{ {
try { try {
$cutting = DB::transaction(function () use ($validated, $user): Cutting { $cutting = DB::transaction(function () use ($validated, $user): Cutting {
/** @var EloquentCollection<int, CuttingMaterial> $draftMaterials */
$draftMaterials = $this->draftMaterialsQuery($user) $draftMaterials = $this->draftMaterialsQuery($user)
->with('rawMaterialPrice.rawMaterial') ->with('rawMaterialPrice.rawMaterial')
->lockForUpdate() ->lockForUpdate()
->get(); ->get();
/** @var EloquentCollection<int, CuttingResult> $draftResults */
$draftResults = $this->draftResultsQuery($user) $draftResults = $this->draftResultsQuery($user)
->lockForUpdate() ->lockForUpdate()
->get(); ->get();
@ -507,12 +440,10 @@ public function create(array $validated, User $user): Cutting
return $cutting; return $cutting;
} }
/**
* @param array<string, mixed> $validated
*/
public function update(Cutting $cutting, array $validated): void public function update(Cutting $cutting, array $validated): void
{ {
if (! $this->isEditable($cutting->status)) { if (! $cutting->status->isEditable()) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'status' => 'Proses cutting tidak dapat diubah.', 'status' => 'Proses cutting tidak dapat diubah.',
]); ]);
@ -630,7 +561,7 @@ public function transitionStatus(
?array $results = null, ?array $results = null,
?array $resultPrices = null, ?array $resultPrices = null,
): void { ): void {
if (! $this->canTransitionTo($cutting->status, $status)) { if (! $cutting->status->canTransitionTo($status)) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'status' => 'Status proses cutting tidak dapat diubah.', 'status' => 'Status proses cutting tidak dapat diubah.',
]); ]);
@ -720,10 +651,7 @@ public function transitionStatus(
); );
} }
/**
* @param list<array{raw_material_price_id: int, material_usage: float|int|string}> $materials
* @return list<array{raw_material_price_id: int, material_usage: float}>
*/
private function buildMaterials(array $materials): array private function buildMaterials(array $materials): array
{ {
return collect($materials) return collect($materials)
@ -754,10 +682,7 @@ private function buildMaterials(array $materials): array
->all(); ->all();
} }
/**
* @param list<array{product_variant_id: int, cutting_result: int|string, sampel: int|string, hasil_cutting_diluar_sampel?: int|string}> $results
* @return list<array{product_variant_id: int, cutting_result: int, sampel: int, hasil_cutting_diluar_sampel: int}>
*/
private function buildResults(array $results): array private function buildResults(array $results): array
{ {
return collect($results) return collect($results)
@ -909,9 +834,7 @@ private function applySorting(Builder $query, string $sort, string $direction):
$query->latest(); $query->latest();
} }
/**
* @return Builder<CuttingMaterial>
*/
private function draftMaterialsQuery(User $user): Builder private function draftMaterialsQuery(User $user): Builder
{ {
return CuttingMaterial::query() return CuttingMaterial::query()
@ -919,9 +842,7 @@ private function draftMaterialsQuery(User $user): Builder
->where('user_id', $user->id); ->where('user_id', $user->id);
} }
/**
* @return Builder<CuttingResult>
*/
private function draftResultsQuery(User $user): Builder private function draftResultsQuery(User $user): Builder
{ {
return CuttingResult::query() return CuttingResult::query()
@ -929,9 +850,7 @@ private function draftResultsQuery(User $user): Builder
->where('user_id', $user->id); ->where('user_id', $user->id);
} }
/**
* @return array<string, mixed>
*/
private function presentDraftMaterial(CuttingMaterial $item): array private function presentDraftMaterial(CuttingMaterial $item): array
{ {
$price = $item->rawMaterialPrice; $price = $item->rawMaterialPrice;
@ -949,9 +868,7 @@ private function presentDraftMaterial(CuttingMaterial $item): array
]; ];
} }
/**
* @return array<string, mixed>
*/
private function presentDraftResult(CuttingResult $item): array private function presentDraftResult(CuttingResult $item): array
{ {
$variant = $item->productVariant; $variant = $item->productVariant;
@ -1019,9 +936,7 @@ public function calculateCostPerUnit(Cutting $cutting): int
return (int) round($this->calculateTotalProductionCost($cutting) / $totalPieces); return (int) round($this->calculateTotalProductionCost($cutting) / $totalPieces);
} }
/**
* @param list<array{product_variant_id: int, prices: list<array{type: string, price: int}>}> $resultPrices
*/
private function storeResultPrices(Cutting $cutting, array $resultPrices): void private function storeResultPrices(Cutting $cutting, array $resultPrices): void
{ {
$costPerUnit = (int) ($cutting->cost_per_unit ?? $this->calculateCostPerUnit($cutting)); $costPerUnit = (int) ($cutting->cost_per_unit ?? $this->calculateCostPerUnit($cutting));

View File

@ -5,7 +5,6 @@
use App\Enums\OrderChannel; use App\Enums\OrderChannel;
use App\Enums\OrderStatus; use App\Enums\OrderStatus;
use App\Enums\PaymentType; use App\Enums\PaymentType;
use App\Enums\Permission;
use App\Enums\PriceType; use App\Enums\PriceType;
use App\Enums\ProductStockQuality; use App\Enums\ProductStockQuality;
use App\Models\Customer; use App\Models\Customer;
@ -39,48 +38,6 @@ public function __construct(
private readonly MediaService $mediaService, private readonly MediaService $mediaService,
) {} ) {}
public function isEditable(OrderStatus $status): bool
{
return $status->isEditable();
}
public function ensureEditable(Order $order): void
{
if (! $this->isEditable($order->status)) {
throw ValidationException::withMessages([
'status' => 'Pesanan tidak dapat diubah.',
]);
}
}
public function transitionStatusMessage(OrderStatus $status): string
{
return match ($status) {
OrderStatus::PROCESSING => 'Pesanan berhasil dikirim.',
OrderStatus::COMPLETED => 'Pesanan berhasil diselesaikan.',
OrderStatus::CANCELLED => 'Pesanan berhasil dibatalkan.',
default => 'Status pesanan berhasil diperbarui.',
};
}
public function canTransitionTo(OrderStatus $from, OrderStatus $to): bool
{
return $from->canTransitionTo($to);
}
public function transitionPermission(OrderStatus $status): Permission
{
return $status->transitionPermission();
}
/**
* @return list<array{status: string, label: string, destructive: bool, permission: string, icon_only: bool}>
*/
public function availableActions(OrderStatus $status): array
{
return $status->availableActions();
}
public function defaultPriceType(OrderChannel $channel): ?PriceType public function defaultPriceType(OrderChannel $channel): ?PriceType
{ {
return match ($channel) { return match ($channel) {
@ -99,9 +56,7 @@ public function stockColumn(ProductStockQuality $quality): string
}; };
} }
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex(array $tableQuery, User $user): LengthAwarePaginator public function paginateForIndex(array $tableQuery, User $user): LengthAwarePaginator
{ {
$query = Order::query() $query = Order::query()
@ -131,24 +86,22 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString() ->withQueryString()
->through(function (Order $order) use ($user) { ->through(function (Order $order) use ($user) {
$actions = collect($this->availableActions($order->status)) $actions = collect($order->status->availableActions())
->filter(fn (array $action) => $user->can($action['permission'])) ->filter(fn (array $action) => $user->can($action['permission']))
->values() ->values()
->all(); ->all();
$order->setAttribute('available_actions', $actions); $order->setAttribute('available_actions', $actions);
$order->setAttribute('is_editable', $this->isEditable($order->status)); $order->setAttribute('is_editable', $order->status->isEditable());
return $order; return $order;
}); });
} }
/**
* @return list<array{value: int, label: string}>
*/
public function customerOptions(): array public function customerOptions(): array
{ {
return Customer::query() return Customer::query()
@ -161,9 +114,7 @@ public function customerOptions(): array
->all(); ->all();
} }
/**
* @return list<array{value: int, label: string}>
*/
public function marketingOptions(): array public function marketingOptions(): array
{ {
return User::query() return User::query()
@ -179,9 +130,7 @@ public function marketingOptions(): array
->all(); ->all();
} }
/**
* @return list<array{value: string, label: string}>
*/
public function storePriceTypeOptions(): array public function storePriceTypeOptions(): array
{ {
return collect(PriceType::cases()) return collect(PriceType::cases())
@ -194,9 +143,7 @@ public function storePriceTypeOptions(): array
->all(); ->all();
} }
/**
* @return Collection<int, Product>
*/
public function catalogItems(?Order $order = null, ?User $user = null): Collection public function catalogItems(?Order $order = null, ?User $user = null): Collection
{ {
$orderVariantIds = $order $orderVariantIds = $order
@ -318,19 +265,17 @@ public function findForShow(Order $order): Order
} }
}); });
$availableActions = collect($this->availableActions($order->status)) $availableActions = collect($order->status->availableActions())
->values() ->values()
->all(); ->all();
$order->setAttribute('available_actions', $availableActions); $order->setAttribute('available_actions', $availableActions);
$order->setAttribute('is_editable', $this->isEditable($order->status)); $order->setAttribute('is_editable', $order->status->isEditable());
return $order; return $order;
} }
/**
* @return list<array<string, mixed>>
*/
public function draftItemsForUser(User $user): array public function draftItemsForUser(User $user): array
{ {
return $this->draftItemsQuery($user) return $this->draftItemsQuery($user)
@ -344,10 +289,7 @@ public function draftItemsForUser(User $user): array
->all(); ->all();
} }
/**
* @param array<string, mixed> $validated
* @return array<string, mixed>
*/
public function syncDraftItem(array $validated, User $user): array public function syncDraftItem(array $validated, User $user): array
{ {
// Force retail price type and stock quality for cashier role // Force retail price type and stock quality for cashier role
@ -405,9 +347,7 @@ public function removeDraftItem(User $user, ProductVariant $productVariant, Prod
->delete(); ->delete();
} }
/**
* @return list<array<string, mixed>>
*/
public function resyncDraftPrices(User $user, string $priceTypeValue): array public function resyncDraftPrices(User $user, string $priceTypeValue): array
{ {
// Force retail price type for cashier role // Force retail price type for cashier role
@ -440,9 +380,7 @@ public function resyncDraftPrices(User $user, string $priceTypeValue): array
return $this->draftItemsForUser($user); return $this->draftItemsForUser($user);
} }
/**
* @param array<string, mixed> $validated
*/
public function create(array $validated, User $user): Order public function create(array $validated, User $user): Order
{ {
try { try {
@ -457,7 +395,7 @@ public function create(array $validated, User $user): Order
$order = DB::transaction(function () use ($validated, $user): Order { $order = DB::transaction(function () use ($validated, $user): Order {
$priceType = $this->resolvePriceType($validated['channel'], $validated['price_type']); $priceType = $this->resolvePriceType($validated['channel'], $validated['price_type']);
/** @var EloquentCollection<int, OrderItem> $draftItems */
$draftItems = $this->draftItemsQuery($user) $draftItems = $this->draftItemsQuery($user)
->lockForUpdate() ->lockForUpdate()
->get(); ->get();
@ -563,16 +501,10 @@ public function create(array $validated, User $user): Order
return $order; return $order;
} }
/**
* @param array<string, mixed> $validated
*/
public function update(Order $order, array $validated): void public function update(Order $order, array $validated): void
{ {
if (! $this->isEditable($order->status)) { $order->ensureEditable();
throw ValidationException::withMessages([
'status' => 'Pesanan tidak dapat diubah.',
]);
}
try { try {
DB::transaction(function () use ($order, $validated): void { DB::transaction(function () use ($order, $validated): void {
@ -591,36 +523,38 @@ public function update(Order $order, array $validated): void
$negoPrice = isset($validated['nego_price']) && $validated['nego_price'] !== '' ? (int) $validated['nego_price'] : null; $negoPrice = isset($validated['nego_price']) && $validated['nego_price'] !== '' ? (int) $validated['nego_price'] : null;
$totalAmount = $negoPrice !== null ? max($negoPrice, 0) : max($subtotal - $discount, 0); $totalAmount = $negoPrice !== null ? max($negoPrice, 0) : max($subtotal - $discount, 0);
$channel = OrderChannel::from($validated['channel']); $channel = OrderChannel::from($validated['channel']);
$status = $order->status;
$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->nego_price = $negoPrice;
$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;
if (isset($validated['status'])) { if (isset($validated['status'])) {
$newStatus = OrderStatus::from($validated['status']); $newStatus = OrderStatus::from($validated['status']);
if ($order->status->canTransitionTo($newStatus) || $newStatus === $order->status) { if ($order->status->canTransitionTo($newStatus) || $newStatus === $order->status) {
$order->status = $newStatus; $status = $newStatus;
} }
} }
$order->save(); $order->update([
'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,
'tiktok_order_id' => $validated['tiktok_order_id'] ?? null,
'shopee_order_id' => $validated['shopee_order_id'] ?? null,
'subtotal' => $subtotal,
'discount' => $discount,
'nego_price' => $negoPrice,
'marketplace_settings_snapshot' => $this->marketplaceService->buildOrderSnapshot(
$channel,
$totalAmount,
$this->lineItemsForSnapshot($lineItems),
$validated['is_affiliate'] ?? false,
),
'total_amount' => $totalAmount,
'notes' => $validated['notes'] ?? null,
'status' => $status,
]);
$this->syncPhotos($order, $validated); $this->syncPhotos($order, $validated);
@ -673,7 +607,7 @@ public function delete(Order $order): void
DB::transaction(function () use ($order): void { DB::transaction(function () use ($order): void {
$order->load('items'); $order->load('items');
if ($this->isEditable($order->status)) { if ($order->status->isEditable()) {
foreach ($order->items as $item) { foreach ($order->items as $item) {
$this->incrementStock($item); $this->incrementStock($item);
} }
@ -708,7 +642,7 @@ public function delete(Order $order): void
public function transitionStatus(Order $order, OrderStatus $status): void public function transitionStatus(Order $order, OrderStatus $status): void
{ {
if (! $this->canTransitionTo($order->status, $status)) { if (! $order->status->canTransitionTo($status)) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'status' => 'Status pesanan tidak dapat diubah.', 'status' => 'Status pesanan tidak dapat diubah.',
]); ]);
@ -752,10 +686,7 @@ public function transitionStatus(Order $order, OrderStatus $status): void
); );
} }
/**
* @param list<array{product_variant_id: int, quantity: int|string, stock_quality?: string}> $items
* @return list<array{product_variant_id: int, stock_quality: string, quantity: int, unit_price: int, subtotal: int}>
*/
private function buildLineItems(array $items, PriceType $priceType): array private function buildLineItems(array $items, PriceType $priceType): array
{ {
return collect($items) return collect($items)
@ -814,9 +745,7 @@ private function resolvePriceType(string $channel, string $priceType): PriceType
return $priceTypeEnum; return $priceTypeEnum;
} }
/**
* @return Builder<OrderItem>
*/
private function draftItemsQuery(User $user): Builder private function draftItemsQuery(User $user): Builder
{ {
return OrderItem::query() return OrderItem::query()
@ -824,9 +753,7 @@ private function draftItemsQuery(User $user): Builder
->where('user_id', $user->id); ->where('user_id', $user->id);
} }
/**
* @return array<string, mixed>
*/
private function presentDraftItem(OrderItem $item): array private function presentDraftItem(OrderItem $item): array
{ {
$variant = $item->productVariant; $variant = $item->productVariant;
@ -843,9 +770,7 @@ private function presentDraftItem(OrderItem $item): array
]; ];
} }
/**
* @param EloquentCollection<int, OrderItem> $items
*/
private function applyDraftPrices(EloquentCollection $items, PriceType $priceType): void private function applyDraftPrices(EloquentCollection $items, PriceType $priceType): void
{ {
foreach ($items as $index => $item) { foreach ($items as $index => $item) {
@ -884,9 +809,7 @@ private function availableStock(ProductVariant $variant, ProductStockQuality $st
return (int) $variant->{$this->stockColumn($stockQuality)}; return (int) $variant->{$this->stockColumn($stockQuality)};
} }
/**
* @param array<string, mixed> $validated
*/
private function syncPhotos(Order $order, array $validated): void private function syncPhotos(Order $order, array $validated): void
{ {
$paymentType = $validated['payment_type'] ?? null; $paymentType = $validated['payment_type'] ?? null;
@ -903,10 +826,7 @@ private function syncPhotos(Order $order, array $validated): void
); );
} }
/**
* @param EloquentCollection<int, OrderItem>|list<array{quantity: int, subtotal: int}> $items
* @return list<array{quantity: int, subtotal: int}>
*/
private function lineItemsForSnapshot(EloquentCollection|array $items): array private function lineItemsForSnapshot(EloquentCollection|array $items): array
{ {
return collect($items) return collect($items)
@ -942,17 +862,7 @@ private function resolveUnitPrice(int $variantId, PriceType $priceType, ?string
return (int) $price->price; return (int) $price->price;
} }
/**
* @return list<array{
* type: string,
* type_label: string,
* price: int,
* price_formatted: string,
* price_input: string,
* cost_per_unit: int,
* cost_per_unit_formatted: string,
* }>
*/
private function presentVariantPrices(int $variantId): array private function presentVariantPrices(int $variantId): array
{ {
return collect($this->cuttingResultPriceResolver->latestPricesForVariant($variantId)) return collect($this->cuttingResultPriceResolver->latestPricesForVariant($variantId))

View File

@ -47,9 +47,7 @@ public function hasPendingMarketplaceVerification(): bool
->exists(); ->exists();
} }
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex( public function paginateForIndex(
User $user, User $user,
array $tableQuery, array $tableQuery,
@ -57,7 +55,7 @@ public function paginateForIndex(
string $subjectType = '', string $subjectType = '',
string $action = '', string $action = '',
): LengthAwarePaginator { ): LengthAwarePaginator {
$perPage = 10; $perPage = 25;
$page = Paginator::resolveCurrentPage(); $page = Paginator::resolveCurrentPage();
$includeCuttings = $this->shouldIncludeCuttings($user, $status, $subjectType, $action); $includeCuttings = $this->shouldIncludeCuttings($user, $status, $subjectType, $action);
$cuttingRows = $includeCuttings $cuttingRows = $includeCuttings
@ -103,9 +101,7 @@ public function paginateForIndex(
->through(fn (OwnerVerificationRequest $request) => $this->presentRequestRow($request)); ->through(fn (OwnerVerificationRequest $request) => $this->presentRequestRow($request));
} }
/**
* @return Collection<int, array<string, mixed>>
*/
private function pendingCuttingRows(User $user, array $tableQuery): Collection private function pendingCuttingRows(User $user, array $tableQuery): Collection
{ {
$rows = $this->stockService $rows = $this->stockService
@ -147,9 +143,7 @@ public function pendingCountForUser(User $user): int
return $requestCount + Cutting::query()->pendingVerification()->count(); return $requestCount + Cutting::query()->pendingVerification()->count();
} }
/**
* @return list<array{value: string, label: string}>
*/
public function subjectTypeOptions(User $user): array public function subjectTypeOptions(User $user): array
{ {
$options = OwnerVerificationRequest::query() $options = OwnerVerificationRequest::query()
@ -323,9 +317,7 @@ private function clearVerificationRequestMedia(OwnerVerificationRequest $request
}; };
} }
/**
* @return array<string, mixed>
*/
private function presentRequestRow(OwnerVerificationRequest $request): array private function presentRequestRow(OwnerVerificationRequest $request): array
{ {
$payload = is_array($request->payload) ? $request->payload : []; $payload = is_array($request->payload) ? $request->payload : [];
@ -355,9 +347,7 @@ private function presentRequestRow(OwnerVerificationRequest $request): array
]; ];
} }
/**
* @return array<string, mixed>
*/
private function presentCuttingRow(Cutting $cutting): array private function presentCuttingRow(Cutting $cutting): array
{ {
$totalPieces = $cutting->results->sum('cutting_result'); $totalPieces = $cutting->results->sum('cutting_result');
@ -389,9 +379,7 @@ private function presentCuttingRow(Cutting $cutting): array
]; ];
} }
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
private function buildVerificationRequestQuery( private function buildVerificationRequestQuery(
User $user, User $user,
array $tableQuery, array $tableQuery,
@ -466,9 +454,7 @@ private function shouldIncludeCuttings(
return Cutting::query()->pendingVerification()->exists(); return Cutting::query()->pendingVerification()->exists();
} }
/**
* @param Collection<int, array<string, mixed>> $items
*/
private function makePaginator( private function makePaginator(
Collection $items, Collection $items,
int $total, int $total,

View File

@ -31,9 +31,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex(array $tableQuery): LengthAwarePaginator public function paginateForIndex(array $tableQuery): LengthAwarePaginator
{ {
$query = Purchase::query() $query = Purchase::query()
@ -59,7 +57,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString() ->withQueryString()
->through(function (Purchase $purchase) { ->through(function (Purchase $purchase) {
$purchase->setAttribute( $purchase->setAttribute(
@ -78,9 +76,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
}); });
} }
/**
* @return list<array{value: int, label: string}>
*/
public function supplierOptions(): array public function supplierOptions(): array
{ {
return Supplier::query() return Supplier::query()
@ -93,9 +89,7 @@ public function supplierOptions(): array
->all(); ->all();
} }
/**
* @return Collection<int, RawMaterial>
*/
public function catalogItems(?Purchase $purchase = null, ?User $user = null): Collection public function catalogItems(?Purchase $purchase = null, ?User $user = null): Collection
{ {
$purchasePriceIds = $purchase $purchasePriceIds = $purchase
@ -156,9 +150,7 @@ public function findForEdit(Purchase $purchase): Purchase
return $purchase; return $purchase;
} }
/**
* @return list<array<string, mixed>>
*/
public function draftItemsForUser(User $user): array public function draftItemsForUser(User $user): array
{ {
return $this->draftItemsQuery($user) return $this->draftItemsQuery($user)
@ -172,10 +164,7 @@ public function draftItemsForUser(User $user): array
->all(); ->all();
} }
/**
* @param array<string, mixed> $validated
* @return array<string, mixed>
*/
public function syncDraftItem(array $validated, User $user): array public function syncDraftItem(array $validated, User $user): array
{ {
$price = RawMaterialPrice::query() $price = RawMaterialPrice::query()
@ -216,14 +205,12 @@ public function removeDraftItem(User $user, RawMaterialPrice $rawMaterialPrice):
->delete(); ->delete();
} }
/**
* @param array<string, mixed> $validated
*/
public function create(array $validated, User $user): Purchase public function create(array $validated, User $user): Purchase
{ {
try { try {
$purchase = DB::transaction(function () use ($validated, $user): Purchase { $purchase = DB::transaction(function () use ($validated, $user): Purchase {
/** @var EloquentCollection<int, PurchaseItem> $draftItems */
$draftItems = $this->draftItemsQuery($user) $draftItems = $this->draftItemsQuery($user)
->lockForUpdate() ->lockForUpdate()
->get(); ->get();
@ -295,9 +282,7 @@ public function create(array $validated, User $user): Purchase
return $purchase; return $purchase;
} }
/**
* @param array<string, mixed> $validated
*/
public function update(Purchase $purchase, array $validated, User $user): void public function update(Purchase $purchase, array $validated, User $user): void
{ {
$purchase->load(['supplier', 'items.rawMaterialPrice.rawMaterial:id,name']); $purchase->load(['supplier', 'items.rawMaterialPrice.rawMaterial:id,name']);
@ -449,10 +434,7 @@ public function applyDelete(OwnerVerificationRequest $verificationRequest): void
$this->executeDelete($purchase); $this->executeDelete($purchase);
} }
/**
* @param list<array{raw_material_price_id: int, quantity: float|int|string}> $items
* @return list<array{raw_material_price_id: int, quantity: float, unit_price: int, subtotal: int}>
*/
private function buildLineItems(array $items): array private function buildLineItems(array $items): array
{ {
return collect($items) return collect($items)
@ -479,9 +461,7 @@ private function buildLineItems(array $items): array
->all(); ->all();
} }
/**
* @param array<string, mixed> $validated
*/
private function syncPhotos(Purchase $purchase, array $validated): void private function syncPhotos(Purchase $purchase, array $validated): void
{ {
$this->mediaService->syncCollection( $this->mediaService->syncCollection(
@ -495,9 +475,7 @@ private function syncPhotos(Purchase $purchase, array $validated): void
); );
} }
/**
* @return Builder<PurchaseItem>
*/
private function draftItemsQuery(User $user): Builder private function draftItemsQuery(User $user): Builder
{ {
return PurchaseItem::query() return PurchaseItem::query()
@ -505,9 +483,7 @@ private function draftItemsQuery(User $user): Builder
->where('user_id', $user->id); ->where('user_id', $user->id);
} }
/**
* @return array<string, mixed>
*/
private function presentDraftItem(PurchaseItem $item): array private function presentDraftItem(PurchaseItem $item): array
{ {
$price = $item->rawMaterialPrice; $price = $item->rawMaterialPrice;
@ -585,9 +561,7 @@ private function executeDelete(Purchase $purchase): void
}); });
} }
/**
* @param array<string, mixed> $payload
*/
private function applyPayloadToPurchase( private function applyPayloadToPurchase(
Purchase $purchase, Purchase $purchase,
array $payload, array $payload,
@ -627,9 +601,7 @@ private function applyPayloadToPurchase(
}); });
} }
/**
* @return array<string, mixed>
*/
private function snapshotPurchase(Purchase $purchase): array private function snapshotPurchase(Purchase $purchase): array
{ {
$purchase->load([ $purchase->load([
@ -659,10 +631,7 @@ private function snapshotPurchase(Purchase $purchase): array
]; ];
} }
/**
* @param array<string, mixed> $validated
* @return array<string, mixed>
*/
private function buildPayloadFromValidated(array $validated): array private function buildPayloadFromValidated(array $validated): array
{ {
$lineItems = $this->enrichLineItems($this->buildLineItems($validated['items'])); $lineItems = $this->enrichLineItems($this->buildLineItems($validated['items']));
@ -685,10 +654,7 @@ private function buildPayloadFromValidated(array $validated): array
]; ];
} }
/**
* @param list<array{raw_material_price_id: int, quantity: float, unit_price: int, subtotal: int}> $lineItems
* @return list<array<string, mixed>>
*/
private function enrichLineItems(array $lineItems): array private function enrichLineItems(array $lineItems): array
{ {
$prices = RawMaterialPrice::query() $prices = RawMaterialPrice::query()
@ -709,9 +675,7 @@ private function enrichLineItems(array $lineItems): array
->all(); ->all();
} }
/**
* @param array<string, mixed> $validated
*/
private function syncRequestPhotos(OwnerVerificationRequest $verificationRequest, array $validated): void private function syncRequestPhotos(OwnerVerificationRequest $verificationRequest, array $validated): void
{ {
$this->mediaService->syncCollection( $this->mediaService->syncCollection(
@ -725,9 +689,7 @@ private function syncRequestPhotos(OwnerVerificationRequest $verificationRequest
); );
} }
/**
* @param array<string, mixed> $payload
*/
private function applyRequestPhotos( private function applyRequestPhotos(
OwnerVerificationRequest $verificationRequest, OwnerVerificationRequest $verificationRequest,
Purchase $purchase, Purchase $purchase,
@ -748,9 +710,7 @@ private function applyRequestPhotos(
} }
} }
/**
* @return array<string, mixed>
*/
private function payloadNew(OwnerVerificationRequest $verificationRequest): array private function payloadNew(OwnerVerificationRequest $verificationRequest): array
{ {
$payload = $verificationRequest->payload ?? []; $payload = $verificationRequest->payload ?? [];

View File

@ -19,9 +19,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* Submit a stock retail transfer request for owner verification.
*/
public function transfer(int $variantId, int $quantity, User $user, ?string $notes = null): OwnerVerificationRequest public function transfer(int $variantId, int $quantity, User $user, ?string $notes = null): OwnerVerificationRequest
{ {
if ($quantity <= 0) { if ($quantity <= 0) {
@ -102,9 +100,7 @@ public function transfer(int $variantId, int $quantity, User $user, ?string $not
} }
} }
/**
* Apply the stock retail transfer when owner approves.
*/
public function applyStockRetailTransfer(OwnerVerificationRequest $request): void public function applyStockRetailTransfer(OwnerVerificationRequest $request): void
{ {
$payload = $request->payload ?? []; $payload = $request->payload ?? [];
@ -141,9 +137,7 @@ public function applyStockRetailTransfer(OwnerVerificationRequest $request): voi
}); });
} }
/**
* Reject the stock retail transfer (no-op since nothing changed).
*/
public function rejectStockRetailTransfer(OwnerVerificationRequest $request): void public function rejectStockRetailTransfer(OwnerVerificationRequest $request): void
{ {
// No-op: nothing was changed yet, so nothing to rollback. // No-op: nothing was changed yet, so nothing to rollback.

View File

@ -20,11 +20,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* Get all completed cuttings pending verification.
*
* @return Collection<int, Cutting>
*/
public function getPendingVerificationCuttings(User $user): Collection public function getPendingVerificationCuttings(User $user): Collection
{ {
return Cutting::query() return Cutting::query()
@ -43,11 +39,7 @@ public function getPendingVerificationCuttings(User $user): Collection
}); });
} }
/**
* Get all cuttings pending owner approval.
*
* @return Collection<int, Cutting>
*/
public function getPendingApprovalCuttings(User $user): Collection public function getPendingApprovalCuttings(User $user): Collection
{ {
return Cutting::query() return Cutting::query()
@ -68,12 +60,7 @@ public function getPendingApprovalCuttings(User $user): Collection
}); });
} }
/**
* Submit verification for a completed cutting - saves data but does NOT add stock yet.
*
* @param list<array{product_variant_id: int, sampel: int, hasil_cutting_diluar_sampel: int}>|null $results
* @param list<array{product_variant_id: int, prices: list<array{type: string, price: int}>}>|null $resultPrices
*/
public function submitVerification( public function submitVerification(
Cutting $cutting, Cutting $cutting,
User $user, User $user,
@ -138,9 +125,7 @@ public function submitVerification(
); );
} }
/**
* Owner approves verification - adds stock to store.
*/
public function approveVerification( public function approveVerification(
Cutting $cutting, Cutting $cutting,
User $user, User $user,
@ -191,9 +176,7 @@ public function approveVerification(
); );
} }
/**
* Owner rejects verification - sends back to completed.
*/
public function rejectVerification( public function rejectVerification(
Cutting $cutting, Cutting $cutting,
User $user, User $user,
@ -262,9 +245,7 @@ private function applyProductStockOnVerify(Cutting $cutting): void
} }
} }
/**
* @param list<array{product_variant_id: int, prices: list<array{type: string, price: int}>}> $resultPrices
*/
private function storeResultPrices(Cutting $cutting, array $resultPrices): void private function storeResultPrices(Cutting $cutting, array $resultPrices): void
{ {
// Extract harga_modal from the first variant's prices and set as cost_per_unit // Extract harga_modal from the first variant's prices and set as cost_per_unit
@ -304,9 +285,7 @@ private function storeResultPrices(Cutting $cutting, array $resultPrices): void
} }
} }
/**
* Update product_prices from cutting result prices (called on owner approval).
*/
private function applyResultPricesToProducts(Cutting $cutting): void private function applyResultPricesToProducts(Cutting $cutting): void
{ {
$resultPrices = $cutting->resultPrices()->with('productVariant')->get(); $resultPrices = $cutting->resultPrices()->with('productVariant')->get();

View File

@ -19,9 +19,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex(array $tableQuery): LengthAwarePaginator public function paginateForIndex(array $tableQuery): LengthAwarePaginator
{ {
$query = StokOpname::query() $query = StokOpname::query()
@ -42,13 +40,11 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString(); ->withQueryString();
} }
/**
* Get product variants with current stock for the opname form.
*/
public function catalogItems(): array public function catalogItems(): array
{ {
return Product::query() return Product::query()
@ -72,9 +68,7 @@ public function catalogItems(): array
->toArray(); ->toArray();
} }
/**
* Get stok opname with items for editing.
*/
public function findForEdit(StokOpname $stokOpname): array public function findForEdit(StokOpname $stokOpname): array
{ {
$stokOpname->load(['items.productVariant.product', 'createdBy.profile']); $stokOpname->load(['items.productVariant.product', 'createdBy.profile']);
@ -101,9 +95,7 @@ public function findForEdit(StokOpname $stokOpname): array
]; ];
} }
/**
* @param array{opname_date: string, notes: string|null, items: list<array{product_variant_id: int, physical_stock: int, notes: string|null}>} $validated
*/
public function create(array $validated, User $user): StokOpname public function create(array $validated, User $user): StokOpname
{ {
try { try {
@ -132,9 +124,7 @@ public function create(array $validated, User $user): StokOpname
} }
} }
/**
* @param array{opname_date: string, notes: string|null, items: list<array{product_variant_id: int, physical_stock: int, notes: string|null}>} $validated
*/
public function update(StokOpname $stokOpname, array $validated): void public function update(StokOpname $stokOpname, array $validated): void
{ {
if ($stokOpname->status !== StokOpnameStatus::DRAFT && $stokOpname->status !== StokOpnameStatus::REJECTED) { if ($stokOpname->status !== StokOpnameStatus::DRAFT && $stokOpname->status !== StokOpnameStatus::REJECTED) {
@ -192,9 +182,7 @@ public function delete(StokOpname $stokOpname): void
} }
} }
/**
* Submit stok opname for verification.
*/
public function submit(StokOpname $stokOpname, User $user): void public function submit(StokOpname $stokOpname, User $user): void
{ {
if ($stokOpname->status !== StokOpnameStatus::DRAFT && $stokOpname->status !== StokOpnameStatus::REJECTED) { if ($stokOpname->status !== StokOpnameStatus::DRAFT && $stokOpname->status !== StokOpnameStatus::REJECTED) {
@ -235,9 +223,7 @@ public function submit(StokOpname $stokOpname, User $user): void
); );
} }
/**
* Verify stok opname - apply stock adjustments.
*/
public function verify(StokOpname $stokOpname, User $user, ?string $verificationNotes = null): void public function verify(StokOpname $stokOpname, User $user, ?string $verificationNotes = null): void
{ {
if ($stokOpname->status !== StokOpnameStatus::PENDING) { if ($stokOpname->status !== StokOpnameStatus::PENDING) {
@ -282,9 +268,7 @@ public function verify(StokOpname $stokOpname, User $user, ?string $verification
); );
} }
/**
* Reject stok opname.
*/
public function reject(StokOpname $stokOpname, User $user, string $reason): void public function reject(StokOpname $stokOpname, User $user, string $reason): void
{ {
if ($stokOpname->status !== StokOpnameStatus::PENDING) { if ($stokOpname->status !== StokOpnameStatus::PENDING) {
@ -321,11 +305,7 @@ public function reject(StokOpname $stokOpname, User $user, string $reason): void
); );
} }
/**
* Auto-save: create or update a draft silently.
*
* @param array{stok_opname_id?: int|null, opname_date: string, notes: string|null, items: list<array{product_variant_id: int, physical_stock: int|null, notes: string|null}>|null} $validated
*/
public function autoSave(array $validated, User $user): StokOpname public function autoSave(array $validated, User $user): StokOpname
{ {
try { try {
@ -364,9 +344,7 @@ public function autoSave(array $validated, User $user): StokOpname
} }
} }
/**
* @param list<array{product_variant_id: int, physical_stock: int, notes: string|null}> $items
*/
private function syncItems(StokOpname $stokOpname, array $items): void private function syncItems(StokOpname $stokOpname, array $items): void
{ {
$stokOpname->items()->delete(); $stokOpname->items()->delete();

View File

@ -21,7 +21,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString(); ->withQueryString();
} }

View File

@ -23,7 +23,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString(); ->withQueryString();
} }

View File

@ -27,9 +27,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex(array $tableQuery, string $isActive, string $categoryId = '', string $stockStatus = ''): LengthAwarePaginator public function paginateForIndex(array $tableQuery, string $isActive, string $categoryId = '', string $stockStatus = ''): LengthAwarePaginator
{ {
$query = Product::query() $query = Product::query()
@ -64,7 +62,7 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $ca
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString() ->withQueryString()
->through(function (Product $product) { ->through(function (Product $product) {
$product->variants->each(function (ProductVariant $variant): void { $product->variants->each(function (ProductVariant $variant): void {
@ -116,9 +114,7 @@ public function findForEdit(Product $product): Product
return $product; return $product;
} }
/**
* @param array<string, mixed> $validated
*/
public function create(array $validated, User $user): void public function create(array $validated, User $user): void
{ {
try { try {
@ -173,9 +169,7 @@ public function create(array $validated, User $user): void
); );
} }
/**
* @param array<string, mixed> $validated
*/
public function update(Product $product, array $validated, User $user): void public function update(Product $product, array $validated, User $user): void
{ {
try { try {
@ -249,9 +243,7 @@ public function delete(Product $product, User $user): void
); );
} }
/**
* @param array<string, mixed> $validated
*/
public function toggleStatus(Product $product, array $validated, User $user): void public function toggleStatus(Product $product, array $validated, User $user): void
{ {
try { try {
@ -356,9 +348,7 @@ public function applyUpdate(OwnerVerificationRequest $verificationRequest): void
$this->applyPayloadToProduct($product, $this->payloadNew($verificationRequest), $verificationRequest); $this->applyPayloadToProduct($product, $this->payloadNew($verificationRequest), $verificationRequest);
} }
/**
* @param array<string, mixed> $payload
*/
private function applyPayloadToProduct( private function applyPayloadToProduct(
Product $product, Product $product,
array $payload, array $payload,
@ -510,9 +500,7 @@ private function rollbackUpdate(OwnerVerificationRequest $verificationRequest):
$this->applyPayloadToProduct($product, $oldPayload); $this->applyPayloadToProduct($product, $oldPayload);
} }
/**
* @return array<string, mixed>
*/
private function payloadOld(OwnerVerificationRequest $verificationRequest): array private function payloadOld(OwnerVerificationRequest $verificationRequest): array
{ {
$payload = $verificationRequest->payload ?? []; $payload = $verificationRequest->payload ?? [];
@ -524,9 +512,7 @@ private function payloadOld(OwnerVerificationRequest $verificationRequest): arra
return []; return [];
} }
/**
* @return array<string, mixed>
*/
private function payloadNew(OwnerVerificationRequest $verificationRequest): array private function payloadNew(OwnerVerificationRequest $verificationRequest): array
{ {
$payload = $verificationRequest->payload ?? []; $payload = $verificationRequest->payload ?? [];
@ -538,9 +524,7 @@ private function payloadNew(OwnerVerificationRequest $verificationRequest): arra
return $payload; return $payload;
} }
/**
* @return array<string, mixed>
*/
private function snapshotProduct(Product $product): array private function snapshotProduct(Product $product): array
{ {
$product->load(['categories', 'variants']); $product->load(['categories', 'variants']);
@ -561,10 +545,7 @@ private function snapshotProduct(Product $product): array
]); ]);
} }
/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
private function enrichPayload(array $data): array private function enrichPayload(array $data): array
{ {
$categoryIds = $data['category_ids'] ?? []; $categoryIds = $data['category_ids'] ?? [];
@ -576,10 +557,7 @@ private function enrichPayload(array $data): array
return $data; return $data;
} }
/**
* @param array<string, mixed> $validated
* @return array<string, mixed>
*/
private function buildPayloadFromValidated(array $validated): array private function buildPayloadFromValidated(array $validated): array
{ {
return [ return [
@ -598,9 +576,7 @@ private function buildPayloadFromValidated(array $validated): array
]; ];
} }
/**
* @param array<string, mixed> $variantData
*/
private function syncVariantImages( private function syncVariantImages(
ProductVariant $variant, ProductVariant $variant,
array $variantData, array $variantData,
@ -617,9 +593,7 @@ private function syncVariantImages(
); );
} }
/**
* @param array<string, mixed> $variantData
*/
private function syncRequestVariantImages( private function syncRequestVariantImages(
OwnerVerificationRequest $verificationRequest, OwnerVerificationRequest $verificationRequest,
array $variantData, array $variantData,
@ -639,9 +613,7 @@ private function syncRequestVariantImages(
); );
} }
/**
* @param array<string, mixed> $variantData
*/
private function applyVariantImageChanges( private function applyVariantImageChanges(
OwnerVerificationRequest $verificationRequest, OwnerVerificationRequest $verificationRequest,
ProductVariant $variant, ProductVariant $variant,

View File

@ -27,9 +27,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/
public function paginateForIndex(array $tableQuery, string $isActive, string $stockStatus = ''): LengthAwarePaginator public function paginateForIndex(array $tableQuery, string $isActive, string $stockStatus = ''): LengthAwarePaginator
{ {
$query = RawMaterial::query() $query = RawMaterial::query()
@ -72,7 +70,7 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $st
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString() ->withQueryString()
->through(function (RawMaterial $rawMaterial) { ->through(function (RawMaterial $rawMaterial) {
$rawMaterial->prices->each(function (RawMaterialPrice $price): void { $rawMaterial->prices->each(function (RawMaterialPrice $price): void {
@ -110,9 +108,7 @@ public function findForEdit(RawMaterial $rawMaterial): RawMaterial
return $rawMaterial; return $rawMaterial;
} }
/**
* @param array<string, mixed> $validated
*/
public function create(array $validated, User $user): void public function create(array $validated, User $user): void
{ {
try { try {
@ -159,9 +155,7 @@ public function create(array $validated, User $user): void
); );
} }
/**
* @param array<string, mixed> $validated
*/
public function update(RawMaterial $rawMaterial, array $validated, User $user): void public function update(RawMaterial $rawMaterial, array $validated, User $user): void
{ {
try { try {
@ -235,9 +229,7 @@ public function delete(RawMaterial $rawMaterial, User $user): void
); );
} }
/**
* @param array<string, mixed> $validated
*/
public function toggleStatus(RawMaterial $rawMaterial, array $validated, User $user): void public function toggleStatus(RawMaterial $rawMaterial, array $validated, User $user): void
{ {
try { try {
@ -395,9 +387,7 @@ public function applyToggleStatus(OwnerVerificationRequest $verificationRequest)
]); ]);
} }
/**
* @param array<string, mixed> $payload
*/
private function applyPayloadToRawMaterial( private function applyPayloadToRawMaterial(
RawMaterial $rawMaterial, RawMaterial $rawMaterial,
array $payload, array $payload,
@ -492,9 +482,7 @@ private function rollbackUpdate(OwnerVerificationRequest $verificationRequest):
$this->applyPayloadToRawMaterial($rawMaterial, $oldPayload); $this->applyPayloadToRawMaterial($rawMaterial, $oldPayload);
} }
/**
* @return array<string, mixed>
*/
private function payloadOld(OwnerVerificationRequest $verificationRequest): array private function payloadOld(OwnerVerificationRequest $verificationRequest): array
{ {
$payload = $verificationRequest->payload ?? []; $payload = $verificationRequest->payload ?? [];
@ -506,9 +494,7 @@ private function payloadOld(OwnerVerificationRequest $verificationRequest): arra
return []; return [];
} }
/**
* @return array<string, mixed>
*/
private function payloadNew(OwnerVerificationRequest $verificationRequest): array private function payloadNew(OwnerVerificationRequest $verificationRequest): array
{ {
$payload = $verificationRequest->payload ?? []; $payload = $verificationRequest->payload ?? [];
@ -520,9 +506,7 @@ private function payloadNew(OwnerVerificationRequest $verificationRequest): arra
return $payload; return $payload;
} }
/**
* @return array<string, mixed>
*/
private function snapshotRawMaterial(RawMaterial $rawMaterial): array private function snapshotRawMaterial(RawMaterial $rawMaterial): array
{ {
$rawMaterial->load(['prices']); $rawMaterial->load(['prices']);
@ -542,10 +526,7 @@ private function snapshotRawMaterial(RawMaterial $rawMaterial): array
]); ]);
} }
/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
private function enrichPayload(array $data): array private function enrichPayload(array $data): array
{ {
if (isset($data['unit'])) { if (isset($data['unit'])) {
@ -560,10 +541,7 @@ private function enrichPayload(array $data): array
return $data; return $data;
} }
/**
* @param array<string, mixed> $validated
* @return array<string, mixed>
*/
private function buildPayloadFromValidated(array $validated): array private function buildPayloadFromValidated(array $validated): array
{ {
return [ return [
@ -592,9 +570,7 @@ private function applySorting(Builder $query, string $sort, string $direction):
$query->latest(); $query->latest();
} }
/**
* @param array<string, mixed> $priceData
*/
private function createPrice(RawMaterial $rawMaterial, array $priceData, int $index): RawMaterialPrice private function createPrice(RawMaterial $rawMaterial, array $priceData, int $index): RawMaterialPrice
{ {
$price = $rawMaterial->prices()->create([ $price = $rawMaterial->prices()->create([
@ -608,9 +584,7 @@ private function createPrice(RawMaterial $rawMaterial, array $priceData, int $in
return $price; return $price;
} }
/**
* @param array<string, mixed> $priceData
*/
private function syncPriceImages(RawMaterialPrice $price, array $priceData, int $index): void private function syncPriceImages(RawMaterialPrice $price, array $priceData, int $index): void
{ {
$this->mediaService->syncCollection( $this->mediaService->syncCollection(
@ -624,9 +598,7 @@ private function syncPriceImages(RawMaterialPrice $price, array $priceData, int
); );
} }
/**
* @param array<string, mixed> $priceData
*/
private function syncRequestPriceImages( private function syncRequestPriceImages(
OwnerVerificationRequest $verificationRequest, OwnerVerificationRequest $verificationRequest,
array $priceData, array $priceData,
@ -646,9 +618,7 @@ private function syncRequestPriceImages(
); );
} }
/**
* @param array<string, mixed> $priceData
*/
private function applyPriceImageChanges( private function applyPriceImageChanges(
OwnerVerificationRequest $verificationRequest, OwnerVerificationRequest $verificationRequest,
RawMaterialPrice $price, RawMaterialPrice $price,

View File

@ -23,7 +23,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString(); ->withQueryString();
} }

View File

@ -12,10 +12,7 @@
class MediaService class MediaService
{ {
/**
* @param list<UploadedFile>|null $newFiles
* @param list<int>|null $removeIds
*/
public function syncCollection( public function syncCollection(
HasMedia $model, HasMedia $model,
string $collection, string $collection,
@ -118,9 +115,7 @@ public function replaceSingleFile(
return $this->addUploadedFile($model, $file, $collection, $type); return $this->addUploadedFile($model, $file, $collection, $type);
} }
/**
* @return array{module: string, type?: string}
*/
private function customProperties(HasMedia $model, ?string $type): array private function customProperties(HasMedia $model, ?string $type): array
{ {
$properties = [ $properties = [

View File

@ -36,7 +36,7 @@ public function paginateForIndex(array $tableQuery, string $event = '', string $
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString() ->withQueryString()
->through(fn (Activity $activity) => [ ->through(fn (Activity $activity) => [
'id' => $activity->id, 'id' => $activity->id,

View File

@ -19,9 +19,7 @@
class DashboardService class DashboardService
{ {
/**
* @return array<string, mixed>|null
*/
public function getTodayAttendanceForUser(User $user): ?array public function getTodayAttendanceForUser(User $user): ?array
{ {
$employee = $user->employee; $employee = $user->employee;

View File

@ -6,27 +6,19 @@
class PushNotificationService class PushNotificationService
{ {
/**
* Send notification to users with specific roles.
*
* @param list<string> $roles
*/
public function sendToRoles(string $title, string $body, array $roles, string $url = '/admin/dashboard'): void public function sendToRoles(string $title, string $body, array $roles, string $url = '/admin/dashboard'): void
{ {
SendPushNotificationJob::dispatch($title, $body, $url, $roles); SendPushNotificationJob::dispatch($title, $body, $url, $roles);
} }
/**
* Send notification to a specific user.
*/
public function sendToUser(string $title, string $body, int $userId, string $url = '/admin/dashboard'): void public function sendToUser(string $title, string $body, int $userId, string $url = '/admin/dashboard'): void
{ {
SendPushNotificationJob::dispatch($title, $body, $url, [], $userId); SendPushNotificationJob::dispatch($title, $body, $url, [], $userId);
} }
/**
* Send notification to all users.
*/
public function sendToAll(string $title, string $body, string $url = '/admin/dashboard'): void public function sendToAll(string $title, string $body, string $url = '/admin/dashboard'): void
{ {
SendPushNotificationJob::dispatch($title, $body, $url, []); SendPushNotificationJob::dispatch($title, $body, $url, []);

View File

@ -6,11 +6,7 @@
class PushSubscriptionService class PushSubscriptionService
{ {
/**
* Store or update a push subscription.
*
* @param array<string, mixed> $validated
*/
public function updateOrCreateSubscription(array $validated, int $userId): void public function updateOrCreateSubscription(array $validated, int $userId): void
{ {
PushSubscription::updateOrCreate( PushSubscription::updateOrCreate(
@ -25,9 +21,7 @@ public function updateOrCreateSubscription(array $validated, int $userId): void
); );
} }
/**
* Delete a push subscription.
*/
public function deleteSubscription(string $endpoint, int $userId): void public function deleteSubscription(string $endpoint, int $userId): void
{ {
PushSubscription::where('user_id', $userId) PushSubscription::where('user_id', $userId)

View File

@ -28,7 +28,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query return $query
->paginate(10) ->paginate(25)
->withQueryString(); ->withQueryString();
} }

View File

@ -20,9 +20,7 @@ public function __construct(
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
/**
* @return list<string>
*/
private function tiktokFeeKeys(): array private function tiktokFeeKeys(): array
{ {
return [ return [
@ -35,9 +33,7 @@ private function tiktokFeeKeys(): array
]; ];
} }
/**
* @return list<string>
*/
private function shopeeFeeKeys(): array private function shopeeFeeKeys(): array
{ {
return [ return [
@ -157,10 +153,7 @@ public function applyVerificationRequest(OwnerVerificationRequest $request): voi
$this->saveSettings($newPayload); $this->saveSettings($newPayload);
} }
/**
* @param list<array{quantity: int, subtotal: int}> $lineItems
* @return array<string, mixed>|null
*/
public function buildOrderSnapshot(OrderChannel $channel, int $totalAmount, array $lineItems, bool $isAffiliate = false): ?array public function buildOrderSnapshot(OrderChannel $channel, int $totalAmount, array $lineItems, bool $isAffiliate = false): ?array
{ {
$feeSnapshot = $this->feeRulesForChannel($channel); $feeSnapshot = $this->feeRulesForChannel($channel);
@ -190,10 +183,7 @@ public function buildOrderSnapshot(OrderChannel $channel, int $totalAmount, arra
]; ];
} }
/**
* @param array<string, array{scope: string, value_type: string, value: float}> $fees
* @return array<string, array{scope: string, value_type: string, value: float}>
*/
private function excludeAffiliateFee(array $fees, OrderChannel $channel): array private function excludeAffiliateFee(array $fees, OrderChannel $channel): array
{ {
$affiliateKey = match ($channel) { $affiliateKey = match ($channel) {
@ -213,9 +203,7 @@ private function excludeAffiliateFee(array $fees, OrderChannel $channel): array
return $fees; return $fees;
} }
/**
* @return array{platform: string, fees: array<string, array{scope: string, value_type: string, value: float}>}|null
*/
private function feeRulesForChannel(OrderChannel $channel): ?array private function feeRulesForChannel(OrderChannel $channel): ?array
{ {
$settings = app(MarketplaceSettings::class); $settings = app(MarketplaceSettings::class);
@ -250,19 +238,13 @@ private function feeRulesForChannel(OrderChannel $channel): ?array
}; };
} }
/**
* @param array{scope: string, value_type: string, value: float|int|string} $data
* @return array{scope: string, value_type: string, value: float}
*/
private function presentFeeRule(array $data): array private function presentFeeRule(array $data): array
{ {
return MarketplaceFeeRule::fromArray($data)->toArray(); return MarketplaceFeeRule::fromArray($data)->toArray();
} }
/**
* @param array{scope: string, value_type: string, value: float|int|string} $data
* @return array{scope: string, value_type: string, value: float}
*/
private function normalizeFeeRule(array $data): array private function normalizeFeeRule(array $data): array
{ {
return MarketplaceFeeRule::fromArray($data)->toArray(); return MarketplaceFeeRule::fromArray($data)->toArray();