feat: add predis/predis dependency and implement caching in various services for improved performance
This commit is contained in:
parent
632ca69c3a
commit
00ef124fb3
@ -3,7 +3,9 @@
|
|||||||
namespace App\Http\Requests\Admin\Manage;
|
namespace App\Http\Requests\Admin\Manage;
|
||||||
|
|
||||||
use App\Enums\Permission;
|
use App\Enums\Permission;
|
||||||
|
use App\Enums\ProductStockQuality;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class StokOpnameAutoSaveRequest extends FormRequest
|
class StokOpnameAutoSaveRequest extends FormRequest
|
||||||
{
|
{
|
||||||
@ -25,7 +27,7 @@ public function rules(): array
|
|||||||
'notes' => ['nullable', 'string', 'max:1000'],
|
'notes' => ['nullable', 'string', 'max:1000'],
|
||||||
'items' => ['nullable', 'array'],
|
'items' => ['nullable', 'array'],
|
||||||
'items.*.product_variant_id' => ['required', 'integer', 'exists:product_variants,id'],
|
'items.*.product_variant_id' => ['required', 'integer', 'exists:product_variants,id'],
|
||||||
'items.*.stock_quality' => ['required', \Illuminate\Validation\Rule::enum(\App\Enums\ProductStockQuality::class)],
|
'items.*.stock_quality' => ['required', Rule::enum(ProductStockQuality::class)],
|
||||||
'items.*.physical_stock' => ['nullable', 'integer', 'min:0'],
|
'items.*.physical_stock' => ['nullable', 'integer', 'min:0'],
|
||||||
'items.*.notes' => ['nullable', 'string', 'max:500'],
|
'items.*.notes' => ['nullable', 'string', 'max:500'],
|
||||||
];
|
];
|
||||||
|
|||||||
@ -3,7 +3,9 @@
|
|||||||
namespace App\Http\Requests\Admin\Manage;
|
namespace App\Http\Requests\Admin\Manage;
|
||||||
|
|
||||||
use App\Enums\Permission;
|
use App\Enums\Permission;
|
||||||
|
use App\Enums\ProductStockQuality;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class StokOpnameRequest extends FormRequest
|
class StokOpnameRequest extends FormRequest
|
||||||
{
|
{
|
||||||
@ -23,7 +25,7 @@ public function rules(): array
|
|||||||
'notes' => ['nullable', 'string', 'max:1000'],
|
'notes' => ['nullable', 'string', 'max:1000'],
|
||||||
'items' => ['required', 'array', 'min:1'],
|
'items' => ['required', 'array', 'min:1'],
|
||||||
'items.*.product_variant_id' => ['required', 'integer', 'exists:product_variants,id'],
|
'items.*.product_variant_id' => ['required', 'integer', 'exists:product_variants,id'],
|
||||||
'items.*.stock_quality' => ['required', \Illuminate\Validation\Rule::enum(\App\Enums\ProductStockQuality::class)],
|
'items.*.stock_quality' => ['required', Rule::enum(ProductStockQuality::class)],
|
||||||
'items.*.physical_stock' => ['required', 'integer', 'min:0'],
|
'items.*.physical_stock' => ['required', 'integer', 'min:0'],
|
||||||
'items.*.notes' => ['nullable', 'string', 'max:500'],
|
'items.*.notes' => ['nullable', 'string', 'max:500'],
|
||||||
];
|
];
|
||||||
|
|||||||
@ -35,8 +35,9 @@ public function getProductNameAttribute(): string
|
|||||||
|
|
||||||
public function getVariantNameAttribute(): string
|
public function getVariantNameAttribute(): string
|
||||||
{
|
{
|
||||||
$qualityLabel = $this->stock_quality ? ' (' . $this->stock_quality->label() . ')' : '';
|
$qualityLabel = $this->stock_quality ? ' ('.$this->stock_quality->label().')' : '';
|
||||||
return $this->productVariant->name . $qualityLabel;
|
|
||||||
|
return $this->productVariant->name.$qualityLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Relation
|
// 3. Relation
|
||||||
|
|||||||
49
app/Services/Concerns/CachesQuery.php
Normal file
49
app/Services/Concerns/CachesQuery.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Concerns;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
|
trait CachesQuery
|
||||||
|
{
|
||||||
|
protected function cacheRemember(string $key, int $ttl, callable $callback)
|
||||||
|
{
|
||||||
|
return Cache::remember($key, $ttl, $callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cacheForever(string $key, callable $callback)
|
||||||
|
{
|
||||||
|
return Cache::rememberForever($key, $callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cacheForget(string $key): void
|
||||||
|
{
|
||||||
|
Cache::forget($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cacheForgetByPattern(string $pattern): void
|
||||||
|
{
|
||||||
|
if (config('cache.default') !== 'redis') {
|
||||||
|
Cache::flush();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$redis = Redis::connection('cache');
|
||||||
|
|
||||||
|
$prefix = config('cache.prefix', 'laravel_cache');
|
||||||
|
|
||||||
|
$fullPattern = "{$prefix}:{$pattern}";
|
||||||
|
|
||||||
|
$cursor = null;
|
||||||
|
|
||||||
|
do {
|
||||||
|
[$cursor, $keys] = $redis->scan($cursor ?? 0, ['match' => $fullPattern, 'count' => 100]);
|
||||||
|
|
||||||
|
if (! empty($keys)) {
|
||||||
|
$redis->del($keys);
|
||||||
|
}
|
||||||
|
} while ($cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@
|
|||||||
use App\Models\CashAccount;
|
use App\Models\CashAccount;
|
||||||
use App\Models\CashTransaction;
|
use App\Models\CashTransaction;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\Concerns\SyncsPhotos;
|
use App\Services\Concerns\SyncsPhotos;
|
||||||
use App\Services\Media\MediaService;
|
use App\Services\Media\MediaService;
|
||||||
@ -18,7 +19,7 @@
|
|||||||
|
|
||||||
class CashService
|
class CashService
|
||||||
{
|
{
|
||||||
use RunsInTransaction, SyncsPhotos;
|
use CachesQuery, RunsInTransaction, SyncsPhotos;
|
||||||
|
|
||||||
private const MAX_PHOTOS = 1;
|
private const MAX_PHOTOS = 1;
|
||||||
|
|
||||||
@ -29,7 +30,9 @@ public function __construct(
|
|||||||
|
|
||||||
public function getDefaultAccount(): CashAccount
|
public function getDefaultAccount(): CashAccount
|
||||||
{
|
{
|
||||||
return CashAccount::query()->firstOrFail();
|
return $this->cacheRemember('finance:default_account', 86400, function (): CashAccount {
|
||||||
|
return CashAccount::query()->firstOrFail();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paginateForIndex(CashAccount $cashAccount, array $tableQuery, string $referenceType = ''): LengthAwarePaginator
|
public function paginateForIndex(CashAccount $cashAccount, array $tableQuery, string $referenceType = ''): LengthAwarePaginator
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
use App\Models\PayrollAdjustment;
|
use App\Models\PayrollAdjustment;
|
||||||
use App\Models\PayrollPeriod;
|
use App\Models\PayrollPeriod;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\System\PushNotificationService;
|
use App\Services\System\PushNotificationService;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
@ -22,7 +23,7 @@
|
|||||||
|
|
||||||
class PayrollService
|
class PayrollService
|
||||||
{
|
{
|
||||||
use RunsInTransaction;
|
use CachesQuery, RunsInTransaction;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly CashService $cashService,
|
private readonly CashService $cashService,
|
||||||
@ -31,10 +32,12 @@ public function __construct(
|
|||||||
|
|
||||||
public function listPeriods(): Collection
|
public function listPeriods(): Collection
|
||||||
{
|
{
|
||||||
return PayrollPeriod::query()
|
return $this->cacheRemember('payroll:periods', 3600, function (): Collection {
|
||||||
->orderByDesc('year')
|
return PayrollPeriod::query()
|
||||||
->orderByDesc('month')
|
->orderByDesc('year')
|
||||||
->get();
|
->orderByDesc('month')
|
||||||
|
->get();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resolvePeriod(?int $periodId): ?PayrollPeriod
|
public function resolvePeriod(?int $periodId): ?PayrollPeriod
|
||||||
@ -43,15 +46,17 @@ public function resolvePeriod(?int $periodId): ?PayrollPeriod
|
|||||||
return PayrollPeriod::query()->find($periodId);
|
return PayrollPeriod::query()->find($periodId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PayrollPeriod::query()
|
return $this->cacheRemember('payroll:current_period', 3600, function (): ?PayrollPeriod {
|
||||||
->where('status', PayrollPeriodStatus::OPEN)
|
return PayrollPeriod::query()
|
||||||
->orderByDesc('year')
|
->where('status', PayrollPeriodStatus::OPEN)
|
||||||
->orderByDesc('month')
|
|
||||||
->first()
|
|
||||||
?? PayrollPeriod::query()
|
|
||||||
->orderByDesc('year')
|
->orderByDesc('year')
|
||||||
->orderByDesc('month')
|
->orderByDesc('month')
|
||||||
->first();
|
->first()
|
||||||
|
?? PayrollPeriod::query()
|
||||||
|
->orderByDesc('year')
|
||||||
|
->orderByDesc('month')
|
||||||
|
->first();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function periodSummary(PayrollPeriod $period, User $user): array
|
public function periodSummary(PayrollPeriod $period, User $user): array
|
||||||
@ -161,6 +166,8 @@ function () use ($user): PayrollPeriod {
|
|||||||
'Gagal membuka periode payroll',
|
'Gagal membuka periode payroll',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('payroll:*');
|
||||||
|
|
||||||
return $period;
|
return $period;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,6 +219,8 @@ function () use ($payroll, $validated, $user): void {
|
|||||||
'Gagal menambahkan penyesuaian gaji',
|
'Gagal menambahkan penyesuaian gaji',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('payroll:*');
|
||||||
|
|
||||||
if ($payroll->employee?->user_id) {
|
if ($payroll->employee?->user_id) {
|
||||||
$typeLabel = PayrollAdjustmentType::from($validated['type'])->label();
|
$typeLabel = PayrollAdjustmentType::from($validated['type'])->label();
|
||||||
$formattedAmount = 'Rp '.number_format($validated['amount'], 0, ',', '.');
|
$formattedAmount = 'Rp '.number_format($validated['amount'], 0, ',', '.');
|
||||||
@ -243,6 +252,8 @@ function () use ($payroll, $adjustment, $validated): void {
|
|||||||
'Gagal memperbarui penyesuaian gaji',
|
'Gagal memperbarui penyesuaian gaji',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('payroll:*');
|
||||||
|
|
||||||
if ($payroll->employee?->user_id) {
|
if ($payroll->employee?->user_id) {
|
||||||
$typeLabel = PayrollAdjustmentType::from($validated['type'])->label();
|
$typeLabel = PayrollAdjustmentType::from($validated['type'])->label();
|
||||||
$formattedAmount = 'Rp '.number_format($validated['amount'], 0, ',', '.');
|
$formattedAmount = 'Rp '.number_format($validated['amount'], 0, ',', '.');
|
||||||
@ -271,6 +282,8 @@ function () use ($payroll, $adjustment): void {
|
|||||||
'Gagal menghapus penyesuaian gaji',
|
'Gagal menghapus penyesuaian gaji',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('payroll:*');
|
||||||
|
|
||||||
if ($payroll->employee?->user_id) {
|
if ($payroll->employee?->user_id) {
|
||||||
$this->pushNotificationService->sendToUser(
|
$this->pushNotificationService->sendToUser(
|
||||||
'📊 Penyesuaian Gaji Dihapus',
|
'📊 Penyesuaian Gaji Dihapus',
|
||||||
@ -298,6 +311,8 @@ function () use ($payroll, $user): void {
|
|||||||
'Gagal membayar gaji (total 0)',
|
'Gagal membayar gaji (total 0)',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('payroll:*');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,6 +342,8 @@ function () use ($payroll, $user): void {
|
|||||||
'Gagal membayar gaji',
|
'Gagal membayar gaji',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('payroll:*');
|
||||||
|
|
||||||
if ($payroll->employee?->user_id) {
|
if ($payroll->employee?->user_id) {
|
||||||
$this->pushNotificationService->sendToUser(
|
$this->pushNotificationService->sendToUser(
|
||||||
'💸 Gaji Dibayarkan',
|
'💸 Gaji Dibayarkan',
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
use App\Models\Employee;
|
use App\Models\Employee;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\UserProfile;
|
use App\Models\UserProfile;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\Concerns\SyncsPhotos;
|
use App\Services\Concerns\SyncsPhotos;
|
||||||
use App\Services\Media\MediaService;
|
use App\Services\Media\MediaService;
|
||||||
@ -17,7 +18,7 @@
|
|||||||
|
|
||||||
class EmployeeService
|
class EmployeeService
|
||||||
{
|
{
|
||||||
use RunsInTransaction, SyncsPhotos;
|
use CachesQuery, RunsInTransaction, SyncsPhotos;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly MediaService $mediaService,
|
private readonly MediaService $mediaService,
|
||||||
@ -60,7 +61,9 @@ public function paginateForIndex(
|
|||||||
|
|
||||||
public function assignableRoleOptions(): array
|
public function assignableRoleOptions(): array
|
||||||
{
|
{
|
||||||
return Role::assignableSelectOptions();
|
return $this->cacheRemember('system:assignable_roles', 86400, function (): array {
|
||||||
|
return Role::assignableSelectOptions();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findForEdit(User $user): array
|
public function findForEdit(User $user): array
|
||||||
@ -109,6 +112,8 @@ function () use ($validated): void {
|
|||||||
},
|
},
|
||||||
'Gagal membuat karyawan',
|
'Gagal membuat karyawan',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('hr:employees:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(User $user, array $validated): void
|
public function update(User $user, array $validated): void
|
||||||
@ -162,6 +167,8 @@ function () use ($validated, $user, $employee): void {
|
|||||||
},
|
},
|
||||||
'Gagal memperbarui karyawan',
|
'Gagal memperbarui karyawan',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('hr:employees:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toggleStatus(User $user, array $validated): void
|
public function toggleStatus(User $user, array $validated): void
|
||||||
@ -178,6 +185,8 @@ function () use ($user, $validated): void {
|
|||||||
},
|
},
|
||||||
'Gagal memperbarui status karyawan',
|
'Gagal memperbarui status karyawan',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('hr:employees:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resetPassword(User $user): void
|
public function resetPassword(User $user): void
|
||||||
@ -192,6 +201,8 @@ function () use ($user): void {
|
|||||||
},
|
},
|
||||||
'Gagal mereset kata sandi karyawan',
|
'Gagal mereset kata sandi karyawan',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('hr:employees:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(User $user): void
|
public function delete(User $user): void
|
||||||
@ -204,6 +215,8 @@ function () use ($user): void {
|
|||||||
},
|
},
|
||||||
'Gagal menghapus karyawan',
|
'Gagal menghapus karyawan',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('hr:employees:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function syncProfilePhoto(UserProfile $profile, array $validated): void
|
private function syncProfilePhoto(UserProfile $profile, array $validated): void
|
||||||
|
|||||||
@ -5,10 +5,13 @@
|
|||||||
use App\Enums\PriceType;
|
use App\Enums\PriceType;
|
||||||
use App\Models\CuttingResultPrice;
|
use App\Models\CuttingResultPrice;
|
||||||
use App\Models\ProductPrice;
|
use App\Models\ProductPrice;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class CuttingResultPriceResolver
|
class CuttingResultPriceResolver
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function resolve(int $productVariantId, PriceType $priceType): ?CuttingResultPrice
|
public function resolve(int $productVariantId, PriceType $priceType): ?CuttingResultPrice
|
||||||
{
|
{
|
||||||
$price = CuttingResultPrice::query()
|
$price = CuttingResultPrice::query()
|
||||||
@ -43,57 +46,61 @@ public function resolve(int $productVariantId, PriceType $priceType): ?CuttingRe
|
|||||||
|
|
||||||
public function latestPricesForVariant(int $productVariantId): array
|
public function latestPricesForVariant(int $productVariantId): array
|
||||||
{
|
{
|
||||||
$prices = [];
|
return $this->cacheRemember("prices:variant:{$productVariantId}", 900, function () use ($productVariantId) {
|
||||||
|
$prices = [];
|
||||||
|
|
||||||
foreach (PriceType::cases() as $priceType) {
|
foreach (PriceType::cases() as $priceType) {
|
||||||
$price = $this->resolve($productVariantId, $priceType);
|
$price = $this->resolve($productVariantId, $priceType);
|
||||||
|
|
||||||
if ($price !== null) {
|
if ($price !== null) {
|
||||||
$prices[] = $price;
|
$prices[] = $price;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return $prices;
|
return $prices;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function latestPricesForVariants(array $variantIds): Collection
|
public function latestPricesForVariants(array $variantIds): Collection
|
||||||
{
|
{
|
||||||
if (empty($variantIds)) {
|
return $this->cacheRemember('prices:variants:'.md5(implode(',', $variantIds)), 900, function () use ($variantIds) {
|
||||||
return collect();
|
if (empty($variantIds)) {
|
||||||
}
|
return collect();
|
||||||
|
}
|
||||||
|
|
||||||
$cuttingPrices = CuttingResultPrice::query()
|
$cuttingPrices = CuttingResultPrice::query()
|
||||||
->whereIn('product_variant_id', $variantIds)
|
->whereIn('product_variant_id', $variantIds)
|
||||||
->whereHas('cutting', fn ($query) => $query->verified())
|
->whereHas('cutting', fn ($query) => $query->verified())
|
||||||
->join('cuttings', 'cutting_result_prices.cutting_id', '=', 'cuttings.id')
|
->join('cuttings', 'cutting_result_prices.cutting_id', '=', 'cuttings.id')
|
||||||
->orderByDesc('cuttings.created_at')
|
->orderByDesc('cuttings.created_at')
|
||||||
->select('cutting_result_prices.*')
|
->select('cutting_result_prices.*')
|
||||||
->get()
|
->get()
|
||||||
->groupBy(fn (CuttingResultPrice $price) => $price->product_variant_id.'-'.$price->price_type->value)
|
->groupBy(fn (CuttingResultPrice $price) => $price->product_variant_id.'-'.$price->price_type->value)
|
||||||
->map(fn (Collection $group) => $group->first());
|
->map(fn (Collection $group) => $group->first());
|
||||||
|
|
||||||
$productPrices = ProductPrice::query()
|
$productPrices = ProductPrice::query()
|
||||||
->whereIn('variant_id', $variantIds)
|
->whereIn('variant_id', $variantIds)
|
||||||
->get()
|
->get()
|
||||||
->groupBy(fn (ProductPrice $price) => $price->variant_id.'-'.$price->type->value);
|
->groupBy(fn (ProductPrice $price) => $price->variant_id.'-'.$price->type->value);
|
||||||
|
|
||||||
$results = collect();
|
$results = collect();
|
||||||
foreach ($variantIds as $variantId) {
|
foreach ($variantIds as $variantId) {
|
||||||
foreach (PriceType::cases() as $priceType) {
|
foreach (PriceType::cases() as $priceType) {
|
||||||
$key = $variantId.'-'.$priceType->value;
|
$key = $variantId.'-'.$priceType->value;
|
||||||
if ($cuttingPrices->has($key)) {
|
if ($cuttingPrices->has($key)) {
|
||||||
$results->push($cuttingPrices->get($key));
|
$results->push($cuttingPrices->get($key));
|
||||||
} elseif ($productPrices->has($key)) {
|
} elseif ($productPrices->has($key)) {
|
||||||
$pp = $productPrices->get($key)->first();
|
$pp = $productPrices->get($key)->first();
|
||||||
$cp = new CuttingResultPrice;
|
$cp = new CuttingResultPrice;
|
||||||
$cp->product_variant_id = $variantId;
|
$cp->product_variant_id = $variantId;
|
||||||
$cp->price_type = $priceType;
|
$cp->price_type = $priceType;
|
||||||
$cp->price = $pp->price;
|
$cp->price = $pp->price;
|
||||||
$results->push($cp);
|
$results->push($cp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return $results->groupBy('product_variant_id');
|
return $results->groupBy('product_variant_id');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
use App\Models\RawMaterial;
|
use App\Models\RawMaterial;
|
||||||
use App\Models\RawMaterialPrice;
|
use App\Models\RawMaterialPrice;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\Media\MediaService;
|
use App\Services\Media\MediaService;
|
||||||
use App\Services\System\PushNotificationService;
|
use App\Services\System\PushNotificationService;
|
||||||
@ -26,7 +27,7 @@
|
|||||||
|
|
||||||
class CuttingService
|
class CuttingService
|
||||||
{
|
{
|
||||||
use RunsInTransaction;
|
use CachesQuery, RunsInTransaction;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PushNotificationService $pushNotificationService,
|
private readonly PushNotificationService $pushNotificationService,
|
||||||
@ -555,6 +556,8 @@ function () use ($validated, $user): Cutting {
|
|||||||
route('admin.manage.cuttings.index'),
|
route('admin.manage.cuttings.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:cuttings:*');
|
||||||
|
|
||||||
return $cutting;
|
return $cutting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,6 +653,8 @@ function () use ($cutting, $validated): void {
|
|||||||
['owner', 'developer', 'direktur'],
|
['owner', 'developer', 'direktur'],
|
||||||
route('admin.manage.cuttings.index'),
|
route('admin.manage.cuttings.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:cuttings:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Cutting $cutting): void
|
public function delete(Cutting $cutting): void
|
||||||
@ -686,6 +691,8 @@ function () use ($cutting): void {
|
|||||||
['owner', 'developer', 'direktur'],
|
['owner', 'developer', 'direktur'],
|
||||||
route('admin.manage.cuttings.index'),
|
route('admin.manage.cuttings.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:cuttings:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transitionStatus(
|
public function transitionStatus(
|
||||||
@ -785,6 +792,9 @@ function () use ($cutting, $status, $verificationNote, $results, $resultPrices,
|
|||||||
route('admin.manage.stocks.index'),
|
route('admin.manage.stocks.index'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:cuttings:*');
|
||||||
|
$this->cacheForgetByPattern('prices:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildMaterials(array $materials): array
|
private function buildMaterials(array $materials): array
|
||||||
@ -1217,6 +1227,8 @@ public function quickCreateRawMaterial(array $validated): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:cuttings:*');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $rawMaterial->id,
|
'id' => $rawMaterial->id,
|
||||||
'name' => $rawMaterial->name,
|
'name' => $rawMaterial->name,
|
||||||
@ -1283,6 +1295,8 @@ public function quickCreateProduct(array $validated): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:cuttings:*');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $product->id,
|
'id' => $product->id,
|
||||||
'name' => $product->name,
|
'name' => $product->name,
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use App\Models\ProductVariant;
|
use App\Models\ProductVariant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\Finance\CashService;
|
use App\Services\Finance\CashService;
|
||||||
use App\Services\Media\MediaService;
|
use App\Services\Media\MediaService;
|
||||||
@ -27,7 +28,7 @@
|
|||||||
|
|
||||||
class OrderService
|
class OrderService
|
||||||
{
|
{
|
||||||
use RunsInTransaction;
|
use CachesQuery, RunsInTransaction;
|
||||||
|
|
||||||
private const MAX_PHOTOS = 1;
|
private const MAX_PHOTOS = 1;
|
||||||
|
|
||||||
@ -496,6 +497,8 @@ function () use ($validated, $user): Order {
|
|||||||
route('admin.manage.orders.index'),
|
route('admin.manage.orders.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:orders:*');
|
||||||
|
|
||||||
return $order;
|
return $order;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -584,6 +587,8 @@ function () use ($order, $validated): void {
|
|||||||
['owner', 'developer', 'direktur'],
|
['owner', 'developer', 'direktur'],
|
||||||
route('admin.manage.orders.index'),
|
route('admin.manage.orders.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:orders:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Order $order): void
|
public function delete(Order $order): void
|
||||||
@ -617,6 +622,8 @@ function () use ($order): void {
|
|||||||
['owner', 'developer', 'direktur'],
|
['owner', 'developer', 'direktur'],
|
||||||
route('admin.manage.orders.index'),
|
route('admin.manage.orders.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:orders:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transitionStatus(Order $order, OrderStatus $status): void
|
public function transitionStatus(Order $order, OrderStatus $status): void
|
||||||
@ -654,6 +661,8 @@ function () use ($order, $status): void {
|
|||||||
['owner', 'developer', 'direktur'],
|
['owner', 'developer', 'direktur'],
|
||||||
route('admin.manage.orders.index'),
|
route('admin.manage.orders.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:orders:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildLineItems(array $items, PriceType $priceType): array
|
private function buildLineItems(array $items, PriceType $priceType): array
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
use App\Models\Purchase;
|
use App\Models\Purchase;
|
||||||
use App\Models\RawMaterial;
|
use App\Models\RawMaterial;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Master\ProductService;
|
use App\Services\Master\ProductService;
|
||||||
use App\Services\Master\RawMaterialService;
|
use App\Services\Master\RawMaterialService;
|
||||||
use App\Services\System\PushNotificationService;
|
use App\Services\System\PushNotificationService;
|
||||||
@ -29,6 +30,8 @@
|
|||||||
|
|
||||||
class OwnerVerificationService
|
class OwnerVerificationService
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly StockService $stockService,
|
private readonly StockService $stockService,
|
||||||
private readonly ProductService $productService,
|
private readonly ProductService $productService,
|
||||||
@ -129,16 +132,18 @@ private function pendingCuttingRows(User $user, array $tableQuery): Collection
|
|||||||
|
|
||||||
public function pendingCountForUser(User $user): int
|
public function pendingCountForUser(User $user): int
|
||||||
{
|
{
|
||||||
$requestCount = OwnerVerificationRequest::query()
|
return $this->cacheRemember("verification:pending_count:{$user->id}", 30, function () use ($user) {
|
||||||
->pending()
|
$requestCount = OwnerVerificationRequest::query()
|
||||||
->visibleTo($user)
|
->pending()
|
||||||
->count();
|
->visibleTo($user)
|
||||||
|
->count();
|
||||||
|
|
||||||
if (! $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
|
if (! $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
|
||||||
return $requestCount;
|
return $requestCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $requestCount + Cutting::query()->pendingVerification()->count();
|
return $requestCount + Cutting::query()->pendingVerification()->count();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function subjectTypeOptions(User $user): array
|
public function subjectTypeOptions(User $user): array
|
||||||
@ -217,6 +222,8 @@ public function approveRequest(
|
|||||||
'✅ Pengajuan Disetujui',
|
'✅ Pengajuan Disetujui',
|
||||||
"Pengajuan {$request->action->label()} {$subjectLabel} '{$this->requestTitle($request)}' telah disetujui owner.",
|
"Pengajuan {$request->action->label()} {$subjectLabel} '{$this->requestTitle($request)}' telah disetujui owner.",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('verification:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rejectRequest(
|
public function rejectRequest(
|
||||||
@ -274,6 +281,8 @@ public function rejectRequest(
|
|||||||
'❌ Pengajuan Ditolak',
|
'❌ Pengajuan Ditolak',
|
||||||
"Pengajuan {$request->action->label()} {$subjectLabel} '{$title}' ditolak dengan alasan: '{$reason}'.",
|
"Pengajuan {$request->action->label()} {$subjectLabel} '{$title}' ditolak dengan alasan: '{$reason}'.",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('verification:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function rejectVerificationRequest(OwnerVerificationRequest $request): void
|
private function rejectVerificationRequest(OwnerVerificationRequest $request): void
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
use App\Models\RawMaterialPrice;
|
use App\Models\RawMaterialPrice;
|
||||||
use App\Models\Supplier;
|
use App\Models\Supplier;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\Media\MediaService;
|
use App\Services\Media\MediaService;
|
||||||
use App\Services\System\PushNotificationService;
|
use App\Services\System\PushNotificationService;
|
||||||
@ -23,7 +24,7 @@
|
|||||||
|
|
||||||
class PurchaseService
|
class PurchaseService
|
||||||
{
|
{
|
||||||
use RunsInTransaction;
|
use CachesQuery, RunsInTransaction;
|
||||||
|
|
||||||
private const MAX_PHOTOS = 1;
|
private const MAX_PHOTOS = 1;
|
||||||
|
|
||||||
@ -324,6 +325,8 @@ function () use ($validated, $user, $isOwner): Purchase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:purchases:*');
|
||||||
|
|
||||||
return $purchase;
|
return $purchase;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,6 +376,8 @@ function () use ($purchase, $validated, $user, $isOwner): void {
|
|||||||
$purchase->supplier->name,
|
$purchase->supplier->name,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:purchases:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Purchase $purchase, User $user): void
|
public function delete(Purchase $purchase, User $user): void
|
||||||
@ -381,6 +386,7 @@ public function delete(Purchase $purchase, User $user): void
|
|||||||
|
|
||||||
if ($isOwner) {
|
if ($isOwner) {
|
||||||
$this->executeDelete($purchase);
|
$this->executeDelete($purchase);
|
||||||
|
$this->cacheForgetByPattern('manage:purchases:*');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -425,6 +431,8 @@ public function applyVerificationRequest(OwnerVerificationRequest $verificationR
|
|||||||
'action' => 'Aksi verifikasi belanja tidak didukung.',
|
'action' => 'Aksi verifikasi belanja tidak didukung.',
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:purchases:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rejectVerificationRequest(OwnerVerificationRequest $verificationRequest): void
|
public function rejectVerificationRequest(OwnerVerificationRequest $verificationRequest): void
|
||||||
@ -436,6 +444,8 @@ public function rejectVerificationRequest(OwnerVerificationRequest $verification
|
|||||||
'action' => 'Aksi verifikasi belanja tidak didukung.',
|
'action' => 'Aksi verifikasi belanja tidak didukung.',
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:purchases:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clearVerificationRequestMedia(OwnerVerificationRequest $verificationRequest): void
|
public function clearVerificationRequestMedia(OwnerVerificationRequest $verificationRequest): void
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
use App\Models\ProductPrice;
|
use App\Models\ProductPrice;
|
||||||
use App\Models\ProductVariant;
|
use App\Models\ProductVariant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\System\PushNotificationService;
|
use App\Services\System\PushNotificationService;
|
||||||
use App\Support\Media\MediaPresenter;
|
use App\Support\Media\MediaPresenter;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@ -19,6 +20,8 @@
|
|||||||
|
|
||||||
class StockService
|
class StockService
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PushNotificationService $pushNotificationService,
|
private readonly PushNotificationService $pushNotificationService,
|
||||||
) {}
|
) {}
|
||||||
@ -131,6 +134,8 @@ public function submitVerification(
|
|||||||
['owner', 'developer', 'direktur'],
|
['owner', 'developer', 'direktur'],
|
||||||
route('admin.manage.stocks.index'),
|
route('admin.manage.stocks.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:stocks:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function approveVerification(
|
public function approveVerification(
|
||||||
@ -181,6 +186,9 @@ public function approveVerification(
|
|||||||
['owner', 'developer', 'direktur'],
|
['owner', 'developer', 'direktur'],
|
||||||
route('admin.manage.stocks.index'),
|
route('admin.manage.stocks.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:stocks:*');
|
||||||
|
$this->cacheForgetByPattern('prices:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rejectVerification(
|
public function rejectVerification(
|
||||||
@ -232,6 +240,8 @@ public function rejectVerification(
|
|||||||
route('admin.manage.stocks.index'),
|
route('admin.manage.stocks.index'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:stocks:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function breakMaterialCircularReference(CuttingMaterial $material): void
|
private function breakMaterialCircularReference(CuttingMaterial $material): void
|
||||||
|
|||||||
@ -3,11 +3,13 @@
|
|||||||
namespace App\Services\Manage;
|
namespace App\Services\Manage;
|
||||||
|
|
||||||
use App\Enums\Permission;
|
use App\Enums\Permission;
|
||||||
|
use App\Enums\ProductStockQuality;
|
||||||
use App\Enums\StokOpnameStatus;
|
use App\Enums\StokOpnameStatus;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use App\Models\ProductVariant;
|
use App\Models\ProductVariant;
|
||||||
use App\Models\StokOpname;
|
use App\Models\StokOpname;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\System\PushNotificationService;
|
use App\Services\System\PushNotificationService;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@ -17,6 +19,8 @@
|
|||||||
|
|
||||||
class StokOpnameService
|
class StokOpnameService
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PushNotificationService $pushNotificationService,
|
private readonly PushNotificationService $pushNotificationService,
|
||||||
) {}
|
) {}
|
||||||
@ -106,7 +110,7 @@ public function findForEdit(StokOpname $stokOpname): array
|
|||||||
public function create(array $validated, User $user): StokOpname
|
public function create(array $validated, User $user): StokOpname
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return DB::transaction(function () use ($validated, $user): StokOpname {
|
$stokOpname = DB::transaction(function () use ($validated, $user): StokOpname {
|
||||||
$stokOpname = StokOpname::create([
|
$stokOpname = StokOpname::create([
|
||||||
'opname_date' => $validated['opname_date'],
|
'opname_date' => $validated['opname_date'],
|
||||||
'notes' => $validated['notes'],
|
'notes' => $validated['notes'],
|
||||||
@ -118,6 +122,10 @@ public function create(array $validated, User $user): StokOpname
|
|||||||
|
|
||||||
return $stokOpname;
|
return $stokOpname;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:stok_opname:*');
|
||||||
|
|
||||||
|
return $stokOpname;
|
||||||
} catch (ValidationException $e) {
|
} catch (ValidationException $e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@ -160,6 +168,8 @@ public function update(StokOpname $stokOpname, array $validated): void
|
|||||||
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:stok_opname:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(StokOpname $stokOpname): void
|
public function delete(StokOpname $stokOpname): void
|
||||||
@ -186,6 +196,8 @@ public function delete(StokOpname $stokOpname): void
|
|||||||
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:stok_opname:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit(StokOpname $stokOpname, User $user): void
|
public function submit(StokOpname $stokOpname, User $user): void
|
||||||
@ -226,6 +238,8 @@ public function submit(StokOpname $stokOpname, User $user): void
|
|||||||
['owner', 'developer', 'admin-toko'],
|
['owner', 'developer', 'admin-toko'],
|
||||||
route('admin.manage.stok-opnames.index'),
|
route('admin.manage.stok-opnames.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:stok_opname:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function verify(StokOpname $stokOpname, User $user, ?string $verificationNotes = null): void
|
public function verify(StokOpname $stokOpname, User $user, ?string $verificationNotes = null): void
|
||||||
@ -241,9 +255,9 @@ public function verify(StokOpname $stokOpname, User $user, ?string $verification
|
|||||||
foreach ($stokOpname->items as $item) {
|
foreach ($stokOpname->items as $item) {
|
||||||
if ($item->difference !== 0) {
|
if ($item->difference !== 0) {
|
||||||
$column = match ($item->stock_quality) {
|
$column = match ($item->stock_quality) {
|
||||||
\App\Enums\ProductStockQuality::GOOD => 'stock',
|
ProductStockQuality::GOOD => 'stock',
|
||||||
\App\Enums\ProductStockQuality::RETAIL => 'retail_stock',
|
ProductStockQuality::RETAIL => 'retail_stock',
|
||||||
\App\Enums\ProductStockQuality::REJECT => 'reject_stock',
|
ProductStockQuality::REJECT => 'reject_stock',
|
||||||
};
|
};
|
||||||
$item->productVariant()->update([
|
$item->productVariant()->update([
|
||||||
$column => $item->physical_stock,
|
$column => $item->physical_stock,
|
||||||
@ -275,6 +289,8 @@ public function verify(StokOpname $stokOpname, User $user, ?string $verification
|
|||||||
$stokOpname->created_by_id,
|
$stokOpname->created_by_id,
|
||||||
route('admin.manage.stok-opnames.index'),
|
route('admin.manage.stok-opnames.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:stok_opname:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reject(StokOpname $stokOpname, User $user, string $reason): void
|
public function reject(StokOpname $stokOpname, User $user, string $reason): void
|
||||||
@ -311,12 +327,14 @@ public function reject(StokOpname $stokOpname, User $user, string $reason): void
|
|||||||
$stokOpname->created_by_id,
|
$stokOpname->created_by_id,
|
||||||
route('admin.manage.stok-opnames.index'),
|
route('admin.manage.stok-opnames.index'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:stok_opname:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function autoSave(array $validated, User $user): StokOpname
|
public function autoSave(array $validated, User $user): StokOpname
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return DB::transaction(function () use ($validated, $user): StokOpname {
|
$stokOpname = DB::transaction(function () use ($validated, $user): StokOpname {
|
||||||
$stokOpname = null;
|
$stokOpname = null;
|
||||||
|
|
||||||
if (! empty($validated['stok_opname_id'])) {
|
if (! empty($validated['stok_opname_id'])) {
|
||||||
@ -342,6 +360,10 @@ public function autoSave(array $validated, User $user): StokOpname
|
|||||||
|
|
||||||
return $stokOpname;
|
return $stokOpname;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('manage:stok_opname:*');
|
||||||
|
|
||||||
|
return $stokOpname;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Log::error('Gagal auto-save stok opname: '.$e->getMessage(), [
|
Log::error('Gagal auto-save stok opname: '.$e->getMessage(), [
|
||||||
'trace' => $e->getTraceAsString(),
|
'trace' => $e->getTraceAsString(),
|
||||||
@ -357,15 +379,15 @@ private function syncItems(StokOpname $stokOpname, array $items): void
|
|||||||
|
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
$variant = ProductVariant::find($item['product_variant_id']);
|
$variant = ProductVariant::find($item['product_variant_id']);
|
||||||
if (!$variant) {
|
if (! $variant) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$quality = \App\Enums\ProductStockQuality::from($item['stock_quality']);
|
$quality = ProductStockQuality::from($item['stock_quality']);
|
||||||
$column = match ($quality) {
|
$column = match ($quality) {
|
||||||
\App\Enums\ProductStockQuality::GOOD => 'stock',
|
ProductStockQuality::GOOD => 'stock',
|
||||||
\App\Enums\ProductStockQuality::RETAIL => 'retail_stock',
|
ProductStockQuality::RETAIL => 'retail_stock',
|
||||||
\App\Enums\ProductStockQuality::REJECT => 'reject_stock',
|
ProductStockQuality::REJECT => 'reject_stock',
|
||||||
};
|
};
|
||||||
|
|
||||||
$systemStock = $variant->$column ?? 0;
|
$systemStock = $variant->$column ?? 0;
|
||||||
|
|||||||
@ -3,11 +3,14 @@
|
|||||||
namespace App\Services\Master;
|
namespace App\Services\Master;
|
||||||
|
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
class CategoryService
|
class CategoryService
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
$query = Category::query()
|
$query = Category::query()
|
||||||
@ -28,28 +31,36 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
|||||||
public function create(array $validated): void
|
public function create(array $validated): void
|
||||||
{
|
{
|
||||||
Category::create($validated);
|
Category::create($validated);
|
||||||
|
$this->cacheForget('master:categories:options');
|
||||||
|
$this->cacheForget('homepage:page_data');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Category $category, array $validated): void
|
public function update(Category $category, array $validated): void
|
||||||
{
|
{
|
||||||
$category->update($validated);
|
$category->update($validated);
|
||||||
|
$this->cacheForget('master:categories:options');
|
||||||
|
$this->cacheForget('homepage:page_data');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Category $category): void
|
public function delete(Category $category): void
|
||||||
{
|
{
|
||||||
$category->delete();
|
$category->delete();
|
||||||
|
$this->cacheForget('master:categories:options');
|
||||||
|
$this->cacheForget('homepage:page_data');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSelectOptions(): array
|
public function getSelectOptions(): array
|
||||||
{
|
{
|
||||||
return Category::query()
|
return $this->cacheRemember('master:categories:options', 3600, function () {
|
||||||
->orderBy('name')
|
return Category::query()
|
||||||
->get(['id', 'name'])
|
->orderBy('name')
|
||||||
->map(fn (Category $category) => [
|
->get(['id', 'name'])
|
||||||
'value' => $category->id,
|
->map(fn (Category $category) => [
|
||||||
'label' => $category->name,
|
'value' => $category->id,
|
||||||
])
|
'label' => $category->name,
|
||||||
->all();
|
])
|
||||||
|
->all();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function applySorting(Builder $query, string $sort, string $direction): void
|
private function applySorting(Builder $query, string $sort, string $direction): void
|
||||||
|
|||||||
@ -3,11 +3,14 @@
|
|||||||
namespace App\Services\Master;
|
namespace App\Services\Master;
|
||||||
|
|
||||||
use App\Models\Customer;
|
use App\Models\Customer;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
class CustomerService
|
class CustomerService
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
$query = Customer::query()
|
$query = Customer::query()
|
||||||
@ -30,33 +33,41 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
|||||||
public function create(array $validated): void
|
public function create(array $validated): void
|
||||||
{
|
{
|
||||||
Customer::create($validated);
|
Customer::create($validated);
|
||||||
|
$this->cacheForget('master:customers:options');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createAndReturn(array $validated): Customer
|
public function createAndReturn(array $validated): Customer
|
||||||
{
|
{
|
||||||
return Customer::create($validated);
|
$customer = Customer::create($validated);
|
||||||
|
$this->cacheForget('master:customers:options');
|
||||||
|
|
||||||
|
return $customer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Customer $customer, array $validated): void
|
public function update(Customer $customer, array $validated): void
|
||||||
{
|
{
|
||||||
$customer->update($validated);
|
$customer->update($validated);
|
||||||
|
$this->cacheForget('master:customers:options');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Customer $customer): void
|
public function delete(Customer $customer): void
|
||||||
{
|
{
|
||||||
$customer->delete();
|
$customer->delete();
|
||||||
|
$this->cacheForget('master:customers:options');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSelectOptions(): array
|
public function getSelectOptions(): array
|
||||||
{
|
{
|
||||||
return Customer::query()
|
return $this->cacheRemember('master:customers:options', 3600, function () {
|
||||||
->orderBy('name')
|
return Customer::query()
|
||||||
->get(['id', 'name'])
|
->orderBy('name')
|
||||||
->map(fn (Customer $customer) => [
|
->get(['id', 'name'])
|
||||||
'value' => $customer->id,
|
->map(fn (Customer $customer) => [
|
||||||
'label' => $customer->name,
|
'value' => $customer->id,
|
||||||
])
|
'label' => $customer->name,
|
||||||
->all();
|
])
|
||||||
|
->all();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function applySorting(Builder $query, string $sort, string $direction): void
|
private function applySorting(Builder $query, string $sort, string $direction): void
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use App\Models\ProductVariant;
|
use App\Models\ProductVariant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\Media\MediaService;
|
use App\Services\Media\MediaService;
|
||||||
use App\Services\System\PushNotificationService;
|
use App\Services\System\PushNotificationService;
|
||||||
@ -20,7 +21,7 @@
|
|||||||
|
|
||||||
class ProductService
|
class ProductService
|
||||||
{
|
{
|
||||||
use RunsInTransaction;
|
use CachesQuery, RunsInTransaction;
|
||||||
|
|
||||||
private const MAX_VARIANT_IMAGES = 5;
|
private const MAX_VARIANT_IMAGES = 5;
|
||||||
|
|
||||||
@ -166,6 +167,10 @@ function () use ($validated, $user, $isOwner): void {
|
|||||||
'Gagal membuat produk',
|
'Gagal membuat produk',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($isOwner) {
|
||||||
|
$this->cacheForgetByPattern('master:products:*');
|
||||||
|
}
|
||||||
|
|
||||||
if (! $isOwner) {
|
if (! $isOwner) {
|
||||||
$this->notifyForPendingRequest(
|
$this->notifyForPendingRequest(
|
||||||
$user,
|
$user,
|
||||||
@ -240,6 +245,9 @@ function () use ($validated, $product, $user, $isOwner): void {
|
|||||||
'Gagal memperbarui produk',
|
'Gagal memperbarui produk',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('master:products:*');
|
||||||
|
$this->cacheForget('homepage:page_data');
|
||||||
|
|
||||||
if (! $isOwner) {
|
if (! $isOwner) {
|
||||||
$this->notifyForPendingRequest(
|
$this->notifyForPendingRequest(
|
||||||
$user,
|
$user,
|
||||||
@ -257,6 +265,8 @@ public function delete(Product $product, User $user): void
|
|||||||
|
|
||||||
if ($isOwner) {
|
if ($isOwner) {
|
||||||
$this->applyDeleteSubject($product);
|
$this->applyDeleteSubject($product);
|
||||||
|
$this->cacheForgetByPattern('master:products:*');
|
||||||
|
$this->cacheForget('homepage:page_data');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -296,6 +306,9 @@ public function toggleStatus(Product $product, array $validated, User $user): vo
|
|||||||
'is_active' => (bool) $validated['is_active'],
|
'is_active' => (bool) $validated['is_active'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('master:products:*');
|
||||||
|
$this->cacheForget('homepage:page_data');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,6 +357,8 @@ public function applyVerificationRequest(OwnerVerificationRequest $verificationR
|
|||||||
'action' => 'Aksi verifikasi produk tidak didukung.',
|
'action' => 'Aksi verifikasi produk tidak didukung.',
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('master:products:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rejectVerificationRequest(OwnerVerificationRequest $verificationRequest): void
|
public function rejectVerificationRequest(OwnerVerificationRequest $verificationRequest): void
|
||||||
@ -356,6 +371,9 @@ public function rejectVerificationRequest(OwnerVerificationRequest $verification
|
|||||||
'action' => 'Aksi verifikasi produk tidak didukung.',
|
'action' => 'Aksi verifikasi produk tidak didukung.',
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('master:products:*');
|
||||||
|
$this->cacheForget('homepage:page_data');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clearVerificationRequestMedia(OwnerVerificationRequest $verificationRequest): void
|
public function clearVerificationRequestMedia(OwnerVerificationRequest $verificationRequest): void
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
use App\Models\RawMaterial;
|
use App\Models\RawMaterial;
|
||||||
use App\Models\RawMaterialPrice;
|
use App\Models\RawMaterialPrice;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\Media\MediaService;
|
use App\Services\Media\MediaService;
|
||||||
use App\Services\System\PushNotificationService;
|
use App\Services\System\PushNotificationService;
|
||||||
@ -20,7 +21,7 @@
|
|||||||
|
|
||||||
class RawMaterialService
|
class RawMaterialService
|
||||||
{
|
{
|
||||||
use RunsInTransaction;
|
use CachesQuery, RunsInTransaction;
|
||||||
|
|
||||||
private const MAX_VARIANT_IMAGES = 5;
|
private const MAX_VARIANT_IMAGES = 5;
|
||||||
|
|
||||||
@ -144,6 +145,10 @@ function () use ($validated, $user, $isOwner): void {
|
|||||||
'Gagal membuat bahan baku',
|
'Gagal membuat bahan baku',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($isOwner) {
|
||||||
|
$this->cacheForgetByPattern('master:raw_materials:*');
|
||||||
|
}
|
||||||
|
|
||||||
if (! $isOwner) {
|
if (! $isOwner) {
|
||||||
$this->notifyForPendingRequest(
|
$this->notifyForPendingRequest(
|
||||||
$user,
|
$user,
|
||||||
@ -197,6 +202,10 @@ function () use ($validated, $rawMaterial, $user, $isOwner): void {
|
|||||||
'Gagal memperbarui bahan baku',
|
'Gagal memperbarui bahan baku',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($isOwner) {
|
||||||
|
$this->cacheForgetByPattern('master:raw_materials:*');
|
||||||
|
}
|
||||||
|
|
||||||
if (! $isOwner) {
|
if (! $isOwner) {
|
||||||
$this->notifyForPendingRequest(
|
$this->notifyForPendingRequest(
|
||||||
$user,
|
$user,
|
||||||
@ -214,6 +223,7 @@ public function delete(RawMaterial $rawMaterial, User $user): void
|
|||||||
|
|
||||||
if ($isOwner) {
|
if ($isOwner) {
|
||||||
$this->applyDeleteSubject($rawMaterial);
|
$this->applyDeleteSubject($rawMaterial);
|
||||||
|
$this->cacheForgetByPattern('master:raw_materials:*');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -253,6 +263,8 @@ public function toggleStatus(RawMaterial $rawMaterial, array $validated, User $u
|
|||||||
'is_active' => (bool) $validated['is_active'],
|
'is_active' => (bool) $validated['is_active'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('master:raw_materials:*');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,6 +313,8 @@ public function applyVerificationRequest(OwnerVerificationRequest $verificationR
|
|||||||
'action' => 'Aksi verifikasi bahan baku tidak didukung.',
|
'action' => 'Aksi verifikasi bahan baku tidak didukung.',
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('master:raw_materials:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rejectVerificationRequest(OwnerVerificationRequest $verificationRequest): void
|
public function rejectVerificationRequest(OwnerVerificationRequest $verificationRequest): void
|
||||||
@ -313,6 +327,8 @@ public function rejectVerificationRequest(OwnerVerificationRequest $verification
|
|||||||
'action' => 'Aksi verifikasi bahan baku tidak didukung.',
|
'action' => 'Aksi verifikasi bahan baku tidak didukung.',
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('master:raw_materials:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clearVerificationRequestMedia(OwnerVerificationRequest $verificationRequest): void
|
public function clearVerificationRequestMedia(OwnerVerificationRequest $verificationRequest): void
|
||||||
|
|||||||
@ -3,11 +3,14 @@
|
|||||||
namespace App\Services\Master;
|
namespace App\Services\Master;
|
||||||
|
|
||||||
use App\Models\Supplier;
|
use App\Models\Supplier;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
class SupplierService
|
class SupplierService
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
$query = Supplier::query()
|
$query = Supplier::query()
|
||||||
@ -30,28 +33,33 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
|||||||
public function create(array $validated): void
|
public function create(array $validated): void
|
||||||
{
|
{
|
||||||
Supplier::create($validated);
|
Supplier::create($validated);
|
||||||
|
$this->cacheForget('master:suppliers:options');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Supplier $supplier, array $validated): void
|
public function update(Supplier $supplier, array $validated): void
|
||||||
{
|
{
|
||||||
$supplier->update($validated);
|
$supplier->update($validated);
|
||||||
|
$this->cacheForget('master:suppliers:options');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Supplier $supplier): void
|
public function delete(Supplier $supplier): void
|
||||||
{
|
{
|
||||||
$supplier->delete();
|
$supplier->delete();
|
||||||
|
$this->cacheForget('master:suppliers:options');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSelectOptions(): array
|
public function getSelectOptions(): array
|
||||||
{
|
{
|
||||||
return Supplier::query()
|
return $this->cacheRemember('master:suppliers:options', 3600, function () {
|
||||||
->orderBy('name')
|
return Supplier::query()
|
||||||
->get(['id', 'name'])
|
->orderBy('name')
|
||||||
->map(fn (Supplier $supplier) => [
|
->get(['id', 'name'])
|
||||||
'value' => $supplier->id,
|
->map(fn (Supplier $supplier) => [
|
||||||
'label' => $supplier->name,
|
'value' => $supplier->id,
|
||||||
])
|
'label' => $supplier->name,
|
||||||
->all();
|
])
|
||||||
|
->all();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function applySorting(Builder $query, string $sort, string $direction): void
|
private function applySorting(Builder $query, string $sort, string $direction): void
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@
|
|||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use App\Models\SystemConfiguration;
|
use App\Models\SystemConfiguration;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Manage\CuttingResultPriceResolver;
|
use App\Services\Manage\CuttingResultPriceResolver;
|
||||||
use App\Services\System\Setting\HomepageSettingService;
|
use App\Services\System\Setting\HomepageSettingService;
|
||||||
use App\Settings\SocialMediaSettings;
|
use App\Settings\SocialMediaSettings;
|
||||||
@ -13,6 +14,8 @@
|
|||||||
|
|
||||||
class HomepageService
|
class HomepageService
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly CuttingResultPriceResolver $cuttingResultPriceResolver,
|
private readonly CuttingResultPriceResolver $cuttingResultPriceResolver,
|
||||||
private readonly HomepageSettingService $homepageSettingService,
|
private readonly HomepageSettingService $homepageSettingService,
|
||||||
@ -20,30 +23,32 @@ public function __construct(
|
|||||||
|
|
||||||
public function pageData(): array
|
public function pageData(): array
|
||||||
{
|
{
|
||||||
$categories = Category::getActiveWithProducts();
|
return $this->cacheRemember('homepage:page_data', 900, function () {
|
||||||
$products = $this->getProducts();
|
$categories = Category::getActiveWithProducts();
|
||||||
|
$products = $this->getProducts();
|
||||||
|
|
||||||
$configuration = SystemConfiguration::instance();
|
$configuration = SystemConfiguration::instance();
|
||||||
$logo = MediaPresenter::first($configuration, 'logo');
|
$logo = MediaPresenter::first($configuration, 'logo');
|
||||||
$logoUrl = $logo['url'] ?? null;
|
$logoUrl = $logo['url'] ?? null;
|
||||||
|
|
||||||
$settings = app(SystemSettings::class);
|
$settings = app(SystemSettings::class);
|
||||||
$socialSettings = app(SocialMediaSettings::class);
|
$socialSettings = app(SocialMediaSettings::class);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'categories' => $categories,
|
'categories' => $categories,
|
||||||
'products' => $products,
|
'products' => $products,
|
||||||
'appName' => $settings->app_name ?? 'DST Collection',
|
'appName' => $settings->app_name ?? 'DST Collection',
|
||||||
'aboutApp' => $settings->about_app ?? '',
|
'aboutApp' => $settings->about_app ?? '',
|
||||||
'contactEmail' => $settings->email ?? '',
|
'contactEmail' => $settings->email ?? '',
|
||||||
'contactPhone' => $settings->phone ?? '',
|
'contactPhone' => $settings->phone ?? '',
|
||||||
'contactAddress' => $settings->address ?? '',
|
'contactAddress' => $settings->address ?? '',
|
||||||
'logoUrl' => $logoUrl,
|
'logoUrl' => $logoUrl,
|
||||||
'instagramUrl' => $socialSettings->instagram_url ?? null,
|
'instagramUrl' => $socialSettings->instagram_url ?? null,
|
||||||
'facebookUrl' => $socialSettings->facebook_url ?? null,
|
'facebookUrl' => $socialSettings->facebook_url ?? null,
|
||||||
'tiktokUrl' => $socialSettings->tiktok_url ?? null,
|
'tiktokUrl' => $socialSettings->tiktok_url ?? null,
|
||||||
'homepage' => $this->homepageSettingService->homepageData(),
|
'homepage' => $this->homepageSettingService->homepageData(),
|
||||||
];
|
];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getProducts()
|
private function getProducts()
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Enums\Permission as PermissionEnum;
|
use App\Enums\Permission as PermissionEnum;
|
||||||
use App\Enums\Role as EnumsRole;
|
use App\Enums\Role as EnumsRole;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@ -14,6 +15,8 @@
|
|||||||
|
|
||||||
class RoleService
|
class RoleService
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
$query = Role::query()
|
$query = Role::query()
|
||||||
@ -34,14 +37,16 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
|||||||
|
|
||||||
public function permissionOptions(): array
|
public function permissionOptions(): array
|
||||||
{
|
{
|
||||||
return collect(PermissionEnum::cases())
|
return $this->cacheRemember('system:permissions', 86400, function (): array {
|
||||||
->map(fn (PermissionEnum $permission) => [
|
return collect(PermissionEnum::cases())
|
||||||
'value' => $permission->value,
|
->map(fn (PermissionEnum $permission) => [
|
||||||
'label' => $permission->label(),
|
'value' => $permission->value,
|
||||||
'group' => $permission->group(),
|
'label' => $permission->label(),
|
||||||
])
|
'group' => $permission->group(),
|
||||||
->values()
|
])
|
||||||
->all();
|
->values()
|
||||||
|
->all();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findForEdit(Role $role): array
|
public function findForEdit(Role $role): array
|
||||||
@ -81,6 +86,8 @@ public function create(array $validated): void
|
|||||||
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('system:roles:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Role $role, array $validated): void
|
public function update(Role $role, array $validated): void
|
||||||
@ -106,11 +113,15 @@ public function update(Role $role, array $validated): void
|
|||||||
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('system:roles:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Role $role): void
|
public function delete(Role $role): void
|
||||||
{
|
{
|
||||||
$role->delete();
|
$role->delete();
|
||||||
|
|
||||||
|
$this->cacheForgetByPattern('system:roles:*');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function applySorting(Builder $query, string $sort, string $direction): void
|
private function applySorting(Builder $query, string $sort, string $direction): void
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
namespace App\Services\System\Setting;
|
namespace App\Services\System\Setting;
|
||||||
|
|
||||||
use App\Models\HomepageConfiguration;
|
use App\Models\HomepageConfiguration;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\Concerns\SyncsPhotos;
|
use App\Services\Concerns\SyncsPhotos;
|
||||||
use App\Services\Media\MediaService;
|
use App\Services\Media\MediaService;
|
||||||
@ -10,7 +11,7 @@
|
|||||||
|
|
||||||
class HomepageSettingService
|
class HomepageSettingService
|
||||||
{
|
{
|
||||||
use RunsInTransaction, SyncsPhotos;
|
use CachesQuery, RunsInTransaction, SyncsPhotos;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly MediaService $mediaService,
|
private readonly MediaService $mediaService,
|
||||||
@ -18,18 +19,20 @@ public function __construct(
|
|||||||
|
|
||||||
public function homepageData(): array
|
public function homepageData(): array
|
||||||
{
|
{
|
||||||
$configuration = HomepageConfiguration::instance();
|
return $this->cacheRemember('homepage:settings', 86400, function () {
|
||||||
$configuration->load('media');
|
$configuration = HomepageConfiguration::instance();
|
||||||
|
$configuration->load('media');
|
||||||
|
|
||||||
$heroImage = MediaPresenter::first($configuration, 'hero_image');
|
$heroImage = MediaPresenter::first($configuration, 'hero_image');
|
||||||
$aboutImage = MediaPresenter::first($configuration, 'about_image');
|
$aboutImage = MediaPresenter::first($configuration, 'about_image');
|
||||||
$galleryImages = MediaPresenter::collection($configuration, 'gallery');
|
$galleryImages = MediaPresenter::collection($configuration, 'gallery');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'hero_image_url' => $heroImage['url'] ?? null,
|
'hero_image_url' => $heroImage['url'] ?? null,
|
||||||
'about_image_url' => $aboutImage['url'] ?? null,
|
'about_image_url' => $aboutImage['url'] ?? null,
|
||||||
'gallery_images' => $galleryImages,
|
'gallery_images' => $galleryImages,
|
||||||
];
|
];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateHomepage(array $validated): void
|
public function updateHomepage(array $validated): void
|
||||||
@ -74,5 +77,8 @@ function () use ($validated): void {
|
|||||||
},
|
},
|
||||||
'Gagal memperbarui pengaturan homepage',
|
'Gagal memperbarui pengaturan homepage',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForget('homepage:settings');
|
||||||
|
$this->cacheForget('homepage:page_data');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,20 +2,25 @@
|
|||||||
|
|
||||||
namespace App\Services\System\Setting;
|
namespace App\Services\System\Setting;
|
||||||
|
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Settings\HrSettings;
|
use App\Settings\HrSettings;
|
||||||
|
|
||||||
class HrSettingService
|
class HrSettingService
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function hrData(): array
|
public function hrData(): array
|
||||||
{
|
{
|
||||||
$settings = app(HrSettings::class);
|
return $this->cacheRemember('system:hr_settings', 86400, function () {
|
||||||
|
$settings = app(HrSettings::class);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'scheduled_check_in_time' => $settings->scheduled_check_in_time,
|
'scheduled_check_in_time' => $settings->scheduled_check_in_time,
|
||||||
'scheduled_check_out_time' => $settings->scheduled_check_out_time,
|
'scheduled_check_out_time' => $settings->scheduled_check_out_time,
|
||||||
'late_penalty_amount' => $settings->late_penalty_amount,
|
'late_penalty_amount' => $settings->late_penalty_amount,
|
||||||
'absent_penalty_amount' => $settings->absent_penalty_amount,
|
'absent_penalty_amount' => $settings->absent_penalty_amount,
|
||||||
];
|
];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateHr(array $validated): void
|
public function updateHr(array $validated): void
|
||||||
@ -27,5 +32,7 @@ public function updateHr(array $validated): void
|
|||||||
$settings->late_penalty_amount = (int) $validated['late_penalty_amount'];
|
$settings->late_penalty_amount = (int) $validated['late_penalty_amount'];
|
||||||
$settings->absent_penalty_amount = (int) $validated['absent_penalty_amount'];
|
$settings->absent_penalty_amount = (int) $validated['absent_penalty_amount'];
|
||||||
$settings->save();
|
$settings->save();
|
||||||
|
|
||||||
|
$this->cacheForget('system:hr_settings');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
use App\Enums\Permission;
|
use App\Enums\Permission;
|
||||||
use App\Models\OwnerVerificationRequest;
|
use App\Models\OwnerVerificationRequest;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\System\PushNotificationService;
|
use App\Services\System\PushNotificationService;
|
||||||
use App\Settings\MarketplaceSettings;
|
use App\Settings\MarketplaceSettings;
|
||||||
@ -17,7 +18,7 @@
|
|||||||
|
|
||||||
class MarketplaceService
|
class MarketplaceService
|
||||||
{
|
{
|
||||||
use RunsInTransaction;
|
use CachesQuery, RunsInTransaction;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PushNotificationService $pushNotificationService,
|
private readonly PushNotificationService $pushNotificationService,
|
||||||
@ -52,25 +53,27 @@ private function shopeeFeeKeys(): array
|
|||||||
|
|
||||||
public function marketplaceData(): array
|
public function marketplaceData(): array
|
||||||
{
|
{
|
||||||
$settings = app(MarketplaceSettings::class);
|
return $this->cacheRemember('system:marketplace', 86400, function () {
|
||||||
|
$settings = app(MarketplaceSettings::class);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'tiktok_shop_platform_commission' => $this->presentFeeRule($settings->tiktok_shop_platform_commission),
|
'tiktok_shop_platform_commission' => $this->presentFeeRule($settings->tiktok_shop_platform_commission),
|
||||||
'tiktok_shop_logistics_service_fee' => $this->presentFeeRule($settings->tiktok_shop_logistics_service_fee),
|
'tiktok_shop_logistics_service_fee' => $this->presentFeeRule($settings->tiktok_shop_logistics_service_fee),
|
||||||
'tiktok_shop_dynamic_commission' => $this->presentFeeRule($settings->tiktok_shop_dynamic_commission),
|
'tiktok_shop_dynamic_commission' => $this->presentFeeRule($settings->tiktok_shop_dynamic_commission),
|
||||||
'tiktok_shop_order_processing_fee' => $this->presentFeeRule($settings->tiktok_shop_order_processing_fee),
|
'tiktok_shop_order_processing_fee' => $this->presentFeeRule($settings->tiktok_shop_order_processing_fee),
|
||||||
'tiktok_shop_affiliate' => $this->presentFeeRule($settings->tiktok_shop_affiliate),
|
'tiktok_shop_affiliate' => $this->presentFeeRule($settings->tiktok_shop_affiliate),
|
||||||
'tiktok_shop_pre_order_service_fee' => $this->presentFeeRule($settings->tiktok_shop_pre_order_service_fee),
|
'tiktok_shop_pre_order_service_fee' => $this->presentFeeRule($settings->tiktok_shop_pre_order_service_fee),
|
||||||
'shopee_admin_fee' => $this->presentFeeRule($settings->shopee_admin_fee),
|
'shopee_admin_fee' => $this->presentFeeRule($settings->shopee_admin_fee),
|
||||||
'shopee_program_fee' => $this->presentFeeRule($settings->shopee_program_fee),
|
'shopee_program_fee' => $this->presentFeeRule($settings->shopee_program_fee),
|
||||||
'shopee_shipping_savings' => $this->presentFeeRule($settings->shopee_shipping_savings),
|
'shopee_shipping_savings' => $this->presentFeeRule($settings->shopee_shipping_savings),
|
||||||
'shopee_premium' => $this->presentFeeRule($settings->shopee_premium),
|
'shopee_premium' => $this->presentFeeRule($settings->shopee_premium),
|
||||||
'shopee_service_fee' => $this->presentFeeRule($settings->shopee_service_fee),
|
'shopee_service_fee' => $this->presentFeeRule($settings->shopee_service_fee),
|
||||||
'shopee_order_processing_fee' => $this->presentFeeRule($settings->shopee_order_processing_fee),
|
'shopee_order_processing_fee' => $this->presentFeeRule($settings->shopee_order_processing_fee),
|
||||||
'shopee_ams_commission_fee' => $this->presentFeeRule($settings->shopee_ams_commission_fee),
|
'shopee_ams_commission_fee' => $this->presentFeeRule($settings->shopee_ams_commission_fee),
|
||||||
'shopee_pre_order' => $this->presentFeeRule($settings->shopee_pre_order),
|
'shopee_pre_order' => $this->presentFeeRule($settings->shopee_pre_order),
|
||||||
'shopee_live_extra' => $this->presentFeeRule($settings->shopee_live_extra),
|
'shopee_live_extra' => $this->presentFeeRule($settings->shopee_live_extra),
|
||||||
];
|
];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateMarketplace(array $validated, User $user): void
|
public function updateMarketplace(array $validated, User $user): void
|
||||||
@ -151,6 +154,8 @@ public function saveSettings(array $validated): void
|
|||||||
}
|
}
|
||||||
|
|
||||||
$settings->save();
|
$settings->save();
|
||||||
|
|
||||||
|
$this->cacheForget('system:marketplace');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function applyVerificationRequest(OwnerVerificationRequest $request): void
|
public function applyVerificationRequest(OwnerVerificationRequest $request): void
|
||||||
@ -159,6 +164,7 @@ public function applyVerificationRequest(OwnerVerificationRequest $request): voi
|
|||||||
function () use ($request): void {
|
function () use ($request): void {
|
||||||
$newPayload = $request->payload['new'] ?? [];
|
$newPayload = $request->payload['new'] ?? [];
|
||||||
$this->saveSettings($newPayload);
|
$this->saveSettings($newPayload);
|
||||||
|
$this->cacheForget('system:marketplace');
|
||||||
},
|
},
|
||||||
'Gagal menerapkan pengajuan verifikasi owner',
|
'Gagal menerapkan pengajuan verifikasi owner',
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,19 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Services\System\Setting;
|
namespace App\Services\System\Setting;
|
||||||
|
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Settings\SocialMediaSettings;
|
use App\Settings\SocialMediaSettings;
|
||||||
|
|
||||||
class SocialMediaService
|
class SocialMediaService
|
||||||
{
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
public function socialMediaData(): array
|
public function socialMediaData(): array
|
||||||
{
|
{
|
||||||
$settings = app(SocialMediaSettings::class);
|
return $this->cacheRemember('system:social_media', 86400, function () {
|
||||||
|
$settings = app(SocialMediaSettings::class);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'instagram_url' => $settings->instagram_url,
|
'instagram_url' => $settings->instagram_url,
|
||||||
'facebook_url' => $settings->facebook_url,
|
'facebook_url' => $settings->facebook_url,
|
||||||
'tiktok_url' => $settings->tiktok_url,
|
'tiktok_url' => $settings->tiktok_url,
|
||||||
];
|
];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateSocialMedia(array $validated): void
|
public function updateSocialMedia(array $validated): void
|
||||||
@ -26,5 +31,8 @@ public function updateSocialMedia(array $validated): void
|
|||||||
$settings->tiktok_url = $validated['tiktok_url'] ?? null;
|
$settings->tiktok_url = $validated['tiktok_url'] ?? null;
|
||||||
|
|
||||||
$settings->save();
|
$settings->save();
|
||||||
|
|
||||||
|
$this->cacheForget('system:social_media');
|
||||||
|
$this->cacheForget('homepage:page_data');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
namespace App\Services\System\Setting;
|
namespace App\Services\System\Setting;
|
||||||
|
|
||||||
use App\Models\SystemConfiguration;
|
use App\Models\SystemConfiguration;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
use App\Services\Concerns\RunsInTransaction;
|
use App\Services\Concerns\RunsInTransaction;
|
||||||
use App\Services\Concerns\SyncsPhotos;
|
use App\Services\Concerns\SyncsPhotos;
|
||||||
use App\Services\Media\MediaService;
|
use App\Services\Media\MediaService;
|
||||||
@ -12,7 +13,7 @@
|
|||||||
|
|
||||||
class SystemService
|
class SystemService
|
||||||
{
|
{
|
||||||
use RunsInTransaction, SyncsPhotos;
|
use CachesQuery, RunsInTransaction, SyncsPhotos;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly MediaService $mediaService,
|
private readonly MediaService $mediaService,
|
||||||
@ -20,24 +21,26 @@ public function __construct(
|
|||||||
|
|
||||||
public function systemData(): array
|
public function systemData(): array
|
||||||
{
|
{
|
||||||
$settings = app(SystemSettings::class);
|
return $this->cacheRemember('system:settings', 86400, function () {
|
||||||
$configuration = SystemConfiguration::instance();
|
$settings = app(SystemSettings::class);
|
||||||
$configuration->load('media');
|
$configuration = SystemConfiguration::instance();
|
||||||
|
$configuration->load('media');
|
||||||
|
|
||||||
$logo = MediaPresenter::first($configuration, 'logo');
|
$logo = MediaPresenter::first($configuration, 'logo');
|
||||||
$favicon = MediaPresenter::first($configuration, 'favicon');
|
$favicon = MediaPresenter::first($configuration, 'favicon');
|
||||||
$loginCover = MediaPresenter::first($configuration, 'login_cover');
|
$loginCover = MediaPresenter::first($configuration, 'login_cover');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'app_name' => $settings->app_name,
|
'app_name' => $settings->app_name,
|
||||||
'about_app' => $settings->about_app,
|
'about_app' => $settings->about_app,
|
||||||
'email' => $settings->email,
|
'email' => $settings->email,
|
||||||
'phone' => $settings->phone,
|
'phone' => $settings->phone,
|
||||||
'address' => $settings->address,
|
'address' => $settings->address,
|
||||||
'logo_url' => $logo['url'] ?? null,
|
'logo_url' => $logo['url'] ?? null,
|
||||||
'favicon_url' => $favicon['url'] ?? null,
|
'favicon_url' => $favicon['url'] ?? null,
|
||||||
'login_cover_url' => $loginCover['url'] ?? null,
|
'login_cover_url' => $loginCover['url'] ?? null,
|
||||||
];
|
];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateSystem(array $validated): void
|
public function updateSystem(array $validated): void
|
||||||
@ -107,5 +110,8 @@ function () use ($validated): void {
|
|||||||
},
|
},
|
||||||
'Gagal memperbarui pengaturan sistem',
|
'Gagal memperbarui pengaturan sistem',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->cacheForget('system:settings');
|
||||||
|
$this->cacheForget('homepage:page_data');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
"league/flysystem-aws-s3-v3": "^3.0",
|
"league/flysystem-aws-s3-v3": "^3.0",
|
||||||
"league/flysystem-ftp": "^3.0",
|
"league/flysystem-ftp": "^3.0",
|
||||||
"minishlink/web-push": "^9.0",
|
"minishlink/web-push": "^9.0",
|
||||||
|
"predis/predis": "^3.5",
|
||||||
"spatie/laravel-activitylog": "^5.0",
|
"spatie/laravel-activitylog": "^5.0",
|
||||||
"spatie/laravel-medialibrary": "^11.23",
|
"spatie/laravel-medialibrary": "^11.23",
|
||||||
"spatie/laravel-permission": "^8.0",
|
"spatie/laravel-permission": "^8.0",
|
||||||
|
|||||||
65
composer.lock
generated
65
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "ea578c35bcf9ab3ac130534272e48937",
|
"content-hash": "af3de95989c6c9eabd05600486ed22da",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
@ -3502,6 +3502,69 @@
|
|||||||
},
|
},
|
||||||
"time": "2026-01-25T14:56:51+00:00"
|
"time": "2026-01-25T14:56:51+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "predis/predis",
|
||||||
|
"version": "v3.5.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/predis/predis.git",
|
||||||
|
"reference": "5c996db191ee2d9bafe651f454b1fca16754271b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/predis/predis/zipball/5c996db191ee2d9bafe651f454b1fca16754271b",
|
||||||
|
"reference": "5c996db191ee2d9bafe651f454b1fca16754271b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2 || ^8.0",
|
||||||
|
"psr/http-message": "^1.0|^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.3",
|
||||||
|
"phpstan/phpstan": "^1.9",
|
||||||
|
"phpunit/phpcov": "^6.0 || ^8.0",
|
||||||
|
"phpunit/phpunit": "^8.0 || ~9.4.4"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Predis\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Till Krüss",
|
||||||
|
"homepage": "https://till.im",
|
||||||
|
"role": "Maintainer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A flexible and feature-complete Redis/Valkey client for PHP.",
|
||||||
|
"homepage": "http://github.com/predis/predis",
|
||||||
|
"keywords": [
|
||||||
|
"nosql",
|
||||||
|
"predis",
|
||||||
|
"redis"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/predis/predis/issues",
|
||||||
|
"source": "https://github.com/predis/predis/tree/v3.5.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/sponsors/tillkruss",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-06-11T16:56:53+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/clock",
|
"name": "psr/clock",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|||||||
@ -16,10 +16,10 @@ public function up(): void
|
|||||||
$table->dropForeign(['stok_opname_id']);
|
$table->dropForeign(['stok_opname_id']);
|
||||||
$table->dropForeign(['product_variant_id']);
|
$table->dropForeign(['product_variant_id']);
|
||||||
$table->dropUnique('stok_opname_items_stok_opname_id_product_variant_id_unique');
|
$table->dropUnique('stok_opname_items_stok_opname_id_product_variant_id_unique');
|
||||||
|
|
||||||
$table->enum('stock_quality', ProductStockQuality::values())->default(ProductStockQuality::GOOD->value)->after('product_variant_id');
|
$table->enum('stock_quality', ProductStockQuality::values())->default(ProductStockQuality::GOOD->value)->after('product_variant_id');
|
||||||
$table->unique(['stok_opname_id', 'product_variant_id', 'stock_quality'], 'stok_opname_items_opname_variant_quality_unique');
|
$table->unique(['stok_opname_id', 'product_variant_id', 'stock_quality'], 'stok_opname_items_opname_variant_quality_unique');
|
||||||
|
|
||||||
$table->foreign('stok_opname_id')->references('id')->on('stok_opnames')->cascadeOnDelete();
|
$table->foreign('stok_opname_id')->references('id')->on('stok_opnames')->cascadeOnDelete();
|
||||||
$table->foreign('product_variant_id')->references('id')->on('product_variants')->cascadeOnDelete();
|
$table->foreign('product_variant_id')->references('id')->on('product_variants')->cascadeOnDelete();
|
||||||
});
|
});
|
||||||
@ -34,10 +34,10 @@ public function down(): void
|
|||||||
$table->dropForeign(['stok_opname_id']);
|
$table->dropForeign(['stok_opname_id']);
|
||||||
$table->dropForeign(['product_variant_id']);
|
$table->dropForeign(['product_variant_id']);
|
||||||
$table->dropUnique('stok_opname_items_opname_variant_quality_unique');
|
$table->dropUnique('stok_opname_items_opname_variant_quality_unique');
|
||||||
|
|
||||||
$table->dropColumn('stock_quality');
|
$table->dropColumn('stock_quality');
|
||||||
$table->unique(['stok_opname_id', 'product_variant_id']);
|
$table->unique(['stok_opname_id', 'product_variant_id']);
|
||||||
|
|
||||||
$table->foreign('stok_opname_id')->references('id')->on('stok_opnames')->cascadeOnDelete();
|
$table->foreign('stok_opname_id')->references('id')->on('stok_opnames')->cascadeOnDelete();
|
||||||
$table->foreign('product_variant_id')->references('id')->on('product_variants')->cascadeOnDelete();
|
$table->foreign('product_variant_id')->references('id')->on('product_variants')->cascadeOnDelete();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -338,7 +338,6 @@
|
|||||||
->middleware('permission:'.Permission::CUTTINGS_COMPLETE->value.'|'.Permission::CUTTINGS_VERIFY->value.'|'.Permission::CUTTINGS_REJECT->value)
|
->middleware('permission:'.Permission::CUTTINGS_COMPLETE->value.'|'.Permission::CUTTINGS_VERIFY->value.'|'.Permission::CUTTINGS_REJECT->value)
|
||||||
->name('transition_status');
|
->name('transition_status');
|
||||||
|
|
||||||
|
|
||||||
Route::post('draft-materials', [CuttingDraftItemController::class, 'storeMaterial'])
|
Route::post('draft-materials', [CuttingDraftItemController::class, 'storeMaterial'])
|
||||||
->middleware('permission:'.Permission::CUTTINGS_CREATE->value)
|
->middleware('permission:'.Permission::CUTTINGS_CREATE->value)
|
||||||
->name('draft_materials.store');
|
->name('draft_materials.store');
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use App\Enums\CuttingStatus;
|
use App\Enums\CuttingStatus;
|
||||||
use App\Enums\Permission as PermissionEnum;
|
use App\Enums\Permission as PermissionEnum;
|
||||||
|
use App\Models\Category;
|
||||||
use App\Models\Cutting;
|
use App\Models\Cutting;
|
||||||
use App\Models\CuttingMaterial;
|
use App\Models\CuttingMaterial;
|
||||||
use App\Models\CuttingMaterialCombination;
|
use App\Models\CuttingMaterialCombination;
|
||||||
@ -761,7 +762,7 @@ function setupDraftItems(User $user): array
|
|||||||
test('quick create product with categories', function () {
|
test('quick create product with categories', function () {
|
||||||
$user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);
|
$user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);
|
||||||
|
|
||||||
$category = \App\Models\Category::factory()->create();
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->postJson('/admin/manage/cuttings/quick-create-product', [
|
->postJson('/admin/manage/cuttings/quick-create-product', [
|
||||||
@ -777,7 +778,7 @@ function setupDraftItems(User $user): array
|
|||||||
])
|
])
|
||||||
->assertOk();
|
->assertOk();
|
||||||
|
|
||||||
$product = \App\Models\Product::where('name', 'Product With Category')->first();
|
$product = Product::where('name', 'Product With Category')->first();
|
||||||
expect($product)->not->toBeNull();
|
expect($product)->not->toBeNull();
|
||||||
expect($product->categories)->toHaveCount(1);
|
expect($product->categories)->toHaveCount(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -102,7 +102,7 @@ function createCompletedCuttingSetup(): array
|
|||||||
'product_variant_id' => $setup['variant']->id,
|
'product_variant_id' => $setup['variant']->id,
|
||||||
'good' => 6, // changed from 5 to 6
|
'good' => 6, // changed from 5 to 6
|
||||||
'reject' => 1, // changed from 2 to 1
|
'reject' => 1, // changed from 2 to 1
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'result_prices' => [
|
'result_prices' => [
|
||||||
[
|
[
|
||||||
@ -115,9 +115,9 @@ function createCompletedCuttingSetup(): array
|
|||||||
[
|
[
|
||||||
'type' => 'retail',
|
'type' => 'retail',
|
||||||
'price' => 75000,
|
'price' => 75000,
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -105,7 +105,7 @@ function createCatalogSetup(): array
|
|||||||
'stock_quality' => ProductStockQuality::REJECT->value,
|
'stock_quality' => ProductStockQuality::REJECT->value,
|
||||||
'physical_stock' => 3,
|
'physical_stock' => 3,
|
||||||
'notes' => 'Kelebihan 1 reject',
|
'notes' => 'Kelebihan 1 reject',
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ function createCatalogSetup(): array
|
|||||||
'stock_quality' => ProductStockQuality::RETAIL->value,
|
'stock_quality' => ProductStockQuality::RETAIL->value,
|
||||||
'physical_stock' => 6,
|
'physical_stock' => 6,
|
||||||
'notes' => 'Tambah eceran',
|
'notes' => 'Tambah eceran',
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ function createCatalogSetup(): array
|
|||||||
'stock_quality' => ProductStockQuality::GOOD->value,
|
'stock_quality' => ProductStockQuality::GOOD->value,
|
||||||
'physical_stock' => 9,
|
'physical_stock' => 9,
|
||||||
'notes' => 'Diubah jadi 9',
|
'notes' => 'Diubah jadi 9',
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
294
tests/Feature/CacheTest.php
Normal file
294
tests/Feature/CacheTest.php
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Models\Customer;
|
||||||
|
use App\Models\Supplier;
|
||||||
|
use App\Services\Concerns\CachesQuery;
|
||||||
|
use App\Services\Master\CategoryService;
|
||||||
|
use App\Services\Master\CustomerService;
|
||||||
|
use App\Services\Master\SupplierService;
|
||||||
|
use Database\Seeders\RolePermissionSeeder;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->seed(RolePermissionSeeder::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── CachesQuery Trait ─────────────────────────────────────
|
||||||
|
|
||||||
|
describe('CachesQuery Trait', function () {
|
||||||
|
test('cacheRemember stores and retrieves value', function () {
|
||||||
|
$service = new class
|
||||||
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
|
public function callRemember(): array
|
||||||
|
{
|
||||||
|
return $this->cacheRemember('test:key', 60, function () {
|
||||||
|
return ['foo' => 'bar'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$result = $service->callRemember();
|
||||||
|
expect($result)->toBe(['foo' => 'bar']);
|
||||||
|
expect(Cache::has('test:key'))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cacheRemember returns cached value on second call', function () {
|
||||||
|
$service = new class
|
||||||
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
|
public int $callCount = 0;
|
||||||
|
|
||||||
|
public function callRemember(): array
|
||||||
|
{
|
||||||
|
return $this->cacheRemember('test:counter', 60, function () {
|
||||||
|
$this->callCount++;
|
||||||
|
|
||||||
|
return ['count' => $this->callCount];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$first = $service->callRemember();
|
||||||
|
$second = $service->callRemember();
|
||||||
|
|
||||||
|
expect($first)->toBe(['count' => 1]);
|
||||||
|
expect($second)->toBe(['count' => 1]);
|
||||||
|
expect($service->callCount)->toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cacheForget removes cached value', function () {
|
||||||
|
$service = new class
|
||||||
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
|
public function callRemember(): array
|
||||||
|
{
|
||||||
|
return $this->cacheRemember('test:forget', 60, function () {
|
||||||
|
return ['data' => true];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function callForget(): void
|
||||||
|
{
|
||||||
|
$this->cacheForget('test:forget');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$service->callRemember();
|
||||||
|
expect(Cache::has('test:forget'))->toBeTrue();
|
||||||
|
|
||||||
|
$service->callForget();
|
||||||
|
expect(Cache::has('test:forget'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cacheForever stores value without expiration', function () {
|
||||||
|
$service = new class
|
||||||
|
{
|
||||||
|
use CachesQuery;
|
||||||
|
|
||||||
|
public function callForever(): string
|
||||||
|
{
|
||||||
|
return $this->cacheForever('test:forever', function () {
|
||||||
|
return 'permanent';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$result = $service->callForever();
|
||||||
|
expect($result)->toBe('permanent');
|
||||||
|
expect(Cache::has('test:forever'))->toBeTrue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Category Cache ────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Category Caching', function () {
|
||||||
|
test('getSelectOptions caches results', function () {
|
||||||
|
Category::factory()->count(3)->create();
|
||||||
|
|
||||||
|
$service = app(CategoryService::class);
|
||||||
|
|
||||||
|
$first = $service->getSelectOptions();
|
||||||
|
expect($first)->toHaveCount(3);
|
||||||
|
expect(Cache::has('master:categories:options'))->toBeTrue();
|
||||||
|
|
||||||
|
Category::create(['name' => 'New Category']);
|
||||||
|
|
||||||
|
$second = $service->getSelectOptions();
|
||||||
|
expect($second)->toHaveCount(3);
|
||||||
|
|
||||||
|
Cache::forget('master:categories:options');
|
||||||
|
$third = $service->getSelectOptions();
|
||||||
|
expect($third)->toHaveCount(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create invalidates category cache', function () {
|
||||||
|
$service = app(CategoryService::class);
|
||||||
|
|
||||||
|
$service->getSelectOptions();
|
||||||
|
expect(Cache::has('master:categories:options'))->toBeTrue();
|
||||||
|
|
||||||
|
$service->create(['name' => 'Test']);
|
||||||
|
expect(Cache::has('master:categories:options'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update invalidates category cache', function () {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
$service = app(CategoryService::class);
|
||||||
|
|
||||||
|
$service->getSelectOptions();
|
||||||
|
expect(Cache::has('master:categories:options'))->toBeTrue();
|
||||||
|
|
||||||
|
$service->update($category, ['name' => 'Updated']);
|
||||||
|
expect(Cache::has('master:categories:options'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('delete invalidates category cache', function () {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
$service = app(CategoryService::class);
|
||||||
|
|
||||||
|
$service->getSelectOptions();
|
||||||
|
expect(Cache::has('master:categories:options'))->toBeTrue();
|
||||||
|
|
||||||
|
$service->delete($category);
|
||||||
|
expect(Cache::has('master:categories:options'))->toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Supplier Cache ────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Supplier Caching', function () {
|
||||||
|
test('getSelectOptions caches results', function () {
|
||||||
|
Supplier::factory()->count(3)->create();
|
||||||
|
|
||||||
|
$service = app(SupplierService::class);
|
||||||
|
|
||||||
|
$first = $service->getSelectOptions();
|
||||||
|
expect($first)->toHaveCount(3);
|
||||||
|
expect(Cache::has('master:suppliers:options'))->toBeTrue();
|
||||||
|
|
||||||
|
Cache::forget('master:suppliers:options');
|
||||||
|
$second = $service->getSelectOptions();
|
||||||
|
expect($second)->toHaveCount(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create invalidates supplier cache', function () {
|
||||||
|
$service = app(SupplierService::class);
|
||||||
|
|
||||||
|
$service->getSelectOptions();
|
||||||
|
expect(Cache::has('master:suppliers:options'))->toBeTrue();
|
||||||
|
|
||||||
|
$service->create(['name' => 'Test Supplier', 'phone_number' => '08123', 'address' => 'Jl. Test']);
|
||||||
|
expect(Cache::has('master:suppliers:options'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update invalidates supplier cache', function () {
|
||||||
|
$supplier = Supplier::factory()->create();
|
||||||
|
$service = app(SupplierService::class);
|
||||||
|
|
||||||
|
$service->getSelectOptions();
|
||||||
|
expect(Cache::has('master:suppliers:options'))->toBeTrue();
|
||||||
|
|
||||||
|
$service->update($supplier, ['name' => 'Updated']);
|
||||||
|
expect(Cache::has('master:suppliers:options'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('delete invalidates supplier cache', function () {
|
||||||
|
$supplier = Supplier::factory()->create();
|
||||||
|
$service = app(SupplierService::class);
|
||||||
|
|
||||||
|
$service->getSelectOptions();
|
||||||
|
expect(Cache::has('master:suppliers:options'))->toBeTrue();
|
||||||
|
|
||||||
|
$service->delete($supplier);
|
||||||
|
expect(Cache::has('master:suppliers:options'))->toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Customer Cache ────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Customer Caching', function () {
|
||||||
|
test('getSelectOptions caches results', function () {
|
||||||
|
Customer::factory()->count(3)->create();
|
||||||
|
|
||||||
|
$service = app(CustomerService::class);
|
||||||
|
|
||||||
|
$first = $service->getSelectOptions();
|
||||||
|
expect($first)->toHaveCount(3);
|
||||||
|
expect(Cache::has('master:customers:options'))->toBeTrue();
|
||||||
|
|
||||||
|
Cache::forget('master:customers:options');
|
||||||
|
$second = $service->getSelectOptions();
|
||||||
|
expect($second)->toHaveCount(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create invalidates customer cache', function () {
|
||||||
|
$service = app(CustomerService::class);
|
||||||
|
|
||||||
|
$service->getSelectOptions();
|
||||||
|
expect(Cache::has('master:customers:options'))->toBeTrue();
|
||||||
|
|
||||||
|
$service->create(['name' => 'Test Customer', 'phone_number' => '08123', 'address' => 'Jl. Test']);
|
||||||
|
expect(Cache::has('master:customers:options'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createAndReturn invalidates customer cache', function () {
|
||||||
|
$service = app(CustomerService::class);
|
||||||
|
|
||||||
|
$service->getSelectOptions();
|
||||||
|
expect(Cache::has('master:customers:options'))->toBeTrue();
|
||||||
|
|
||||||
|
$customer = $service->createAndReturn(['name' => 'Test', 'phone_number' => '08123', 'address' => 'Jl. Test']);
|
||||||
|
expect($customer)->toBeInstanceOf(Customer::class);
|
||||||
|
expect(Cache::has('master:customers:options'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update invalidates customer cache', function () {
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
$service = app(CustomerService::class);
|
||||||
|
|
||||||
|
$service->getSelectOptions();
|
||||||
|
expect(Cache::has('master:customers:options'))->toBeTrue();
|
||||||
|
|
||||||
|
$service->update($customer, ['name' => 'Updated']);
|
||||||
|
expect(Cache::has('master:customers:options'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('delete invalidates customer cache', function () {
|
||||||
|
$customer = Customer::factory()->create();
|
||||||
|
$service = app(CustomerService::class);
|
||||||
|
|
||||||
|
$service->getSelectOptions();
|
||||||
|
expect(Cache::has('master:customers:options'))->toBeTrue();
|
||||||
|
|
||||||
|
$service->delete($customer);
|
||||||
|
expect(Cache::has('master:customers:options'))->toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Cache Key Isolation ───────────────────────────────────
|
||||||
|
|
||||||
|
describe('Cache Key Isolation', function () {
|
||||||
|
test('different services use different cache keys', function () {
|
||||||
|
Category::factory()->count(2)->create();
|
||||||
|
Supplier::factory()->count(3)->create();
|
||||||
|
|
||||||
|
app(CategoryService::class)->getSelectOptions();
|
||||||
|
app(SupplierService::class)->getSelectOptions();
|
||||||
|
|
||||||
|
expect(Cache::has('master:categories:options'))->toBeTrue();
|
||||||
|
expect(Cache::has('master:suppliers:options'))->toBeTrue();
|
||||||
|
|
||||||
|
Cache::forget('master:categories:options');
|
||||||
|
|
||||||
|
expect(Cache::has('master:categories:options'))->toBeFalse();
|
||||||
|
expect(Cache::has('master:suppliers:options'))->toBeTrue();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user