feat: add partially paid status to employee advances and update related functionalities
This commit is contained in:
parent
eb5eeaf73b
commit
7c0b59b998
@ -11,6 +11,7 @@ enum EmployeeAdvanceStatus: string
|
|||||||
case PENDING = 'pending';
|
case PENDING = 'pending';
|
||||||
case APPROVED = 'approved';
|
case APPROVED = 'approved';
|
||||||
case REJECTED = 'rejected';
|
case REJECTED = 'rejected';
|
||||||
|
case PARTIALLY_PAID = 'partially_paid';
|
||||||
case PAID = 'paid';
|
case PAID = 'paid';
|
||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
@ -19,6 +20,7 @@ public function label(): string
|
|||||||
self::PENDING => 'Menunggu',
|
self::PENDING => 'Menunggu',
|
||||||
self::APPROVED => 'Disetujui',
|
self::APPROVED => 'Disetujui',
|
||||||
self::REJECTED => 'Ditolak',
|
self::REJECTED => 'Ditolak',
|
||||||
|
self::PARTIALLY_PAID => 'Dibayar Sebagian',
|
||||||
self::PAID => 'Lunas',
|
self::PAID => 'Lunas',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,8 @@ enum Role: string
|
|||||||
case ADMIN_TOKO = 'admin-toko';
|
case ADMIN_TOKO = 'admin-toko';
|
||||||
case ADMIN_BAHAN_BAKU = 'admin-bahan-baku';
|
case ADMIN_BAHAN_BAKU = 'admin-bahan-baku';
|
||||||
case DIREKTUR = 'direktur';
|
case DIREKTUR = 'direktur';
|
||||||
case MARKETING = 'marketing';
|
case MARKETING_OFFLINE = 'marketing-offline';
|
||||||
|
case MARKETING_ONLINE = 'marketing-online';
|
||||||
case CASHIER = 'cashier';
|
case CASHIER = 'cashier';
|
||||||
case NON_OPERATOR = 'non-operator';
|
case NON_OPERATOR = 'non-operator';
|
||||||
|
|
||||||
@ -25,7 +26,8 @@ public function label(): string
|
|||||||
self::ADMIN_TOKO => 'Admin Toko',
|
self::ADMIN_TOKO => 'Admin Toko',
|
||||||
self::ADMIN_BAHAN_BAKU => 'Admin Bahan Baku',
|
self::ADMIN_BAHAN_BAKU => 'Admin Bahan Baku',
|
||||||
self::DIREKTUR => 'Direktur',
|
self::DIREKTUR => 'Direktur',
|
||||||
self::MARKETING => 'Marketing',
|
self::MARKETING_OFFLINE => 'Marketing Offline',
|
||||||
|
self::MARKETING_ONLINE => 'Marketing Online',
|
||||||
self::CASHIER => 'Kasir',
|
self::CASHIER => 'Kasir',
|
||||||
self::NON_OPERATOR => 'Non Operator',
|
self::NON_OPERATOR => 'Non Operator',
|
||||||
};
|
};
|
||||||
@ -63,7 +65,6 @@ public function permissions(): array
|
|||||||
|
|
||||||
Permission::ANALYSIS_ATTENDANCE,
|
Permission::ANALYSIS_ATTENDANCE,
|
||||||
Permission::ANALYSIS_CASH,
|
Permission::ANALYSIS_CASH,
|
||||||
Permission::ANALYSIS_RAW_MATERIALS,
|
|
||||||
Permission::ANALYSIS_PRODUCT_STOCK,
|
Permission::ANALYSIS_PRODUCT_STOCK,
|
||||||
Permission::ANALYSIS_REVENUE,
|
Permission::ANALYSIS_REVENUE,
|
||||||
Permission::ANALYSIS_EXPENSE,
|
Permission::ANALYSIS_EXPENSE,
|
||||||
@ -72,14 +73,11 @@ public function permissions(): array
|
|||||||
Permission::ANALYSIS_PROFIT_HPP,
|
Permission::ANALYSIS_PROFIT_HPP,
|
||||||
Permission::ANALYSIS_PROFIT_GROSS,
|
Permission::ANALYSIS_PROFIT_GROSS,
|
||||||
Permission::ANALYSIS_PROFIT_MARGIN,
|
Permission::ANALYSIS_PROFIT_MARGIN,
|
||||||
Permission::ANALYSIS_TOP_SUPPLIERS,
|
|
||||||
Permission::ANALYSIS_TOP_CUSTOMERS,
|
Permission::ANALYSIS_TOP_CUSTOMERS,
|
||||||
Permission::ANALYSIS_TOP_PRODUCTS,
|
Permission::ANALYSIS_TOP_PRODUCTS,
|
||||||
|
|
||||||
Permission::EMPLOYEES_VIEW,
|
Permission::EMPLOYEES_VIEW,
|
||||||
|
|
||||||
Permission::STOCKS_VIEW,
|
|
||||||
|
|
||||||
Permission::ATTENDANCES_VIEW,
|
Permission::ATTENDANCES_VIEW,
|
||||||
Permission::ATTENDANCES_CREATE,
|
Permission::ATTENDANCES_CREATE,
|
||||||
Permission::ATTENDANCES_DELETE,
|
Permission::ATTENDANCES_DELETE,
|
||||||
@ -92,16 +90,10 @@ public function permissions(): array
|
|||||||
|
|
||||||
Permission::CATEGORIES_VIEW,
|
Permission::CATEGORIES_VIEW,
|
||||||
|
|
||||||
Permission::SUPPLIERS_VIEW,
|
|
||||||
|
|
||||||
Permission::CUSTOMERS_VIEW,
|
Permission::CUSTOMERS_VIEW,
|
||||||
|
|
||||||
Permission::PRODUCTS_VIEW,
|
Permission::PRODUCTS_VIEW,
|
||||||
|
|
||||||
Permission::RAW_MATERIALS_VIEW,
|
|
||||||
|
|
||||||
Permission::PURCHASES_VIEW,
|
|
||||||
|
|
||||||
Permission::ORDERS_VIEW,
|
Permission::ORDERS_VIEW,
|
||||||
|
|
||||||
Permission::CUTTINGS_VIEW,
|
Permission::CUTTINGS_VIEW,
|
||||||
@ -203,7 +195,6 @@ public function permissions(): array
|
|||||||
self::CASHIER => [
|
self::CASHIER => [
|
||||||
Permission::DASHBOARD_VIEW,
|
Permission::DASHBOARD_VIEW,
|
||||||
Permission::DASHBOARD_ATTENDANCE,
|
Permission::DASHBOARD_ATTENDANCE,
|
||||||
Permission::DASHBOARD_CASH,
|
|
||||||
Permission::DASHBOARD_REVENUE,
|
Permission::DASHBOARD_REVENUE,
|
||||||
Permission::DASHBOARD_ORDERS_CHANNEL,
|
Permission::DASHBOARD_ORDERS_CHANNEL,
|
||||||
Permission::DASHBOARD_ORDERS_PAYMENT,
|
Permission::DASHBOARD_ORDERS_PAYMENT,
|
||||||
@ -211,13 +202,8 @@ public function permissions(): array
|
|||||||
|
|
||||||
Permission::ANALYSIS_VIEW,
|
Permission::ANALYSIS_VIEW,
|
||||||
Permission::ANALYSIS_ATTENDANCE,
|
Permission::ANALYSIS_ATTENDANCE,
|
||||||
Permission::ANALYSIS_CASH,
|
|
||||||
Permission::ANALYSIS_REVENUE,
|
Permission::ANALYSIS_REVENUE,
|
||||||
Permission::ANALYSIS_EXPENSE,
|
|
||||||
Permission::ANALYSIS_PROFIT_ORDERS,
|
Permission::ANALYSIS_PROFIT_ORDERS,
|
||||||
Permission::ANALYSIS_PROFIT_HPP,
|
|
||||||
Permission::ANALYSIS_PROFIT_GROSS,
|
|
||||||
Permission::ANALYSIS_PROFIT_MARGIN,
|
|
||||||
Permission::ANALYSIS_TOP_CUSTOMERS,
|
Permission::ANALYSIS_TOP_CUSTOMERS,
|
||||||
Permission::ANALYSIS_TOP_PRODUCTS,
|
Permission::ANALYSIS_TOP_PRODUCTS,
|
||||||
|
|
||||||
@ -303,7 +289,7 @@ public function permissions(): array
|
|||||||
Permission::PAYROLL_VIEW,
|
Permission::PAYROLL_VIEW,
|
||||||
],
|
],
|
||||||
|
|
||||||
self::MARKETING => [
|
self::MARKETING_OFFLINE, self::MARKETING_ONLINE => [
|
||||||
Permission::DASHBOARD_VIEW,
|
Permission::DASHBOARD_VIEW,
|
||||||
Permission::DASHBOARD_ATTENDANCE,
|
Permission::DASHBOARD_ATTENDANCE,
|
||||||
Permission::DASHBOARD_REVENUE,
|
Permission::DASHBOARD_REVENUE,
|
||||||
@ -316,10 +302,6 @@ public function permissions(): array
|
|||||||
Permission::ANALYSIS_ATTENDANCE,
|
Permission::ANALYSIS_ATTENDANCE,
|
||||||
Permission::ANALYSIS_REVENUE,
|
Permission::ANALYSIS_REVENUE,
|
||||||
Permission::ANALYSIS_PROFIT_ORDERS,
|
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::ANALYSIS_TOP_PRODUCTS,
|
||||||
|
|
||||||
Permission::ATTENDANCES_VIEW,
|
Permission::ATTENDANCES_VIEW,
|
||||||
@ -330,8 +312,6 @@ public function permissions(): array
|
|||||||
Permission::LEAVE_REQUESTS_UPDATE,
|
Permission::LEAVE_REQUESTS_UPDATE,
|
||||||
Permission::LEAVE_REQUESTS_DELETE,
|
Permission::LEAVE_REQUESTS_DELETE,
|
||||||
|
|
||||||
Permission::CUSTOMERS_VIEW,
|
|
||||||
|
|
||||||
Permission::PRODUCTS_VIEW,
|
Permission::PRODUCTS_VIEW,
|
||||||
|
|
||||||
Permission::ORDERS_VIEW,
|
Permission::ORDERS_VIEW,
|
||||||
|
|||||||
@ -91,11 +91,13 @@ public function reject(RejectEmployeeAdvanceRequest $request, EmployeeAdvance $e
|
|||||||
return redirect()->route('admin.finance.employee_advances.index');
|
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');
|
return redirect()->route('admin.finance.employee_advances.index');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,6 @@ public function rules(): array
|
|||||||
'tiktok_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::TIKTOK->value), 'string', 'max:100'],
|
'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'],
|
'shopee_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::SHOPEE->value), 'string', 'max:100'],
|
||||||
'discount' => ['nullable', 'integer', 'min:0'],
|
'discount' => ['nullable', 'integer', 'min:0'],
|
||||||
'shipping_cost' => ['nullable', 'integer', 'min:0'],
|
|
||||||
'notes' => ['nullable', 'string'],
|
'notes' => ['nullable', 'string'],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -71,7 +70,6 @@ public function attributes(): array
|
|||||||
'tiktok_order_id' => 'ID pesanan TikTok Shop',
|
'tiktok_order_id' => 'ID pesanan TikTok Shop',
|
||||||
'shopee_order_id' => 'ID pesanan Shopee',
|
'shopee_order_id' => 'ID pesanan Shopee',
|
||||||
'discount' => 'diskon',
|
'discount' => 'diskon',
|
||||||
'shipping_cost' => 'ongkos kirim',
|
|
||||||
'notes' => 'keterangan',
|
'notes' => 'keterangan',
|
||||||
'items' => 'produk',
|
'items' => 'produk',
|
||||||
'items.*.product_variant_id' => 'varian produk',
|
'items.*.product_variant_id' => 'varian produk',
|
||||||
@ -82,17 +80,19 @@ public function attributes(): array
|
|||||||
|
|
||||||
public function withValidator(Validator $validator): void
|
public function withValidator(Validator $validator): void
|
||||||
{
|
{
|
||||||
if (! $this->isMethod('PUT') && ! $this->isMethod('PATCH')) {
|
$validator->after(function (Validator $validator): void {
|
||||||
return;
|
if ($this->filled('marketing_id') && $this->marketing_id !== 'none' && ! $this->filled('customer_id')) {
|
||||||
|
$validator->errors()->add('customer_id', 'Pelanggan wajib diisi jika marketing dipilih.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$validator->after(function (Validator $validator): void {
|
if ($this->isMethod('PUT') || $this->isMethod('PATCH')) {
|
||||||
/** @var Order $order */
|
/** @var Order $order */
|
||||||
$order = $this->route('order');
|
$order = $this->route('order');
|
||||||
|
|
||||||
if (! $order->status->isEditable()) {
|
if (! $order->status->isEditable()) {
|
||||||
$validator->errors()->add('status', 'Pesanan tidak dapat diubah.');
|
$validator->errors()->add('status', 'Pesanan tidak dapat diubah.');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,9 @@
|
|||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
#[Appends([
|
#[Appends([
|
||||||
'amount_formatted',
|
'amount_formatted',
|
||||||
|
'paid_amount_formatted',
|
||||||
|
'remaining_amount',
|
||||||
|
'remaining_amount_formatted',
|
||||||
'due_date_formatted',
|
'due_date_formatted',
|
||||||
'due_date_input',
|
'due_date_input',
|
||||||
'status_label',
|
'status_label',
|
||||||
@ -35,6 +38,7 @@ protected function casts(): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'amount' => 'integer',
|
'amount' => 'integer',
|
||||||
|
'paid_amount' => 'integer',
|
||||||
'due_date' => 'date',
|
'due_date' => 'date',
|
||||||
'status' => EmployeeAdvanceStatus::class,
|
'status' => EmployeeAdvanceStatus::class,
|
||||||
'verified_at' => 'datetime',
|
'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
|
public function canPay(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn () => $this->status === EmployeeAdvanceStatus::APPROVED,
|
get: fn () => in_array($this->status, [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID], true),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,6 @@
|
|||||||
#[Appends([
|
#[Appends([
|
||||||
'subtotal_formatted',
|
'subtotal_formatted',
|
||||||
'discount_formatted',
|
'discount_formatted',
|
||||||
'shipping_cost_formatted',
|
|
||||||
'total_amount_formatted',
|
'total_amount_formatted',
|
||||||
'created_at_formatted',
|
'created_at_formatted',
|
||||||
'channel_label',
|
'channel_label',
|
||||||
@ -46,7 +45,6 @@ protected function casts(): array
|
|||||||
'status' => OrderStatus::class,
|
'status' => OrderStatus::class,
|
||||||
'subtotal' => 'integer',
|
'subtotal' => 'integer',
|
||||||
'discount' => 'integer',
|
'discount' => 'integer',
|
||||||
'shipping_cost' => 'integer',
|
|
||||||
'marketplace_settings_snapshot' => 'array',
|
'marketplace_settings_snapshot' => 'array',
|
||||||
'total_amount' => 'integer',
|
'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
|
public function totalAmountFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|||||||
@ -129,8 +129,9 @@ public function calculateKasbonDeduction(): int
|
|||||||
{
|
{
|
||||||
$outstanding = (int) EmployeeAdvance::query()
|
$outstanding = (int) EmployeeAdvance::query()
|
||||||
->where('employee_id', $this->employee_id)
|
->where('employee_id', $this->employee_id)
|
||||||
->where('status', EmployeeAdvanceStatus::APPROVED)
|
->whereIn('status', [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID])
|
||||||
->sum('amount');
|
->selectRaw('SUM(amount - paid_amount) as remaining')
|
||||||
|
->value('remaining');
|
||||||
|
|
||||||
return min($outstanding, (int) $this->base_salary + (int) $this->adjustments()
|
return min($outstanding, (int) $this->base_salary + (int) $this->adjustments()
|
||||||
->where('type', PayrollAdjustmentType::BONUS)
|
->where('type', PayrollAdjustmentType::BONUS)
|
||||||
|
|||||||
@ -28,10 +28,11 @@ public function __construct(
|
|||||||
*/
|
*/
|
||||||
public function outstandingSummary(?User $user = null): array
|
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));
|
->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();
|
$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 {
|
try {
|
||||||
DB::transaction(function () use ($employeeAdvance, $user): void {
|
DB::transaction(function () use ($employeeAdvance, $user, $payAmount): void {
|
||||||
$employeeAdvance->loadMissing('employee.user.profile');
|
$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(
|
$description = sprintf(
|
||||||
'Pelunasan kasbon: %s',
|
'%s kasbon: %s',
|
||||||
|
$isFullPayment ? 'Pelunasan' : 'Pembayaran sebagian kasbon',
|
||||||
$employeeAdvance->employeeName,
|
$employeeAdvance->employeeName,
|
||||||
);
|
);
|
||||||
|
|
||||||
$cashTransaction = $this->cashService->recordIncoming(
|
$cashTransaction = $this->cashService->recordIncoming(
|
||||||
$employeeAdvance,
|
$employeeAdvance,
|
||||||
$employeeAdvance->amount,
|
$amountToPay,
|
||||||
$description,
|
$description,
|
||||||
$user,
|
$user,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$newPaidAmount = $employeeAdvance->paid_amount + $amountToPay;
|
||||||
|
|
||||||
$employeeAdvance->update([
|
$employeeAdvance->update([
|
||||||
'repayment_cash_transaction_id' => $cashTransaction->id,
|
'paid_amount' => $newPaidAmount,
|
||||||
'paid_at' => now(),
|
'repayment_cash_transaction_id' => $isFullPayment ? $cashTransaction->id : $employeeAdvance->repayment_cash_transaction_id,
|
||||||
'paid_by_id' => $user->id,
|
'paid_at' => $isFullPayment ? now() : $employeeAdvance->paid_at,
|
||||||
'status' => EmployeeAdvanceStatus::PAID,
|
'paid_by_id' => $isFullPayment ? $user->id : $employeeAdvance->paid_by_id,
|
||||||
|
'status' => $isFullPayment ? EmployeeAdvanceStatus::PAID : EmployeeAdvanceStatus::PARTIALLY_PAID,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
} catch (ValidationException $e) {
|
} catch (ValidationException $e) {
|
||||||
|
|||||||
@ -437,7 +437,7 @@ private function settleKasbonFromPayroll(Payroll $payroll, User $user): void
|
|||||||
|
|
||||||
$advances = EmployeeAdvance::query()
|
$advances = EmployeeAdvance::query()
|
||||||
->where('employee_id', $payroll->employee_id)
|
->where('employee_id', $payroll->employee_id)
|
||||||
->where('status', EmployeeAdvanceStatus::APPROVED)
|
->whereIn('status', [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID])
|
||||||
->orderBy('created_at')
|
->orderBy('created_at')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
@ -446,12 +446,21 @@ private function settleKasbonFromPayroll(Payroll $payroll, User $user): void
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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_at = now();
|
||||||
$advance->paid_by_id = $user->id;
|
$advance->paid_by_id = $user->id;
|
||||||
$advance->status = EmployeeAdvanceStatus::PAID;
|
$advance->status = EmployeeAdvanceStatus::PAID;
|
||||||
|
} else {
|
||||||
|
$advance->status = EmployeeAdvanceStatus::PARTIALLY_PAID;
|
||||||
|
}
|
||||||
|
|
||||||
$advance->save();
|
$advance->save();
|
||||||
|
|
||||||
$remaining -= $advance->amount;
|
$remaining -= $paymentAmount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -87,7 +87,8 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
|
|||||||
'items.productVariant.product:id,name',
|
'items.productVariant.product:id,name',
|
||||||
'items.productVariant:id,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 {
|
->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void {
|
||||||
$search = $tableQuery['search'];
|
$search = $tableQuery['search'];
|
||||||
$query->where(function (Builder $query) use ($search): void {
|
$query->where(function (Builder $query) use ($search): void {
|
||||||
@ -143,7 +144,7 @@ public function marketingOptions(): array
|
|||||||
{
|
{
|
||||||
return User::query()
|
return User::query()
|
||||||
->active()
|
->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')
|
->with('profile:user_id,full_name')
|
||||||
->orderBy('username')
|
->orderBy('username')
|
||||||
->get(['id', 'username'])
|
->get(['id', 'username'])
|
||||||
@ -417,8 +418,7 @@ public function create(array $validated, User $user): Order
|
|||||||
|
|
||||||
$subtotal = $draftItems->sum('subtotal');
|
$subtotal = $draftItems->sum('subtotal');
|
||||||
$discount = (int) ($validated['discount'] ?? 0);
|
$discount = (int) ($validated['discount'] ?? 0);
|
||||||
$shippingCost = (int) ($validated['shipping_cost'] ?? 0);
|
$totalAmount = max($subtotal - $discount, 0);
|
||||||
$totalAmount = max($subtotal - $discount + $shippingCost, 0);
|
|
||||||
$channel = OrderChannel::from($validated['channel']);
|
$channel = OrderChannel::from($validated['channel']);
|
||||||
|
|
||||||
$order = Order::create([
|
$order = Order::create([
|
||||||
@ -434,7 +434,6 @@ public function create(array $validated, User $user): Order
|
|||||||
'created_by_id' => $user->id,
|
'created_by_id' => $user->id,
|
||||||
'subtotal' => $subtotal,
|
'subtotal' => $subtotal,
|
||||||
'discount' => $discount,
|
'discount' => $discount,
|
||||||
'shipping_cost' => $shippingCost,
|
|
||||||
'marketplace_settings_snapshot' => $this->marketplaceService->buildOrderSnapshot(
|
'marketplace_settings_snapshot' => $this->marketplaceService->buildOrderSnapshot(
|
||||||
$channel,
|
$channel,
|
||||||
$totalAmount,
|
$totalAmount,
|
||||||
@ -527,8 +526,7 @@ public function update(Order $order, array $validated): void
|
|||||||
$lineItems = $this->buildLineItems($validated['items'], $priceType);
|
$lineItems = $this->buildLineItems($validated['items'], $priceType);
|
||||||
$subtotal = array_sum(array_column($lineItems, 'subtotal'));
|
$subtotal = array_sum(array_column($lineItems, 'subtotal'));
|
||||||
$discount = (int) ($validated['discount'] ?? 0);
|
$discount = (int) ($validated['discount'] ?? 0);
|
||||||
$shippingCost = (int) ($validated['shipping_cost'] ?? 0);
|
$totalAmount = max($subtotal - $discount, 0);
|
||||||
$totalAmount = max($subtotal - $discount + $shippingCost, 0);
|
|
||||||
$channel = OrderChannel::from($validated['channel']);
|
$channel = OrderChannel::from($validated['channel']);
|
||||||
|
|
||||||
$order->customer_id = $validated['customer_id'] ?? null;
|
$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->shopee_order_id = $validated['shopee_order_id'] ?? null;
|
||||||
$order->subtotal = $subtotal;
|
$order->subtotal = $subtotal;
|
||||||
$order->discount = $discount;
|
$order->discount = $discount;
|
||||||
$order->shipping_cost = $shippingCost;
|
|
||||||
$order->marketplace_settings_snapshot = $this->marketplaceService->buildOrderSnapshot(
|
$order->marketplace_settings_snapshot = $this->marketplaceService->buildOrderSnapshot(
|
||||||
$channel,
|
$channel,
|
||||||
$totalAmount,
|
$totalAmount,
|
||||||
|
|||||||
@ -152,8 +152,14 @@ public function getTopSuppliers(?Carbon $startDate = null, ?Carbon $endDate = nu
|
|||||||
|
|
||||||
public function getTopCustomers(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
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()
|
return Order::query()
|
||||||
->completed()
|
->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]))
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
||||||
->join('customers', 'orders.customer_id', '=', 'customers.id')
|
->join('customers', 'orders.customer_id', '=', 'customers.id')
|
||||||
->selectRaw('customers.name, SUM(orders.total_amount) as total_amount, COUNT(orders.id) as order_count')
|
->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
|
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()
|
$revenueSummary = Order::query()
|
||||||
->completed()
|
->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]))
|
->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();
|
->first();
|
||||||
|
|
||||||
$totalMarketplaceFees = Order::query()
|
$totalMarketplaceFees = Order::query()
|
||||||
->completed()
|
->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')
|
->whereNotNull('marketplace_settings_snapshot')
|
||||||
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
||||||
->get()
|
->get()
|
||||||
@ -189,7 +203,6 @@ public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate =
|
|||||||
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
|
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
|
||||||
'total_marketplace_fees' => $totalMarketplaceFees,
|
'total_marketplace_fees' => $totalMarketplaceFees,
|
||||||
'total_potongan' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
|
'total_potongan' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
|
||||||
'total_shipping' => (int) ($revenueSummary->total_shipping ?? 0),
|
|
||||||
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
|
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
|
||||||
'avg_order' => (int) ($revenueSummary->avg_order ?? 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
|
public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
||||||
{
|
{
|
||||||
$query = Order::query()->completed();
|
$user = auth()->user();
|
||||||
$feeQuery = Order::query()->completed()->whereNotNull('marketplace_settings_snapshot');
|
$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) {
|
if ($startDate && $endDate) {
|
||||||
$query->whereBetween('orders.created_at', [$startDate, $endDate]);
|
$query->whereBetween('orders.created_at', [$startDate, $endDate]);
|
||||||
@ -209,8 +231,7 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate =
|
|||||||
->selectRaw("
|
->selectRaw("
|
||||||
DATE_FORMAT(orders.created_at, '%Y-%m') as month_key,
|
DATE_FORMAT(orders.created_at, '%Y-%m') as month_key,
|
||||||
SUM(total_amount) as total_revenue,
|
SUM(total_amount) as total_revenue,
|
||||||
SUM(discount) as total_discount,
|
SUM(discount) as total_discount
|
||||||
SUM(shipping_cost) as total_shipping
|
|
||||||
")
|
")
|
||||||
->groupBy('month_key')
|
->groupBy('month_key')
|
||||||
->orderBy('month_key')
|
->orderBy('month_key')
|
||||||
@ -245,7 +266,6 @@ public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate =
|
|||||||
'total' => (int) ($revenue->total_revenue ?? 0),
|
'total' => (int) ($revenue->total_revenue ?? 0),
|
||||||
'net' => (int) ($revenue->total_revenue ?? 0) - $potongan,
|
'net' => (int) ($revenue->total_revenue ?? 0) - $potongan,
|
||||||
'potongan' => $potongan,
|
'potongan' => $potongan,
|
||||||
'ongkir' => (int) ($revenue->total_shipping ?? 0),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$current->addMonth();
|
$current->addMonth();
|
||||||
@ -353,7 +373,13 @@ public function getMonthlyExpense(?Carbon $startDate = null, ?Carbon $endDate =
|
|||||||
|
|
||||||
public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = null): array
|
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) {
|
if ($startDate && $endDate) {
|
||||||
$orderQuery->whereBetween('orders.created_at', [$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
|
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()
|
$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]))
|
->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
|
||||||
->selectRaw('HOUR(orders.created_at) as hour, COUNT(*) as order_count')
|
->selectRaw('HOUR(orders.created_at) as hour, COUNT(*) as order_count')
|
||||||
->groupBy('hour')
|
->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
|
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()
|
return OrderItem::query()
|
||||||
->join('orders', 'order_items.order_id', '=', 'orders.id')
|
->join('orders', 'order_items.order_id', '=', 'orders.id')
|
||||||
->join('product_variants', 'order_items.product_variant_id', '=', 'product_variants.id')
|
->join('product_variants', 'order_items.product_variant_id', '=', 'product_variants.id')
|
||||||
->join('products', 'product_variants.product_id', '=', 'products.id')
|
->join('products', 'product_variants.product_id', '=', 'products.id')
|
||||||
->where('orders.status', OrderStatus::COMPLETED)
|
->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]))
|
->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")
|
->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')
|
->groupBy('order_items.product_variant_id', 'products.name', 'product_variants.name')
|
||||||
|
|||||||
@ -111,14 +111,22 @@ public function getCashOverview(): array
|
|||||||
|
|
||||||
public function getRevenueSummary(): array
|
public function getRevenueSummary(): array
|
||||||
{
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
$isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
|
||||||
|
$isCashier = $user?->hasRole('cashier') ?? false;
|
||||||
|
|
||||||
$revenueSummary = Order::query()
|
$revenueSummary = Order::query()
|
||||||
->completed()
|
->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())
|
->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();
|
->first();
|
||||||
|
|
||||||
$totalMarketplaceFees = Order::query()
|
$totalMarketplaceFees = Order::query()
|
||||||
->completed()
|
->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')
|
->whereNotNull('marketplace_settings_snapshot')
|
||||||
->whereDate('created_at', Carbon::today())
|
->whereDate('created_at', Carbon::today())
|
||||||
->get()
|
->get()
|
||||||
@ -129,7 +137,6 @@ public function getRevenueSummary(): array
|
|||||||
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
|
'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
|
||||||
'total_marketplace_fees' => $totalMarketplaceFees,
|
'total_marketplace_fees' => $totalMarketplaceFees,
|
||||||
'total_potongan' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
|
'total_potongan' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
|
||||||
'total_shipping' => (int) ($revenueSummary->total_shipping ?? 0),
|
|
||||||
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
|
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
|
||||||
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
|
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
|
||||||
];
|
];
|
||||||
@ -171,7 +178,13 @@ public function getExpenseSummary(): array
|
|||||||
|
|
||||||
public function getOrderStats(): array
|
public function getOrderStats(): array
|
||||||
{
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
$isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
|
||||||
|
$isCashier = $user?->hasRole('cashier') ?? false;
|
||||||
|
|
||||||
$byChannel = Order::query()
|
$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())
|
->whereDate('created_at', Carbon::today())
|
||||||
->selectRaw('channel, COUNT(*) as count, SUM(total_amount) as total')
|
->selectRaw('channel, COUNT(*) as count, SUM(total_amount) as total')
|
||||||
->groupBy('channel')
|
->groupBy('channel')
|
||||||
@ -184,6 +197,8 @@ public function getOrderStats(): array
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$byPaymentType = Order::query()
|
$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())
|
->whereDate('created_at', Carbon::today())
|
||||||
->selectRaw('payment_type, COUNT(*) as count, SUM(total_amount) as total')
|
->selectRaw('payment_type, COUNT(*) as count, SUM(total_amount) as total')
|
||||||
->groupBy('payment_type')
|
->groupBy('payment_type')
|
||||||
@ -196,6 +211,8 @@ public function getOrderStats(): array
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$byMarketing = Order::query()
|
$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')
|
->whereNotNull('marketing_id')
|
||||||
->whereDate('orders.created_at', Carbon::today())
|
->whereDate('orders.created_at', Carbon::today())
|
||||||
->join('users', 'orders.marketing_id', '=', 'users.id')
|
->join('users', 'orders.marketing_id', '=', 'users.id')
|
||||||
@ -212,6 +229,8 @@ public function getOrderStats(): array
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$byStatus = Order::query()
|
$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())
|
->whereDate('created_at', Carbon::today())
|
||||||
->selectRaw('status, COUNT(*) as count')
|
->selectRaw('status, COUNT(*) as count')
|
||||||
->groupBy('status')
|
->groupBy('status')
|
||||||
|
|||||||
@ -64,7 +64,6 @@ private static function label(string $field): string
|
|||||||
'supplier_id' => 'Supplier',
|
'supplier_id' => 'Supplier',
|
||||||
'subtotal' => 'Subtotal',
|
'subtotal' => 'Subtotal',
|
||||||
'discount' => 'Diskon',
|
'discount' => 'Diskon',
|
||||||
'shipping_cost' => 'Ongkir',
|
|
||||||
'total' => 'Total',
|
'total' => 'Total',
|
||||||
'notes' => 'Keterangan',
|
'notes' => 'Keterangan',
|
||||||
'items' => 'Item Belanja',
|
'items' => 'Item Belanja',
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('employee_advances', function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('paid_amount')->default(0)->after('amount');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('employee_advances', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('paid_amount');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('orders', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('shipping_cost');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('orders', function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('shipping_cost')->default(0)->after('discount');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -33,10 +33,15 @@ public function run(): void
|
|||||||
'email' => 'direktur@gmail.com',
|
'email' => 'direktur@gmail.com',
|
||||||
'full_name' => 'Direktur',
|
'full_name' => 'Direktur',
|
||||||
],
|
],
|
||||||
Role::MARKETING->value => [
|
Role::MARKETING_OFFLINE->value => [
|
||||||
'username' => 'marketing',
|
'username' => 'marketing-offline',
|
||||||
'email' => 'marketing@gmail.com',
|
'email' => 'marketing.offline@gmail.com',
|
||||||
'full_name' => 'Marketing',
|
'full_name' => 'Marketing Offline',
|
||||||
|
],
|
||||||
|
Role::MARKETING_ONLINE->value => [
|
||||||
|
'username' => 'marketing-online',
|
||||||
|
'email' => 'marketing.online@gmail.com',
|
||||||
|
'full_name' => 'Marketing Online',
|
||||||
],
|
],
|
||||||
Role::CASHIER->value => [
|
Role::CASHIER->value => [
|
||||||
'username' => 'cashier',
|
'username' => 'cashier',
|
||||||
|
|||||||
@ -56,7 +56,7 @@ const menuGroups: MenuGroup[] = [
|
|||||||
items: [
|
items: [
|
||||||
{ title: 'Belanja', href: admin.manage.purchases.index.url(), icon: ShoppingBag, permission: 'purchases.view' },
|
{ 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: '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: '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' },
|
{ title: 'Pesanan', href: admin.manage.orders.index.url(), icon: ShoppingCart, permission: 'orders.view' },
|
||||||
],
|
],
|
||||||
|
|||||||
@ -2,5 +2,6 @@ export const EmployeeAdvanceStatus = {
|
|||||||
PENDING: 'pending',
|
PENDING: 'pending',
|
||||||
APPROVED: 'approved',
|
APPROVED: 'approved',
|
||||||
REJECTED: 'rejected',
|
REJECTED: 'rejected',
|
||||||
|
PARTIALLY_PAID: 'partially_paid',
|
||||||
PAID: 'paid',
|
PAID: 'paid',
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@ -97,14 +97,9 @@ export function encodeOrderReceipt(
|
|||||||
const summaryRows: string[][] = [
|
const summaryRows: string[][] = [
|
||||||
['Subtotal', order.subtotal_formatted],
|
['Subtotal', order.subtotal_formatted],
|
||||||
['Diskon', order.discount_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(
|
encoder.table(
|
||||||
[
|
[
|
||||||
{ width: labelColumnWidth, align: 'left' },
|
{ width: labelColumnWidth, align: 'left' },
|
||||||
|
|||||||
@ -71,7 +71,6 @@ const props = defineProps<{
|
|||||||
total_discount: number;
|
total_discount: number;
|
||||||
total_marketplace_fees: number;
|
total_marketplace_fees: number;
|
||||||
total_potongan: number;
|
total_potongan: number;
|
||||||
total_shipping: number;
|
|
||||||
total_orders: number;
|
total_orders: number;
|
||||||
avg_order: number;
|
avg_order: number;
|
||||||
};
|
};
|
||||||
@ -80,7 +79,6 @@ const props = defineProps<{
|
|||||||
total: number;
|
total: number;
|
||||||
net: number;
|
net: number;
|
||||||
potongan: number;
|
potongan: number;
|
||||||
ongkir: number;
|
|
||||||
}>;
|
}>;
|
||||||
expenseSummary: {
|
expenseSummary: {
|
||||||
total: number;
|
total: number;
|
||||||
@ -168,7 +166,7 @@ function onPresetChange(value: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Revenue Chart
|
// 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 = {
|
const revenueChartConfig = {
|
||||||
total: {
|
total: {
|
||||||
@ -183,19 +181,14 @@ const revenueChartConfig = {
|
|||||||
label: 'Potongan',
|
label: 'Potongan',
|
||||||
color: 'var(--chart-3)',
|
color: 'var(--chart-3)',
|
||||||
},
|
},
|
||||||
ongkir: {
|
|
||||||
label: 'Ongkir',
|
|
||||||
color: 'var(--chart-4)',
|
|
||||||
},
|
|
||||||
} satisfies ChartConfig;
|
} satisfies ChartConfig;
|
||||||
|
|
||||||
const activeRevenueChart = ref<'total' | 'net' | 'potongan' | 'ongkir'>('total');
|
const activeRevenueChart = ref<'total' | 'net' | 'potongan'>('total');
|
||||||
|
|
||||||
const revenueTotals = computed(() => ({
|
const revenueTotals = computed(() => ({
|
||||||
total: props.revenueSummary.total_revenue,
|
total: props.revenueSummary.total_revenue,
|
||||||
net: props.revenueSummary.total_revenue - props.revenueSummary.total_potongan,
|
net: props.revenueSummary.total_revenue - props.revenueSummary.total_potongan,
|
||||||
potongan: props.revenueSummary.total_potongan,
|
potongan: props.revenueSummary.total_potongan,
|
||||||
ongkir: props.revenueSummary.total_shipping,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Expense Chart
|
// Expense Chart
|
||||||
@ -458,7 +451,7 @@ watch([startDate, endDate], () => {
|
|||||||
<CardTitle>Pendapatan</CardTitle>
|
<CardTitle>Pendapatan</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button v-for="chart in ['total', 'net', 'potongan', 'ongkir'] as const" :key="chart"
|
<button v-for="chart in ['total', 'net', 'potongan'] as const" :key="chart"
|
||||||
:data-active="activeRevenueChart === chart"
|
:data-active="activeRevenueChart === chart"
|
||||||
class="data-[active=true]:bg-muted/50 flex flex-1 flex-col justify-center gap-1 border-t px-6 py-4 text-left even:border-l sm:border-t-0 sm:border-l sm:px-8 sm:py-6"
|
class="data-[active=true]:bg-muted/50 flex flex-1 flex-col justify-center gap-1 border-t px-6 py-4 text-left even:border-l sm:border-t-0 sm:border-l sm:px-8 sm:py-6"
|
||||||
@click="activeRevenueChart = chart">
|
@click="activeRevenueChart = chart">
|
||||||
|
|||||||
@ -47,7 +47,6 @@ interface DashboardProps {
|
|||||||
total_discount: number;
|
total_discount: number;
|
||||||
total_marketplace_fees: number;
|
total_marketplace_fees: number;
|
||||||
total_potongan: number;
|
total_potongan: number;
|
||||||
total_shipping: number;
|
|
||||||
total_orders: number;
|
total_orders: number;
|
||||||
avg_order: number;
|
avg_order: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,6 +10,8 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case EmployeeAdvanceStatus.APPROVED:
|
case EmployeeAdvanceStatus.APPROVED:
|
||||||
return 'default';
|
return 'default';
|
||||||
|
case EmployeeAdvanceStatus.PARTIALLY_PAID:
|
||||||
|
return 'outline';
|
||||||
case EmployeeAdvanceStatus.PAID:
|
case EmployeeAdvanceStatus.PAID:
|
||||||
return 'secondary';
|
return 'secondary';
|
||||||
case EmployeeAdvanceStatus.REJECTED:
|
case EmployeeAdvanceStatus.REJECTED:
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { router } from '@inertiajs/vue3';
|
import { router } from '@inertiajs/vue3';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import { RowApproveAction, RowDeleteAction, RowEditAction, RowPayAction, RowRejectAction } from '@/components/button';
|
import { RowApproveAction, RowDeleteAction, RowEditAction, RowPayAction, RowRejectAction } from '@/components/button';
|
||||||
import ConfirmDialog from '@/components/ConfirmDialog.vue';
|
import ConfirmDialog from '@/components/ConfirmDialog.vue';
|
||||||
|
import { RupiahInput } from '@/components/form/rupiah-input';
|
||||||
|
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
|
||||||
import { useCan } from '@/composables/useCan';
|
import { useCan } from '@/composables/useCan';
|
||||||
|
import { parseRupiah } from '@/lib/rupiah';
|
||||||
import { destroy, approve, pay } from '@/routes/admin/finance/employee_advances';
|
import { destroy, approve, pay } from '@/routes/admin/finance/employee_advances';
|
||||||
import type { EmployeeAdvanceListItem } from '@/types/employee-advance';
|
import type { EmployeeAdvanceListItem } from '@/types/employee-advance';
|
||||||
|
|
||||||
@ -43,6 +46,15 @@ const approveConfirmOpen = ref(false);
|
|||||||
const payConfirmOpen = ref(false);
|
const payConfirmOpen = ref(false);
|
||||||
const approveProcessing = ref(false);
|
const approveProcessing = ref(false);
|
||||||
const payProcessing = ref(false);
|
const payProcessing = ref(false);
|
||||||
|
const payAmount = ref('');
|
||||||
|
const payError = ref('');
|
||||||
|
|
||||||
|
watch(payConfirmOpen, (open) => {
|
||||||
|
if (open) {
|
||||||
|
payAmount.value = String(props.employeeAdvance.remaining_amount);
|
||||||
|
payError.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function approveEmployeeAdvance() {
|
function approveEmployeeAdvance() {
|
||||||
approveProcessing.value = true;
|
approveProcessing.value = true;
|
||||||
@ -64,9 +76,22 @@ function approveEmployeeAdvance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function payEmployeeAdvance() {
|
function payEmployeeAdvance() {
|
||||||
|
const amount = Number(parseRupiah(payAmount.value));
|
||||||
|
|
||||||
|
if (!amount || amount <= 0) {
|
||||||
|
payError.value = 'Jumlah pembayaran harus lebih dari 0.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > props.employeeAdvance.remaining_amount) {
|
||||||
|
payError.value = 'Jumlah pembayaran melebihi sisa kasbon.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
payError.value = '';
|
||||||
payProcessing.value = true;
|
payProcessing.value = true;
|
||||||
|
|
||||||
router.post(pay.url(props.employeeAdvance.id), {}, {
|
router.post(pay.url(props.employeeAdvance.id), { amount }, {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
payConfirmOpen.value = false;
|
payConfirmOpen.value = false;
|
||||||
@ -93,7 +118,7 @@ function payEmployeeAdvance() {
|
|||||||
<RowRejectAction v-if="employeeAdvance.can_verify && can('employee_advances.verify')" tooltip="Tolak"
|
<RowRejectAction v-if="employeeAdvance.can_verify && can('employee_advances.verify')" tooltip="Tolak"
|
||||||
@click="emit('reject', employeeAdvance)" />
|
@click="emit('reject', employeeAdvance)" />
|
||||||
|
|
||||||
<RowPayAction v-if="employeeAdvance.can_pay && can('employee_advances.pay')" tooltip="Lunasi Kasbon"
|
<RowPayAction v-if="employeeAdvance.can_pay && can('employee_advances.pay')" tooltip="Bayar Kasbon"
|
||||||
@click="payConfirmOpen = true" />
|
@click="payConfirmOpen = true" />
|
||||||
|
|
||||||
<template v-if="canDelete">
|
<template v-if="canDelete">
|
||||||
@ -107,7 +132,23 @@ function payEmployeeAdvance() {
|
|||||||
:description="`Kasbon ${employeeAdvance.amount_formatted} untuk ${employeeAdvance.employee_name} akan dicairkan. Saldo kas akan berkurang.`"
|
:description="`Kasbon ${employeeAdvance.amount_formatted} untuk ${employeeAdvance.employee_name} akan dicairkan. Saldo kas akan berkurang.`"
|
||||||
confirm-label="Setujui" cancel-label="Batal" :loading="approveProcessing" @confirm="approveEmployeeAdvance" />
|
confirm-label="Setujui" cancel-label="Batal" :loading="approveProcessing" @confirm="approveEmployeeAdvance" />
|
||||||
|
|
||||||
<ConfirmDialog v-if="can('employee_advances.pay')" v-model:open="payConfirmOpen" title="Lunasi kasbon?"
|
<ConfirmDialog v-if="can('employee_advances.pay')" v-model:open="payConfirmOpen"
|
||||||
:description="`Pelunasan kasbon ${employeeAdvance.amount_formatted} dari ${employeeAdvance.employee_name}. Saldo kas akan bertambah.`"
|
:title="employeeAdvance.remaining_amount < employeeAdvance.amount ? 'Bayar sisa kasbon?' : 'Bayar kasbon?'"
|
||||||
confirm-label="Lunasi" cancel-label="Batal" :loading="payProcessing" @confirm="payEmployeeAdvance" />
|
:description="`Pembayaran kasbon ${employeeAdvance.employee_name}. Sisa: ${employeeAdvance.remaining_amount_formatted}.`"
|
||||||
|
confirm-label="Bayar" cancel-label="Batal" :loading="payProcessing" @confirm="payEmployeeAdvance">
|
||||||
|
<template #content>
|
||||||
|
<div class="space-y-3 py-2">
|
||||||
|
<div class="text-sm text-muted-foreground">
|
||||||
|
<p>Total kasbon: <strong>{{ employeeAdvance.amount_formatted }}</strong></p>
|
||||||
|
<p>Sudah dibayar: <strong>{{ employeeAdvance.paid_amount_formatted }}</strong></p>
|
||||||
|
<p>Sisa: <strong class="text-primary">{{ employeeAdvance.remaining_amount_formatted }}</strong></p>
|
||||||
|
</div>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel for="pay-amount" required>Jumlah Pembayaran</FieldLabel>
|
||||||
|
<RupiahInput id="pay-amount" v-model="payAmount" placeholder="0" />
|
||||||
|
<FieldError v-if="payError" :errors="{ amount: [payError] }" />
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ConfirmDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -36,7 +36,6 @@ const initialData = computed(() => ({
|
|||||||
tiktok_order_id: props.order.tiktok_order_id ?? '',
|
tiktok_order_id: props.order.tiktok_order_id ?? '',
|
||||||
shopee_order_id: props.order.shopee_order_id ?? '',
|
shopee_order_id: props.order.shopee_order_id ?? '',
|
||||||
discount: String(props.order.discount),
|
discount: String(props.order.discount),
|
||||||
shipping_cost: String(props.order.shipping_cost),
|
|
||||||
notes: props.order.notes ?? '',
|
notes: props.order.notes ?? '',
|
||||||
items: props.order.items.map((item) => ({
|
items: props.order.items.map((item) => ({
|
||||||
product_variant_id: item.product_variant_id,
|
product_variant_id: item.product_variant_id,
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import {
|
|||||||
Receipt,
|
Receipt,
|
||||||
Send,
|
Send,
|
||||||
Trash2,
|
Trash2,
|
||||||
Truck,
|
|
||||||
User,
|
User,
|
||||||
X,
|
X,
|
||||||
} from '@lucide/vue';
|
} from '@lucide/vue';
|
||||||
@ -143,10 +142,6 @@ const summaryRows = computed(() => {
|
|||||||
rows.push({ label: 'Diskon', value: `-${props.order.discount_formatted}` });
|
rows.push({ label: 'Diskon', value: `-${props.order.discount_formatted}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.order.shipping_cost > 0) {
|
|
||||||
rows.push({ label: 'Ongkos Kirim', value: props.order.shipping_cost_formatted });
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -523,25 +518,6 @@ const feeEntries = computed(() => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Shipping Info -->
|
|
||||||
<Card v-if="order.shipping_cost > 0">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle class="flex items-center gap-2">
|
|
||||||
<Truck class="size-5" />
|
|
||||||
Pengiriman
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="flex items-center justify-between text-sm">
|
|
||||||
<span class="text-muted-foreground">Ongkos Kirim</span>
|
|
||||||
<span class="font-medium tabular-nums">
|
|
||||||
{{ order.shipping_cost_formatted }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
|
|||||||
@ -74,7 +74,6 @@ const props = defineProps<{
|
|||||||
tiktok_order_id: string;
|
tiktok_order_id: string;
|
||||||
shopee_order_id: string;
|
shopee_order_id: string;
|
||||||
discount: string;
|
discount: string;
|
||||||
shipping_cost: string;
|
|
||||||
notes: string;
|
notes: string;
|
||||||
items: OrderCartItem[];
|
items: OrderCartItem[];
|
||||||
};
|
};
|
||||||
@ -95,7 +94,7 @@ const { can } = useCan();
|
|||||||
const page = usePage();
|
const page = usePage();
|
||||||
const authUser = computed(() => (page.props.auth as Auth).user);
|
const authUser = computed(() => (page.props.auth as Auth).user);
|
||||||
const isMarketingUser = computed(() =>
|
const isMarketingUser = computed(() =>
|
||||||
authUser.value?.roles?.includes('marketing') ?? false,
|
(authUser.value?.roles?.includes('marketing-offline') || authUser.value?.roles?.includes('marketing-online')) ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const search = ref('');
|
const search = ref('');
|
||||||
@ -126,7 +125,6 @@ const form = useForm({
|
|||||||
tiktok_order_id: '',
|
tiktok_order_id: '',
|
||||||
shopee_order_id: '',
|
shopee_order_id: '',
|
||||||
discount: '',
|
discount: '',
|
||||||
shipping_cost: '',
|
|
||||||
notes: '',
|
notes: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -147,7 +145,6 @@ function populateForm() {
|
|||||||
form.tiktok_order_id = props.initialData.tiktok_order_id;
|
form.tiktok_order_id = props.initialData.tiktok_order_id;
|
||||||
form.shopee_order_id = props.initialData.shopee_order_id;
|
form.shopee_order_id = props.initialData.shopee_order_id;
|
||||||
form.discount = props.initialData.discount;
|
form.discount = props.initialData.discount;
|
||||||
form.shipping_cost = String(props.initialData.shipping_cost);
|
|
||||||
form.notes = props.initialData.notes;
|
form.notes = props.initialData.notes;
|
||||||
cart.value = props.initialData.items.map((item) => ({
|
cart.value = props.initialData.items.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
@ -253,8 +250,7 @@ const subtotal = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const discountAmount = computed(() => Number(parseRupiah(form.discount)) || 0);
|
const discountAmount = computed(() => Number(parseRupiah(form.discount)) || 0);
|
||||||
const shippingAmount = computed(() => Number(parseRupiah(form.shipping_cost)) || 0);
|
const totalAmount = computed(() => Math.max(subtotal.value - discountAmount.value, 0));
|
||||||
const totalAmount = computed(() => Math.max(subtotal.value - discountAmount.value + shippingAmount.value, 0));
|
|
||||||
|
|
||||||
function getVariantPrice(variant: ProductVariantItem): ProductPriceItem | undefined {
|
function getVariantPrice(variant: ProductVariantItem): ProductPriceItem | undefined {
|
||||||
return variant.prices.find((price) => price.type === form.price_type);
|
return variant.prices.find((price) => price.type === form.price_type);
|
||||||
@ -460,7 +456,6 @@ function buildFormData(): FormData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
formData.append('discount', parseRupiah(form.discount));
|
formData.append('discount', parseRupiah(form.discount));
|
||||||
formData.append('shipping_cost', parseRupiah(form.shipping_cost));
|
|
||||||
formData.append('notes', form.notes);
|
formData.append('notes', form.notes);
|
||||||
|
|
||||||
if (isCreateMode.value && printAfterSave.value) {
|
if (isCreateMode.value && printAfterSave.value) {
|
||||||
@ -822,11 +817,6 @@ function submit() {
|
|||||||
<RupiahInput id="discount" v-model="form.discount" placeholder="0" />
|
<RupiahInput id="discount" v-model="form.discount" placeholder="0" />
|
||||||
<FieldError :errors="formErrors(form, 'discount')" />
|
<FieldError :errors="formErrors(form, 'discount')" />
|
||||||
</Field>
|
</Field>
|
||||||
<Field v-if="isStoreChannel">
|
|
||||||
<FieldLabel for="shipping_cost">Ongkir</FieldLabel>
|
|
||||||
<RupiahInput id="shipping_cost" v-model="form.shipping_cost" placeholder="0" />
|
|
||||||
<FieldError :errors="formErrors(form, 'shipping_cost')" />
|
|
||||||
</Field>
|
|
||||||
<div class="flex justify-between text-base font-semibold">
|
<div class="flex justify-between text-base font-semibold">
|
||||||
<span>Total</span>
|
<span>Total</span>
|
||||||
<span class="text-primary">Rp {{ formatRupiah(totalAmount) }}</span>
|
<span class="text-primary">Rp {{ formatRupiah(totalAmount) }}</span>
|
||||||
|
|||||||
@ -126,8 +126,6 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
|||||||
}}</strong></span>
|
}}</strong></span>
|
||||||
<span>Diskon <strong class="text-primary">{{ order.discount_formatted
|
<span>Diskon <strong class="text-primary">{{ order.discount_formatted
|
||||||
}}</strong></span>
|
}}</strong></span>
|
||||||
<span v-if="order.shipping_cost > 0">Ongkir <strong class="text-primary">{{
|
|
||||||
order.shipping_cost_formatted }}</strong></span>
|
|
||||||
<span>Total <strong class="text-primary">{{ order.total_amount_formatted
|
<span>Total <strong class="text-primary">{{ order.total_amount_formatted
|
||||||
}}</strong></span>
|
}}</strong></span>
|
||||||
<span v-if="order.marketplace_settings_snapshot?.total_fee_amount" class="text-destructive">
|
<span v-if="order.marketplace_settings_snapshot?.total_fee_amount" class="text-destructive">
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const props = defineProps<{
|
|||||||
const { can } = useCan();
|
const { can } = useCan();
|
||||||
|
|
||||||
const isSystemRole = computed(() => {
|
const isSystemRole = computed(() => {
|
||||||
return ['developer', 'owner', 'admin-toko', 'admin-bahan-baku', 'direktur', 'marketing', 'cashier', 'non-operator'].includes(props.role.name);
|
return ['developer', 'owner', 'admin-toko', 'admin-bahan-baku', 'direktur', 'marketing-offline', 'marketing-online', 'cashier', 'non-operator'].includes(props.role.name);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,10 @@ export type EmployeeAdvanceListItem = {
|
|||||||
employee_id: number;
|
employee_id: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
amount_formatted: string;
|
amount_formatted: string;
|
||||||
|
paid_amount: number;
|
||||||
|
paid_amount_formatted: string;
|
||||||
|
remaining_amount: number;
|
||||||
|
remaining_amount_formatted: string;
|
||||||
description: string;
|
description: string;
|
||||||
due_date_input: string;
|
due_date_input: string;
|
||||||
due_date_formatted: string;
|
due_date_formatted: string;
|
||||||
|
|||||||
@ -52,8 +52,6 @@ export type OrderListItem = {
|
|||||||
shopee_order_id: string | null;
|
shopee_order_id: string | null;
|
||||||
subtotal_formatted: string;
|
subtotal_formatted: string;
|
||||||
discount_formatted: string;
|
discount_formatted: string;
|
||||||
shipping_cost: number;
|
|
||||||
shipping_cost_formatted: string;
|
|
||||||
total_amount: number;
|
total_amount: number;
|
||||||
total_amount_formatted: string;
|
total_amount_formatted: string;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
@ -116,7 +114,6 @@ export type OrderEditItem = {
|
|||||||
tiktok_order_id: string | null;
|
tiktok_order_id: string | null;
|
||||||
shopee_order_id: string | null;
|
shopee_order_id: string | null;
|
||||||
discount: number;
|
discount: number;
|
||||||
shipping_cost: number;
|
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
items: Array<{
|
items: Array<{
|
||||||
product_variant_id: number;
|
product_variant_id: number;
|
||||||
@ -151,8 +148,6 @@ export type OrderDetail = {
|
|||||||
subtotal_formatted: string;
|
subtotal_formatted: string;
|
||||||
discount: number;
|
discount: number;
|
||||||
discount_formatted: string;
|
discount_formatted: string;
|
||||||
shipping_cost: number;
|
|
||||||
shipping_cost_formatted: string;
|
|
||||||
total_amount: number;
|
total_amount: number;
|
||||||
total_amount_formatted: string;
|
total_amount_formatted: string;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user