diff --git a/app/Enums/EmployeeAdvanceStatus.php b/app/Enums/EmployeeAdvanceStatus.php
index 2233737..8d8f29b 100644
--- a/app/Enums/EmployeeAdvanceStatus.php
+++ b/app/Enums/EmployeeAdvanceStatus.php
@@ -11,6 +11,7 @@ enum EmployeeAdvanceStatus: string
case PENDING = 'pending';
case APPROVED = 'approved';
case REJECTED = 'rejected';
+ case PARTIALLY_PAID = 'partially_paid';
case PAID = 'paid';
public function label(): string
@@ -19,6 +20,7 @@ public function label(): string
self::PENDING => 'Menunggu',
self::APPROVED => 'Disetujui',
self::REJECTED => 'Ditolak',
+ self::PARTIALLY_PAID => 'Dibayar Sebagian',
self::PAID => 'Lunas',
};
}
diff --git a/app/Enums/Role.php b/app/Enums/Role.php
index b26bd39..fb7f1b9 100644
--- a/app/Enums/Role.php
+++ b/app/Enums/Role.php
@@ -13,7 +13,8 @@ enum Role: string
case ADMIN_TOKO = 'admin-toko';
case ADMIN_BAHAN_BAKU = 'admin-bahan-baku';
case DIREKTUR = 'direktur';
- case MARKETING = 'marketing';
+ case MARKETING_OFFLINE = 'marketing-offline';
+ case MARKETING_ONLINE = 'marketing-online';
case CASHIER = 'cashier';
case NON_OPERATOR = 'non-operator';
@@ -25,7 +26,8 @@ public function label(): string
self::ADMIN_TOKO => 'Admin Toko',
self::ADMIN_BAHAN_BAKU => 'Admin Bahan Baku',
self::DIREKTUR => 'Direktur',
- self::MARKETING => 'Marketing',
+ self::MARKETING_OFFLINE => 'Marketing Offline',
+ self::MARKETING_ONLINE => 'Marketing Online',
self::CASHIER => 'Kasir',
self::NON_OPERATOR => 'Non Operator',
};
@@ -63,7 +65,6 @@ public function permissions(): array
Permission::ANALYSIS_ATTENDANCE,
Permission::ANALYSIS_CASH,
- Permission::ANALYSIS_RAW_MATERIALS,
Permission::ANALYSIS_PRODUCT_STOCK,
Permission::ANALYSIS_REVENUE,
Permission::ANALYSIS_EXPENSE,
@@ -72,14 +73,11 @@ public function permissions(): array
Permission::ANALYSIS_PROFIT_HPP,
Permission::ANALYSIS_PROFIT_GROSS,
Permission::ANALYSIS_PROFIT_MARGIN,
- Permission::ANALYSIS_TOP_SUPPLIERS,
Permission::ANALYSIS_TOP_CUSTOMERS,
Permission::ANALYSIS_TOP_PRODUCTS,
Permission::EMPLOYEES_VIEW,
- Permission::STOCKS_VIEW,
-
Permission::ATTENDANCES_VIEW,
Permission::ATTENDANCES_CREATE,
Permission::ATTENDANCES_DELETE,
@@ -92,16 +90,10 @@ public function permissions(): array
Permission::CATEGORIES_VIEW,
- Permission::SUPPLIERS_VIEW,
-
Permission::CUSTOMERS_VIEW,
Permission::PRODUCTS_VIEW,
- Permission::RAW_MATERIALS_VIEW,
-
- Permission::PURCHASES_VIEW,
-
Permission::ORDERS_VIEW,
Permission::CUTTINGS_VIEW,
@@ -203,7 +195,6 @@ public function permissions(): array
self::CASHIER => [
Permission::DASHBOARD_VIEW,
Permission::DASHBOARD_ATTENDANCE,
- Permission::DASHBOARD_CASH,
Permission::DASHBOARD_REVENUE,
Permission::DASHBOARD_ORDERS_CHANNEL,
Permission::DASHBOARD_ORDERS_PAYMENT,
@@ -211,13 +202,8 @@ public function permissions(): array
Permission::ANALYSIS_VIEW,
Permission::ANALYSIS_ATTENDANCE,
- Permission::ANALYSIS_CASH,
Permission::ANALYSIS_REVENUE,
- Permission::ANALYSIS_EXPENSE,
Permission::ANALYSIS_PROFIT_ORDERS,
- Permission::ANALYSIS_PROFIT_HPP,
- Permission::ANALYSIS_PROFIT_GROSS,
- Permission::ANALYSIS_PROFIT_MARGIN,
Permission::ANALYSIS_TOP_CUSTOMERS,
Permission::ANALYSIS_TOP_PRODUCTS,
@@ -303,7 +289,7 @@ public function permissions(): array
Permission::PAYROLL_VIEW,
],
- self::MARKETING => [
+ self::MARKETING_OFFLINE, self::MARKETING_ONLINE => [
Permission::DASHBOARD_VIEW,
Permission::DASHBOARD_ATTENDANCE,
Permission::DASHBOARD_REVENUE,
@@ -316,10 +302,6 @@ public function permissions(): array
Permission::ANALYSIS_ATTENDANCE,
Permission::ANALYSIS_REVENUE,
Permission::ANALYSIS_PROFIT_ORDERS,
- Permission::ANALYSIS_PROFIT_HPP,
- Permission::ANALYSIS_PROFIT_GROSS,
- Permission::ANALYSIS_PROFIT_MARGIN,
- Permission::ANALYSIS_TOP_CUSTOMERS,
Permission::ANALYSIS_TOP_PRODUCTS,
Permission::ATTENDANCES_VIEW,
@@ -330,8 +312,6 @@ public function permissions(): array
Permission::LEAVE_REQUESTS_UPDATE,
Permission::LEAVE_REQUESTS_DELETE,
- Permission::CUSTOMERS_VIEW,
-
Permission::PRODUCTS_VIEW,
Permission::ORDERS_VIEW,
diff --git a/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php b/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php
index d4951d0..3b5d709 100644
--- a/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php
+++ b/app/Http/Controllers/Admin/Finance/EmployeeAdvanceController.php
@@ -91,11 +91,13 @@ public function reject(RejectEmployeeAdvanceRequest $request, EmployeeAdvance $e
return redirect()->route('admin.finance.employee_advances.index');
}
- public function pay(EmployeeAdvance $employeeAdvance): RedirectResponse
+ public function pay(Request $request, EmployeeAdvance $employeeAdvance): RedirectResponse
{
- $this->employeeAdvanceService->pay($employeeAdvance, auth()->user());
+ $payAmount = $request->input('amount') ? (int) $request->input('amount') : null;
- $this->flashSuccess('Kasbon berhasil dilunasi.');
+ $this->employeeAdvanceService->pay($employeeAdvance, auth()->user(), $payAmount);
+
+ $this->flashSuccess('Kasbon berhasil dibayar.');
return redirect()->route('admin.finance.employee_advances.index');
}
diff --git a/app/Http/Requests/Admin/Manage/OrderRequest.php b/app/Http/Requests/Admin/Manage/OrderRequest.php
index 9095ca0..d19f987 100644
--- a/app/Http/Requests/Admin/Manage/OrderRequest.php
+++ b/app/Http/Requests/Admin/Manage/OrderRequest.php
@@ -38,7 +38,6 @@ public function rules(): array
'tiktok_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::TIKTOK->value), 'string', 'max:100'],
'shopee_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::SHOPEE->value), 'string', 'max:100'],
'discount' => ['nullable', 'integer', 'min:0'],
- 'shipping_cost' => ['nullable', 'integer', 'min:0'],
'notes' => ['nullable', 'string'],
];
@@ -71,7 +70,6 @@ public function attributes(): array
'tiktok_order_id' => 'ID pesanan TikTok Shop',
'shopee_order_id' => 'ID pesanan Shopee',
'discount' => 'diskon',
- 'shipping_cost' => 'ongkos kirim',
'notes' => 'keterangan',
'items' => 'produk',
'items.*.product_variant_id' => 'varian produk',
@@ -82,16 +80,18 @@ public function attributes(): array
public function withValidator(Validator $validator): void
{
- if (! $this->isMethod('PUT') && ! $this->isMethod('PATCH')) {
- return;
- }
-
$validator->after(function (Validator $validator): void {
- /** @var Order $order */
- $order = $this->route('order');
+ if ($this->filled('marketing_id') && $this->marketing_id !== 'none' && ! $this->filled('customer_id')) {
+ $validator->errors()->add('customer_id', 'Pelanggan wajib diisi jika marketing dipilih.');
+ }
- if (! $order->status->isEditable()) {
- $validator->errors()->add('status', 'Pesanan tidak dapat diubah.');
+ if ($this->isMethod('PUT') || $this->isMethod('PATCH')) {
+ /** @var Order $order */
+ $order = $this->route('order');
+
+ if (! $order->status->isEditable()) {
+ $validator->errors()->add('status', 'Pesanan tidak dapat diubah.');
+ }
}
});
}
diff --git a/app/Models/EmployeeAdvance.php b/app/Models/EmployeeAdvance.php
index 5d5809b..a011f3f 100644
--- a/app/Models/EmployeeAdvance.php
+++ b/app/Models/EmployeeAdvance.php
@@ -17,6 +17,9 @@
#[Guarded(['id'])]
#[Appends([
'amount_formatted',
+ 'paid_amount_formatted',
+ 'remaining_amount',
+ 'remaining_amount_formatted',
'due_date_formatted',
'due_date_input',
'status_label',
@@ -35,6 +38,7 @@ protected function casts(): array
{
return [
'amount' => 'integer',
+ 'paid_amount' => 'integer',
'due_date' => 'date',
'status' => EmployeeAdvanceStatus::class,
'verified_at' => 'datetime',
@@ -74,10 +78,31 @@ public function amountFormatted(): Attribute
);
}
+ public function paidAmountFormatted(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => 'Rp '.number_format($this->paid_amount, 0, ',', '.'),
+ );
+ }
+
+ public function remainingAmount(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => $this->amount - $this->paid_amount,
+ );
+ }
+
+ public function remainingAmountFormatted(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => 'Rp '.number_format($this->remaining_amount, 0, ',', '.'),
+ );
+ }
+
public function canPay(): Attribute
{
return Attribute::make(
- get: fn () => $this->status === EmployeeAdvanceStatus::APPROVED,
+ get: fn () => in_array($this->status, [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID], true),
);
}
diff --git a/app/Models/Order.php b/app/Models/Order.php
index e8e3824..27eb30a 100644
--- a/app/Models/Order.php
+++ b/app/Models/Order.php
@@ -22,7 +22,6 @@
#[Appends([
'subtotal_formatted',
'discount_formatted',
- 'shipping_cost_formatted',
'total_amount_formatted',
'created_at_formatted',
'channel_label',
@@ -46,7 +45,6 @@ protected function casts(): array
'status' => OrderStatus::class,
'subtotal' => 'integer',
'discount' => 'integer',
- 'shipping_cost' => 'integer',
'marketplace_settings_snapshot' => 'array',
'total_amount' => 'integer',
];
@@ -98,13 +96,6 @@ public function discountFormatted(): Attribute
);
}
- public function shippingCostFormatted(): Attribute
- {
- return Attribute::make(
- get: fn () => 'Rp '.number_format($this->shipping_cost, 0, ',', '.'),
- );
- }
-
public function totalAmountFormatted(): Attribute
{
return Attribute::make(
diff --git a/app/Models/Payroll.php b/app/Models/Payroll.php
index 4a58ddf..91fc447 100644
--- a/app/Models/Payroll.php
+++ b/app/Models/Payroll.php
@@ -129,8 +129,9 @@ public function calculateKasbonDeduction(): int
{
$outstanding = (int) EmployeeAdvance::query()
->where('employee_id', $this->employee_id)
- ->where('status', EmployeeAdvanceStatus::APPROVED)
- ->sum('amount');
+ ->whereIn('status', [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID])
+ ->selectRaw('SUM(amount - paid_amount) as remaining')
+ ->value('remaining');
return min($outstanding, (int) $this->base_salary + (int) $this->adjustments()
->where('type', PayrollAdjustmentType::BONUS)
diff --git a/app/Services/Finance/EmployeeAdvanceService.php b/app/Services/Finance/EmployeeAdvanceService.php
index f375c2b..a73d0da 100644
--- a/app/Services/Finance/EmployeeAdvanceService.php
+++ b/app/Services/Finance/EmployeeAdvanceService.php
@@ -28,10 +28,11 @@ public function __construct(
*/
public function outstandingSummary(?User $user = null): array
{
- $query = EmployeeAdvance::query()->approved()
+ $query = EmployeeAdvance::query()
+ ->whereIn('status', [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID])
->when(! $user->hasAnyRole([Role::OWNER->value, Role::DEVELOPER->value, Role::DIREKTUR->value]), fn (Builder $query) => $query->where('employee_id', $user->employee?->id ?? -1));
- $outstandingAmount = (int) $query->sum('amount');
+ $outstandingAmount = (int) $query->selectRaw('SUM(amount - paid_amount) as remaining')->value('remaining');
$outstandingCount = $query->count();
@@ -242,29 +243,37 @@ public function reject(EmployeeAdvance $employeeAdvance, string $reason, User $u
}
}
- public function pay(EmployeeAdvance $employeeAdvance, User $user): void
+ public function pay(EmployeeAdvance $employeeAdvance, User $user, ?int $payAmount = null): void
{
try {
- DB::transaction(function () use ($employeeAdvance, $user): void {
+ DB::transaction(function () use ($employeeAdvance, $user, $payAmount): void {
$employeeAdvance->loadMissing('employee.user.profile');
+ $remaining = $employeeAdvance->amount - $employeeAdvance->paid_amount;
+ $amountToPay = $payAmount !== null ? min($payAmount, $remaining) : $remaining;
+ $isFullPayment = ($employeeAdvance->paid_amount + $amountToPay) >= $employeeAdvance->amount;
+
$description = sprintf(
- 'Pelunasan kasbon: %s',
+ '%s kasbon: %s',
+ $isFullPayment ? 'Pelunasan' : 'Pembayaran sebagian kasbon',
$employeeAdvance->employeeName,
);
$cashTransaction = $this->cashService->recordIncoming(
$employeeAdvance,
- $employeeAdvance->amount,
+ $amountToPay,
$description,
$user,
);
+ $newPaidAmount = $employeeAdvance->paid_amount + $amountToPay;
+
$employeeAdvance->update([
- 'repayment_cash_transaction_id' => $cashTransaction->id,
- 'paid_at' => now(),
- 'paid_by_id' => $user->id,
- 'status' => EmployeeAdvanceStatus::PAID,
+ 'paid_amount' => $newPaidAmount,
+ 'repayment_cash_transaction_id' => $isFullPayment ? $cashTransaction->id : $employeeAdvance->repayment_cash_transaction_id,
+ 'paid_at' => $isFullPayment ? now() : $employeeAdvance->paid_at,
+ 'paid_by_id' => $isFullPayment ? $user->id : $employeeAdvance->paid_by_id,
+ 'status' => $isFullPayment ? EmployeeAdvanceStatus::PAID : EmployeeAdvanceStatus::PARTIALLY_PAID,
]);
});
} catch (ValidationException $e) {
diff --git a/app/Services/Finance/PayrollService.php b/app/Services/Finance/PayrollService.php
index 8d76015..ed27925 100644
--- a/app/Services/Finance/PayrollService.php
+++ b/app/Services/Finance/PayrollService.php
@@ -437,7 +437,7 @@ private function settleKasbonFromPayroll(Payroll $payroll, User $user): void
$advances = EmployeeAdvance::query()
->where('employee_id', $payroll->employee_id)
- ->where('status', EmployeeAdvanceStatus::APPROVED)
+ ->whereIn('status', [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID])
->orderBy('created_at')
->get();
@@ -446,12 +446,21 @@ private function settleKasbonFromPayroll(Payroll $payroll, User $user): void
break;
}
- $advance->paid_at = now();
- $advance->paid_by_id = $user->id;
- $advance->status = EmployeeAdvanceStatus::PAID;
+ $advanceRemaining = $advance->amount - $advance->paid_amount;
+ $paymentAmount = min($remaining, $advanceRemaining);
+ $advance->paid_amount += $paymentAmount;
+
+ if ($advance->paid_amount >= $advance->amount) {
+ $advance->paid_at = now();
+ $advance->paid_by_id = $user->id;
+ $advance->status = EmployeeAdvanceStatus::PAID;
+ } else {
+ $advance->status = EmployeeAdvanceStatus::PARTIALLY_PAID;
+ }
+
$advance->save();
- $remaining -= $advance->amount;
+ $remaining -= $paymentAmount;
}
}
diff --git a/app/Services/Manage/OrderService.php b/app/Services/Manage/OrderService.php
index 35b16b5..5cab355 100644
--- a/app/Services/Manage/OrderService.php
+++ b/app/Services/Manage/OrderService.php
@@ -87,7 +87,8 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
'items.productVariant.product:id,name',
'items.productVariant:id,product_id,name',
])
- ->when($user->hasRole('marketing'), fn (Builder $query) => $query->where('marketing_id', $user->id))
+ ->when($user->hasAnyRole(['marketing-offline', 'marketing-online']), fn (Builder $query) => $query->where('marketing_id', $user->id))
+ ->when($user->hasRole('cashier'), fn (Builder $query) => $query->where('created_by_id', $user->id))
->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void {
$search = $tableQuery['search'];
$query->where(function (Builder $query) use ($search): void {
@@ -143,7 +144,7 @@ public function marketingOptions(): array
{
return User::query()
->active()
- ->whereHas('roles', fn (Builder $roleQuery) => $roleQuery->where('name', 'marketing'))
+ ->whereHas('roles', fn (Builder $roleQuery) => $roleQuery->whereIn('name', ['marketing-offline', 'marketing-online']))
->with('profile:user_id,full_name')
->orderBy('username')
->get(['id', 'username'])
@@ -417,8 +418,7 @@ public function create(array $validated, User $user): Order
$subtotal = $draftItems->sum('subtotal');
$discount = (int) ($validated['discount'] ?? 0);
- $shippingCost = (int) ($validated['shipping_cost'] ?? 0);
- $totalAmount = max($subtotal - $discount + $shippingCost, 0);
+ $totalAmount = max($subtotal - $discount, 0);
$channel = OrderChannel::from($validated['channel']);
$order = Order::create([
@@ -434,7 +434,6 @@ public function create(array $validated, User $user): Order
'created_by_id' => $user->id,
'subtotal' => $subtotal,
'discount' => $discount,
- 'shipping_cost' => $shippingCost,
'marketplace_settings_snapshot' => $this->marketplaceService->buildOrderSnapshot(
$channel,
$totalAmount,
@@ -527,8 +526,7 @@ public function update(Order $order, array $validated): void
$lineItems = $this->buildLineItems($validated['items'], $priceType);
$subtotal = array_sum(array_column($lineItems, 'subtotal'));
$discount = (int) ($validated['discount'] ?? 0);
- $shippingCost = (int) ($validated['shipping_cost'] ?? 0);
- $totalAmount = max($subtotal - $discount + $shippingCost, 0);
+ $totalAmount = max($subtotal - $discount, 0);
$channel = OrderChannel::from($validated['channel']);
$order->customer_id = $validated['customer_id'] ?? null;
@@ -541,7 +539,6 @@ public function update(Order $order, array $validated): void
$order->shopee_order_id = $validated['shopee_order_id'] ?? null;
$order->subtotal = $subtotal;
$order->discount = $discount;
- $order->shipping_cost = $shippingCost;
$order->marketplace_settings_snapshot = $this->marketplaceService->buildOrderSnapshot(
$channel,
$totalAmount,
diff --git a/app/Services/System/AnalysisService.php b/app/Services/System/AnalysisService.php
index 952252a..1e6e37b 100644
--- a/app/Services/System/AnalysisService.php
+++ b/app/Services/System/AnalysisService.php
@@ -152,8 +152,14 @@ public function getTopSuppliers(?Carbon $startDate = null, ?Carbon $endDate = nu
public function getTopCustomers(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
+ $user = auth()->user();
+ $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
+ $isCashier = $user?->hasRole('cashier') ?? false;
+
return Order::query()
->completed()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
->join('customers', 'orders.customer_id', '=', 'customers.id')
->selectRaw('customers.name, SUM(orders.total_amount) as total_amount, COUNT(orders.id) as order_count')
@@ -171,14 +177,22 @@ public function getTopCustomers(?Carbon $startDate = null, ?Carbon $endDate = nu
public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
+ $user = auth()->user();
+ $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
+ $isCashier = $user?->hasRole('cashier') ?? false;
+
$revenueSummary = Order::query()
->completed()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
- ->selectRaw('SUM(total_amount) as total_revenue, SUM(discount) as total_discount, SUM(shipping_cost) as total_shipping, COUNT(*) as total_orders, AVG(total_amount) as avg_order')
+ ->selectRaw('SUM(total_amount) as total_revenue, SUM(discount) as total_discount, COUNT(*) as total_orders, AVG(total_amount) as avg_order')
->first();
$totalMarketplaceFees = Order::query()
->completed()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
->whereNotNull('marketplace_settings_snapshot')
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
->get()
@@ -189,7 +203,6 @@ public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate =
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
'total_marketplace_fees' => $totalMarketplaceFees,
'total_potongan' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
- 'total_shipping' => (int) ($revenueSummary->total_shipping ?? 0),
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
];
@@ -197,8 +210,17 @@ public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate =
public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
- $query = Order::query()->completed();
- $feeQuery = Order::query()->completed()->whereNotNull('marketplace_settings_snapshot');
+ $user = auth()->user();
+ $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
+ $isCashier = $user?->hasRole('cashier') ?? false;
+
+ $query = Order::query()->completed()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id));
+ $feeQuery = Order::query()->completed()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
+ ->whereNotNull('marketplace_settings_snapshot');
if ($startDate && $endDate) {
$query->whereBetween('orders.created_at', [$startDate, $endDate]);
@@ -209,8 +231,7 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate =
->selectRaw("
DATE_FORMAT(orders.created_at, '%Y-%m') as month_key,
SUM(total_amount) as total_revenue,
- SUM(discount) as total_discount,
- SUM(shipping_cost) as total_shipping
+ SUM(discount) as total_discount
")
->groupBy('month_key')
->orderBy('month_key')
@@ -245,7 +266,6 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate =
'total' => (int) ($revenue->total_revenue ?? 0),
'net' => (int) ($revenue->total_revenue ?? 0) - $potongan,
'potongan' => $potongan,
- 'ongkir' => (int) ($revenue->total_shipping ?? 0),
];
$current->addMonth();
@@ -353,7 +373,13 @@ public function getMonthlyExpense(?Carbon $startDate = null, ?Carbon $endDate =
public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
- $orderQuery = Order::query()->completed();
+ $user = auth()->user();
+ $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
+ $isCashier = $user?->hasRole('cashier') ?? false;
+
+ $orderQuery = Order::query()->completed()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id));
if ($startDate && $endDate) {
$orderQuery->whereBetween('orders.created_at', [$startDate, $endDate]);
}
@@ -498,7 +524,13 @@ public function getProductStock(): array
public function getBusyHours(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
+ $user = auth()->user();
+ $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
+ $isCashier = $user?->hasRole('cashier') ?? false;
+
$hourlyData = Order::query()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
->selectRaw('HOUR(orders.created_at) as hour, COUNT(*) as order_count')
->groupBy('hour')
@@ -519,11 +551,17 @@ public function getBusyHours(?Carbon $startDate = null, ?Carbon $endDate = null)
public function getTopProducts(?Carbon $startDate = null, ?Carbon $endDate = null): array
{
+ $user = auth()->user();
+ $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
+ $isCashier = $user?->hasRole('cashier') ?? false;
+
return OrderItem::query()
->join('orders', 'order_items.order_id', '=', 'orders.id')
->join('product_variants', 'order_items.product_variant_id', '=', 'product_variants.id')
->join('products', 'product_variants.product_id', '=', 'products.id')
->where('orders.status', OrderStatus::COMPLETED)
+ ->when($isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('orders.created_by_id', $user->id))
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
->selectRaw("CONCAT(products.name, ' - ', product_variants.name) as full_name, SUM(order_items.quantity) as total_qty, SUM(order_items.subtotal) as total_revenue")
->groupBy('order_items.product_variant_id', 'products.name', 'product_variants.name')
diff --git a/app/Services/System/DashboardService.php b/app/Services/System/DashboardService.php
index 21fa3f3..929ebf9 100644
--- a/app/Services/System/DashboardService.php
+++ b/app/Services/System/DashboardService.php
@@ -111,14 +111,22 @@ public function getCashOverview(): array
public function getRevenueSummary(): array
{
+ $user = auth()->user();
+ $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
+ $isCashier = $user?->hasRole('cashier') ?? false;
+
$revenueSummary = Order::query()
->completed()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
->whereDate('created_at', Carbon::today())
- ->selectRaw('SUM(total_amount) as total_revenue, SUM(discount) as total_discount, SUM(shipping_cost) as total_shipping, COUNT(*) as total_orders, AVG(total_amount) as avg_order')
+ ->selectRaw('SUM(total_amount) as total_revenue, SUM(discount) as total_discount, COUNT(*) as total_orders, AVG(total_amount) as avg_order')
->first();
$totalMarketplaceFees = Order::query()
->completed()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
->whereNotNull('marketplace_settings_snapshot')
->whereDate('created_at', Carbon::today())
->get()
@@ -129,7 +137,6 @@ public function getRevenueSummary(): array
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
'total_marketplace_fees' => $totalMarketplaceFees,
'total_potongan' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
- 'total_shipping' => (int) ($revenueSummary->total_shipping ?? 0),
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
];
@@ -171,7 +178,13 @@ public function getExpenseSummary(): array
public function getOrderStats(): array
{
+ $user = auth()->user();
+ $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
+ $isCashier = $user?->hasRole('cashier') ?? false;
+
$byChannel = Order::query()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
->whereDate('created_at', Carbon::today())
->selectRaw('channel, COUNT(*) as count, SUM(total_amount) as total')
->groupBy('channel')
@@ -184,6 +197,8 @@ public function getOrderStats(): array
]);
$byPaymentType = Order::query()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
->whereDate('created_at', Carbon::today())
->selectRaw('payment_type, COUNT(*) as count, SUM(total_amount) as total')
->groupBy('payment_type')
@@ -196,6 +211,8 @@ public function getOrderStats(): array
]);
$byMarketing = Order::query()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
->whereNotNull('marketing_id')
->whereDate('orders.created_at', Carbon::today())
->join('users', 'orders.marketing_id', '=', 'users.id')
@@ -212,6 +229,8 @@ public function getOrderStats(): array
]);
$byStatus = Order::query()
+ ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
+ ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
->whereDate('created_at', Carbon::today())
->selectRaw('status, COUNT(*) as count')
->groupBy('status')
diff --git a/app/Support/OwnerVerification/VerificationChangeFormatter.php b/app/Support/OwnerVerification/VerificationChangeFormatter.php
index b9a09ea..260b875 100644
--- a/app/Support/OwnerVerification/VerificationChangeFormatter.php
+++ b/app/Support/OwnerVerification/VerificationChangeFormatter.php
@@ -64,7 +64,6 @@ private static function label(string $field): string
'supplier_id' => 'Supplier',
'subtotal' => 'Subtotal',
'discount' => 'Diskon',
- 'shipping_cost' => 'Ongkir',
'total' => 'Total',
'notes' => 'Keterangan',
'items' => 'Item Belanja',
diff --git a/database/migrations/2026_06_27_120001_add_paid_amount_to_employee_advances_table.php b/database/migrations/2026_06_27_120001_add_paid_amount_to_employee_advances_table.php
new file mode 100644
index 0000000..9de644d
--- /dev/null
+++ b/database/migrations/2026_06_27_120001_add_paid_amount_to_employee_advances_table.php
@@ -0,0 +1,22 @@
+unsignedBigInteger('paid_amount')->default(0)->after('amount');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('employee_advances', function (Blueprint $table) {
+ $table->dropColumn('paid_amount');
+ });
+ }
+};
diff --git a/database/migrations/2026_06_27_120002_drop_shipping_cost_from_orders_table.php b/database/migrations/2026_06_27_120002_drop_shipping_cost_from_orders_table.php
new file mode 100644
index 0000000..af2ade9
--- /dev/null
+++ b/database/migrations/2026_06_27_120002_drop_shipping_cost_from_orders_table.php
@@ -0,0 +1,22 @@
+dropColumn('shipping_cost');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('orders', function (Blueprint $table) {
+ $table->unsignedBigInteger('shipping_cost')->default(0)->after('discount');
+ });
+ }
+};
diff --git a/database/seeders/EmployeeSeeder.php b/database/seeders/EmployeeSeeder.php
index e1a67f4..30b8a34 100644
--- a/database/seeders/EmployeeSeeder.php
+++ b/database/seeders/EmployeeSeeder.php
@@ -33,10 +33,15 @@ public function run(): void
'email' => 'direktur@gmail.com',
'full_name' => 'Direktur',
],
- Role::MARKETING->value => [
- 'username' => 'marketing',
- 'email' => 'marketing@gmail.com',
- 'full_name' => 'Marketing',
+ Role::MARKETING_OFFLINE->value => [
+ 'username' => 'marketing-offline',
+ 'email' => 'marketing.offline@gmail.com',
+ 'full_name' => 'Marketing Offline',
+ ],
+ Role::MARKETING_ONLINE->value => [
+ 'username' => 'marketing-online',
+ 'email' => 'marketing.online@gmail.com',
+ 'full_name' => 'Marketing Online',
],
Role::CASHIER->value => [
'username' => 'cashier',
diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue
index 4946428..4335808 100644
--- a/resources/js/components/AppSidebar.vue
+++ b/resources/js/components/AppSidebar.vue
@@ -56,7 +56,7 @@ const menuGroups: MenuGroup[] = [
items: [
{ title: 'Belanja', href: admin.manage.purchases.index.url(), icon: ShoppingBag, permission: 'purchases.view' },
{ title: 'Cutting', href: admin.manage.cuttings.index.url(), icon: Scissors, permission: 'cuttings.view' },
- { title: 'Stok', href: admin.manage.stocks.index.url(), icon: Warehouse, permission: 'stocks.view', badgeKey: 'pendingCuttings' },
+ { title: 'Stok Gudang', href: admin.manage.stocks.index.url(), icon: Warehouse, permission: 'stocks.view', badgeKey: 'pendingCuttings' },
{ title: 'Verifikasi Owner', href: admin.manage.owner_verifications.index.url(), icon: CheckCircle, permission: 'owner_verifications.view', badgeKey: 'pendingOwnerVerifications' },
{ title: 'Pesanan', href: admin.manage.orders.index.url(), icon: ShoppingCart, permission: 'orders.view' },
],
diff --git a/resources/js/constants/employee-advance-status.ts b/resources/js/constants/employee-advance-status.ts
index 72b2ee5..70452f9 100644
--- a/resources/js/constants/employee-advance-status.ts
+++ b/resources/js/constants/employee-advance-status.ts
@@ -2,5 +2,6 @@ export const EmployeeAdvanceStatus = {
PENDING: 'pending',
APPROVED: 'approved',
REJECTED: 'rejected',
+ PARTIALLY_PAID: 'partially_paid',
PAID: 'paid',
} as const;
diff --git a/resources/js/lib/thermal-printer/encode-order-receipt.ts b/resources/js/lib/thermal-printer/encode-order-receipt.ts
index e706485..8fc68d1 100644
--- a/resources/js/lib/thermal-printer/encode-order-receipt.ts
+++ b/resources/js/lib/thermal-printer/encode-order-receipt.ts
@@ -97,14 +97,9 @@ export function encodeOrderReceipt(
const summaryRows: string[][] = [
['Subtotal', order.subtotal_formatted],
['Diskon', order.discount_formatted],
+ ['Total', order.total_amount_formatted],
];
- if (order.shipping_cost > 0) {
- summaryRows.push(['Ongkir', order.shipping_cost_formatted]);
- }
-
- summaryRows.push(['Total', order.total_amount_formatted]);
-
encoder.table(
[
{ width: labelColumnWidth, align: 'left' },
diff --git a/resources/js/pages/admin/Analysis.vue b/resources/js/pages/admin/Analysis.vue
index d60b954..3bdeee6 100644
--- a/resources/js/pages/admin/Analysis.vue
+++ b/resources/js/pages/admin/Analysis.vue
@@ -71,7 +71,6 @@ const props = defineProps<{
total_discount: number;
total_marketplace_fees: number;
total_potongan: number;
- total_shipping: number;
total_orders: number;
avg_order: number;
};
@@ -80,7 +79,6 @@ const props = defineProps<{
total: number;
net: number;
potongan: number;
- ongkir: number;
}>;
expenseSummary: {
total: number;
@@ -168,7 +166,7 @@ function onPresetChange(value: any) {
}
// Revenue Chart
-type MonthlyRevenueData = { month: string; total: number; net: number; potongan: number; ongkir: number };
+type MonthlyRevenueData = { month: string; total: number; net: number; potongan: number };
const revenueChartConfig = {
total: {
@@ -183,19 +181,14 @@ const revenueChartConfig = {
label: 'Potongan',
color: 'var(--chart-3)',
},
- ongkir: {
- label: 'Ongkir',
- color: 'var(--chart-4)',
- },
} satisfies ChartConfig;
-const activeRevenueChart = ref<'total' | 'net' | 'potongan' | 'ongkir'>('total');
+const activeRevenueChart = ref<'total' | 'net' | 'potongan'>('total');
const revenueTotals = computed(() => ({
total: props.revenueSummary.total_revenue,
net: props.revenueSummary.total_revenue - props.revenueSummary.total_potongan,
potongan: props.revenueSummary.total_potongan,
- ongkir: props.revenueSummary.total_shipping,
}));
// Expense Chart
@@ -458,7 +451,7 @@ watch([startDate, endDate], () => {