diff --git a/app/Admin/Controllers/AdminController.php b/app/Admin/Controllers/AdminController.php
index ad5abd0..3382a67 100644
--- a/app/Admin/Controllers/AdminController.php
+++ b/app/Admin/Controllers/AdminController.php
@@ -16,6 +16,24 @@ use App\Traits\CustomActionTrait;
abstract class AdminController extends Controller
{
use CustomActionTrait;
+
+ public function store(Request $request)
+ {
+ try {
+ if ($this->actionOfQuickEdit()) {
+ $result = $this->service->quickEdit($request->all());
+ } else {
+ $result = $this->service->store($request->all());
+ }
+ } catch (Throwable $th) {
+ return $this->renderExceptionResponse(
+ tap($th, fn () => report($th))
+ );
+ }
+
+ return $this->autoResponse($result, __('admin.save'));
+ }
+
public function update(Request $request)
{
$input = $request->all();
diff --git a/app/Admin/Controllers/Finance/CommissionIncomeController.php b/app/Admin/Controllers/Finance/CommissionIncomeController.php
new file mode 100644
index 0000000..6a6e201
--- /dev/null
+++ b/app/Admin/Controllers/Finance/CommissionIncomeController.php
@@ -0,0 +1,88 @@
+actionOfGetData()) {
+ return $this->response()->success([
+ 'items' => [$this->getCommissionIncomeStatistics(request())],
+ ]);
+ }
+
+ return $this->response()->success(
+ $this->baseList(
+ $this->baseCRUD()
+ ->headerToolbar([
+ amis('filter-toggler')->align('right'),
+ ])
+ ->footerToolbar([])
+ ->bulkActions([])
+ ->filter($this->baseFilter()->body([
+ amis()->GroupControl()->mode('horizontal')->body([
+ amis()->DateRangeControl('date_range', '日期')
+ ->valueFormat('YYYY-MM-DD')
+ ->columnRatio(6),
+ amis()->InputCityControl('region', '区域')
+ ->allowDistrict(false)
+ ->extractValue(false),
+ amis()->SelectControl('store_id', __('finance.ledger.store'))
+ ->source(admin_url('api/stores?region=${region}'))
+ ->labelField('title')
+ ->valueField('id')
+ ->clearable(),
+ ]),
+ ]))
+ ->columns([
+ amis()->TableColumn('expected_commission', '预期佣金'),
+ amis()->TableColumn('actual_commission', '实际佣金'),
+ amis()->TableColumn('diff_commission', '佣金差异'),
+ amis()->TableColumn('expected_income', '预期收益'),
+ amis()->TableColumn('actual_income', '实际收益'),
+ amis()->TableColumn('diff_income', '收益差异'),
+ ])
+ )
+ );
+ }
+
+ protected function getCommissionIncomeStatistics(Request $request): array
+ {
+ $aggregate = Ledger::select([
+ DB::raw('SUM(expenditure) as expenditure'),
+ DB::raw('SUM(expected_commission) as expected_commission'),
+ DB::raw('SUM(actual_commission) as actual_commission'),
+ DB::raw('SUM(expected_income) as expected_income'),
+ DB::raw('SUM(actual_income) as actual_income'),
+ ])
+ ->filter($request->input(), LedgerFilter::class)
+ ->first();
+
+ $expectedCommission = $aggregate->expected_commission ?? '0';
+ $actualCommission = $aggregate->actual_commission ?? '0';
+ $expectedIncome = $aggregate->expected_income ?? '0';
+ $actualIncome = $aggregate->actual_income ?? '0';
+
+ return [
+ 'expected_commission' => trim_zeros($expectedCommission),
+ 'actual_commission' => trim_zeros($actualCommission),
+ 'diff_commission' => trim_zeros(bcsub($actualCommission, $expectedCommission, 2)),
+ 'expected_income' => trim_zeros($expectedIncome),
+ 'actual_income' => trim_zeros($actualIncome),
+ 'diff_income' => trim_zeros(bcsub($actualIncome, $expectedIncome, 2)),
+ ];
+ }
+}
diff --git a/app/Admin/Controllers/Finance/SalesStatisticController.php b/app/Admin/Controllers/Finance/SalesStatisticController.php
index a66fc42..bf1ac4a 100644
--- a/app/Admin/Controllers/Finance/SalesStatisticController.php
+++ b/app/Admin/Controllers/Finance/SalesStatisticController.php
@@ -36,7 +36,6 @@ class SalesStatisticController extends AdminController
->valueFormat('YYYY-MM-DD')
->columnRatio(6),
amis()->InputCityControl('region', '区域')
- ->inputClassName('w-40')
->allowDistrict(false)
->extractValue(false),
amis()->SelectControl('store_id', __('finance.ledger.store'))
diff --git a/app/Admin/Controllers/Finance/StoreMasterCommissionController.php b/app/Admin/Controllers/Finance/StoreMasterCommissionController.php
new file mode 100644
index 0000000..57a1f19
--- /dev/null
+++ b/app/Admin/Controllers/Finance/StoreMasterCommissionController.php
@@ -0,0 +1,184 @@
+all(),
+ rules: [
+ 'approval_result' => ['bail', 'required', Rule::in([1, 2])],
+ 'failed_reason' => ['bail', 'required_if:approval_result,2'],
+ ],
+ attributes: [
+ 'approval_result' => __('finance.store_master_commission.approval_result'),
+ 'failed_reason' => __('finance.store_master_commission.failed_reason'),
+ ],
+ );
+
+ if ($validator->fails()) {
+ return $this->response()->fail($validator->errors()->first());
+ }
+
+ $masterCommission = StoreMasterCommission::findOrFail($id);
+
+ if ($masterCommission->approval_status !== StoreMasterCommissionApprovalStatus::Pending) {
+ return $this->response()->fail('店长提成不是待审核状态');
+ }
+
+ if ($request->input('approval_result') == 1) {
+ $masterCommission->approval_status = StoreMasterCommissionApprovalStatus::Passed;
+ } else {
+ $masterCommission->approval_status = StoreMasterCommissionApprovalStatus::Rejected;
+ }
+
+ $masterCommission->save();
+
+ return $this->response()->success(null, '操作成功');
+ }
+
+ public function list(): Page
+ {
+ $crud = $this->baseCRUD()
+ ->headerToolbar([
+ $this->createTypeButton('drawer', 'lg')
+ ->visible(Admin::user()->can('admin.finance.store_master_commissions.create')),
+ ...$this->baseHeaderToolBar(),
+ ])
+ ->bulkActions([])
+ ->filter($this->baseFilter()->body([
+ amis()->GroupControl()->mode('horizontal')->body([
+ amis()->MonthControl()
+ ->name('month')
+ ->label(__('finance.store_master_commission.month'))
+ ->valueFormat('YYYY-MM')
+ ->columnRatio(3),
+ amis()->SelectControl('store_id', __('finance.ledger.store'))
+ ->source(admin_url('api/stores'))
+ ->labelField('title')
+ ->valueField('id')
+ ->clearable()
+ ->columnRatio(3),
+ amis()->SelectControl('status', __('finance.store_master_commission.approval_status'))
+ ->multiple()
+ ->options(StoreMasterCommissionApprovalStatus::options())
+ ->columnRatio(3),
+ ]),
+ ]))
+ ->columns([
+ amis()->TableColumn()->name('id')->label(__('finance.store_master_commission.id')),
+ amis()->TableColumn()->name('month')->label(__('finance.store_master_commission.month')),
+ amis()->TableColumn()->name('store.title')->label(__('finance.store_master_commission.store')),
+ amis()->TableColumn()->name('master.name')->label(__('finance.store_master_commission.store_master')),
+ amis()->TableColumn()->name('commission')->label(__('finance.store_master_commission.commission')),
+ amis()->TableColumn()->name('daily_expenses')->label(__('finance.store_master_commission.daily_expenses')),
+ amis()->TableColumn()->name('employee_expenses')->label(__('finance.store_master_commission.employee_expenses')),
+ amis()->TableColumn()->name('other_expenses')->label(__('finance.store_master_commission.other_expenses')),
+ amis()->TableColumn()
+ ->name('approval_status')
+ ->label(__('finance.store_master_commission.approval_status'))
+ ->type('mapping')
+ ->map(StoreMasterCommissionApprovalStatus::labelMap()),
+ amis()->TableColumn()->name('created_at')->label(__('finance.store_master_commission.created_at')),
+ $this->rowActions([
+ $this->rowApprovalButton()
+ ->visibleOn('${approval_status == '.StoreMasterCommissionApprovalStatus::Pending->value.'}'),
+ $this->rowEditTypeButton('drawer', 'lg')
+ ->visible(Admin::user()->can('admin.finance.store_master_commissions.update'))
+ ->visibleOn('${approval_status == '.StoreMasterCommissionApprovalStatus::Rejected->value.'}'),
+ $this->rowDeleteButton()->visible(Admin::user()->can('admin.finance.store_master_commissions.delete')),
+ ]),
+ ]);
+
+ return $this->baseList($crud);
+ }
+
+ public function form(): Form
+ {
+ return $this->baseForm()->title('')->body([
+ amis()->MonthControl()
+ ->name('month')
+ ->label(__('finance.store_master_commission.month'))
+ ->required()
+ ->valueFormat('YYYY-MM'),
+
+ amis()->SelectControl('store_id', __('finance.store_master_commission.store'))
+ ->source(admin_url('api/stores'))
+ ->labelField('title')
+ ->valueField('id')
+ ->required()
+ ->clearable(),
+
+ amis()->NumberControl()
+ ->name('commission')
+ ->label(__('finance.store_master_commission.commission'))
+ ->precision(2)
+ ->showSteps(false)
+ ->required(),
+
+ amis()->NumberControl()
+ ->name('daily_expenses')
+ ->label(__('finance.store_master_commission.daily_expenses'))
+ ->precision(2)
+ ->showSteps(false)
+ ->required(),
+
+ amis()->NumberControl()
+ ->name('employee_expenses')
+ ->label(__('finance.store_master_commission.employee_expenses'))
+ ->precision(2)
+ ->showSteps(false)
+ ->required(),
+
+ amis()->NumberControl()
+ ->name('other_expenses')
+ ->label(__('finance.store_master_commission.other_expenses'))
+ ->precision(2)
+ ->showSteps(false)
+ ->required(),
+ ]);
+ }
+
+ protected function rowApprovalButton()
+ {
+ return amis()->DialogAction()->icon('fa-regular fa-check-circle')->label(__('finance.store_master_commission.approval'))->level('link')->dialog(
+ amis()->Dialog()->title(__('finance.store_master_commission.approval'))->body([
+ amis()->Form()->title('')
+ ->api('post:'.admin_url('finance/store-master-commissions/${id}/approval'))
+ ->body([
+ amis()->RadiosControl('approval_result', __('finance.store_master_commission.approval_result'))
+ ->options([
+ ['label' => '通过', 'value' => 1],
+ ['label' => '驳回', 'value' => 2],
+ ])
+ ->selectFirst()
+ ->required(),
+ amis()->TextareaControl('failed_reason', __('finance.store_master_commission.failed_reason'))
+ ->visibleOn('${approval_result == 2}')
+ ->required(),
+ ]),
+ ])->size('md')
+ );
+ }
+}
diff --git a/app/Admin/Filters/LedgerFilter.php b/app/Admin/Filters/LedgerFilter.php
index 5a80b70..6c2e6fc 100644
--- a/app/Admin/Filters/LedgerFilter.php
+++ b/app/Admin/Filters/LedgerFilter.php
@@ -24,11 +24,12 @@ class LedgerFilter extends ModelFilter
public function region($region)
{
- if (! is_array($region)) {
+ if (array_key_exists('store_id', $this->input) || ! is_array($region)) {
return;
}
$provinceCode = Arr::get($region, 'provinceCode');
+
$cityCode = Arr::get($region, 'cityCode');
if (empty($provinceCode) && empty($cityCode)) {
diff --git a/app/Admin/Filters/LedgerItemFilter.php b/app/Admin/Filters/LedgerItemFilter.php
index a8d2b9e..f8c7865 100644
--- a/app/Admin/Filters/LedgerItemFilter.php
+++ b/app/Admin/Filters/LedgerItemFilter.php
@@ -14,7 +14,7 @@ class LedgerItemFilter extends ModelFilter
public function region($region)
{
- if ($this->input('store_id') !== null || ! is_array($region)) {
+ if (array_key_exists('store_id', $this->input) || ! is_array($region)) {
return;
}
diff --git a/app/Admin/Filters/StoreMasterCommissionFilter.php b/app/Admin/Filters/StoreMasterCommissionFilter.php
new file mode 100644
index 0000000..48865d6
--- /dev/null
+++ b/app/Admin/Filters/StoreMasterCommissionFilter.php
@@ -0,0 +1,23 @@
+where('month', $month);
+ }
+
+ public function store($id)
+ {
+ $this->where('store_id', $id);
+ }
+
+ public function status($status)
+ {
+ $this->whereIn('approval_status', explode(',', $status));
+ }
+}
diff --git a/app/Admin/Services/Finance/StoreMasterCommissionService.php b/app/Admin/Services/Finance/StoreMasterCommissionService.php
new file mode 100644
index 0000000..fded0e0
--- /dev/null
+++ b/app/Admin/Services/Finance/StoreMasterCommissionService.php
@@ -0,0 +1,77 @@
+ ['bail', 'required', 'date_format:Y-m'],
+ 'store_id' => ['bail', 'required', 'int'],
+ 'commission' => ['bail', 'required', 'numeric'],
+ 'daily_expenses' => ['bail', 'required', 'numeric'],
+ 'employee_expenses' => ['bail', 'required', 'numeric'],
+ 'other_expenses' => ['bail', 'required', 'numeric'],
+ ], [], [
+ 'month' => __('finance.store_master_commission.month'),
+ 'store_id' => __('finance.store_master_commission.store'),
+ 'commission' => __('finance.store_master_commission.commission'),
+ 'daily_expenses' => __('finance.store_master_commission.daily_expenses'),
+ 'employee_expenses' => __('finance.store_master_commission.employee_expenses'),
+ 'other_expenses' => __('finance.store_master_commission.other_expenses'),
+ ]);
+
+ $store = Store::findOrFail($validated['store_id']);
+
+ $this->modelName::create(
+ array_merge($validated, ['store_master_id' => $store->master_id])
+ );
+
+ return true;
+ }
+
+ public function update($primaryKey, $data): bool
+ {
+ $validated = Validator::validate($data, [
+ 'month' => ['bail', 'required', 'date_format:Y-m'],
+ 'store_id' => ['bail', 'required', 'int'],
+ 'commission' => ['bail', 'required', 'numeric'],
+ 'daily_expenses' => ['bail', 'required', 'numeric'],
+ 'employee_expenses' => ['bail', 'required', 'numeric'],
+ 'other_expenses' => ['bail', 'required', 'numeric'],
+ ], [], [
+ 'month' => __('finance.store_master_commission.month'),
+ 'store_id' => __('finance.store_master_commission.store'),
+ 'commission' => __('finance.store_master_commission.commission'),
+ 'daily_expenses' => __('finance.store_master_commission.daily_expenses'),
+ 'employee_expenses' => __('finance.store_master_commission.employee_expenses'),
+ 'other_expenses' => __('finance.store_master_commission.other_expenses'),
+ ]);
+
+ $model = $this->query()->whereKey($primaryKey)->firstOrFail();
+
+ if ($model->approval_status !== StoreMasterCommissionApprovalStatus::Rejected) {
+ abort(400, '只能编辑状态是“未通过”的店长提成');
+ }
+
+ $attributes = array_merge(
+ $validated, ['approval_status' => StoreMasterCommissionApprovalStatus::Pending]
+ );
+
+ return $model->update($attributes);
+ }
+}
diff --git a/app/Admin/routes.php b/app/Admin/routes.php
index 6bfe51b..1b80cd4 100644
--- a/app/Admin/routes.php
+++ b/app/Admin/routes.php
@@ -1,8 +1,10 @@
resource('ledgers', LedgerController::class);
$router->post('ledgers/{ledger}/approval', [LedgerController::class, 'approval'])->name('ledgers.approval');
+ // 佣金收入
+ $router->get('commission-incomes', [CommissionIncomeController::class, 'index'])->name('commission_incomes.index');
// 销售统计
$router->get('sales-statistics', [SalesStatisticController::class, 'index'])->name('sales_statistics.index');
// 门店统计
$router->get('store-statistics', [StoreStatisticController::class, 'index'])->name('store_statistics.index');
+ // 店长提成
+ $router->resource('store-master-commissions', StoreMasterCommissionController::class);
+ $router->post('store-master-commissions/{store_master_commission}/approval', [StoreMasterCommissionController::class, 'approval'])->name('store_statistics.approval');
});
/*
diff --git a/app/Enums/StoreMasterCommissionApprovalStatus.php b/app/Enums/StoreMasterCommissionApprovalStatus.php
new file mode 100644
index 0000000..4d56047
--- /dev/null
+++ b/app/Enums/StoreMasterCommissionApprovalStatus.php
@@ -0,0 +1,38 @@
+ '待审核',
+ self::Passed => '已通过',
+ self::Rejected => '未通过',
+ };
+ }
+
+ public static function options(): array
+ {
+ return collect(self::cases())
+ ->map(fn (StoreMasterCommissionApprovalStatus $case) => [
+ 'label' => $case->label(),
+ 'value' => $case->value,
+ ])
+ ->all();
+ }
+
+ public static function labelMap(): array
+ {
+ return [
+ self::Pending->value => ''.self::Pending->label().'',
+ self::Passed->value => ''.self::Passed->label().'',
+ self::Rejected->value => ''.self::Rejected->label().'',
+ ];
+ }
+}
diff --git a/app/Models/StoreMasterCommission.php b/app/Models/StoreMasterCommission.php
new file mode 100644
index 0000000..b67c36b
--- /dev/null
+++ b/app/Models/StoreMasterCommission.php
@@ -0,0 +1,44 @@
+ StoreMasterCommissionApprovalStatus::Pending,
+ ];
+
+ protected $casts = [
+ 'approval_status' => StoreMasterCommissionApprovalStatus::class,
+ ];
+
+ protected $fillable = [
+ 'month',
+ 'store_id',
+ 'store_master_id',
+ 'commission',
+ 'daily_expenses',
+ 'employee_expenses',
+ 'other_expenses',
+ 'approval_status',
+ ];
+
+ public function store()
+ {
+ return $this->belongsTo(Store::class, 'store_id');
+ }
+
+ public function master(): BelongsTo
+ {
+ return $this->belongsTo(Employee::class, 'store_master_id');
+ }
+}
diff --git a/database/migrations/2024_03_29_155959_create_store_master_commissions_table.php b/database/migrations/2024_03_29_155959_create_store_master_commissions_table.php
new file mode 100644
index 0000000..f525b01
--- /dev/null
+++ b/database/migrations/2024_03_29_155959_create_store_master_commissions_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->string('month')->comment('月份: 2024-03');
+ $table->foreignId('store_id')->comment('门店ID');
+ $table->foreignId('store_master_id')->comment('店长ID');
+ $table->decimal('commission', 10, 2)->comment('佣金');
+ $table->decimal('daily_expenses', 10, 2)->comment('日常支出');
+ $table->decimal('employee_expenses', 10, 2)->comment('员工支出');
+ $table->decimal('other_expenses', 10, 2)->comment('其他支出');
+ $table->tinyInteger('approval_status')->default(1)->comment('状态: 1 待审核, 2 已通过, 3 已拒绝');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('store_master_commissions');
+ }
+};
diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php
index e8482c1..9b3ae8d 100644
--- a/database/seeders/AdminPermissionSeeder.php
+++ b/database/seeders/AdminPermissionSeeder.php
@@ -148,6 +148,15 @@ class AdminPermissionSeeder extends Seeder
'resource' => ['list', 'update', 'view'],
'children' => [],
],
+ 'commission_incomes' => [
+ 'name' => '佣金收入',
+ 'icon' => 'ri:money-cny-circle-line',
+ 'uri' => '/finance/commission-incomes',
+ 'resource' => false,
+ 'children' => [
+ 'index' => '佣金收入',
+ ],
+ ],
'sales_statistics' => [
'name' => '销售统计',
'icon' => 'ri:bar-chart-2-line',
@@ -166,6 +175,13 @@ class AdminPermissionSeeder extends Seeder
'index' => '门店统计',
],
],
+ 'store_master_commissions' => [
+ 'name' => '店长提成',
+ 'icon' => 'icon-park-outline:paper-money',
+ 'uri' => '/finance/store-master-commissions',
+ 'resource' => ['list', 'create', 'update', 'delete'],
+ 'children' => [],
+ ],
],
],
diff --git a/lang/zh_CN/finance.php b/lang/zh_CN/finance.php
index a98bf5c..7ac469d 100644
--- a/lang/zh_CN/finance.php
+++ b/lang/zh_CN/finance.php
@@ -19,4 +19,21 @@ return [
'approval_result' => '审核结果',
'failed_reason' => '驳回原因',
],
+
+ 'store_master_commission' => [
+ 'id' => 'ID',
+ 'month' => '月份',
+ 'store' => '门店',
+ 'store_master' => '店长',
+ 'commission' => '佣金',
+ 'daily_expenses' => '日常支出',
+ 'employee_expenses' => '员工支出',
+ 'other_expenses' => '其他支出',
+ 'approval' => '审核',
+ 'approval_status' => '状态',
+ 'approval_result' => '审核结果',
+ 'failed_reason' => '驳回原因',
+ 'created_at' => '创建时间',
+ 'updated_at' => '更新时间',
+ ],
];