diff --git a/app/Admin/Controllers/Finance/CommissionIncomeController.php b/app/Admin/Controllers/Finance/CommissionIncomeController.php index d05b868..ab315aa 100644 --- a/app/Admin/Controllers/Finance/CommissionIncomeController.php +++ b/app/Admin/Controllers/Finance/CommissionIncomeController.php @@ -5,8 +5,8 @@ namespace App\Admin\Controllers\Finance; use App\Admin\Controllers\AdminController; use App\Admin\Filters\LedgerFilter; use App\Models\Ledger; -use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Slowlyo\OwlAdmin\Admin; /** * @property mixed $name @@ -17,16 +17,21 @@ class CommissionIncomeController extends AdminController { if ($this->actionOfGetData()) { return $this->response()->success([ - 'items' => [$this->getCommissionIncomeStatistics(request())], + 'items' => [$this->getCommissionIncomeStatistics()], ]); } + if ($this->actionOfExport()) { + return $this->export(); + } + return $this->response()->success( $this->baseList( $this->baseCRUD() - ->headerToolbar([ - amis('filter-toggler')->align('right'), - ]) + ->headerToolbar( + collect($this->baseHeaderToolBar()) + ->when(Admin::user()->can('admin.finance.commission_incomes.export'), fn ($collection) => $collection->push($this->exportAction(true))) + ) ->footerToolbar([]) ->bulkActions([]) ->filter($this->baseFilter()->body([ @@ -57,7 +62,7 @@ class CommissionIncomeController extends AdminController ); } - protected function getCommissionIncomeStatistics(Request $request): array + protected function getCommissionIncomeStatistics(): array { $aggregate = Ledger::select([ DB::raw('SUM(expenditure) as expenditure'), @@ -66,7 +71,7 @@ class CommissionIncomeController extends AdminController DB::raw('SUM(expected_income) as expected_income'), DB::raw('SUM(actual_income) as actual_income'), ]) - ->filter($request->input(), LedgerFilter::class) + ->filter(request()->input(), LedgerFilter::class) ->first(); $expectedCommission = $aggregate->expected_commission ?? '0'; @@ -83,4 +88,69 @@ class CommissionIncomeController extends AdminController 'diff_income' => trim_zeros(bcsub($actualIncome, $expectedIncome, 2)), ]; } + + /** + * 导出按钮 + * + * @param bool $disableSelectedItem + * + * @return \Slowlyo\OwlAdmin\Renderers\Service + */ + protected function exportAction($disableSelectedItem = false) + { + // 下载路径 + $downloadPath = admin_url('_download_export', true); + // 导出接口地址 + $exportPath = $this->getExportPath(); + // 按钮点击事件 + $event = fn($script) => ['click' => ['actions' => [['actionType' => 'custom', 'script' => $script]]]]; + // 导出处理动作 + $doAction = "doAction([{actionType:'setValue',componentId:'export-action',args:{value:{showExportLoading:true}}},{actionType:'ajax',args:{api:{url:url.toString(),method:'get'}}},{actionType:'setValue',componentId:'export-action',args:{value:{showExportLoading:false}}},{actionType:'custom',expression:'\${event.data.responseResult.responseStatus===0}',script:'window.open(\'{$downloadPath}?path=\'+event.data.responseResult.responseData.path)'}])"; + // 按钮 + $button = amis()->Button() + ->label(__('admin.export.title')) + ->icon('fa-solid fa-download') + ->onEvent( + $event("let data=event.data;let params=Object.keys(data).filter(key=>key!=='page' && key!=='__super').reduce((obj,key)=>{obj[key]=data[key];return obj;},{});let url=new URL('{$exportPath}',window.location.origin);Object.keys(params).forEach(key=>url.searchParams.append(key,params[key]));{$doAction}") + ); + + return amis()->Service() + ->id('export-action') + ->set('align', 'right') + ->set('data', ['showExportLoading' => false]) + ->body( + amis()->Spinner()->set('showOn', '${showExportLoading}')->overlay()->body($button) + ); + } + + /** + * 导出 + * + * @return JsonResponse|JsonResource + */ + protected function export() + { + admin_abort_if(!class_exists('\Rap2hpoutre\FastExcel\FastExcel'), __('admin.export.please_install_laravel_excel')); + + // 默认在 storage/app/ 下 + $path = sprintf('佣金收入-%s.xlsx', date('YmdHis')); + + $data = [$this->getCommissionIncomeStatistics()]; + + try { + fastexcel($data)->export(storage_path('app/' . $path), fn($row) => [ + '预期佣金' => $row['expected_commission'], + '实际佣金' => $row['actual_commission'], + '佣金差异' => $row['diff_commission'], + '预期收益' => $row['expected_income'], + '实际收益' => $row['actual_income'], + '收益差异' => $row['diff_income'], + ]); + } catch (\Throwable $e) { + report($e); + admin_abort(__('admin.action_failed')); + } + + return $this->response()->success(compact('path')); + } } diff --git a/app/Admin/Controllers/Finance/SalesStatisticController.php b/app/Admin/Controllers/Finance/SalesStatisticController.php index 5af8607..db1649d 100644 --- a/app/Admin/Controllers/Finance/SalesStatisticController.php +++ b/app/Admin/Controllers/Finance/SalesStatisticController.php @@ -6,8 +6,8 @@ use App\Admin\Controllers\AdminController; use App\Admin\Filters\LedgerItemFilter; use App\Models\Keyword; use App\Models\LedgerItem; -use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Slowlyo\OwlAdmin\Admin; /** * @property mixed $name @@ -18,16 +18,21 @@ class SalesStatisticController extends AdminController { if ($this->actionOfGetData()) { return $this->response()->success([ - 'items' => $this->getLotteryTypeStatistics(request()), + 'items' => $this->getLotteryTypeStatistics(), ]); } + if ($this->actionOfExport()) { + return $this->export(); + } + return $this->response()->success( $this->baseList( $this->baseCRUD() - ->headerToolbar([ - amis('filter-toggler')->align('right'), - ]) + ->headerToolbar( + collect($this->baseHeaderToolBar()) + ->when(Admin::user()->can('admin.finance.sales_statistics.export'), fn ($collection) => $collection->push($this->exportAction(true))) + ) ->footerToolbar([]) ->bulkActions([]) ->filter($this->baseFilter()->body([ @@ -60,7 +65,7 @@ class SalesStatisticController extends AdminController ); } - protected function getLotteryTypeStatistics(Request $request): array + protected function getLotteryTypeStatistics(): array { /** @var \Illuminate\Database\Eloquent\Collection */ $lotteryTypes = Keyword::where('parent_key', 'lottery_type')->get(); @@ -71,7 +76,7 @@ class SalesStatisticController extends AdminController DB::raw('SUM(sales) as sales'), DB::raw('SUM(expenditure) as expenditure'), ]) - ->filter($request->input(), LedgerItemFilter::class) + ->filter(request()->input(), LedgerItemFilter::class) ->whereIn('ledger_item_type_id', $lotteryTypes->pluck('key')) ->groupBy('ledger_item_type_id') ->get() @@ -97,4 +102,65 @@ class SalesStatisticController extends AdminController ]; })->all(); } + + /** + * 导出按钮 + * + * @param bool $disableSelectedItem + * + * @return \Slowlyo\OwlAdmin\Renderers\Service + */ + protected function exportAction($disableSelectedItem = false) + { + // 下载路径 + $downloadPath = admin_url('_download_export', true); + // 导出接口地址 + $exportPath = $this->getExportPath(); + // 按钮点击事件 + $event = fn($script) => ['click' => ['actions' => [['actionType' => 'custom', 'script' => $script]]]]; + // 导出处理动作 + $doAction = "doAction([{actionType:'setValue',componentId:'export-action',args:{value:{showExportLoading:true}}},{actionType:'ajax',args:{api:{url:url.toString(),method:'get'}}},{actionType:'setValue',componentId:'export-action',args:{value:{showExportLoading:false}}},{actionType:'custom',expression:'\${event.data.responseResult.responseStatus===0}',script:'window.open(\'{$downloadPath}?path=\'+event.data.responseResult.responseData.path)'}])"; + // 按钮 + $button = amis()->Button() + ->label(__('admin.export.title')) + ->icon('fa-solid fa-download') + ->onEvent( + $event("let data=event.data;let params=Object.keys(data).filter(key=>key!=='page' && key!=='__super').reduce((obj,key)=>{obj[key]=data[key];return obj;},{});let url=new URL('{$exportPath}',window.location.origin);Object.keys(params).forEach(key=>url.searchParams.append(key,params[key]));{$doAction}") + ); + + return amis()->Service() + ->id('export-action') + ->set('align', 'right') + ->set('data', ['showExportLoading' => false]) + ->body( + amis()->Spinner()->set('showOn', '${showExportLoading}')->overlay()->body($button) + ); + } + + /** + * 导出 + * + * @return JsonResponse|JsonResource + */ + protected function export() + { + admin_abort_if(!class_exists('\Rap2hpoutre\FastExcel\FastExcel'), __('admin.export.please_install_laravel_excel')); + + // 默认在 storage/app/ 下 + $path = sprintf('销售统计-%s.xlsx', date('YmdHis')); + + $data = $this->getLotteryTypeStatistics(); + + try { + fastexcel($data)->export(storage_path('app/' . $path), fn($row) => [ + '彩种' => $row['name'], + '销量' => $row['sales'], + '兑奖' => $row['expenditure'], + ]); + } catch (\Throwable $e) { + admin_abort(__('admin.action_failed')); + } + + return $this->response()->success(compact('path')); + } } diff --git a/app/Admin/Controllers/Finance/StoreStatisticController.php b/app/Admin/Controllers/Finance/StoreStatisticController.php index 33d976a..cbed5f8 100644 --- a/app/Admin/Controllers/Finance/StoreStatisticController.php +++ b/app/Admin/Controllers/Finance/StoreStatisticController.php @@ -5,30 +5,29 @@ namespace App\Admin\Controllers\Finance; use App\Admin\Controllers\AdminController; use App\Services\StatisticService; use Illuminate\Support\Arr; +use Slowlyo\OwlAdmin\Admin; class StoreStatisticController extends AdminController { public function index() { if ($this->actionOfGetData()) { - $input = Arr::except(request()->input(), ['orderBy', 'orderDir']); - - $orderBy = request()->input('orderBy') ?: 'sales'; - $orderDir = request()->input('orderDir') ?: 'desc'; - - $input['sort'] = ($orderDir === 'desc' ? '-' : '').$orderBy; - return $this->response()->success([ - 'items' => (new StatisticService())->storeRanking($input), + 'items' => $this->getStoreRanking(), ]); } + if ($this->actionOfExport()) { + return $this->export(); + } + return $this->response()->success( $this->baseList( $this->baseCRUD() - ->headerToolbar([ - amis('filter-toggler')->align('right'), - ]) + ->headerToolbar( + collect($this->baseHeaderToolBar()) + ->when(Admin::user()->can('admin.finance.store_statistics.export'), fn ($collection) => $collection->push($this->exportAction(true))) + ) ->footerToolbar([]) ->bulkActions([]) ->filter($this->baseFilter()->body([ @@ -55,4 +54,81 @@ class StoreStatisticController extends AdminController ) ); } + + protected function getStoreRanking(): array + { + $input = Arr::except(request()->input(), ['orderBy', 'orderDir']); + + $orderBy = request()->input('orderBy') ?: 'sales'; + $orderDir = request()->input('orderDir') ?: 'desc'; + + $input['sort'] = ($orderDir === 'desc' ? '-' : '').$orderBy; + + return (new StatisticService())->storeRanking($input); + } + + /** + * 导出按钮 + * + * @param bool $disableSelectedItem + * + * @return \Slowlyo\OwlAdmin\Renderers\Service + */ + protected function exportAction($disableSelectedItem = false) + { + // 下载路径 + $downloadPath = admin_url('_download_export', true); + // 导出接口地址 + $exportPath = $this->getExportPath(); + // 按钮点击事件 + $event = fn($script) => ['click' => ['actions' => [['actionType' => 'custom', 'script' => $script]]]]; + // 导出处理动作 + $doAction = "doAction([{actionType:'setValue',componentId:'export-action',args:{value:{showExportLoading:true}}},{actionType:'ajax',args:{api:{url:url.toString(),method:'get'}}},{actionType:'setValue',componentId:'export-action',args:{value:{showExportLoading:false}}},{actionType:'custom',expression:'\${event.data.responseResult.responseStatus===0}',script:'window.open(\'{$downloadPath}?path=\'+event.data.responseResult.responseData.path)'}])"; + // 按钮 + $button = amis()->Button() + ->label(__('admin.export.title')) + ->icon('fa-solid fa-download') + ->onEvent( + $event("let data=event.data;let params=Object.keys(data).filter(key=>key!=='page' && key!=='__super').reduce((obj,key)=>{obj[key]=data[key];return obj;},{});let url=new URL('{$exportPath}',window.location.origin);Object.keys(params).forEach(key=>url.searchParams.append(key,params[key]));{$doAction}") + ); + + return amis()->Service() + ->id('export-action') + ->set('align', 'right') + ->set('data', ['showExportLoading' => false]) + ->body( + amis()->Spinner()->set('showOn', '${showExportLoading}')->overlay()->body($button) + ); + } + + /** + * 导出 + * + * @return JsonResponse|JsonResource + */ + protected function export() + { + admin_abort_if(!class_exists('\Rap2hpoutre\FastExcel\FastExcel'), __('admin.export.please_install_laravel_excel')); + + // 默认在 storage/app/ 下 + $path = sprintf('门店统计-%s.xlsx', date('YmdHis')); + + $data = $this->getStoreRanking(); + + try { + fastexcel($data)->export(storage_path('app/' . $path), function($row) { + return [ + '排序' => $row['ranking'], + '门店' => Arr::get($row, 'store.title'), + '收入' => $row['sales'], + '支出' => $row['expenditure'], + ]; + }); + } catch (\Throwable $e) { + report($e); + admin_abort(__('admin.action_failed')); + } + + return $this->response()->success(compact('path')); + } } diff --git a/app/Services/StatisticService.php b/app/Services/StatisticService.php index 9a149fb..1db3b2e 100644 --- a/app/Services/StatisticService.php +++ b/app/Services/StatisticService.php @@ -192,6 +192,7 @@ class StatisticService ->groupBy('store_id'); $stores = Store::filter($input, StoreFilter::class) + ->selectRaw('stores.*,IFNULL(store_ledgers.sales, 0) as sales,IFNULL(store_ledgers.expenditure, 0) as expenditure') ->leftJoinSub($storeLedgers, 'store_ledgers', fn ($join) => $join->on('stores.id', '=', 'store_ledgers.store_id')) ->when($input['sort'] ?? '', function ($query, $sort) { foreach (explode(',', $sort) as $sort) { diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php index 9c5d0ed..8041e2c 100644 --- a/database/seeders/AdminPermissionSeeder.php +++ b/database/seeders/AdminPermissionSeeder.php @@ -278,6 +278,7 @@ class AdminPermissionSeeder extends Seeder 'resource' => false, 'children' => [ 'index' => '佣金收入', + 'export' => '导出', ], ], 'reimbursements' => [ @@ -296,6 +297,7 @@ class AdminPermissionSeeder extends Seeder 'resource' => false, 'children' => [ 'index' => '销售统计', + 'export' => '导出', ], ], 'store_statistics' => [ @@ -305,6 +307,7 @@ class AdminPermissionSeeder extends Seeder 'resource' => false, 'children' => [ 'index' => '门店统计', + 'export' => '导出', ], ], 'store_master_commissions' => [