Compare commits

...

5 Commits

16 changed files with 705 additions and 719 deletions

View File

@ -95,6 +95,8 @@ function () use ($cashAccount, $validated, $user): CashTransaction {
'Gagal melakukan setoran kas',
);
$this->cacheForget('finance:default_account');
$this->pushNotificationService->sendToRoles(
'💰 Setoran Kas',
"Setoran kas baru {$transaction->amount_formatted} dengan keterangan: {$transaction->description} oleh {$user->profile?->full_name}.",
@ -138,6 +140,8 @@ function () use ($cashAccount, $validated, $user): CashTransaction {
'Gagal melakukan tarik kas',
);
$this->cacheForget('finance:default_account');
$this->pushNotificationService->sendToRoles(
'🏦 Tarik Kas',
"Tarik kas {$transaction->amount_formatted} dengan keterangan: {$transaction->description} oleh {$user->profile?->full_name}.",
@ -155,7 +159,7 @@ public function recordOutgoing(
User $user,
?CashAccount $cashAccount = null,
): CashTransaction {
return $this->runInTransaction(
$transaction = $this->runInTransaction(
function () use ($reference, $amount, $description, $user, $cashAccount): CashTransaction {
$account = CashAccount::query()->lockForUpdate()->findOrFail(
($cashAccount ?? $this->getDefaultAccount())->id,
@ -187,6 +191,10 @@ function () use ($reference, $amount, $description, $user, $cashAccount): CashTr
},
'Gagal mencatat transaksi keluar kas',
);
$this->cacheForget('finance:default_account');
return $transaction;
}
public function recordIncoming(
@ -196,7 +204,7 @@ public function recordIncoming(
User $user,
?CashAccount $cashAccount = null,
): CashTransaction {
return $this->runInTransaction(
$transaction = $this->runInTransaction(
function () use ($reference, $amount, $description, $user, $cashAccount): CashTransaction {
$account = CashAccount::query()->lockForUpdate()->findOrFail(
($cashAccount ?? $this->getDefaultAccount())->id,
@ -222,6 +230,10 @@ function () use ($reference, $amount, $description, $user, $cashAccount): CashTr
},
'Gagal mencatat transaksi masuk kas',
);
$this->cacheForget('finance:default_account');
return $transaction;
}
public function updateDeposit(CashTransaction $transaction, array $validated, User $user): void
@ -250,6 +262,8 @@ function () use ($transaction, $validated): void {
'Gagal memperbarui setoran kas',
);
$this->cacheForget('finance:default_account');
$this->pushNotificationService->sendToRoles(
'✏️ Transaksi Kas Diperbarui',
"Transaksi kas dengan keterangan {$transaction->description} diperbarui menjadi senilai {$transaction->amount_formatted} oleh {$user->profile?->full_name}.",
@ -269,7 +283,6 @@ function () use ($transaction): void {
$account = $transaction->cashAccount;
$transaction->clearMediaCollection('photos');
$transaction->delete();
$this->recalculateBalances($account);
@ -283,6 +296,8 @@ function () use ($transaction): void {
'Gagal menghapus transaksi kas',
);
$this->cacheForget('finance:default_account');
$this->pushNotificationService->sendToRoles(
'🗑️ Transaksi Kas Dihapus',
"Transaksi kas senilai {$amountFormatted} dengan keterangan {$description} telah dihapus oleh {$user->profile?->full_name}.",
@ -317,6 +332,8 @@ function () use ($transaction, $amount, $description): void {
},
'Gagal memperbarui transaksi kas referensi',
);
$this->cacheForget('finance:default_account');
}
public function deleteReferencedTransaction(CashTransaction $transaction): void
@ -338,6 +355,8 @@ function () use ($transaction): void {
},
'Gagal menghapus transaksi kas referensi',
);
$this->cacheForget('finance:default_account');
}
private function recalculateBalances(CashAccount $cashAccount): void

View File

@ -133,7 +133,6 @@ function () use ($expense): void {
$this->cashService->deleteReferencedTransaction($expense->cashTransaction);
}
$expense->clearMediaCollection('photos');
$expense->delete();
},
'Gagal menghapus pengeluaran',

View File

@ -172,8 +172,6 @@ public function delete(Attendance $attendance, User $user): void
$this->runInTransaction(
function () use ($attendance): void {
$attendance->clearMediaCollection('checkin');
$attendance->clearMediaCollection('checkout');
$attendance->delete();
},
'Gagal menghapus presensi',

View File

@ -128,6 +128,7 @@ function () use ($validated): User {
'Gagal membuat karyawan',
);
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('hr:employees:*');
$this->notifyOwner(
@ -189,6 +190,7 @@ function () use ($validated, $user, $employee): void {
'Gagal memperbarui karyawan',
);
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('hr:employees:*');
$this->notifyOwner(
@ -213,6 +215,7 @@ function () use ($user, $validated): void {
'Gagal memperbarui status karyawan',
);
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('hr:employees:*');
$statusLabel = $validated['is_active'] ? 'aktif' : 'nonaktif';
@ -237,6 +240,7 @@ function () use ($user): void {
'Gagal mereset kata sandi karyawan',
);
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('hr:employees:*');
$this->notifyOwner(
@ -259,6 +263,7 @@ function () use ($user): void {
'Gagal menghapus karyawan',
);
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('hr:employees:*');
$this->notifyOwner(

View File

@ -680,7 +680,6 @@ function () use ($cutting): void {
$cutting->materials()->delete();
$cutting->results()->delete();
$cutting->clearMediaCollection('images');
$cutting->delete();
},
'Gagal menghapus proses cutting',

View File

@ -716,7 +716,6 @@ private function rejectCreate(OwnerVerificationRequest $verificationRequest): vo
$this->runInTransaction(
function () use ($purchase): void {
$purchase->clearMediaCollection('photos');
$purchase->items()->delete();
$purchase->delete();
},
@ -734,7 +733,6 @@ function () use ($purchase): void {
$this->decrementStock($item);
}
$purchase->clearMediaCollection('photos');
$purchase->items()->delete();
$purchase->delete();
},

View File

@ -573,7 +573,6 @@ private function rejectCreate(OwnerVerificationRequest $verificationRequest): vo
$this->runInTransaction(
function () use ($restock): void {
$restock->clearMediaCollection('photos');
$restock->items()->delete();
$restock->delete();
},
@ -593,7 +592,6 @@ function () use ($restock): void {
$this->decrementStock($item, $stockType);
}
$restock->clearMediaCollection('photos');
$restock->items()->delete();
$restock->delete();
},

View File

@ -156,10 +156,8 @@ public function submitVerification(
}
$this->cacheForgetByPattern('manage:stocks:*');
if ($isOwner) {
$this->cacheForgetByPattern('prices:*');
}
}
public function approveVerification(
Cutting $cutting,

View File

@ -254,6 +254,7 @@ function () use ($validated, $product, $user, $isOwner): void {
);
$this->cacheForgetByPattern('master:products:*');
$this->cacheForgetByPattern('prices:*');
$this->cacheForget('homepage:page_data');
if ($isOwner) {
@ -328,6 +329,7 @@ public function delete(Product $product, User $user): void
$name = $product->name;
$this->applyDeleteSubject($product);
$this->cacheForgetByPattern('master:products:*');
$this->cacheForgetByPattern('prices:*');
$this->cacheForget('homepage:page_data');
$this->notifyOwner(
@ -435,6 +437,7 @@ public function applyVerificationRequest(OwnerVerificationRequest $verificationR
};
$this->cacheForgetByPattern('master:products:*');
$this->cacheForgetByPattern('prices:*');
}
public function rejectVerificationRequest(OwnerVerificationRequest $verificationRequest): void
@ -449,6 +452,7 @@ public function rejectVerificationRequest(OwnerVerificationRequest $verification
};
$this->cacheForgetByPattern('master:products:*');
$this->cacheForgetByPattern('prices:*');
$this->cacheForget('homepage:page_data');
}
@ -519,7 +523,6 @@ private function applyPayloadToProduct(
->whereNotIn('id', $submittedVariantIds)
->get()
->each(function (ProductVariant $variant): void {
$variant->clearMediaCollection('images');
$variant->delete();
});
@ -586,9 +589,6 @@ private function applyDeleteSubject(Product $product): void
{
$this->runInTransaction(
function () use ($product): void {
$product->variants()->each(function (ProductVariant $variant): void {
$variant->clearMediaCollection('images');
});
$product->variants()->delete();
$product->categories()->detach();
$product->delete();
@ -656,9 +656,6 @@ private function rejectCreate(OwnerVerificationRequest $verificationRequest): vo
$this->runInTransaction(
function () use ($product): void {
$product->variants()->each(function (ProductVariant $variant): void {
$variant->clearMediaCollection('images');
});
$product->variants()->delete();
$product->categories()->detach();
$product->delete();

View File

@ -37,6 +37,24 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $st
->with('media')
->when($tableQuery['search'] !== '', function ($query) use ($tableQuery): void {
$query->where('variant', 'like', "%{$tableQuery['search']}%");
})
->when($stockStatus === 'out_of_stock', fn ($q) => $q->where('stock', '<=', 0))
->when($stockStatus === 'low_stock', function ($q): void {
$q->where('stock', '>', 0)
->where(function ($q): void {
$q->orWhere(function ($q): void {
$q->whereHas('rawMaterial', fn ($q) => $q->where('unit', RawMaterialUnit::YARD->value))
->where('stock', '<', 20);
})
->orWhere(function ($q): void {
$q->whereHas('rawMaterial', fn ($q) => $q->where('unit', RawMaterialUnit::METER->value))
->where('stock', '<', 10);
})
->orWhere(function ($q): void {
$q->whereHas('rawMaterial', fn ($q) => $q->where('unit', RawMaterialUnit::KILOGRAM->value))
->where('stock', '<', 5);
});
});
}),
])
->when(($tableQuery['search_id'] ?? '') !== '', function (Builder $query) use ($tableQuery): void {
@ -52,21 +70,15 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $st
$query->whereHas('prices', fn (Builder $priceQuery) => $priceQuery->where('stock', '<=', 0));
})
->when($stockStatus === 'low_stock', function (Builder $query): void {
$minStock = [
RawMaterialUnit::YARD->value => 20,
RawMaterialUnit::METER->value => 10,
RawMaterialUnit::KILOGRAM->value => 5,
];
$query->whereHas('prices', function (Builder $priceQuery) use ($minStock): void {
$priceQuery->where('stock', '>', 0)->where(function (Builder $priceQuery) use ($minStock): void {
foreach (RawMaterialUnit::cases() as $unit) {
$priceQuery->orWhere(function (Builder $priceQuery) use ($unit, $minStock): void {
$priceQuery->whereHas('rawMaterial', fn (Builder $rawMaterialQuery) => $rawMaterialQuery->where('unit', $unit->value))
->where('stock', '<', $minStock[$unit->value]);
});
}
});
$query->where(function (Builder $query): void {
$query->where('unit', RawMaterialUnit::YARD->value)
->whereHas('prices', fn (Builder $q) => $q->where('stock', '>', 0)->where('stock', '<', 20));
})->orWhere(function (Builder $query): void {
$query->where('unit', RawMaterialUnit::METER->value)
->whereHas('prices', fn (Builder $q) => $q->where('stock', '>', 0)->where('stock', '<', 10));
})->orWhere(function (Builder $query): void {
$query->where('unit', RawMaterialUnit::KILOGRAM->value)
->whereHas('prices', fn (Builder $q) => $q->where('stock', '>', 0)->where('stock', '<', 5));
});
});
@ -274,9 +286,6 @@ private function applyDeleteSubject(RawMaterial $rawMaterial): void
{
$this->runInTransaction(
function () use ($rawMaterial): void {
$rawMaterial->prices()->each(function (RawMaterialPrice $price): void {
$price->clearMediaCollection('images');
});
$rawMaterial->prices()->delete();
$rawMaterial->delete();
},
@ -328,7 +337,6 @@ private function applyPayloadToRawMaterial(
->whereNotIn('id', $submittedPriceIds)
->get()
->each(function (RawMaterialPrice $price): void {
$price->clearMediaCollection('images');
$price->delete();
});
@ -374,9 +382,6 @@ private function rejectCreate(OwnerVerificationRequest $verificationRequest): vo
$this->runInTransaction(
function () use ($rawMaterial): void {
$rawMaterial->prices()->each(function (RawMaterialPrice $price): void {
$price->clearMediaCollection('images');
});
$rawMaterial->prices()->delete();
$rawMaterial->delete();
},

View File

@ -20,17 +20,13 @@
use App\Models\Purchase;
use App\Models\RawMaterialPrice;
use App\Models\User;
use App\Services\Concerns\CachesQuery;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class AnalysisService
{
use CachesQuery;
public function getAttendance(): array
{
return $this->cacheRemember('analysis:get_attendance', 900, function () {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -61,12 +57,10 @@ public function getAttendance(): array
'absent' => $absent,
'on_leave' => $onLeave,
];
});
}
public function getMyAttendance(User $user, ?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_my_attendance', 900, function () use ($user, $startDate, $endDate) {
$employee = $user->employee;
if ($employee === null) {
@ -126,12 +120,10 @@ public function getMyAttendance(User $user, ?Carbon $startDate = null, ?Carbon $
'leave_days' => $leaveDays,
'percentage' => $percentage,
];
});
}
public function getCashOverview(): array
{
return $this->cacheRemember('analysis:get_cash_overview', 900, function () {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -159,12 +151,10 @@ public function getCashOverview(): array
'total_deposit' => (int) ($summary->total_deposit ?? 0),
'total_withdrawal' => (int) ($summary->total_withdrawal ?? 0),
];
});
}
public function getTopSuppliers(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_top_suppliers', 900, function () use ($startDate, $endDate) {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -184,12 +174,10 @@ public function getTopSuppliers(?Carbon $startDate = null, ?Carbon $endDate = nu
'purchase_count' => (int) $item->purchase_count,
])
->toArray();
});
}
public function getTopCustomers(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_top_customers', 900, function () use ($startDate, $endDate) {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -212,12 +200,10 @@ public function getTopCustomers(?Carbon $startDate = null, ?Carbon $endDate = nu
'order_count' => (int) $item->order_count,
])
->toArray();
});
}
public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_revenue_summary', 900, function () use ($startDate, $endDate) {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -262,12 +248,10 @@ public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate =
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
];
});
}
public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_monthly_revenue', 900, function () use ($startDate, $endDate) {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -381,12 +365,10 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate =
}
return $result;
});
}
public function getExpenseSummary(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_expense_summary', 900, function () use ($startDate, $endDate) {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -423,12 +405,10 @@ public function getExpenseSummary(?Carbon $startDate = null, ?Carbon $endDate =
'expense_total' => $expenseTotal,
'advance_total' => $advanceTotal,
];
});
}
public function getMonthlyExpense(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_monthly_expense', 900, function () use ($startDate, $endDate) {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -498,12 +478,10 @@ public function getMonthlyExpense(?Carbon $startDate = null, ?Carbon $endDate =
}
return $result;
});
}
public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_profit_metrics', 900, function () use ($startDate, $endDate) {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -605,12 +583,10 @@ public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = n
'aov' => $aov,
'items_per_transaction' => $itemsPerTransaction,
];
});
}
public function getRawMaterialStock(): array
{
return $this->cacheRemember('analysis:get_raw_material_stock', 900, function () {
$prices = RawMaterialPrice::query()
->join('raw_materials', 'raw_material_prices.raw_material_id', '=', 'raw_materials.id')
->selectRaw('
@ -634,12 +610,10 @@ public function getRawMaterialStock(): array
'kilogram' => round((float) ($prices->get('kilogram')->total_stock ?? 0), 2),
],
];
});
}
public function getProductStock(): array
{
return $this->cacheRemember('analysis:get_product_stock', 900, function () {
$variants = ProductVariant::query()
->join('products', 'product_variants.product_id', '=', 'products.id')
->selectRaw('
@ -672,12 +646,10 @@ public function getProductStock(): array
'total_variants' => (int) ($variants->total_variants ?? 0),
'total_categories' => (int) $totalCategories,
];
});
}
public function getBusyHours(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_busy_hours', 900, function () use ($startDate, $endDate) {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -702,12 +674,10 @@ public function getBusyHours(?Carbon $startDate = null, ?Carbon $endDate = null)
}
return $result;
});
}
public function getTopProducts(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_top_products', 900, function () use ($startDate, $endDate) {
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
@ -732,12 +702,10 @@ public function getTopProducts(?Carbon $startDate = null, ?Carbon $endDate = nul
'total_revenue' => (int) $item->total_revenue,
])
->toArray();
});
}
public function getMarketingSales(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
return $this->cacheRemember('analysis:get_marketing_sales', 900, function () use ($startDate, $endDate) {
$qtySubquery = DB::table('order_items')
->select('order_id', DB::raw('SUM(quantity) as total_qty'))
->whereNull('deleted_at')
@ -775,7 +743,6 @@ public function getMarketingSales(?Carbon $startDate = null, ?Carbon $endDate =
'avg_order' => (int) $item->avg_order,
])
->toArray();
});
}
public function isManager(?User $user): bool

