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:
parent
7a1c4efe4a
commit
b3790f2b2f
@ -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
|
||||
|
||||
### Struktur File Migrasi
|
||||
|
||||
@ -31,6 +31,17 @@ public function isEditable(): bool
|
||||
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
|
||||
{
|
||||
return match ($this) {
|
||||
|
||||
@ -29,6 +29,16 @@ public function isEditable(): bool
|
||||
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
|
||||
{
|
||||
return match ($this) {
|
||||
|
||||
@ -59,7 +59,7 @@ public function store(CuttingRequest $request): RedirectResponse
|
||||
public function edit(Cutting $cutting): Response|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->cuttingService->ensureEditable($cutting);
|
||||
$cutting->ensureEditable();
|
||||
} catch (ValidationException $exception) {
|
||||
$this->flashError($exception->validator->errors()->first('status'));
|
||||
|
||||
@ -105,7 +105,7 @@ public function transitionStatus(CuttingStatusTransitionRequest $request, Cuttin
|
||||
$request->validated('result_prices'),
|
||||
);
|
||||
|
||||
$this->flashSuccess($this->cuttingService->transitionStatusMessage($status));
|
||||
$this->flashSuccess($status->transitionStatusMessage());
|
||||
|
||||
return redirect()->route('admin.manage.cuttings.index');
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ public function show(Order $order): Response
|
||||
public function edit(Order $order): Response|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->orderService->ensureEditable($order);
|
||||
$order->ensureEditable();
|
||||
} catch (ValidationException $exception) {
|
||||
$this->flashError($exception->validator->errors()->first('status'));
|
||||
|
||||
@ -119,7 +119,7 @@ public function transitionStatus(OrderStatusTransitionRequest $request, Order $o
|
||||
|
||||
$this->orderService->transitionStatus($order, $status);
|
||||
|
||||
$this->flashSuccess($this->orderService->transitionStatusMessage($status));
|
||||
$this->flashSuccess($status->transitionStatusMessage());
|
||||
|
||||
return redirect()->route('admin.manage.orders.index');
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
#[Guarded(['id'])]
|
||||
#[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
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_id');
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
|
||||
#[Guarded(['id'])]
|
||||
@ -271,6 +272,15 @@ public static function mediaModuleName(): string
|
||||
return 'order';
|
||||
}
|
||||
|
||||
public function ensureEditable(): void
|
||||
{
|
||||
if (! $this->status->isEditable()) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => 'Pesanan tidak dapat diubah.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function registerMediaCollections(): void
|
||||
{
|
||||
$this->addMediaCollection('photos');
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
namespace App\Services\Account;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserProfile;
|
||||
use App\Services\Media\MediaService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
@ -16,9 +15,6 @@ public function __construct(
|
||||
private readonly MediaService $mediaService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
public function update(array $validated, User $user): void
|
||||
{
|
||||
try {
|
||||
@ -28,7 +24,6 @@ public function update(array $validated, User $user): void
|
||||
'username' => $validated['username'],
|
||||
]);
|
||||
|
||||
/** @var UserProfile $profile */
|
||||
$profile = $user->profile()->updateOrCreate(
|
||||
['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
|
||||
{
|
||||
$user->update([
|
||||
|
||||
@ -30,9 +30,7 @@ public function getDefaultAccount(): CashAccount
|
||||
return CashAccount::query()->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
|
||||
public function paginateForIndex(CashAccount $cashAccount, array $tableQuery, string $referenceType = ''): LengthAwarePaginator
|
||||
{
|
||||
$query = CashTransaction::query()
|
||||
@ -55,7 +53,7 @@ public function paginateForIndex(CashAccount $cashAccount, array $tableQuery, st
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString()
|
||||
->through(function (CashTransaction $transaction) {
|
||||
$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
|
||||
{
|
||||
try {
|
||||
@ -78,8 +74,7 @@ public function deposit(CashAccount $cashAccount, array $validated, User $user):
|
||||
$amount = (int) $validated['amount'];
|
||||
$newBalance = $account->balance + $amount;
|
||||
|
||||
$account->balance = $newBalance;
|
||||
$account->save();
|
||||
$account->update(['balance' => $newBalance]);
|
||||
|
||||
$transaction = CashTransaction::create([
|
||||
'cash_account_id' => $account->id,
|
||||
@ -116,9 +111,7 @@ public function deposit(CashAccount $cashAccount, array $validated, User $user):
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{amount: int, description: string} $validated
|
||||
*/
|
||||
|
||||
public function withdraw(CashAccount $cashAccount, array $validated, User $user): CashTransaction
|
||||
{
|
||||
try {
|
||||
@ -134,8 +127,7 @@ public function withdraw(CashAccount $cashAccount, array $validated, User $user)
|
||||
|
||||
$newBalance = $account->balance - $amount;
|
||||
|
||||
$account->balance = $newBalance;
|
||||
$account->save();
|
||||
$account->update(['balance' => $newBalance]);
|
||||
|
||||
$transaction = CashTransaction::create([
|
||||
'cash_account_id' => $account->id,
|
||||
@ -193,8 +185,7 @@ public function recordOutgoing(
|
||||
|
||||
$newBalance = $account->balance - $amount;
|
||||
|
||||
$account->balance = $newBalance;
|
||||
$account->save();
|
||||
$account->update(['balance' => $newBalance]);
|
||||
|
||||
$transaction = new CashTransaction([
|
||||
'cash_account_id' => $account->id,
|
||||
@ -238,8 +229,7 @@ public function recordIncoming(
|
||||
|
||||
$newBalance = $account->balance + $amount;
|
||||
|
||||
$account->balance = $newBalance;
|
||||
$account->save();
|
||||
$account->update(['balance' => $newBalance]);
|
||||
|
||||
$transaction = new CashTransaction([
|
||||
'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
|
||||
{
|
||||
$this->ensureEditable($transaction);
|
||||
@ -279,9 +267,10 @@ public function updateDeposit(CashTransaction $transaction, array $validated): v
|
||||
DB::transaction(function () use ($transaction, $validated): void {
|
||||
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id);
|
||||
|
||||
$transaction->amount = (int) $validated['amount'];
|
||||
$transaction->description = $validated['description'];
|
||||
$transaction->save();
|
||||
$transaction->update([
|
||||
'amount' => (int) $validated['amount'],
|
||||
'description' => $validated['description'],
|
||||
]);
|
||||
|
||||
$this->syncPhotos($transaction, $validated);
|
||||
|
||||
@ -365,9 +354,10 @@ public function updateReferencedTransaction(
|
||||
DB::transaction(function () use ($transaction, $amount, $description): void {
|
||||
CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id);
|
||||
|
||||
$transaction->amount = $amount;
|
||||
$transaction->description = $description;
|
||||
$transaction->save();
|
||||
$transaction->update([
|
||||
'amount' => $amount,
|
||||
'description' => $description,
|
||||
]);
|
||||
|
||||
$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
|
||||
{
|
||||
$this->mediaService->syncCollection(
|
||||
@ -466,12 +454,10 @@ private function recalculateBalances(CashAccount $cashAccount): void
|
||||
$runningBalance -= $transaction->amount;
|
||||
}
|
||||
|
||||
$transaction->balance_after = $runningBalance;
|
||||
$transaction->saveQuietly();
|
||||
$transaction->updateQuietly(['balance_after' => $runningBalance]);
|
||||
}
|
||||
|
||||
$account->balance = $runningBalance;
|
||||
$account->save();
|
||||
$account->update(['balance' => $runningBalance]);
|
||||
}
|
||||
|
||||
private function applySorting(Builder $query, string $sort, string $direction): void
|
||||
|
||||
@ -25,9 +25,7 @@ public function __construct(
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array{outstanding_amount: int, outstanding_amount_formatted: string, outstanding_count: int}
|
||||
*/
|
||||
|
||||
public function outstandingSummary(?User $user = null): array
|
||||
{
|
||||
$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
|
||||
{
|
||||
$query = EmployeeAdvance::query()
|
||||
@ -70,13 +66,11 @@ public function paginateForIndex(array $tableQuery, User $user, string $status =
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{amount: int, description: string, due_date: string} $validated
|
||||
*/
|
||||
|
||||
public function create(array $validated, User $user): void
|
||||
{
|
||||
$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
|
||||
{
|
||||
try {
|
||||
|
||||
@ -23,9 +23,7 @@ public function __construct(
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
|
||||
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
{
|
||||
$query = Expense::query()
|
||||
@ -40,7 +38,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString()
|
||||
->through(function (Expense $expense) {
|
||||
$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
|
||||
{
|
||||
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
|
||||
{
|
||||
try {
|
||||
@ -179,9 +173,7 @@ public function delete(Expense $expense): void
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
private function syncPhotos(Expense $expense, array $validated): void
|
||||
{
|
||||
$this->mediaService->syncCollection(
|
||||
|
||||
@ -29,9 +29,7 @@ public function __construct(
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return Collection<int, PayrollPeriod>
|
||||
*/
|
||||
|
||||
public function listPeriods(): Collection
|
||||
{
|
||||
return PayrollPeriod::query()
|
||||
@ -57,9 +55,7 @@ public function resolvePeriod(?int $periodId): ?PayrollPeriod
|
||||
->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
|
||||
{
|
||||
$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
|
||||
{
|
||||
$query = Payroll::query()
|
||||
@ -106,7 +100,7 @@ public function paginateForPeriod(PayrollPeriod $period, array $tableQuery, User
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->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
|
||||
{
|
||||
|
||||
@ -404,9 +396,7 @@ public function pay(Payroll $payroll, User $user): void
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Employee>
|
||||
*/
|
||||
|
||||
private function payrollEligibleEmployees(PayrollPeriod $period): Collection
|
||||
{
|
||||
$periodStart = Carbon::create($period->year, $period->month, 1)->startOfMonth();
|
||||
|
||||
@ -46,9 +46,7 @@ public function listForCalendar(
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
|
||||
public function todayAttendanceForEmployee(Employee $employee): ?array
|
||||
{
|
||||
$attendance = Attendance::query()
|
||||
@ -70,9 +68,7 @@ public function isOnLeaveToday(Employee $employee): bool
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{photo: string, latitude: float, longitude: float} $validated
|
||||
*/
|
||||
|
||||
public function checkIn(array $validated, User $user): void
|
||||
{
|
||||
$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
|
||||
{
|
||||
$employee = $this->resolveAuthenticatedEmployee($user);
|
||||
|
||||
@ -21,9 +21,7 @@ public function __construct(
|
||||
private readonly MediaService $mediaService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
|
||||
public function paginateForIndex(
|
||||
array $tableQuery,
|
||||
string $role = '',
|
||||
@ -55,7 +53,7 @@ public function paginateForIndex(
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
@ -76,9 +74,7 @@ public function findForEdit(User $user): array
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function create(array $validated): void
|
||||
{
|
||||
try {
|
||||
@ -124,9 +120,7 @@ public function create(array $validated): void
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function update(User $user, array $validated): void
|
||||
{
|
||||
$employee = $user->employee;
|
||||
@ -138,7 +132,7 @@ public function update(User $user, array $validated): void
|
||||
'username' => $validated['username'],
|
||||
]);
|
||||
|
||||
/** @var UserProfile $profile */
|
||||
|
||||
$profile = $user->profile()->updateOrCreate(
|
||||
['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
|
||||
{
|
||||
$newFiles = ! empty($validated['profile_photo']) ? [$validated['profile_photo']] : [];
|
||||
|
||||
@ -23,9 +23,7 @@ public function __construct(
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
|
||||
public function paginateForIndex(array $tableQuery, User $user): LengthAwarePaginator
|
||||
{
|
||||
$query = LeaveRequest::query()
|
||||
@ -46,13 +44,11 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{start_date: string, end_date: string} $validated
|
||||
*/
|
||||
|
||||
public function create(array $validated, User $user): void
|
||||
{
|
||||
$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
|
||||
{
|
||||
$startDate = Carbon::parse($validated['start_date'])->startOfDay();
|
||||
|
||||
@ -20,9 +20,7 @@ public function resolve(int $productVariantId, PriceType $priceType): ?CuttingRe
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<CuttingResultPrice>
|
||||
*/
|
||||
|
||||
public function latestPricesForVariant(int $productVariantId): array
|
||||
{
|
||||
$prices = [];
|
||||
@ -38,14 +36,7 @@ public function latestPricesForVariant(int $productVariantId): array
|
||||
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
|
||||
{
|
||||
if (empty($variantIds)) {
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
namespace App\Services\Manage;
|
||||
|
||||
use App\Enums\CuttingStatus;
|
||||
use App\Enums\Permission;
|
||||
use App\Models\Cutting;
|
||||
use App\Models\CuttingMaterial;
|
||||
use App\Models\CuttingResult;
|
||||
@ -29,52 +28,6 @@ public function __construct(
|
||||
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
|
||||
{
|
||||
$query = Cutting::query()
|
||||
@ -103,25 +56,23 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString()
|
||||
->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']))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$cutting->setAttribute('available_actions', $actions);
|
||||
$cutting->setAttribute('is_editable', $this->isEditable($cutting->status));
|
||||
$cutting->setAttribute('is_editable', $cutting->status->isEditable());
|
||||
$this->appendCostPreview($cutting);
|
||||
|
||||
return $cutting;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Cutting>
|
||||
*/
|
||||
|
||||
public function getInProgressCuttings(User $user): Collection
|
||||
{
|
||||
return Cutting::query()
|
||||
@ -136,20 +87,18 @@ public function getInProgressCuttings(User $user): Collection
|
||||
->latest()
|
||||
->get()
|
||||
->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']))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$cutting->setAttribute('available_actions', $actions);
|
||||
$cutting->setAttribute('is_editable', $this->isEditable($cutting->status));
|
||||
$cutting->setAttribute('is_editable', $cutting->status->isEditable());
|
||||
$this->appendCostPreview($cutting);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Cutting>
|
||||
*/
|
||||
|
||||
public function getCompletedCuttings(User $user): Collection
|
||||
{
|
||||
return Cutting::query()
|
||||
@ -164,20 +113,18 @@ public function getCompletedCuttings(User $user): Collection
|
||||
->latest()
|
||||
->get()
|
||||
->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']))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$cutting->setAttribute('available_actions', $actions);
|
||||
$cutting->setAttribute('is_editable', $this->isEditable($cutting->status));
|
||||
$cutting->setAttribute('is_editable', $cutting->status->isEditable());
|
||||
$this->appendCostPreview($cutting);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, RawMaterial>
|
||||
*/
|
||||
|
||||
public function rawMaterialCatalog(?Cutting $cutting = null, ?User $user = null): Collection
|
||||
{
|
||||
$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
|
||||
{
|
||||
$selectedVariantIds = $cutting
|
||||
@ -284,9 +229,7 @@ public function findForEdit(Cutting $cutting): Cutting
|
||||
return $cutting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
|
||||
public function draftMaterialsForUser(User $user): array
|
||||
{
|
||||
return $this->draftMaterialsQuery($user)
|
||||
@ -300,9 +243,7 @@ public function draftMaterialsForUser(User $user): array
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
|
||||
public function draftResultsForUser(User $user): array
|
||||
{
|
||||
return $this->draftResultsQuery($user)
|
||||
@ -316,10 +257,7 @@ public function draftResultsForUser(User $user): array
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
public function syncDraftMaterial(array $validated, User $user): array
|
||||
{
|
||||
$price = RawMaterialPrice::query()
|
||||
@ -353,10 +291,7 @@ public function syncDraftMaterial(array $validated, User $user): array
|
||||
return $this->presentDraftMaterial($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
public function syncDraftResult(array $validated, User $user): array
|
||||
{
|
||||
$variant = ProductVariant::query()
|
||||
@ -430,20 +365,18 @@ public function removeDraftResult(User $user, ProductVariant $productVariant): v
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function create(array $validated, User $user): Cutting
|
||||
{
|
||||
try {
|
||||
$cutting = DB::transaction(function () use ($validated, $user): Cutting {
|
||||
/** @var EloquentCollection<int, CuttingMaterial> $draftMaterials */
|
||||
|
||||
$draftMaterials = $this->draftMaterialsQuery($user)
|
||||
->with('rawMaterialPrice.rawMaterial')
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
|
||||
/** @var EloquentCollection<int, CuttingResult> $draftResults */
|
||||
|
||||
$draftResults = $this->draftResultsQuery($user)
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
@ -507,12 +440,10 @@ public function create(array $validated, User $user): Cutting
|
||||
return $cutting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function update(Cutting $cutting, array $validated): void
|
||||
{
|
||||
if (! $this->isEditable($cutting->status)) {
|
||||
if (! $cutting->status->isEditable()) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => 'Proses cutting tidak dapat diubah.',
|
||||
]);
|
||||
@ -630,7 +561,7 @@ public function transitionStatus(
|
||||
?array $results = null,
|
||||
?array $resultPrices = null,
|
||||
): void {
|
||||
if (! $this->canTransitionTo($cutting->status, $status)) {
|
||||
if (! $cutting->status->canTransitionTo($status)) {
|
||||
throw ValidationException::withMessages([
|
||||
'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
|
||||
{
|
||||
return collect($materials)
|
||||
@ -754,10 +682,7 @@ private function buildMaterials(array $materials): array
|
||||
->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
|
||||
{
|
||||
return collect($results)
|
||||
@ -909,9 +834,7 @@ private function applySorting(Builder $query, string $sort, string $direction):
|
||||
$query->latest();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder<CuttingMaterial>
|
||||
*/
|
||||
|
||||
private function draftMaterialsQuery(User $user): Builder
|
||||
{
|
||||
return CuttingMaterial::query()
|
||||
@ -919,9 +842,7 @@ private function draftMaterialsQuery(User $user): Builder
|
||||
->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder<CuttingResult>
|
||||
*/
|
||||
|
||||
private function draftResultsQuery(User $user): Builder
|
||||
{
|
||||
return CuttingResult::query()
|
||||
@ -929,9 +850,7 @@ private function draftResultsQuery(User $user): Builder
|
||||
->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function presentDraftMaterial(CuttingMaterial $item): array
|
||||
{
|
||||
$price = $item->rawMaterialPrice;
|
||||
@ -949,9 +868,7 @@ private function presentDraftMaterial(CuttingMaterial $item): array
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function presentDraftResult(CuttingResult $item): array
|
||||
{
|
||||
$variant = $item->productVariant;
|
||||
@ -1019,9 +936,7 @@ public function calculateCostPerUnit(Cutting $cutting): int
|
||||
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
|
||||
{
|
||||
$costPerUnit = (int) ($cutting->cost_per_unit ?? $this->calculateCostPerUnit($cutting));
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
use App\Enums\OrderChannel;
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Enums\PaymentType;
|
||||
use App\Enums\Permission;
|
||||
use App\Enums\PriceType;
|
||||
use App\Enums\ProductStockQuality;
|
||||
use App\Models\Customer;
|
||||
@ -39,48 +38,6 @@ public function __construct(
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
$query = Order::query()
|
||||
@ -131,24 +86,22 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString()
|
||||
->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']))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$order->setAttribute('available_actions', $actions);
|
||||
$order->setAttribute('is_editable', $this->isEditable($order->status));
|
||||
$order->setAttribute('is_editable', $order->status->isEditable());
|
||||
|
||||
return $order;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{value: int, label: string}>
|
||||
*/
|
||||
|
||||
public function customerOptions(): array
|
||||
{
|
||||
return Customer::query()
|
||||
@ -161,9 +114,7 @@ public function customerOptions(): array
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{value: int, label: string}>
|
||||
*/
|
||||
|
||||
public function marketingOptions(): array
|
||||
{
|
||||
return User::query()
|
||||
@ -179,9 +130,7 @@ public function marketingOptions(): array
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{value: string, label: string}>
|
||||
*/
|
||||
|
||||
public function storePriceTypeOptions(): array
|
||||
{
|
||||
return collect(PriceType::cases())
|
||||
@ -194,9 +143,7 @@ public function storePriceTypeOptions(): array
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Product>
|
||||
*/
|
||||
|
||||
public function catalogItems(?Order $order = null, ?User $user = null): Collection
|
||||
{
|
||||
$orderVariantIds = $order
|
||||
@ -318,19 +265,17 @@ public function findForShow(Order $order): Order
|
||||
}
|
||||
});
|
||||
|
||||
$availableActions = collect($this->availableActions($order->status))
|
||||
$availableActions = collect($order->status->availableActions())
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$order->setAttribute('available_actions', $availableActions);
|
||||
$order->setAttribute('is_editable', $this->isEditable($order->status));
|
||||
$order->setAttribute('is_editable', $order->status->isEditable());
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
|
||||
public function draftItemsForUser(User $user): array
|
||||
{
|
||||
return $this->draftItemsQuery($user)
|
||||
@ -344,10 +289,7 @@ public function draftItemsForUser(User $user): array
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
public function syncDraftItem(array $validated, User $user): array
|
||||
{
|
||||
// Force retail price type and stock quality for cashier role
|
||||
@ -405,9 +347,7 @@ public function removeDraftItem(User $user, ProductVariant $productVariant, Prod
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
|
||||
public function resyncDraftPrices(User $user, string $priceTypeValue): array
|
||||
{
|
||||
// Force retail price type for cashier role
|
||||
@ -440,9 +380,7 @@ public function resyncDraftPrices(User $user, string $priceTypeValue): array
|
||||
return $this->draftItemsForUser($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function create(array $validated, User $user): Order
|
||||
{
|
||||
try {
|
||||
@ -457,7 +395,7 @@ public function create(array $validated, User $user): Order
|
||||
$order = DB::transaction(function () use ($validated, $user): Order {
|
||||
$priceType = $this->resolvePriceType($validated['channel'], $validated['price_type']);
|
||||
|
||||
/** @var EloquentCollection<int, OrderItem> $draftItems */
|
||||
|
||||
$draftItems = $this->draftItemsQuery($user)
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
@ -563,16 +501,10 @@ public function create(array $validated, User $user): Order
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function update(Order $order, array $validated): void
|
||||
{
|
||||
if (! $this->isEditable($order->status)) {
|
||||
throw ValidationException::withMessages([
|
||||
'status' => 'Pesanan tidak dapat diubah.',
|
||||
]);
|
||||
}
|
||||
$order->ensureEditable();
|
||||
|
||||
try {
|
||||
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;
|
||||
$totalAmount = $negoPrice !== null ? max($negoPrice, 0) : max($subtotal - $discount, 0);
|
||||
$channel = OrderChannel::from($validated['channel']);
|
||||
|
||||
$order->customer_id = $validated['customer_id'] ?? null;
|
||||
$order->marketing_id = $validated['marketing_id'] ?? null;
|
||||
$order->channel = $channel;
|
||||
$order->price_type = $priceType;
|
||||
$order->payment_type = PaymentType::from($validated['payment_type']);
|
||||
$order->is_affiliate = $validated['is_affiliate'] ?? false;
|
||||
$order->tiktok_order_id = $validated['tiktok_order_id'] ?? null;
|
||||
$order->shopee_order_id = $validated['shopee_order_id'] ?? null;
|
||||
$order->subtotal = $subtotal;
|
||||
$order->discount = $discount;
|
||||
$order->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;
|
||||
$status = $order->status;
|
||||
|
||||
if (isset($validated['status'])) {
|
||||
$newStatus = OrderStatus::from($validated['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);
|
||||
|
||||
@ -673,7 +607,7 @@ public function delete(Order $order): void
|
||||
DB::transaction(function () use ($order): void {
|
||||
$order->load('items');
|
||||
|
||||
if ($this->isEditable($order->status)) {
|
||||
if ($order->status->isEditable()) {
|
||||
foreach ($order->items as $item) {
|
||||
$this->incrementStock($item);
|
||||
}
|
||||
@ -708,7 +642,7 @@ public function delete(Order $order): void
|
||||
|
||||
public function transitionStatus(Order $order, OrderStatus $status): void
|
||||
{
|
||||
if (! $this->canTransitionTo($order->status, $status)) {
|
||||
if (! $order->status->canTransitionTo($status)) {
|
||||
throw ValidationException::withMessages([
|
||||
'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
|
||||
{
|
||||
return collect($items)
|
||||
@ -814,9 +745,7 @@ private function resolvePriceType(string $channel, string $priceType): PriceType
|
||||
return $priceTypeEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder<OrderItem>
|
||||
*/
|
||||
|
||||
private function draftItemsQuery(User $user): Builder
|
||||
{
|
||||
return OrderItem::query()
|
||||
@ -824,9 +753,7 @@ private function draftItemsQuery(User $user): Builder
|
||||
->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function presentDraftItem(OrderItem $item): array
|
||||
{
|
||||
$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
|
||||
{
|
||||
foreach ($items as $index => $item) {
|
||||
@ -884,9 +809,7 @@ private function availableStock(ProductVariant $variant, ProductStockQuality $st
|
||||
return (int) $variant->{$this->stockColumn($stockQuality)};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
private function syncPhotos(Order $order, array $validated): void
|
||||
{
|
||||
$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
|
||||
{
|
||||
return collect($items)
|
||||
@ -942,17 +862,7 @@ private function resolveUnitPrice(int $variantId, PriceType $priceType, ?string
|
||||
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
|
||||
{
|
||||
return collect($this->cuttingResultPriceResolver->latestPricesForVariant($variantId))
|
||||
|
||||
@ -47,9 +47,7 @@ public function hasPendingMarketplaceVerification(): bool
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
|
||||
public function paginateForIndex(
|
||||
User $user,
|
||||
array $tableQuery,
|
||||
@ -57,7 +55,7 @@ public function paginateForIndex(
|
||||
string $subjectType = '',
|
||||
string $action = '',
|
||||
): LengthAwarePaginator {
|
||||
$perPage = 10;
|
||||
$perPage = 25;
|
||||
$page = Paginator::resolveCurrentPage();
|
||||
$includeCuttings = $this->shouldIncludeCuttings($user, $status, $subjectType, $action);
|
||||
$cuttingRows = $includeCuttings
|
||||
@ -103,9 +101,7 @@ public function paginateForIndex(
|
||||
->through(fn (OwnerVerificationRequest $request) => $this->presentRequestRow($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, array<string, mixed>>
|
||||
*/
|
||||
|
||||
private function pendingCuttingRows(User $user, array $tableQuery): Collection
|
||||
{
|
||||
$rows = $this->stockService
|
||||
@ -147,9 +143,7 @@ public function pendingCountForUser(User $user): int
|
||||
return $requestCount + Cutting::query()->pendingVerification()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{value: string, label: string}>
|
||||
*/
|
||||
|
||||
public function subjectTypeOptions(User $user): array
|
||||
{
|
||||
$options = OwnerVerificationRequest::query()
|
||||
@ -323,9 +317,7 @@ private function clearVerificationRequestMedia(OwnerVerificationRequest $request
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function presentRequestRow(OwnerVerificationRequest $request): array
|
||||
{
|
||||
$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
|
||||
{
|
||||
$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(
|
||||
User $user,
|
||||
array $tableQuery,
|
||||
@ -466,9 +454,7 @@ private function shouldIncludeCuttings(
|
||||
return Cutting::query()->pendingVerification()->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, array<string, mixed>> $items
|
||||
*/
|
||||
|
||||
private function makePaginator(
|
||||
Collection $items,
|
||||
int $total,
|
||||
|
||||
@ -31,9 +31,7 @@ public function __construct(
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
|
||||
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
{
|
||||
$query = Purchase::query()
|
||||
@ -59,7 +57,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString()
|
||||
->through(function (Purchase $purchase) {
|
||||
$purchase->setAttribute(
|
||||
@ -78,9 +76,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{value: int, label: string}>
|
||||
*/
|
||||
|
||||
public function supplierOptions(): array
|
||||
{
|
||||
return Supplier::query()
|
||||
@ -93,9 +89,7 @@ public function supplierOptions(): array
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, RawMaterial>
|
||||
*/
|
||||
|
||||
public function catalogItems(?Purchase $purchase = null, ?User $user = null): Collection
|
||||
{
|
||||
$purchasePriceIds = $purchase
|
||||
@ -156,9 +150,7 @@ public function findForEdit(Purchase $purchase): Purchase
|
||||
return $purchase;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
|
||||
public function draftItemsForUser(User $user): array
|
||||
{
|
||||
return $this->draftItemsQuery($user)
|
||||
@ -172,10 +164,7 @@ public function draftItemsForUser(User $user): array
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
public function syncDraftItem(array $validated, User $user): array
|
||||
{
|
||||
$price = RawMaterialPrice::query()
|
||||
@ -216,14 +205,12 @@ public function removeDraftItem(User $user, RawMaterialPrice $rawMaterialPrice):
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function create(array $validated, User $user): Purchase
|
||||
{
|
||||
try {
|
||||
$purchase = DB::transaction(function () use ($validated, $user): Purchase {
|
||||
/** @var EloquentCollection<int, PurchaseItem> $draftItems */
|
||||
|
||||
$draftItems = $this->draftItemsQuery($user)
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
@ -295,9 +282,7 @@ public function create(array $validated, User $user): Purchase
|
||||
return $purchase;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function update(Purchase $purchase, array $validated, User $user): void
|
||||
{
|
||||
$purchase->load(['supplier', 'items.rawMaterialPrice.rawMaterial:id,name']);
|
||||
@ -449,10 +434,7 @@ public function applyDelete(OwnerVerificationRequest $verificationRequest): void
|
||||
$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
|
||||
{
|
||||
return collect($items)
|
||||
@ -479,9 +461,7 @@ private function buildLineItems(array $items): array
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
private function syncPhotos(Purchase $purchase, array $validated): void
|
||||
{
|
||||
$this->mediaService->syncCollection(
|
||||
@ -495,9 +475,7 @@ private function syncPhotos(Purchase $purchase, array $validated): void
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder<PurchaseItem>
|
||||
*/
|
||||
|
||||
private function draftItemsQuery(User $user): Builder
|
||||
{
|
||||
return PurchaseItem::query()
|
||||
@ -505,9 +483,7 @@ private function draftItemsQuery(User $user): Builder
|
||||
->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function presentDraftItem(PurchaseItem $item): array
|
||||
{
|
||||
$price = $item->rawMaterialPrice;
|
||||
@ -585,9 +561,7 @@ private function executeDelete(Purchase $purchase): void
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
|
||||
private function applyPayloadToPurchase(
|
||||
Purchase $purchase,
|
||||
array $payload,
|
||||
@ -627,9 +601,7 @@ private function applyPayloadToPurchase(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function snapshotPurchase(Purchase $purchase): array
|
||||
{
|
||||
$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
|
||||
{
|
||||
$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
|
||||
{
|
||||
$prices = RawMaterialPrice::query()
|
||||
@ -709,9 +675,7 @@ private function enrichLineItems(array $lineItems): array
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
private function syncRequestPhotos(OwnerVerificationRequest $verificationRequest, array $validated): void
|
||||
{
|
||||
$this->mediaService->syncCollection(
|
||||
@ -725,9 +689,7 @@ private function syncRequestPhotos(OwnerVerificationRequest $verificationRequest
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
|
||||
private function applyRequestPhotos(
|
||||
OwnerVerificationRequest $verificationRequest,
|
||||
Purchase $purchase,
|
||||
@ -748,9 +710,7 @@ private function applyRequestPhotos(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function payloadNew(OwnerVerificationRequest $verificationRequest): array
|
||||
{
|
||||
$payload = $verificationRequest->payload ?? [];
|
||||
|
||||
@ -19,9 +19,7 @@ public function __construct(
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
$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
|
||||
{
|
||||
// No-op: nothing was changed yet, so nothing to rollback.
|
||||
|
||||
@ -20,11 +20,7 @@ public function __construct(
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get all completed cuttings pending verification.
|
||||
*
|
||||
* @return Collection<int, Cutting>
|
||||
*/
|
||||
|
||||
public function getPendingVerificationCuttings(User $user): Collection
|
||||
{
|
||||
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
|
||||
{
|
||||
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(
|
||||
Cutting $cutting,
|
||||
User $user,
|
||||
@ -138,9 +125,7 @@ public function submitVerification(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner approves verification - adds stock to store.
|
||||
*/
|
||||
|
||||
public function approveVerification(
|
||||
Cutting $cutting,
|
||||
User $user,
|
||||
@ -191,9 +176,7 @@ public function approveVerification(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner rejects verification - sends back to completed.
|
||||
*/
|
||||
|
||||
public function rejectVerification(
|
||||
Cutting $cutting,
|
||||
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
|
||||
{
|
||||
// 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
|
||||
{
|
||||
$resultPrices = $cutting->resultPrices()->with('productVariant')->get();
|
||||
|
||||
@ -19,9 +19,7 @@ public function __construct(
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
|
||||
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
{
|
||||
$query = StokOpname::query()
|
||||
@ -42,13 +40,11 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product variants with current stock for the opname form.
|
||||
*/
|
||||
|
||||
public function catalogItems(): array
|
||||
{
|
||||
return Product::query()
|
||||
@ -72,9 +68,7 @@ public function catalogItems(): array
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stok opname with items for editing.
|
||||
*/
|
||||
|
||||
public function findForEdit(StokOpname $stokOpname): array
|
||||
{
|
||||
$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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
$stokOpname->items()->delete();
|
||||
|
||||
@ -21,7 +21,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
|
||||
@ -27,9 +27,7 @@ public function __construct(
|
||||
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
|
||||
{
|
||||
$query = Product::query()
|
||||
@ -64,7 +62,7 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $ca
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString()
|
||||
->through(function (Product $product) {
|
||||
$product->variants->each(function (ProductVariant $variant): void {
|
||||
@ -116,9 +114,7 @@ public function findForEdit(Product $product): Product
|
||||
return $product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function create(array $validated, User $user): void
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
try {
|
||||
@ -356,9 +348,7 @@ public function applyUpdate(OwnerVerificationRequest $verificationRequest): void
|
||||
$this->applyPayloadToProduct($product, $this->payloadNew($verificationRequest), $verificationRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
|
||||
private function applyPayloadToProduct(
|
||||
Product $product,
|
||||
array $payload,
|
||||
@ -510,9 +500,7 @@ private function rollbackUpdate(OwnerVerificationRequest $verificationRequest):
|
||||
$this->applyPayloadToProduct($product, $oldPayload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function payloadOld(OwnerVerificationRequest $verificationRequest): array
|
||||
{
|
||||
$payload = $verificationRequest->payload ?? [];
|
||||
@ -524,9 +512,7 @@ private function payloadOld(OwnerVerificationRequest $verificationRequest): arra
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function payloadNew(OwnerVerificationRequest $verificationRequest): array
|
||||
{
|
||||
$payload = $verificationRequest->payload ?? [];
|
||||
@ -538,9 +524,7 @@ private function payloadNew(OwnerVerificationRequest $verificationRequest): arra
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function snapshotProduct(Product $product): array
|
||||
{
|
||||
$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
|
||||
{
|
||||
$categoryIds = $data['category_ids'] ?? [];
|
||||
@ -576,10 +557,7 @@ private function enrichPayload(array $data): array
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function buildPayloadFromValidated(array $validated): array
|
||||
{
|
||||
return [
|
||||
@ -598,9 +576,7 @@ private function buildPayloadFromValidated(array $validated): array
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $variantData
|
||||
*/
|
||||
|
||||
private function syncVariantImages(
|
||||
ProductVariant $variant,
|
||||
array $variantData,
|
||||
@ -617,9 +593,7 @@ private function syncVariantImages(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $variantData
|
||||
*/
|
||||
|
||||
private function syncRequestVariantImages(
|
||||
OwnerVerificationRequest $verificationRequest,
|
||||
array $variantData,
|
||||
@ -639,9 +613,7 @@ private function syncRequestVariantImages(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $variantData
|
||||
*/
|
||||
|
||||
private function applyVariantImageChanges(
|
||||
OwnerVerificationRequest $verificationRequest,
|
||||
ProductVariant $variant,
|
||||
|
||||
@ -27,9 +27,7 @@ public function __construct(
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
|
||||
public function paginateForIndex(array $tableQuery, string $isActive, string $stockStatus = ''): LengthAwarePaginator
|
||||
{
|
||||
$query = RawMaterial::query()
|
||||
@ -72,7 +70,7 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $st
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString()
|
||||
->through(function (RawMaterial $rawMaterial) {
|
||||
$rawMaterial->prices->each(function (RawMaterialPrice $price): void {
|
||||
@ -110,9 +108,7 @@ public function findForEdit(RawMaterial $rawMaterial): RawMaterial
|
||||
return $rawMaterial;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function create(array $validated, User $user): void
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
try {
|
||||
@ -395,9 +387,7 @@ public function applyToggleStatus(OwnerVerificationRequest $verificationRequest)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
|
||||
private function applyPayloadToRawMaterial(
|
||||
RawMaterial $rawMaterial,
|
||||
array $payload,
|
||||
@ -492,9 +482,7 @@ private function rollbackUpdate(OwnerVerificationRequest $verificationRequest):
|
||||
$this->applyPayloadToRawMaterial($rawMaterial, $oldPayload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function payloadOld(OwnerVerificationRequest $verificationRequest): array
|
||||
{
|
||||
$payload = $verificationRequest->payload ?? [];
|
||||
@ -506,9 +494,7 @@ private function payloadOld(OwnerVerificationRequest $verificationRequest): arra
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function payloadNew(OwnerVerificationRequest $verificationRequest): array
|
||||
{
|
||||
$payload = $verificationRequest->payload ?? [];
|
||||
@ -520,9 +506,7 @@ private function payloadNew(OwnerVerificationRequest $verificationRequest): arra
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function snapshotRawMaterial(RawMaterial $rawMaterial): array
|
||||
{
|
||||
$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
|
||||
{
|
||||
if (isset($data['unit'])) {
|
||||
@ -560,10 +541,7 @@ private function enrichPayload(array $data): array
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
private function buildPayloadFromValidated(array $validated): array
|
||||
{
|
||||
return [
|
||||
@ -592,9 +570,7 @@ private function applySorting(Builder $query, string $sort, string $direction):
|
||||
$query->latest();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $priceData
|
||||
*/
|
||||
|
||||
private function createPrice(RawMaterial $rawMaterial, array $priceData, int $index): RawMaterialPrice
|
||||
{
|
||||
$price = $rawMaterial->prices()->create([
|
||||
@ -608,9 +584,7 @@ private function createPrice(RawMaterial $rawMaterial, array $priceData, int $in
|
||||
return $price;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $priceData
|
||||
*/
|
||||
|
||||
private function syncPriceImages(RawMaterialPrice $price, array $priceData, int $index): void
|
||||
{
|
||||
$this->mediaService->syncCollection(
|
||||
@ -624,9 +598,7 @@ private function syncPriceImages(RawMaterialPrice $price, array $priceData, int
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $priceData
|
||||
*/
|
||||
|
||||
private function syncRequestPriceImages(
|
||||
OwnerVerificationRequest $verificationRequest,
|
||||
array $priceData,
|
||||
@ -646,9 +618,7 @@ private function syncRequestPriceImages(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $priceData
|
||||
*/
|
||||
|
||||
private function applyPriceImageChanges(
|
||||
OwnerVerificationRequest $verificationRequest,
|
||||
RawMaterialPrice $price,
|
||||
|
||||
@ -23,7 +23,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
|
||||
@ -12,10 +12,7 @@
|
||||
|
||||
class MediaService
|
||||
{
|
||||
/**
|
||||
* @param list<UploadedFile>|null $newFiles
|
||||
* @param list<int>|null $removeIds
|
||||
*/
|
||||
|
||||
public function syncCollection(
|
||||
HasMedia $model,
|
||||
string $collection,
|
||||
@ -118,9 +115,7 @@ public function replaceSingleFile(
|
||||
return $this->addUploadedFile($model, $file, $collection, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{module: string, type?: string}
|
||||
*/
|
||||
|
||||
private function customProperties(HasMedia $model, ?string $type): array
|
||||
{
|
||||
$properties = [
|
||||
|
||||
@ -36,7 +36,7 @@ public function paginateForIndex(array $tableQuery, string $event = '', string $
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString()
|
||||
->through(fn (Activity $activity) => [
|
||||
'id' => $activity->id,
|
||||
|
||||
@ -19,9 +19,7 @@
|
||||
|
||||
class DashboardService
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
|
||||
public function getTodayAttendanceForUser(User $user): ?array
|
||||
{
|
||||
$employee = $user->employee;
|
||||
|
||||
@ -6,27 +6,19 @@
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
SendPushNotificationJob::dispatch($title, $body, $url, [], $userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification to all users.
|
||||
*/
|
||||
|
||||
public function sendToAll(string $title, string $body, string $url = '/admin/dashboard'): void
|
||||
{
|
||||
SendPushNotificationJob::dispatch($title, $body, $url, []);
|
||||
|
||||
@ -6,11 +6,7 @@
|
||||
|
||||
class PushSubscriptionService
|
||||
{
|
||||
/**
|
||||
* Store or update a push subscription.
|
||||
*
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
|
||||
public function updateOrCreateSubscription(array $validated, int $userId): void
|
||||
{
|
||||
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
|
||||
{
|
||||
PushSubscription::where('user_id', $userId)
|
||||
|
||||
@ -28,7 +28,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->paginate(25)
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
|
||||
@ -20,9 +20,7 @@ public function __construct(
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
|
||||
private function tiktokFeeKeys(): array
|
||||
{
|
||||
return [
|
||||
@ -35,9 +33,7 @@ private function tiktokFeeKeys(): array
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
|
||||
private function shopeeFeeKeys(): array
|
||||
{
|
||||
return [
|
||||
@ -157,10 +153,7 @@ public function applyVerificationRequest(OwnerVerificationRequest $request): voi
|
||||
$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
|
||||
{
|
||||
$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
|
||||
{
|
||||
$affiliateKey = match ($channel) {
|
||||
@ -213,9 +203,7 @@ private function excludeAffiliateFee(array $fees, OrderChannel $channel): array
|
||||
return $fees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{platform: string, fees: array<string, array{scope: string, value_type: string, value: float}>}|null
|
||||
*/
|
||||
|
||||
private function feeRulesForChannel(OrderChannel $channel): ?array
|
||||
{
|
||||
$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
|
||||
{
|
||||
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
|
||||
{
|
||||
return MarketplaceFeeRule::fromArray($data)->toArray();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user