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
|
## Database & Migrasi
|
||||||
|
|
||||||
### Struktur File Migrasi
|
### Struktur File Migrasi
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -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([
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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']] : [];
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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)) {
|
||||||
|
|||||||
@ -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));
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 ?? [];
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 = [
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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, []);
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user