View File

@ -6,7 +6,6 @@
use App\Models\Category;
use App\Models\Product;
use App\Models\SystemConfiguration;
use App\Services\Concerns\CachesQuery;
use App\Services\Manage\CuttingResultPriceResolver;
use App\Services\System\Setting\HomepageSettingService;
use App\Settings\SocialMediaSettings;
@ -15,8 +14,6 @@
class HomepageService
{
use CachesQuery;
public function __construct(
private readonly CuttingResultPriceResolver $cuttingResultPriceResolver,
private readonly HomepageSettingService $homepageSettingService,
@ -24,7 +21,6 @@ public function __construct(
public function pageData(): array
{
return $this->cacheRemember('homepage:page_data', 900, function () {
$categories = Category::getActiveWithProducts();
$products = $this->getProducts();
@ -49,7 +45,6 @@ public function pageData(): array
'tiktokUrl' => $socialSettings->tiktok_url ?? null,
'homepage' => $this->homepageSettingService->homepageData(),
];
});
}
private function getProducts(): array

View File

@ -87,6 +87,8 @@ public function create(array $validated): void
]);
}
$this->cacheForget('system:permissions');
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('system:roles:*');
}
@ -114,6 +116,8 @@ public function update(Role $role, array $validated): void
]);
}
$this->cacheForget('system:permissions');
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('system:roles:*');
}
@ -121,6 +125,8 @@ public function delete(Role $role): void
{
$role->delete();
$this->cacheForget('system:permissions');
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('system:roles:*');
}

View File

@ -16,7 +16,7 @@
*/
'ssr' => [
'enabled' => true,
'enabled' => false,
'url' => 'http://127.0.0.1:13714',
// 'bundle' => base_path('bootstrap/ssr/ssr.mjs'),

View File

@ -5,6 +5,7 @@ import { Toaster } from '@/components/ui/sonner';
import { useFlashToast } from '@/composables/useFlashToast';
import { restoreConnection } from '@/lib/thermal-printer/stable-transport';
if (typeof window !== 'undefined') {
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js', { scope: '/' })
@ -22,6 +23,7 @@ window.addEventListener('beforeinstallprompt', (e) => {
});
void restoreConnection();
}
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';

View File

@ -529,11 +529,11 @@ const productBarData = computed<ProductData[]>(() => {
function applyFilters() {
router.get(
admin.analysis.url({
admin.analysis.url(),
{
start_date: startDate.value,
end_date: endDate.value,
}),
{},
},
{
preserveState: true,
preserveScroll: true,