From a0935c43325bb96f8d991c9888bd35d8f49f481a Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 31 Oct 2025 16:33:02 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E6=9A=82=E5=81=9C=E6=8A=93=E5=8F=96?= =?UTF-8?q?=E8=BD=A6=E8=BE=86=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/VehVehicleSkuController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/common/controller/VehVehicleSkuController.php b/app/common/controller/VehVehicleSkuController.php index a3661da..9c41686 100644 --- a/app/common/controller/VehVehicleSkuController.php +++ b/app/common/controller/VehVehicleSkuController.php @@ -47,6 +47,7 @@ class VehVehicleSkuController extends BaseController public function store(Request $request) { + return $this->error('请优先抓取车辆信息,如抓取不到相应车型请联系管理员,暂停手动录入车辆信息'); try { $data = $this->validate($this->service->getValidator(), $request->post(), 'store'); $res = $this->service->serviceStore($request, $data); From 359755fdf15d5632e3b1060f4563a46868f6b795 Mon Sep 17 00:00:00 2001 From: mzl Date: Mon, 3 Nov 2025 18:16:51 +0800 Subject: [PATCH 2/6] Merge branch 'release' --- .../controller/StockOrdersController.php | 11 +- app/admin/controller/SysConfigController.php | 48 +++ app/admin/controller/UserController.php | 5 + .../dashboard/FinStatisticsController.php | 48 +++ .../dashboard/OfficeClerkController.php | 182 ++++++++++ app/admin/logic/SysConfigLogic.php | 17 + app/admin/service/SysConfigService.php | 83 +++++ app/admin/service/UserService.php | 2 + app/admin/validate/SysConfigValidate.php | 29 ++ .../controller/FinPaymentApplyController.php | 160 +++++++++ .../controller/FinReceiptController.php | 184 ++++++++++ .../controller/FinReceiptHisController.php | 98 ++++++ app/common/logic/AuthLogic.php | 18 +- app/common/logic/FinPaymentApplyLogic.php | 17 + app/common/logic/FinReceiptHisLogic.php | 17 + app/common/logic/FinReceiptLogic.php | 21 ++ app/common/logic/SysConfigLogic.php | 22 ++ app/common/model/FinPaymentApply.php | 65 ++++ app/common/model/FinPaymentStock.php | 67 ++++ app/common/model/FinReceipt.php | 65 ++++ app/common/model/FinReceiptHis.php | 49 +++ app/common/model/StockOrders.php | 16 + app/common/model/StockOrdersFiles.php | 2 +- app/common/model/SysConfig.php | 49 +++ app/common/service/FinPaymentApplyService.php | 267 +++++++++++++++ app/common/service/FinReceiptHisService.php | 62 ++++ app/common/service/FinReceiptService.php | 315 ++++++++++++++++++ app/common/service/StockOrdersService.php | 56 +++- app/common/service/SysConfigService.php | 37 ++ .../approval/service/ApprovalFlowService.php | 10 +- .../scaffold/templates/paymentFiles.stub | 103 ++++++ .../validate/FinPaymentApplyValidate.php | 29 ++ app/common/validate/FinReceiptHisValidate.php | 29 ++ app/common/validate/FinReceiptValidate.php | 56 ++++ app/crm/controller/StockOrdersController.php | 42 ++- app/functions.php | 3 +- app/process/Task.php | 41 ++- config/route.php | 50 ++- 38 files changed, 2339 insertions(+), 36 deletions(-) create mode 100644 app/admin/controller/SysConfigController.php create mode 100644 app/admin/controller/dashboard/FinStatisticsController.php create mode 100644 app/admin/controller/dashboard/OfficeClerkController.php create mode 100644 app/admin/logic/SysConfigLogic.php create mode 100644 app/admin/service/SysConfigService.php create mode 100644 app/admin/validate/SysConfigValidate.php create mode 100644 app/common/controller/FinPaymentApplyController.php create mode 100644 app/common/controller/FinReceiptController.php create mode 100644 app/common/controller/FinReceiptHisController.php create mode 100644 app/common/logic/FinPaymentApplyLogic.php create mode 100644 app/common/logic/FinReceiptHisLogic.php create mode 100644 app/common/logic/FinReceiptLogic.php create mode 100644 app/common/logic/SysConfigLogic.php create mode 100644 app/common/model/FinPaymentApply.php create mode 100644 app/common/model/FinPaymentStock.php create mode 100644 app/common/model/FinReceipt.php create mode 100644 app/common/model/FinReceiptHis.php create mode 100644 app/common/model/SysConfig.php create mode 100644 app/common/service/FinPaymentApplyService.php create mode 100644 app/common/service/FinReceiptHisService.php create mode 100644 app/common/service/FinReceiptService.php create mode 100644 app/common/service/SysConfigService.php create mode 100644 app/common/utils/scaffold/templates/paymentFiles.stub create mode 100644 app/common/validate/FinPaymentApplyValidate.php create mode 100644 app/common/validate/FinReceiptHisValidate.php create mode 100644 app/common/validate/FinReceiptValidate.php diff --git a/app/admin/controller/StockOrdersController.php b/app/admin/controller/StockOrdersController.php index 39806a5..47e694a 100644 --- a/app/admin/controller/StockOrdersController.php +++ b/app/admin/controller/StockOrdersController.php @@ -81,19 +81,22 @@ class StockOrdersController extends BaseController if (!isset($groupedFiles[$file['file_code']])) { $groupedFiles[$file['file_code']] = []; } - $groupedFiles[$file['file_code']][] = $file['file_url']; + $groupedFiles[$file['file_code']][] = [ + 'url' => $file['file_url'], + 'original_name' => $file['original_name'] + ]; } // 映射到对应字段 foreach ($fileTypes as $field => $fileCode) { if (isset($groupedFiles[$fileCode])) { - // 原始URL数组 + // 文件信息数组,包含url和original_name $data[$field] = $groupedFiles[$fileCode]; // 完整URL数组 $fullUrls = []; - foreach ($groupedFiles[$fileCode] as $url) { - $fullUrls[] = (new \app\common\service\OssService)->generatePrivateUrl($url); + foreach ($groupedFiles[$fileCode] as $fileInfo) { + $fullUrls[] = (new \app\common\service\OssService)->generatePrivateUrl($fileInfo['url']); } $data[$field . '_url'] = $fullUrls; } diff --git a/app/admin/controller/SysConfigController.php b/app/admin/controller/SysConfigController.php new file mode 100644 index 0000000..3534079 --- /dev/null +++ b/app/admin/controller/SysConfigController.php @@ -0,0 +1,48 @@ +service = new SysConfigService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + protected function indexFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + return $data; + } + + /** + * 数据格式化 + * + * @param array $data + * @return array + */ + protected function showFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + return $data; + } +} \ No newline at end of file diff --git a/app/admin/controller/UserController.php b/app/admin/controller/UserController.php index 8c24c6c..e9ef8d4 100644 --- a/app/admin/controller/UserController.php +++ b/app/admin/controller/UserController.php @@ -118,6 +118,11 @@ class UserController extends BaseController $data['system_ids'][] = $system['id']; } } + if ($data['reportToInfo']) { + $data['report_to_name'] = $data['reportToInfo']['id'] == $data['id'] ? '/' : $data['reportToInfo']['name']; + } else { + $data['report_to_name'] = '-'; + } return $data; } diff --git a/app/admin/controller/dashboard/FinStatisticsController.php b/app/admin/controller/dashboard/FinStatisticsController.php new file mode 100644 index 0000000..95cf09d --- /dev/null +++ b/app/admin/controller/dashboard/FinStatisticsController.php @@ -0,0 +1,48 @@ +',0); + + $result = []; + //已认领数量 + $result['claimed_num'] = $query->count(); + //已认领金额 + $result['claimed_amount'] = $query->sum('amount'); + + //未入账数量 + $result['unreceived_num'] = FinReceipt::whereNull('received_at')->count(); + //未入账金额 + $result['unreceived_amount'] = FinReceipt::whereNull('received_at')->sum('amount'); + + $query = FinReceipt::whereNull('payment_apply_id'); + + //未认领数量 + $result['unclaimed_num'] = $query->count(); + //未认领金额 + $result['unclaimed_amount'] = $query->sum('amount'); + + //已超时未认领数量 + $result['timeout'] = $query->where('created_at','<',time()-24*3600)->count(); + + return $this->success($result); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + +} \ No newline at end of file diff --git a/app/admin/controller/dashboard/OfficeClerkController.php b/app/admin/controller/dashboard/OfficeClerkController.php new file mode 100644 index 0000000..463a874 --- /dev/null +++ b/app/admin/controller/dashboard/OfficeClerkController.php @@ -0,0 +1,182 @@ +where('approver_id', $request->user->uid) + ->where('status', 'approved') + ->count('id'); + + $pending = Db::table('approval_records') + ->where('approver_id', $request->user->uid) + ->where('status', 'pending') + ->count('id'); + + $unclaimed = Db::table('fin_payment_apply') + ->where('doc_user_id', $request->user->uid) + ->where('receipt_id', 0) + ->count('id'); + + $claimed = Db::table('fin_payment_apply') + ->where('doc_user_id', $request->user->uid) + ->where('receipt_id', '>', 0) + ->count('id'); + + $twentyFourHoursAgo = time() - (24 * 3600); + $timeout = Db::table('fin_payment_apply') + ->where('doc_user_id', $request->user->uid) + ->where('fin_payment_apply.submit_files', '') + ->where('receipt_id', '>', 0) + ->where('claimed_at', '<', $twentyFourHoursAgo) + ->count('id'); + + return $this->success([ + 'approved' => $approved, + 'pending' => $pending, + 'unclaimed' => $unclaimed, + 'claimed' => $claimed, + 'timeout' => $timeout + ]); + } + + // 曲线 + public function curve(Request $request) + { + $groupId = Db::table('user_group_members as ugm') + ->join('user_group as ug', 'ugm.group_id', '=', 'ug.id') + ->where('ugm.user_id', $request->user->uid) + ->where('ugm.is_leader', 1) + ->where('ugm.status', 1) + ->where('ug.scenario_code', 'stock_orders') + ->where('ug.status', 1) + ->value('ug.id'); + $userIds = Db::table('user_group_members') + ->where('group_id', $groupId) + ->pluck('user_id'); + $list = Db::table('stock_orders') + ->join('user', 'stock_orders.salesperson_id', '=', 'user.id') + ->whereIn('stock_orders.salesperson_id', $userIds) + ->whereIn('stock_orders.approval_status', [0, 1, 2]) + ->whereNull('stock_orders.deleted_at') + ->groupBy('stock_orders.salesperson_id', 'user.name') + ->selectRaw('user.name, count(*) as count') + ->pluck('count', 'name') + ->toArray(); + $optionType = []; + $counts = []; + foreach ($list as $key=>$val) { + $optionType[] = $key; + $counts[] = $val; + } + $arr = [ + "optionType" => $optionType, + "counts" => $counts + ]; + return $this->success($arr); + } + + // 审批 + public function approval(Request $request) + { + $list = Db::table('approval_records as ar') + ->join('approval_instances as ai', 'ar.instance_id', '=', 'ai.id') + ->join('stock_orders as so', 'ai.biz_id', '=', 'so.id') + ->where('ar.approver_id', $request->user->uid) + ->where('ar.status', 'pending') + ->where('ai.flow_type', 'stock_orders') + ->where('ai.biz_type', 'stock_orders') + ->where('ai.status', 'processing') + ->whereNull('so.deleted_at') + ->select( + 'so.order_no', + 'ai.created_at as approval_created_at', + 'so.salesperson_snapshot as salesperson_name' + ) + ->get() + ->map(function($item) { + return [ + 'order_no' => $item->order_no, + 'approval_created_at' => date('Y-m-d H:i:s', strtotime($item->approval_created_at)), // 格式化时间 + 'salesperson_name' => $item->salesperson_name + ]; + }) + ->toArray(); + return $this->success($list); + } + + // 认领 + public function claim(Request $request) + { + $type = $request->get('type'); + if ($type == 'unclaimed') { + $list = Db::table('fin_payment_apply') + ->join('sys_currency', 'fin_payment_apply.currency_id', '=', 'sys_currency.id') + ->where('fin_payment_apply.doc_user_id', $request->user->uid) + ->where('fin_payment_apply.receipt_id', 0) + ->get([ + 'fin_payment_apply.id', + 'fin_payment_apply.created_at', + 'fin_payment_apply.doc_user_id', + 'fin_payment_apply.payment_entity', + 'fin_payment_apply.payment_account', + 'fin_payment_apply.currency_id', + 'sys_currency.currency_name', + 'fin_payment_apply.amount', + 'fin_payment_apply.apply_no' + ]); + foreach ($list as $key => &$val) { + $val->created_at = date('Y-m-d H:i:s', $val->created_at); + } + unset($val); + } else { + $twentyFourHoursAgo = time() - (24 * 3600); + $list = Db::table('fin_payment_apply') + ->join('user', 'fin_payment_apply.doc_user_id', '=', 'user.id') + ->join('sys_currency', 'fin_payment_apply.currency_id', '=', 'sys_currency.id') + ->where('fin_payment_apply.doc_user_id', $request->user->uid) + ->where('fin_payment_apply.submit_files', '') + ->where('fin_payment_apply.receipt_id', '>', 0) + ->where('fin_payment_apply.claimed_at', '<', $twentyFourHoursAgo) + ->get([ + 'fin_payment_apply.id', + 'fin_payment_apply.created_at', + 'fin_payment_apply.doc_user_id', + 'user.name', + 'fin_payment_apply.payment_entity', + 'fin_payment_apply.payment_account', + 'fin_payment_apply.currency_id', + 'sys_currency.currency_name', + 'fin_payment_apply.amount', + 'fin_payment_apply.apply_no' + ]); + + foreach ($list as $key => &$val) { + $stockIds = Db::table('fin_payment_stock') + ->where('payment_apply_id', $val->id) + ->pluck('stock_id') + ->toArray(); + $val->order_no = Db::table('stock_orders') + ->whereIn('id', $stockIds) + ->pluck('order_no') + ->toArray(); + $val->created_at = date('Y-m-d H:i:s', $val->created_at); + } + unset($val); + } + return $this->success($list); + } +} \ No newline at end of file diff --git a/app/admin/logic/SysConfigLogic.php b/app/admin/logic/SysConfigLogic.php new file mode 100644 index 0000000..60be4a8 --- /dev/null +++ b/app/admin/logic/SysConfigLogic.php @@ -0,0 +1,17 @@ +validator; + } + + /** + * 构建查询条件 + */ + protected function buildQueryConfig(Request $request, string $scene = 'default'): array + { + $p = $request->all(); + + $config = []; + $config['model'] = $this->modelClass; + switch ($scene) { + case 'index': + // ====== ====== 排序 ====== ====== + $config['order']['id'] = 'desc'; + + // ====== ====== 筛选 ====== ====== + // 状态 + if (isset($p['status']) && in_array($p['status'], [0,1])) { + $config['where']['status'] = $p['status']; + } + // 关键字 + if (isset($p['keyword']) && $p['keyword']) { + $k = '%' . $p['keyword'] . '%'; + foreach (['config_key','config_value','description'] as $col) { + $config['orWhere'][] = [$col, 'like', $k]; + } + } + break; + case 'show': + $config['where']['id'] = $p['id']; + //$config['where']['status'] = 1; + break; + case 'store': + break; + case 'update': + $config['where']['id'] = $p['id']; + break; + case 'destroy': + $config['where']['id'] = $p['id']; + break; + } + + return $config; + } + + + public function beforeServiceUpdate($data, Request $request) + { + Redis::del($data['config_key']); + } +} \ No newline at end of file diff --git a/app/admin/service/UserService.php b/app/admin/service/UserService.php index 7f2fadd..9d850a5 100644 --- a/app/admin/service/UserService.php +++ b/app/admin/service/UserService.php @@ -100,6 +100,8 @@ class UserService $config['with']['roles'][] = 'name'; $config['with']['systems'][] = 'id'; $config['with']['systems'][] = 'name'; + $config['with']['reportToInfo'][] = 'id'; + $config['with']['reportToInfo'][] = 'name'; break; case 'show': $config['where']['id'] = $p['id']; diff --git a/app/admin/validate/SysConfigValidate.php b/app/admin/validate/SysConfigValidate.php new file mode 100644 index 0000000..2bb978a --- /dev/null +++ b/app/admin/validate/SysConfigValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/common/controller/FinPaymentApplyController.php b/app/common/controller/FinPaymentApplyController.php new file mode 100644 index 0000000..0939646 --- /dev/null +++ b/app/common/controller/FinPaymentApplyController.php @@ -0,0 +1,160 @@ +service = new FinPaymentApplyService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + protected function indexFormat($data) + { + if (isset($data['doc_user_id'])) //内勤 + $data['doc_user_name'] = User::where('id', $data['doc_user_id'])->value('name')??''; + if (isset($data['sales_user_id'])) //业务员 + $data['sales_user_name'] = User::where('id', $data['doc_user_id'])->value('name')??''; + if (isset($data['corp_id'])) //公司(渠道) + $data['corp_name'] = SysDictItem::where('id', $data['corp_id'])->value('item_label')??''; + if (isset($data['account_id'])){ //收款账号 + $data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??''; + $data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??''; + } + if (isset($data['country_id'])) //付款来源国家 + $data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??''; + if (isset($data['currency_id'])) //付款币种 + $data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??''; + $OssService = new OssService(); + if (!empty($data['submit_files'])){ + $data['submit_files'] = json_decode($data['submit_files'], true); + foreach ($data['submit_files'] as $key => $value) { + $data['submit_files'][$key] = [ + 'file_src' => $value['file_src'], + 'file_full' => $OssService->generatePrivateUrl($value['file_src']), + 'file_name' => $value['file_name'], + ]; + } + }else{ + $data['submit_files'] = []; + } + + if (!empty($data['voucher_file'])){ + $data['voucher_file'] = json_decode($data['voucher_file'], true); + foreach ($data['voucher_file'] as $key => $value) { + $data['voucher_file'][$key] = [ + 'file_src' => $value['file_src'], + 'file_full' => $OssService->generatePrivateUrl($value['file_src']), + 'file_name' => $value['file_name'], + ]; + } + }else{ + $data['voucher_file'] = []; + } + + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + if (isset($data['claimed_at'])){ + $data['enter_file_last'] = date('Y-m-d H:i:s', ($data['claimed_at']+86400)); //入账资料最后提交时间 + $data['claimed_at'] = date('Y-m-d H:i:s', $data['claimed_at']); //认领时间 + } + if (isset($data['id'])){ + $finPaymentStock = FinPaymentStock::where('payment_apply_id', $data['id'])->get(); + $stock = []; + foreach ($finPaymentStock as $v) { + $stock[$v['pi_no']]['pi_no'] = $v['pi_no']; + $stock[$v['pi_no']]['stock_id'][] = [$v['stock_id']]; + } + $data['stock'] = array_values($stock); + } + + return $data; + } + + /** + * 数据格式化 + * + * @param array $data + * @return array + */ + public function showFormat($data) + { + if (isset($data['doc_user_id'])) //内勤 + $data['doc_user_name'] = User::where('id', $data['doc_user_id'])->value('name')??''; + if (isset($data['sales_user_id'])) //业务员 + $data['sales_user_name'] = User::where('id', $data['doc_user_id'])->value('name')??''; + if (isset($data['corp_id'])) //公司(渠道) + $data['corp_name'] = SysDictItem::where('id', $data['corp_id'])->value('item_label')??''; + if (isset($data['account_id'])){ //收款账号 + $data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??''; + $data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??''; + } + if (isset($data['country_id'])) //付款来源国家 + $data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??''; + if (isset($data['currency_id'])) //付款币种 + $data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??''; + $OssService = new OssService(); + if (!empty($data['submit_files'])){ + $data['submit_files'] = json_decode($data['submit_files'], true); + foreach ($data['submit_files'] as $key => $value) { + $data['submit_files'][$key] = [ + 'file_src' => $value['file_src'], + 'file_full' => $OssService->generatePrivateUrl($value['file_src']), + 'file_name' => $value['file_name'], + ]; + } + }else{ + $data['submit_files'] = []; + } + + if (!empty($data['voucher_file'])){ + $data['voucher_file'] = json_decode($data['voucher_file'], true); + foreach ($data['voucher_file'] as $key => $value) { + $data['voucher_file'][$key] = [ + 'file_src' => $value['file_src'], + 'file_full' => $OssService->generatePrivateUrl($value['file_src']), + 'file_name' => $value['file_name'], + ]; + } + }else{ + $data['voucher_file'] = []; + } + + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + + $finPaymentStock = FinPaymentStock::where('payment_apply_id', $data['id'])->get(); + $stock = []; + foreach ($finPaymentStock as $v) { + $stock[$v['pi_no']]['pi_no'] = $v['pi_no']; + $stock[$v['pi_no']]['stock_id'][] = [$v['stock_id']]; + } + $data['stock'] = array_values($stock); + return $data; + } +} \ No newline at end of file diff --git a/app/common/controller/FinReceiptController.php b/app/common/controller/FinReceiptController.php new file mode 100644 index 0000000..cade259 --- /dev/null +++ b/app/common/controller/FinReceiptController.php @@ -0,0 +1,184 @@ +service = new FinReceiptService(); + } + + /** + * 确认入账 + * @param Request $request + * @return \support\Response + */ + public function credit(Request $request) + { + try { + $id = $request->input('id'); + $raw = $this->service->credit($id); + return $this->success($raw); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function show(Request $request) + { + try { + $raw = $this->service->serviceShow($request); + $data = $this->transformData($raw, method_exists($this, 'showFormat') + ? [$this, 'showFormat'] + : null); + return $this->success($data); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 更新 + * + * @param Request $request 请求对象 + * @return mixed 返回更新成功的数据或信息 + */ + public function payment(Request $request) + { + try { + $data = $this->validate($this->service->getValidator(), $request->post(), 'update'); + $res = $this->service->serviceUpdatePayment($request, $data); + return $this->success([], '更新成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + protected function indexFormat($data) + { + if (isset($data['created_at'])){ + $data['claim_timeout'] = (($data['created_at'] < (time()-86400)) && empty($data['payment_apply_id'])) ?1:0; + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + } + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + if (isset($data['arrived_at'])) + $data['arrived_at'] = date('Y-m-d H:i:s', $data['arrived_at']); + if (isset($data['received_at'])) + $data['received_at'] = date('Y-m-d H:i:s', $data['received_at']); + if (isset($data['account_id'])) { //收款账号 + $data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??''; + $data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??''; + } + if (isset($data['country_id'])) //付款来源国家 + $data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??''; + if (isset($data['currency_id'])) //付款币种 + $data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??''; + + $OssService = new OssService(); + if (!empty($data['fx_receipt_file'])){ + $data['fx_receipt_file'] = json_decode($data['fx_receipt_file'], true); + foreach ($data['fx_receipt_file'] as $key => $value) { + $data['fx_receipt_file'][$key] = [ + 'file_src' => $value['file_src'], + 'file_full' => $OssService->generatePrivateUrl($value['file_src']), + 'file_name' => $value['file_name'], + ]; + } + }else{ + $data['fx_receipt_file'] = []; + } + return $data; + } + + /** + * 数据格式化 + * + * @param array $data + * @return array + */ + protected function showFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + if (isset($data['arrived_at'])) + $data['arrived_at'] = date('Y-m-d H:i:s', $data['arrived_at']); + if (isset($data['received_at'])) + $data['received_at'] = date('Y-m-d H:i:s', $data['received_at']); + if (isset($data['account_id'])) { //收款账号 + $data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??''; + $data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??''; + } + if (isset($data['country_id'])) //付款来源国家 + $data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??''; + if (isset($data['currency_id'])) //付款币种 + $data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??''; + + $OssService = new OssService(); + if (!empty($data['fx_receipt_file'])){ + $data['fx_receipt_file'] = json_decode($data['fx_receipt_file'], true); + foreach ($data['fx_receipt_file'] as $key => $value) { + $data['fx_receipt_file'][$key] = [ + 'file_src' => $value['file_src'], + 'file_full' => $OssService->generatePrivateUrl($value['file_src']), + 'file_name' => $value['file_name'], + ]; + } + }else{ + $data['fx_receipt_file'] = []; + } + + //查询关联的付款记录 + if (!empty($data['payment_apply_id'])) { + $paymentApply = FinPaymentApply::where('id', $data['payment_apply_id'])->first(); + if(!$paymentApply) + return $data; + $paymentApply = $paymentApply->toArray(); + $paymentApply['created_at'] = strtotime($paymentApply['created_at']); + $paymentApply['updated_at'] = strtotime($paymentApply['updated_at']); + $paymentApply = (new FinPaymentApplyController())->showFormat($paymentApply); + $data['payment_apply'] = $paymentApply; + } + return $data; + } + + /** + * 收款记录统计 + * @param Request $request + * @return \support\Response + */ + public function statistics(Request $request) + { + try { + $raw = $this->service->statistics(); + return $this->success($raw); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/common/controller/FinReceiptHisController.php b/app/common/controller/FinReceiptHisController.php new file mode 100644 index 0000000..7adcf0d --- /dev/null +++ b/app/common/controller/FinReceiptHisController.php @@ -0,0 +1,98 @@ +service = new FinReceiptHisService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + protected function indexFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + if (isset($data['arrived_at'])) + $data['arrived_at'] = date('Y-m-d H:i:s', $data['arrived_at']); + if (isset($data['received_at'])) + $data['received_at'] = date('Y-m-d H:i:s', $data['received_at']); + if (isset($data['account_id'])) { //收款账号 + $data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??''; + $data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??''; + } + if (isset($data['country_id'])) //付款来源国家 + $data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??''; + if (isset($data['currency_id'])) //付款币种 + $data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??''; + if (!empty($data['fx_receipt_file'])){ + $data['fx_receipt_file_full'] = (new OssService)->generatePrivateUrl($data['fx_receipt_file']); + }elseif (isset($data['fx_receipt_file'])){ + $data['fx_receipt_file_full'] = ''; + } + + if (isset($data['created_by'])) + $data['created_by'] = User::where('id', $data['created_by'])->value('name')??''; + if (isset($data['updated_by'])) + $data['updated_by'] = User::where('id', $data['updated_by'])->value('name')??''; + return $data; + } + + /** + * 数据格式化 + * + * @param array $data + * @return array + */ + protected function showFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + if (isset($data['arrived_at'])) + $data['arrived_at'] = date('Y-m-d H:i:s', $data['arrived_at']); + if (isset($data['received_at'])) + $data['received_at'] = date('Y-m-d H:i:s', $data['received_at']); + if (isset($data['account_id'])){ //收款账号 + $data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??''; + $data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??''; + } + if (isset($data['country_id'])) //付款来源国家 + $data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??''; + if (isset($data['currency_id'])) //付款币种 + $data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??''; + if (!empty($data['fx_receipt_file'])){ + $data['fx_receipt_file_full'] = (new OssService)->generatePrivateUrl($data['fx_receipt_file']); + }elseif (isset($data['fx_receipt_file'])){ + $data['fx_receipt_file_full'] = ''; + } + + if (isset($data['created_by'])) + $data['created_by'] = User::where('id', $data['created_by'])->value('name')??''; + if (isset($data['updated_by'])) + $data['updated_by'] = User::where('id', $data['updated_by'])->value('name')??''; + return $data; + } +} \ No newline at end of file diff --git a/app/common/logic/AuthLogic.php b/app/common/logic/AuthLogic.php index ee449b9..9abe5e1 100644 --- a/app/common/logic/AuthLogic.php +++ b/app/common/logic/AuthLogic.php @@ -172,7 +172,7 @@ class AuthLogic $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); - $homeUrl = $this->getHome($system->id); + $homeUrl = $this->getHome($system->id, $payload); // 记录 token 到 Redis 和 MySQL $this->recordUserToken($user->id, $token, $data['system_code']); @@ -537,8 +537,22 @@ class AuthLogic } - public function getHome($systemId) + public function getHome($systemId, $data) { + // 2025.10.30 临时处理 ====== start + if ($systemId == 1) { + if (in_array(1, $data['role_ids'])) { + return '/dashboard/console'; + } + if (count($data['role_ids']) == 1 && in_array(5, $data['role_ids'])) { + return '/dashboard/console/backoffice'; + } + if (count($data['role_ids']) == 1 && in_array(6, $data['role_ids'])) { + return '/dashboard/console/finance'; + } + } + // 2025.10.30 临时处理 ====== end + $info = Resource::where('system_id', $systemId) ->where('is_home', 1) ->first(); diff --git a/app/common/logic/FinPaymentApplyLogic.php b/app/common/logic/FinPaymentApplyLogic.php new file mode 100644 index 0000000..09b93e1 --- /dev/null +++ b/app/common/logic/FinPaymentApplyLogic.php @@ -0,0 +1,17 @@ +update(['received_at' => time()]); + } +} \ No newline at end of file diff --git a/app/common/logic/SysConfigLogic.php b/app/common/logic/SysConfigLogic.php new file mode 100644 index 0000000..633d177 --- /dev/null +++ b/app/common/logic/SysConfigLogic.php @@ -0,0 +1,22 @@ +where('config_key', $key)->get(['id', 'config_key', 'config_value']); + } +} \ No newline at end of file diff --git a/app/common/model/FinPaymentApply.php b/app/common/model/FinPaymentApply.php new file mode 100644 index 0000000..0f4d157 --- /dev/null +++ b/app/common/model/FinPaymentApply.php @@ -0,0 +1,65 @@ +hasOne(FinReceipt::class, 'payment_apply_id', 'id'); + } + + /** + * 关联的备货单(通过中间表) + */ + public function stocks() + { + return $this->belongsToMany(StockOrders::class, 'fin_payment_stock', 'payment_apply_id', 'stock_id'); + } +} \ No newline at end of file diff --git a/app/common/model/FinPaymentStock.php b/app/common/model/FinPaymentStock.php new file mode 100644 index 0000000..d5b3d10 --- /dev/null +++ b/app/common/model/FinPaymentStock.php @@ -0,0 +1,67 @@ +belongsTo(FinPaymentApply::class, 'payment_apply_id', 'id'); + } + + // 收款记录 + public function receipt() + { + return $this->belongsTo(FinReceipt::class, 'receipt_id', 'id'); + } + + // 备货单 + public function stock() + { + return $this->belongsTo(StockOrders::class, 'stock_id', 'id'); + } +} \ No newline at end of file diff --git a/app/common/model/FinReceipt.php b/app/common/model/FinReceipt.php new file mode 100644 index 0000000..7347be0 --- /dev/null +++ b/app/common/model/FinReceipt.php @@ -0,0 +1,65 @@ +belongsTo(FinPaymentApply::class, 'payment_apply_id', 'id'); + } + + /** + * 关联的备货单(通过中间表) + */ + public function stocks() + { + return $this->belongsToMany(StockOrders::class, 'fin_payment_stock', 'receipt_id', 'stock_id'); + } +} \ No newline at end of file diff --git a/app/common/model/FinReceiptHis.php b/app/common/model/FinReceiptHis.php new file mode 100644 index 0000000..8a036b3 --- /dev/null +++ b/app/common/model/FinReceiptHis.php @@ -0,0 +1,49 @@ +hasOne(User::class, 'id', 'salesperson_id'); } + + /** + * 客户的标签 + */ + public function payment() + { + return $this->belongsToMany(FinPaymentApply::class, 'fin_payment_stock', 'stock_id', 'payment_apply_id'); + } + + /** + * 关联收款记录(多对多,经由中间表) + */ + public function receipts() + { + return $this->belongsToMany(FinReceipt::class, 'fin_payment_stock', 'stock_id', 'receipt_id'); + } } \ No newline at end of file diff --git a/app/common/model/StockOrdersFiles.php b/app/common/model/StockOrdersFiles.php index 98daa3f..3b78e5c 100644 --- a/app/common/model/StockOrdersFiles.php +++ b/app/common/model/StockOrdersFiles.php @@ -40,7 +40,7 @@ class StockOrdersFiles extends Model /** * 允许自动填充模型字段 */ - protected $fillable = ['id', 'order_id', 'file_name', 'file_code', 'file_url', 'created_at', 'updated_at']; + protected $fillable = ['id', 'order_id', 'file_name', 'file_code', 'file_url', 'original_name', 'created_at', 'updated_at']; /** * 不允许自动填充模型字段 diff --git a/app/common/model/SysConfig.php b/app/common/model/SysConfig.php new file mode 100644 index 0000000..29b4577 --- /dev/null +++ b/app/common/model/SysConfig.php @@ -0,0 +1,49 @@ +validator; + } + + /** + * 构建查询条件 + */ + protected function buildQueryConfig(Request $request, string $scene = 'index'): array + { + $p = $request->all(); + + $config = []; + $config['model'] = $this->modelClass; + switch ($scene) { + case 'index': + // ====== ====== 排序 ====== ====== + $config['order']['id'] = 'desc'; + + // ====== ====== 筛选 ====== ====== + // 汇款国家 + if (isset($p['country_id']) && !empty($p['country_id'])) { + $config['where']['country_id'] = $p['country_id']; + } + + // 收款账户筛选 + if (isset($p['account_id']) && !empty($p['account_id'])) { + $config['where']['account_id'] = $p['account_id']; + } + + // 入账资料超时 + if (isset($p['scene']) && $p['scene'] == 'file_timeout') { + $config['where']['claimed_at'] = ['between', [1, time()-24*60*60]]; + } + + // 关键字 + if (isset($p['keyword']) && $p['keyword']) { + $k = '%' . $p['keyword'] . '%'; + $config['orWhere'][] = ['remitter', 'like', $k]; + } + break; + case 'show': + $config['where']['id'] = $p['id']; + break; + case 'update': + case 'destroy': + $config['where']['id'] = $p['id']; + break; + case 'store': + break; + } + + return $config; + } + + public function beforeServiceStore($data, Request $request) + { + $data['apply_no'] = $this->generateUniqueApplyNo(); + $data['doc_user_id'] = $request->user->uid; + $data['voucher_file'] = json_encode($data['voucher_file']); + $data['submit_files'] = json_encode($data['submit_files']); + redis::set('stock_' . $data['doc_user_id'], json_encode($request->post('stock')), 'EX', 30); + unset($data['stock']); + if($data['voucher_file'] == '[]' || $data['voucher_file'] == '{}' || $data['voucher_file'] == '""') + $data['voucher_file'] = ''; + if($data['submit_files'] == '[]' || $data['submit_files'] == '{}' || $data['submit_files'] == '""') + $data['submit_files'] = ''; + return $data; + } + + + public function afterServiceStore($res) + { + $data = [ + 'payment_apply_id' => $res['id'], + ]; + $piStock = json_decode(redis::get('stock_' . $res['doc_user_id']), true); + redis::del('stock' . $res['doc_user_id']); + foreach ($piStock as $stock) { + foreach ($stock['stock_id'] as $v) { + $data['stock_id'] = $v; + $data['pi_no'] = $stock['pi_on']; + FinPaymentStock::create($data); + } + } + } + + public function beforeServiceUpdate($data, Request $request) + { + unset($data['stock']); + $data['submit_files'] = json_encode($data['submit_files']); + if($data['submit_files'] == '[]' || $data['submit_files'] == '{}' || $data['submit_files'] == '""') + $data['submit_files'] = ''; + $data['voucher_file'] = json_encode($data['voucher_file']); + if($data['voucher_file'] == '[]' || $data['voucher_file'] == '{}' || $data['voucher_file'] == '""') + $data['voucher_file'] = ''; + return $data; + } + + /** + * 生成唯一的付款申请编码 + * 格式:PA + 日期 + 6位字母数字组合 + * + * @return string 唯一编码 + */ + public function generateUniqueApplyNo() + { + $chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $maxAttempts = 10; // 防止无限循环 + $secondChar = 'PA' . date('Ymd'); + + for ($attempt = 0; $attempt < $maxAttempts; $attempt++) { + // 生成后6位随机字符 + $suffix = ''; + for ($i = 0; $i < 6; $i++) { + $suffix .= $chars[rand(0, strlen($chars) - 1)]; + } + + $applyNo = $secondChar . $suffix; + + // 检查是否已存在 + $existing = FinPaymentApply::where('apply_no', $applyNo)->first(); + if (!$existing) { + return $applyNo; + } + } + + // 如果1000次尝试后仍然重复,使用时间戳确保唯一性 + $timestamp = substr(time(), -6); + return $secondChar . $timestamp; + } + + /** + * 根据相似度排序付款记录 + * @param Request $request + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|\Illuminate\Pagination\LengthAwarePaginator + */ + public function complexQueryIndex(Request $request) + { + $limit = (int)$request->get('size', 15); + $page = (int)$request->get('current', 1); + $param = $request->all(); + + $where = []; + $orWhere = []; + + // 汇款国家 + if (isset($param['country_id']) && !empty($param['country_id'])) { + $where[] = ['country_id', $param['country_id']]; + } + // 收款账户筛选 + if (isset($param['account_id']) && !empty($param['account_id'])) { + $where[] = ['account_id', $param['account_id']]; + } + + // 入账资料超时 + if (isset($param['scene']) && $param['scene'] == 'file_timeout') { + $where[] = ['submit_files', '']; + $where[] = ['claimed_at', '<', time()-24*60*60]; + $where[] = ['claimed_at', '>', 1]; + } + + // 关键字 + if (isset($param['keyword']) && $param['keyword']) { + $k = '%' . $param['keyword'] . '%'; + $where[] = ['remitter', 'like', $k]; + } + + // 构建查询 + $query = $this->modelClass::where($where); + + // 是否已关联收款记录的筛选 + $correlation = $param['correlation']??''; + if ($correlation === 'receipt') { //已关联 + $query->whereHas('receipt'); + } elseif ($correlation === 'unbind') { //未关联 + $query->whereDoesntHave('receipt'); + } + + if ((isset($param['scene']) && $param['scene'] == 'list') || empty($param['receipt_id'])) { + return $query->orderBy('id','desc')->paginate($limit, ['*'], 'page', $page); + } + + $receipt = FinReceipt::where('id', $param['receipt_id'])->first(); + $account_id = $receipt->account_id; + $currency_id = $receipt->currency_id; + $remitter = $receipt->remitter; + $amount = $receipt->amount; + + // 计算十天前的时间戳 + $sevenDaysAgo = time() - (10 * 24 * 60 * 60); + + // 构建相似度排序逻辑 + $similarityOrder = []; + + // 如果有传入参数,计算相似度 + if (!empty($account_id)) { + $similarityOrder[] = "CASE WHEN account_id = {$account_id} THEN 0 ELSE 1 END"; + } + + if (!empty($currency_id)) { + $similarityOrder[] = "CASE WHEN currency_id = {$currency_id} THEN 0 ELSE 1 END"; + } + + if (!empty($remitter)) { + $similarityOrder[] = "CASE WHEN remitter = '{$remitter}' THEN 0 ELSE 1 END"; + } + + if (!empty($amount)) { + $amount = (float)$amount; + $similarityOrder[] = "ABS(amount - {$amount})"; + $similarityOrder[] = "CASE WHEN (ABS(amount - {$amount}) / {$amount} * 100) <= 5 THEN 0 ELSE 1 END"; + } + + // 构建排序SQL + $orderByClause = ""; + if (!empty($similarityOrder)) { + $orderByClause = "CASE + WHEN created_at >= {$sevenDaysAgo} THEN + (" . implode(' + ', $similarityOrder) . ") + ELSE 0 + END ASC, "; + } + + $orderByClause .= "CASE + WHEN created_at > {$sevenDaysAgo} THEN id + ELSE 999999 + END DESC, "; + + $orderByClause .= "id DESC"; + + $query->orderByRaw($orderByClause); + + return $query->paginate($limit, ['*'], 'page', $page); + } + +} \ No newline at end of file diff --git a/app/common/service/FinReceiptHisService.php b/app/common/service/FinReceiptHisService.php new file mode 100644 index 0000000..f43f77b --- /dev/null +++ b/app/common/service/FinReceiptHisService.php @@ -0,0 +1,62 @@ +validator; + } + + /** + * 构建查询条件 + */ + protected function buildQueryConfig(Request $request, string $scene = 'index'): array + { + $p = $request->all(); + + $config = []; + $config['model'] = $this->modelClass; + switch ($scene) { + case 'index': + if(empty($p['receipt_id'])){ + throw new BusinessException('receipt_id不能为空'); + } + // ====== ====== 排序 ====== ====== + $config['order']['id'] = 'desc'; + + // ====== ====== 筛选 ====== ====== + $config['where']['receipt_id'] = $p['receipt_id']; + + break; + case 'show': + $config['where']['id'] = $p['id']; + break; + } + + return $config; + } +} \ No newline at end of file diff --git a/app/common/service/FinReceiptService.php b/app/common/service/FinReceiptService.php new file mode 100644 index 0000000..6073cdf --- /dev/null +++ b/app/common/service/FinReceiptService.php @@ -0,0 +1,315 @@ +logic = (new FinReceiptLogic()); + } + + /** + * 获取验证器 + */ + public function getValidator() + { + return new $this->validator; + } + + + /** + * 构建查询条件 + */ + protected function buildQueryConfig(Request $request, string $scene = 'index'): array + { + $p = $request->all(); + + $config = []; + $config['model'] = $this->modelClass; + switch ($scene) { + case 'index': + // ====== ====== 排序 ====== ====== + $config['order']['id'] = 'desc'; + + // ====== ====== 筛选 ====== ====== + // 汇款国家 + if (isset($p['country_id']) && !empty($p['country_id'])) { + $config['where']['country_id'] = $p['country_id']; + } + + // 没有付款记录的收款记录 + if (isset($p['payment_apply']) && $p['payment_apply'] == 'not') { //未认领 + $config['where']['payment_apply_id'] = 0; + } elseif (isset($p['payment_apply']) && $p['payment_apply'] == 'already') { //已认领 + $config['where']['payment_apply_id'] = ['>', 0]; + } elseif (isset($p['payment_apply']) && $p['payment_apply'] == 'timeout') { //超时未认领 + $config['where']['payment_apply_id'] = 0; + $config['where']['created_at'] = ['<' ,time() - 24*60*60]; + } + + // 收款申请筛选 + if (isset($p['payment_apply_id']) && !empty($p['payment_apply_id'])) { + $config['where']['payment_apply_id'] = $p['payment_apply_id']; + } + + // 收款账户筛选 + if (isset($p['account_id']) && !empty($p['account_id'])) { + $config['where']['account_id'] = $p['account_id']; + } + + // 关键字 + if (isset($p['keyword']) && $p['keyword']) { + $k = '%' . $p['keyword'] . '%'; + $config['orWhere'][] = ['remitter', 'like', $k]; + } + break; + case 'show': + $config['where']['id'] = $p['id']; + break; + case 'update': + case 'payment': + case 'destroy': + $config['where']['id'] = $p['id']; + break; + case 'store': + break; + } + + return $config; + } + + public function beforeServiceStore($data, Request $request) + { + if(isset($data['payment_apply_id']) && empty($data['payment_apply_id'])){ + unset($data['payment_apply_id']); + } + $data['receipt_no'] = $this->generateUniqueReceipt(); + $data['arrived_at'] = strtotime( $data['arrived_at']); + $data['created_by'] = $request->user->uid; + $data['updated_by'] = $request->user->uid; + $data['fx_receipt_file'] = json_encode($data['fx_receipt_file']); + if($data['fx_receipt_file'] == '[]' || $data['fx_receipt_file'] == '{}' || $data['fx_receipt_file'] == '""') + $data['fx_receipt_file'] = ''; + return $data; + } + + public function afterServiceStore($res) + { + $receipt = FinReceipt::where('id',$res->id)->first()->toArray(); + $receipt = array_merge($receipt,['receipt_id' => $res->id]); + unset($receipt['id']); + FinReceiptHis::create($receipt); + } + + public function beforeServiceUpdate($data, Request $request) + { + $data['updated_by'] = $request->user->uid; + if(isset($data['arrived_at'])) + $data['arrived_at'] = strtotime($data['arrived_at']); + if(isset($data['received_at'])) + $data['received_at'] = strtotime($data['received_at']); + + $data['fx_receipt_file'] = json_encode($data['fx_receipt_file']); + if($data['fx_receipt_file'] == '[]' || $data['fx_receipt_file'] == '{}' || $data['fx_receipt_file'] == '""') + $data['fx_receipt_file'] = ''; + return $data; + } + public function afterServiceUpdate($res) + { + $receipt = FinReceipt::where('id',$res['id'])->first()->toArray(); + $receipt = array_merge($receipt,['receipt_id' => $res['id']]); + unset($receipt['id']); + FinReceiptHis::create($receipt); + } + + + /** + * 生成唯一的付款申请编码 + * 格式:PA + 日期 + 6位字母数字组合 + * + * @return string 唯一编码 + */ + public function generateUniqueReceipt() + { + $chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $maxAttempts = 10; // 防止无限循环 + $secondChar = 'RE' . date('Ymd'); + + for ($attempt = 0; $attempt < $maxAttempts; $attempt++) { + // 生成后6位随机字符 + $suffix = ''; + for ($i = 0; $i < 6; $i++) { + $suffix .= $chars[rand(0, strlen($chars) - 1)]; + } + + $ReceiptNo = $secondChar . $suffix; + + // 检查是否已存在 + $existing = FinReceipt::where('receipt_no', $ReceiptNo)->first(); + if (!$existing) { + return $ReceiptNo; + } + } + + // 如果1000次尝试后仍然重复,使用时间戳确保唯一性 + $timestamp = substr(time(), -6); + return $secondChar . $timestamp; + } + + /** + * 根据相似度排序付款记录 + * @param Request $request + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|\Illuminate\Pagination\LengthAwarePaginator + */ + public function complexQueryIndex(Request $request) + { + $limit = (int)$request->get('size', 15); + $page = (int)$request->get('current', 1); + $param = $request->all(); + + $where = []; + + // 汇款国家 + if (isset($param['country_id']) && !empty($param['country_id'])) { + $where[] = ['country_id', $param['country_id']]; + } + // 收款账户筛选 + if (isset($param['account_id']) && !empty($param['account_id'])) { + $where[] = ['account_id', $param['account_id']]; + } + // 收款申请筛选 + if (isset($param['payment_apply_id']) && !empty($param['payment_apply_id'])) { + $where[]['payment_apply_id'] = $param['payment_apply_id']; + } + + // 关键字 + if (isset($param['keyword']) && $param['keyword']) { + $k = '%' . $param['keyword'] . '%'; + $where[] = ['remitter', 'like', $k]; + } + + // 构建查询 + $query = $this->modelClass::where($where); + + // 没有付款记录的收款记录 + if (isset($param['payment_apply']) && $param['payment_apply'] == 'not') { + $query->where('payment_apply_id', 0); + } elseif (isset($param['payment_apply']) && $param['payment_apply'] == 'already') { + $query->where('payment_apply_id', '>', 0); + } elseif (isset($param['payment_apply']) && $param['payment_apply'] == 'timeout') { //超时未认领 + $query->where('payment_apply_id', 0); + $query->where('created_at', '<' ,time() - 24*60*60); + } + + if (isset($param['scene']) && $param['scene'] == 'list') { + return $query->paginate($limit, ['*'], 'page', $page); + } + + // 计算七天前的时间戳 + $sevenDaysAgo = time() - (10 * 24 * 60 * 60); + + // 构建相似度排序逻辑 + $similarityOrder = []; + + // 如果有传入参数,计算相似度 + if (isset($param['account_id']) && !empty($param['account_id'])) { + $similarityOrder[] = "CASE WHEN account_id = {$param['account_id']} THEN 0 ELSE 1 END"; + } + + if (isset($param['country_id']) && !empty($param['country_id'])) { + $similarityOrder[] = "CASE WHEN country_id = {$param['country_id']} THEN 0 ELSE 1 END"; + } + + if (isset($param['keyword']) && $param['keyword']) { + $keyword = addslashes($param['keyword']); + $similarityOrder[] = "CASE WHEN remitter LIKE '%{$keyword}%' THEN 0 ELSE 1 END"; + } + + if (isset($param['amount']) && !empty($param['amount'])) { + $amount = (float)$param['amount']; + $similarityOrder[] = "ABS(amount - {$amount})"; + } + + // 构建排序SQL + $orderByClause = ""; + if (!empty($similarityOrder)) { + $orderByClause = "CASE + WHEN created_at >= {$sevenDaysAgo} THEN + (" . implode(' + ', $similarityOrder) . ") + ELSE 999999 + END ASC, "; + } + + $orderByClause .= "CASE + WHEN created_at < {$sevenDaysAgo} THEN id + ELSE 0 + END DESC"; + + $query->orderByRaw($orderByClause); + + return $query->paginate($limit, ['*'], 'page', $page); + } + + public function credit($id) + { + $finReceipt = FinReceipt::find($id); + if(!empty($finReceipt->received_at)){ + throw new BusinessException('该记录已将确认入账'); + } + $res = $this->logic->credit($id); + $this->afterServiceUpdate(['id'=>$id]); + return $res; + } + + /** + * 更新:绑定收款记录,计算折让金额 + * @param Request $request + * @param array $data + * @param string $scene + * @return mixed + */ + public function serviceUpdatePayment(Request $request, array $data = [], string $scene = 'payment') + { + $payment_apply_id = $request->input('payment_apply_id'); + $id = $request->input('id'); + $data = $this->preProcess(ucfirst($scene), $data, $request); + $config = $this->buildQueryConfig($request, $scene); + FinPaymentApply::where('id', $payment_apply_id)->update(['receipt_id' => $id, 'claimed_at' => time(), 'claimed_by' => $request->user->uid]); + FinPaymentStock::where('payment_apply_id', $payment_apply_id)->update(['receipt_id' => $id]); + + //折让金额 + $payment = FinPaymentApply::where('id', $payment_apply_id)->first(); + $receipt = FinReceipt::find($id); + $data['discount_amount'] = $payment->amount - $receipt->amount ?? 0; + + $updated = $this->logicUpdateBy($config, $data); + return $this->postProcess(ucfirst($scene), $data); + } +} \ No newline at end of file diff --git a/app/common/service/StockOrdersService.php b/app/common/service/StockOrdersService.php index 76d2ab5..d4187a1 100644 --- a/app/common/service/StockOrdersService.php +++ b/app/common/service/StockOrdersService.php @@ -161,7 +161,8 @@ class StockOrdersService { // 启动审批 $appro = new \app\common\utils\approval\service\ApprovalFlowService(); - $appro->start('stock_orders', 'stock_orders', $item->id, [], $item->salesperson_id); + $formData = $this->checkNewOrUsedVehicles($item->id); + $appro->start('stock_orders', 'stock_orders', $item->id, $formData, $item->salesperson_id); // 异步生成pdf文件 \Webman\RedisQueue\Redis::send('stock-order-pdf', ['id'=>$item->id]); } @@ -295,7 +296,8 @@ class StockOrdersService public function approvalNullify($item) { $appro = new \app\common\utils\approval\service\ApprovalFlowService(); - $appro->startNullify('stock_orders_nullify', 'stock_orders', $item->id, [], $item->salesperson_id); + $formData = $this->checkNewOrUsedVehicles($item->id); + $appro->startNullify('stock_orders_nullify', 'stock_orders', $item->id, $formData, $item->salesperson_id); } /** @@ -479,14 +481,19 @@ class StockOrdersService // 检查数据中是否存在该字段且为数组 if (!empty($data[$field]) && is_array($data[$field])) { // 处理该字段下的每个文件 - foreach ($data[$field] as $fileUrl) { + foreach ($data[$field] as $fileInfo) { $fileData = [ 'order_id' => $order->id, 'file_code' => $fileCode, - 'file_url' => $fileUrl, + 'file_url' => $fileInfo['url'] ?? '', 'file_name' => StockOrdersFiles::FILE_CODE[$fileCode], + 'original_name' => $fileInfo['original_name'] ?? StockOrdersFiles::FILE_CODE[$fileCode], ]; - StockOrdersFiles::create($fileData); + + // 只有当file_url不为空时才创建记录 + if (!empty($fileData['file_url'])) { + StockOrdersFiles::create($fileData); + } } } } @@ -905,6 +912,7 @@ class StockOrdersService 'id' => $file->id, 'file_url' => $file->file_url, 'file_full_url' => $this->getFileUrl($file->file_url), + 'original_name' => $file->original_name, ]; } @@ -978,4 +986,42 @@ class StockOrdersService } return (new \app\common\service\OssService)->generatePrivateUrl($fileUrl); } + + /** + * 检查备货单中的车辆是新车还是二手车 + * @param int $stockOrderId 备货单ID + * @return array 返回包含has_new和has_used的数组 + */ + public function checkNewOrUsedVehicles(int $stockOrderId): array + { + // 获取备货单关联的所有车辆项 + $vehicles = StockOrderItems::where('order_id', $stockOrderId)->get(); + + $hasNew = false; + $hasUsed = false; + $submitNote = ''; + + foreach ($vehicles as $vehicle) { + // 判断是否为二手车(mileage > 0) + if ($vehicle->mileage > 0) { + $hasUsed = true; + $submitNote = '本单只有二手车'; + } else { + $hasNew = true; + $submitNote = '本单只有新车'; + } + + // 如果两种类型都已经存在,可以提前退出循环 + if ($hasNew && $hasUsed) { + $submitNote = '本单包含新车和二手车'; + break; + } + } + + return [ + 'has_new' => $hasNew ? 1 : 0, + 'has_used' => $hasUsed ? 1 : 0, + 'submit_note' => $submitNote + ]; + } } \ No newline at end of file diff --git a/app/common/service/SysConfigService.php b/app/common/service/SysConfigService.php new file mode 100644 index 0000000..d52972f --- /dev/null +++ b/app/common/service/SysConfigService.php @@ -0,0 +1,37 @@ +logic = (new SysConfigLogic()); + } + + /** + * 获取系统list + */ + public static function getConfig(string $config_key) + { + $value = Redis::get($config_key); + if (empty($value)) { + $config = (new SysConfigLogic())->findByKey($config_key); + if (empty($config)) { + return null; + } + $value = $config->config_value; + Redis::set($config->config_key, $value, 'EX', 7200); + } + + return $value; + } +} \ No newline at end of file diff --git a/app/common/utils/approval/service/ApprovalFlowService.php b/app/common/utils/approval/service/ApprovalFlowService.php index 6eae8d8..a26b79a 100644 --- a/app/common/utils/approval/service/ApprovalFlowService.php +++ b/app/common/utils/approval/service/ApprovalFlowService.php @@ -68,7 +68,7 @@ class ApprovalFlowService } $this->sendNotification($approverIds, $instance, 'pending', false, $extra); - $this->notifyCCs($firstStep, $instance, 'pending'); + // $this->notifyCCs($firstStep, $instance, 'pending'); $this->pushTodoChanged($approverIds); @@ -193,6 +193,12 @@ class ApprovalFlowService 'updated_at' => $now, ]); $this->onFlowFinalized($instance, $finalStatus); + + // 新增:在流程最终完成时才发本步骤的抄送 + if (!empty($step['cc'])) { + $this->notifyCCs($step, $instance, $notifyAction); + } + $this->sendNotification([$instance->initiator_id], $instance, $notifyAction, false); $this->pushTodoChanged([$approverId]); return ['status' => 'completed', 'msg' => ($finalStatus === 'voided' ? '已作废' : '同意')]; @@ -215,7 +221,7 @@ class ApprovalFlowService $this->insertApprovalRecord($instance, $nextIndex, $uid); } $this->sendNotification($nextApprovers, $instance, 'pending', false); - $this->notifyCCs($nextStep, $instance, 'pending'); + // $this->notifyCCs($nextStep, $instance, 'pending'); $this->pushTodoChanged(array_merge([$approverId], $nextApprovers)); return ['status' => 'next_step', 'msg' => '进行下一步:'.$nextStep['name'] ?? ('Step '.$nextIndex)]; }); diff --git a/app/common/utils/scaffold/templates/paymentFiles.stub b/app/common/utils/scaffold/templates/paymentFiles.stub new file mode 100644 index 0000000..6c9cded --- /dev/null +++ b/app/common/utils/scaffold/templates/paymentFiles.stub @@ -0,0 +1,103 @@ + + + + + + 邮箱提醒 + + + + + + + + + + + + + + + +
+

付款单提醒

+
+

您好,

+

您有新的付款单待处理。请及时查看:

+ +
    +
  • 付款单编号:{{code}}
  • +
  • 付款主体:{{payment_entity}}
  • +
  • 问题描述:{{content}}
  • + {{reply}} +
+ +

请点击下方按钮立即查看详情:

+ + + + +
+ + \ No newline at end of file diff --git a/app/common/validate/FinPaymentApplyValidate.php b/app/common/validate/FinPaymentApplyValidate.php new file mode 100644 index 0000000..58145e3 --- /dev/null +++ b/app/common/validate/FinPaymentApplyValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/common/validate/FinReceiptHisValidate.php b/app/common/validate/FinReceiptHisValidate.php new file mode 100644 index 0000000..7a1f7c4 --- /dev/null +++ b/app/common/validate/FinReceiptHisValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/common/validate/FinReceiptValidate.php b/app/common/validate/FinReceiptValidate.php new file mode 100644 index 0000000..237b03e --- /dev/null +++ b/app/common/validate/FinReceiptValidate.php @@ -0,0 +1,56 @@ + 'integer|gt:0', +// 'country_id' => 'require|integer|gt:0', +// 'remitter' => 'require', +// 'account_id' => 'require|integer|gt:0', +// 'currency_id' => 'require|integer|gt:0', +// 'amount' => 'require|gt:0', +// 'exchange_rate' => 'require|gt:0', +// 'arrived_at' => 'require', +// 'is_domestic_return' => 'require|integer', + ]; + + // 提示 + protected $message = [ +// 'payment_apply_id.integer' => '付款申请ID必须是整数', +// 'payment_apply_id.gt' => '付款申请ID必须大于0', +// 'country_id.require' => '付款来源国家ID不能为空', +// 'country_id.integer' => '付款来源国家ID必须是整数', +// 'country_id.gt' => '付款来源国家ID必须大于0', +// 'remitter.require' => '汇款人不能为空', +// 'account_id.require' => '收款账号ID不能为空', +// 'account_id.integer' => '收款账号ID必须是整数', +// 'account_id.gt' => '收款账号ID必须大于0', +// 'currency_id.require' => '收款币种ID不能为空', +// 'currency_id.integer' => '收款币种ID必须是整数', +// 'currency_id.gt' => '收款币种ID必须大于0', +// 'amount.require' => '收款金额不能为空', +// 'amount.gt' => '收款金额必须大于0', +// 'exchange_rate.require' => '实际汇率不能为空', +// 'exchange_rate.gt' => '实际汇率必须大于0', +// 'arrived_at.require' => '到账时间不能为空', +// 'arrived_at.gt' => '到账时间必须大于0', +// 'is_domestic_return.require' => '是否回转内地不能为空', +// 'is_domestic_return.integer' => '是否回转内地必须是整数', + ]; + + // 场景 + protected $scene = [ + 'store' => [ +// 'payment_apply_id', 'country_id', 'remitter', 'account_id', 'currency_id', 'amount', 'exchange_rate', 'arrived_at' + ], + + 'update' => [ +// 'payment_apply_id' + ] + ]; +} \ No newline at end of file diff --git a/app/crm/controller/StockOrdersController.php b/app/crm/controller/StockOrdersController.php index 3716c69..3f6c6e4 100644 --- a/app/crm/controller/StockOrdersController.php +++ b/app/crm/controller/StockOrdersController.php @@ -61,43 +61,49 @@ class StockOrdersController extends BaseController if (isset($data['pick_up_date']) && $data['pick_up_date'] > 0) $data['pick_up_date'] = date('Y-m-d', $data['pick_up_date']); if (isset($data['collect_all_date']) && $data['collect_all_date'] > 0) $data['collect_all_date'] = date('Y-m-d', $data['collect_all_date']); - // 处理文件附件 + // 处理文件附件:只允许 _url 字段调用 OSS 生成私有链接 $fileTypes = [ 'contract' => \app\common\model\StockOrdersFiles::CONTRACT, 'transaction_slip' => \app\common\model\StockOrdersFiles::TRANSACTION_SLIP, 'pi_doc' => \app\common\model\StockOrdersFiles::PI_DOC ]; - + // 初始化文件字段 foreach (array_keys($fileTypes) as $field) { - $data[$field] = []; - $data[$field . '_url'] = []; + $data[$field] = []; // 存储原始文件信息(不包含签名链接) + $data[$field . '_url'] = []; // 存储通过OSS生成的私有链接 } - - // 按file_code分组文件 + + // 按 file_code 分组文件 if (!empty($data['files'])) { $groupedFiles = []; foreach ($data['files'] as $file) { - if (!isset($groupedFiles[$file['file_code']])) { - $groupedFiles[$file['file_code']] = []; + $code = $file['file_code']; + if (!isset($groupedFiles[$code])) { + $groupedFiles[$code] = []; } - $groupedFiles[$file['file_code']][] = $file['file_url']; + $groupedFiles[$code][] = [ + 'url' => $file['file_url'], // 原始路径 + 'original_name' => $file['original_name'] // 原始文件名 + ]; } - - // 映射到对应字段 + + // 映射到对应字段,并仅对 _url 调用 OSS 服务 foreach ($fileTypes as $field => $fileCode) { if (isset($groupedFiles[$fileCode])) { - // 原始URL数组 + // 主字段存储基本信息(无签名链接) $data[$field] = $groupedFiles[$fileCode]; - - // 完整URL数组 - $fullUrls = []; - foreach ($groupedFiles[$fileCode] as $url) { - $fullUrls[] = (new \app\common\service\OssService)->generatePrivateUrl($url); + + // 只有 *_url 字段才生成带签名的完整 URL + $signedUrls = []; + foreach ($groupedFiles[$fileCode] as $fileInfo) { + $signedUrls[] = (new \app\common\service\OssService)->generatePrivateUrl($fileInfo['url']); } - $data[$field . '_url'] = $fullUrls; + $data[$field . '_url'] = $signedUrls; } } + + // 清理临时数据 unset($data['files']); } diff --git a/app/functions.php b/app/functions.php index 21e53c9..43c2f9a 100644 --- a/app/functions.php +++ b/app/functions.php @@ -180,7 +180,8 @@ if (!function_exists('tplUrl')) { { $urlArray = [ 'email' => 'common/utils/scaffold/templates/Email.stub', - 'work_order' => 'common/utils/scaffold/templates/workOrder.stub' + 'work_order' => 'common/utils/scaffold/templates/workOrder.stub', + 'payment_files' => 'common/utils/scaffold/templates/paymentFiles.stub', ]; return $urlArray[$tplKey]; } diff --git a/app/process/Task.php b/app/process/Task.php index ccaa228..7d8059c 100644 --- a/app/process/Task.php +++ b/app/process/Task.php @@ -2,6 +2,8 @@ namespace app\process; +use app\common\model\FinPaymentApply; +use app\common\model\User; use app\crm\model\Customer; use support\Log; use Workerman\Crontab\Crontab; @@ -11,7 +13,7 @@ class Task public function onWorkerStart() { - // 每半小时执行一次 + // 每半小时执行一次,通知待跟进客户 new Crontab('0 */30 * * * *', function(){ echo date('Y-m-d H:i:s') . "\n"; Log::info('send follow start',[time()]); @@ -41,7 +43,42 @@ class Task $item->save(); } - Log::info('send follow start',[time()]); + Log::info('send follow end',[time()]); + }); + + // 检查入账资料是否提交 + new Crontab('* */70 * * * *', function () { + $G = date('G'); + if ($G > 22 || $G < 8) { + return; + } + Log::info('fin_payment_apply submit_files start', [time()]); + $where = [ + ['submit_files', '=', ''], + ['warn', '<', 2], + ['created_at', '>', time() - 60 * 60 * 48], + ['created_at', '<', time() - 60 * 60 * 24], + ]; + $payment = FinPaymentApply::where($where)->orderBy('id', 'desc')->get(); + foreach ($payment as $item) { + Log::info('fin_payment_apply submit_files ', [$item->toArray()]); + $notice = [ + 'code' => $item->apply_no, + 'payment_entity' => $item->payment_entity ?? $item->remitter, + 'content' => '认领收款单后请在24小时内提交入账资料', + 'reply' => '', + ]; + $email = []; + $email[] = User::where('id', $item->claimed_by)->value('email'); + $email[] = User::where('id', $item->doc_user_id)->value('email'); + $email = array_unique($email); + \app\common\utils\email\MailSend::send($email, '【XYH】付款记录', replaceTpl($notice, 'payment_files')); + + $item->warn = ++$item->warn; + $item->save(); + } + + Log::info('fin_payment_apply submit_files end', [time()]); }); } diff --git a/config/route.php b/config/route.php index 2fafd16..22b0862 100644 --- a/config/route.php +++ b/config/route.php @@ -389,7 +389,6 @@ Route::group('/admin', function () { }); }); - Route::group('/purchase/order', function () { Route::any('/predata', [app\common\controller\PurchaseOrdersController::class, 'preQueryData']); Route::any('/list', [app\common\controller\PurchaseOrdersController::class, 'index']); @@ -422,6 +421,55 @@ Route::group('/admin', function () { Route::any('/update', [app\common\controller\SysUpdateLogController::class, 'update']); Route::any('/destroy', [app\common\controller\SysUpdateLogController::class, 'destroy']); }); + + // 收款记录 + Route::group('/receipt', function () { + Route::any('/list', [app\common\controller\FinReceiptController::class, 'index']); + Route::any('/show', [app\common\controller\FinReceiptController::class, 'show']); + Route::any('/store', [app\common\controller\FinReceiptController::class, 'store']); + Route::any('/update', [app\common\controller\FinReceiptController::class, 'update']); + Route::any('/payment', [app\common\controller\FinReceiptController::class, 'payment']); + Route::any('/destroy', [app\common\controller\FinReceiptController::class, 'destroy']); + Route::any('/credit', [app\common\controller\FinReceiptController::class, 'credit']); + }); + // 收款记录历史 + Route::group('/receipt/his', function () { + Route::any('/list', [app\common\controller\FinReceiptHisController::class, 'index']); + Route::any('/show', [app\common\controller\FinReceiptHisController::class, 'show']); + }); + + Route::group('/fin', function () { + Route::any('/statistics', [app\admin\controller\dashboard\FinStatisticsController::class, 'statistics']); + }); + + // 付款申请 + Route::group('/payment', function () { + Route::any('/list', [app\common\controller\FinPaymentApplyController::class, 'index']); + Route::any('/show', [app\common\controller\FinPaymentApplyController::class, 'show']); + Route::any('/store', [app\common\controller\FinPaymentApplyController::class, 'store']); + Route::any('/update', [app\common\controller\FinPaymentApplyController::class, 'update']); + Route::any('/destroy', [app\common\controller\FinPaymentApplyController::class, 'destroy']); + }); + + // 系统配置 + Route::group('/config', function () { + Route::any('/list', [app\admin\controller\SysConfigController::class, 'index']); + Route::any('/show', [app\admin\controller\SysConfigController::class, 'show']); + Route::any('/store', [app\admin\controller\SysConfigController::class, 'store']); + Route::any('/update', [app\admin\controller\SysConfigController::class, 'update']); + Route::any('/destroy', [app\admin\controller\SysConfigController::class, 'destroy']); + }); + + // 仪表盘 + Route::group('/dashboard', function () { + // 内勤 + Route::group('/officeclerk', function () { + Route::any('/card', [app\admin\controller\dashboard\OfficeClerkController::class, 'card']); + Route::any('/curve', [app\admin\controller\dashboard\OfficeClerkController::class, 'curve']); + Route::any('/approval', [app\admin\controller\dashboard\OfficeClerkController::class, 'approval']); + Route::any('/claim', [app\admin\controller\dashboard\OfficeClerkController::class, 'claim']); + }); + }); }); From aabd0f4634ee78736abba6868c6ffb14d9a99d97 Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 4 Nov 2025 10:36:52 +0800 Subject: [PATCH 3/6] Merge branch 'develop' # Conflicts: # app/admin/controller/dashboard/FinStatisticsController.php # app/common/controller/FinPaymentApplyController.php # app/common/controller/FinReceiptController.php # app/common/logic/FinReceiptLogic.php # app/common/model/FinPaymentApply.php # app/common/model/FinReceipt.php # app/common/service/FinPaymentApplyService.php # app/common/service/FinReceiptService.php # app/common/service/StockOrdersService.php # config/route.php --- .../dashboard/FinStatisticsController.php | 100 ++++++++++++- .../controller/FinPaymentApplyController.php | 21 +++ .../controller/FinReceiptController.php | 15 +- app/common/logic/FinReceiptLogic.php | 4 +- app/common/model/FinPaymentApply.php | 13 +- app/common/model/FinReceipt.php | 18 ++- app/common/service/FinPaymentApplyService.php | 28 +++- app/common/service/FinReceiptService.php | 28 +++- app/common/service/StockOrdersService.php | 131 +++++++++++++++--- config/route.php | 2 +- 10 files changed, 321 insertions(+), 39 deletions(-) diff --git a/app/admin/controller/dashboard/FinStatisticsController.php b/app/admin/controller/dashboard/FinStatisticsController.php index 95cf09d..6901082 100644 --- a/app/admin/controller/dashboard/FinStatisticsController.php +++ b/app/admin/controller/dashboard/FinStatisticsController.php @@ -3,7 +3,9 @@ namespace app\admin\controller\dashboard; use app\common\controller\BaseController; +use app\common\model\FinPaymentApply; use app\common\model\FinReceipt; +use app\common\model\SysCurrency; use support\Request; class FinStatisticsController extends BaseController @@ -16,33 +18,117 @@ class FinStatisticsController extends BaseController public function statistics(Request $request) { try { + $currency = SysCurrency::where('status',1)->get(); $query = FinReceipt::where('payment_apply_id','>',0); $result = []; //已认领数量 $result['claimed_num'] = $query->count(); //已认领金额 - $result['claimed_amount'] = $query->sum('amount'); +// $result['claimed_amount'] = $query->sum('amount'); + $result['claimed_amount'] = array_values($this->receiptStatisticsSum($query, $currency)); //各币种的已认领金额 + $result['claimed_amount_sum'] =$this->amountSum($result['claimed_amount']); //已认领的人民币总金额 //未入账数量 - $result['unreceived_num'] = FinReceipt::whereNull('received_at')->count(); + $query2 = FinReceipt::whereNull('received_at'); + $result['unreceived_num'] = $query2->count(); //未入账金额 - $result['unreceived_amount'] = FinReceipt::whereNull('received_at')->sum('amount'); +// $result['unreceived_amount'] = $query2->sum('amount'); + $result['unreceived_amount'] = array_values($this->receiptStatisticsSum($query2, $currency)); //各币种的未入账金额 + $result['unreceived_amount_sum'] = $this->amountSum($result['unreceived_amount']); //未入账的已认领的人民币总金额 - $query = FinReceipt::whereNull('payment_apply_id'); + //今日入账 + $query6 = FinReceipt::where('received_at','>',strtotime(date("Y-m-d"))); + //今日入账数量 + $result['received_num'] = $query6->count(); + //未入账金额 +// $result['unreceived_amount'] = $query2->sum('amount'); + $result['received_amount'] = array_values($this->receiptStatisticsSum($query6, $currency)); //各币种的未入账金额 + $result['received_amount_sum'] = $this->amountSum($result['unreceived_amount']); //未入账的已认领的人民币总金额 + + + $query3 = FinReceipt::where('payment_apply_id',0); //未认领数量 - $result['unclaimed_num'] = $query->count(); + $result['unclaimed_num'] = $query3->count(); //未认领金额 - $result['unclaimed_amount'] = $query->sum('amount'); +// $result['unclaimed_amount'] = $query3->sum('amount'); + $result['unclaimed_amount'] = $this->receiptStatisticsSum($query3, $currency); //各币种的未认领金额 + $result['unclaimed_amount_sum'] = $this->amountSum($result['unclaimed_amount']); //未认领的已认领的人民币总金额 //已超时未认领数量 - $result['timeout'] = $query->where('created_at','<',time()-24*3600)->count(); + $query4 = $query3->where('created_at','<',time()-24*3600); + $result['timeout'] = $query4->count(); + //在途资金 + //1、没有认领的付款资金 + $query5 = FinPaymentApply::where('receipt_id', '=', 0); + $unclaimed_payment_amount = $this->receiptStatisticsSum($query5, $currency); + foreach ($unclaimed_payment_amount as $key => &$value) { + $value['amount'] = $value['amount'] - $result['unclaimed_amount'][$key]['amount']; + } + $result['unclaimed_payment_num'] = $query5->count(); + $result['unclaimed_payment_amount'] = array_values($unclaimed_payment_amount); + $result['unclaimed_payment_amount_sum'] = $this->amountSum($result['unclaimed_payment_amount']); + //在途资金 = 没有认领的付款资金-未认领的收款资金 + $result['intransit_amount_sum'] = $result['unclaimed_payment_amount_sum'] - $result['unclaimed_amount_sum']; + $intransit_amount_amount = []; + //在途资金各币种明细 + foreach ($unclaimed_payment_amount as $key => $value) { + $value['amount'] = $value['amount'] - $result['unclaimed_amount'][$key]['amount']; + $intransit_amount_amount[] = $value; + } + $result['intransit_amount_amount'] = $intransit_amount_amount; + //在途数量 = 没有认领的付款数量-未认领的收款数量 + $result['intransit_count'] = $result['unclaimed_payment_num'] - $result['unclaimed_num']; + + //未认领金额重新组织索引 + $result['unclaimed_amount'] = array_values($result['unclaimed_amount']); return $this->success($result); } catch (\Throwable $e) { return $this->error($e->getMessage()); } } + /** + * 根据不同条件查询收款金额 + * @param $query + * @param $currency + * @return array + */ + public function receiptStatisticsSum($query, $currency) + { + $amount = []; + foreach ($currency as $k => $v) { + $amount[$v->currency_code] = [ + 'currency_code' => $v->currency_code, + 'exchange_rate' => $v->exchange_rate, + 'amount' => $query->where('currency_id',$v->id)->sum('amount'), + ]; + } + return $amount; + } + + /** + * 不同币种汇率换算之后求和 + * @param $query + * @param $currency + */ + public function amountSum($amount) + { + $amountSum = 0; + foreach ($amount as $v) { + // 转换为整数(乘以100避免浮点数问题) + $v['amount'] = (int) round($v['amount'] * 100); + $v['exchange_rate'] = (int) round($v['exchange_rate'] * 10000); // 汇率保留4位小数 + $amountSum += $v['amount'] * $v['exchange_rate']; + } + + // 转换回金额(除以1000000,因为汇率乘了10000) + $result = $amountSum / 1000000; + + // 四舍五入到2位小数 + return round($amountSum, 2); + } + } \ No newline at end of file diff --git a/app/common/controller/FinPaymentApplyController.php b/app/common/controller/FinPaymentApplyController.php index 0939646..98c49c2 100644 --- a/app/common/controller/FinPaymentApplyController.php +++ b/app/common/controller/FinPaymentApplyController.php @@ -5,6 +5,7 @@ namespace app\common\controller; use app\common\controller\BaseController; use app\common\model\Country; use app\common\model\FinPaymentStock; +use app\common\model\StockOrders; use app\common\model\SysAccount; use app\common\model\SysCurrency; use app\common\model\SysDictItem; @@ -84,11 +85,15 @@ class FinPaymentApplyController extends BaseController if (isset($data['id'])){ $finPaymentStock = FinPaymentStock::where('payment_apply_id', $data['id'])->get(); $stock = []; + $stock_order_no = []; foreach ($finPaymentStock as $v) { $stock[$v['pi_no']]['pi_no'] = $v['pi_no']; $stock[$v['pi_no']]['stock_id'][] = [$v['stock_id']]; + $stockOrders = StockOrders::where('id', $v['stock_id'])->first(); + $stock_order_no[] = $stockOrders['order_no']; } $data['stock'] = array_values($stock); + $data['stock_order_no'] = $stock_order_no; } return $data; @@ -157,4 +162,20 @@ class FinPaymentApplyController extends BaseController $data['stock'] = array_values($stock); return $data; } + + /** + * 作废 + * @param Request $request + * @return \support\Response + */ + public function delete(Request $request) + { + try { + $id = $request->input('id'); + $raw = $this->service->delete($id); + return $this->success($raw); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/common/controller/FinReceiptController.php b/app/common/controller/FinReceiptController.php index cade259..4c73b7a 100644 --- a/app/common/controller/FinReceiptController.php +++ b/app/common/controller/FinReceiptController.php @@ -12,6 +12,7 @@ use app\common\model\User; use app\common\service\OssService; use app\common\traits\crud\CrudControllerTrait; use app\common\service\FinReceiptService; +use support\Redis; use support\Request; class FinReceiptController extends BaseController @@ -34,7 +35,11 @@ class FinReceiptController extends BaseController { try { $id = $request->input('id'); - $raw = $this->service->credit($id); + $exchange_rate = $request->input('exchange_rate'); + if(empty($exchange_rate) || !is_numeric($exchange_rate)){ + return $this->error('请输入汇率'); + } + $raw = $this->service->credit($id,$exchange_rate); return $this->success($raw); } catch (\Throwable $e) { return $this->error($e->getMessage()); @@ -81,14 +86,20 @@ class FinReceiptController extends BaseController { if (isset($data['created_at'])){ $data['claim_timeout'] = (($data['created_at'] < (time()-86400)) && empty($data['payment_apply_id'])) ?1:0; + $data['claim_timeout_time'] = date('Y-m-d H:i:s', $data['created_at']+86400); $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); } if (isset($data['updated_at'])) $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); if (isset($data['arrived_at'])) $data['arrived_at'] = date('Y-m-d H:i:s', $data['arrived_at']); - if (isset($data['received_at'])) + if (isset($data['received_at'])){ $data['received_at'] = date('Y-m-d H:i:s', $data['received_at']); + $voucher_no_timeout_hour = redis::get('voucher_no_timeout_hour'); + if(!empty($voucher_no_timeout_hour)){ + $data['voucher_no_timeout_time'] = date('Y-m-d H:i:s', time()+(60*60*$voucher_no_timeout_hour)); + } + } if (isset($data['account_id'])) { //收款账号 $data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??''; $data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??''; diff --git a/app/common/logic/FinReceiptLogic.php b/app/common/logic/FinReceiptLogic.php index 62db50f..622663b 100644 --- a/app/common/logic/FinReceiptLogic.php +++ b/app/common/logic/FinReceiptLogic.php @@ -14,8 +14,8 @@ class FinReceiptLogic */ protected string $modelClass = FinReceipt::class; - public function credit($id) + public function credit($id, $data) { - return FinReceipt::where('id', $id)->update(['received_at' => time()]); + return FinReceipt::where('id', $id)->update($data); } } \ No newline at end of file diff --git a/app/common/model/FinPaymentApply.php b/app/common/model/FinPaymentApply.php index 0f4d157..af52c85 100644 --- a/app/common/model/FinPaymentApply.php +++ b/app/common/model/FinPaymentApply.php @@ -2,10 +2,12 @@ namespace app\common\model; +use Illuminate\Database\Eloquent\SoftDeletes; use support\Model; class FinPaymentApply extends Model { + use SoftDeletes; /** * The connection name for the model. * @@ -40,7 +42,7 @@ class FinPaymentApply extends Model /** * 允许自动填充模型字段 */ - protected $fillable = ['id', 'apply_no', 'doc_user_id', 'sales_user_id', 'corp_id', 'account_id', 'payment_account', 'payment_entity', 'entity_type', 'currency_id', 'amount', 'swift', 'remit_bank', 'country_id', 'remitter', 'exchange_rate', 'total_amount_cny', 'voucher_file', 'voucher_name', 'submit_files','receipt_id','claimed_at','warn', 'created_at', 'updated_at', 'claimed_by',]; + protected $fillable = ['id', 'apply_no', 'doc_user_id', 'sales_user_id', 'corp_id', 'account_id', 'payment_account', 'payment_entity', 'entity_type', 'currency_id', 'amount', 'swift', 'remit_bank', 'country_id', 'remitter', 'exchange_rate', 'total_amount_cny', 'voucher_file', 'voucher_name', 'submit_files','receipt_id','claimed_at','warn', 'received_at', 'created_at', 'updated_at', 'deleted_at', 'claimed_by', 'created_by',]; /** * 不允许自动填充模型字段 @@ -62,4 +64,13 @@ class FinPaymentApply extends Model { return $this->belongsToMany(StockOrders::class, 'fin_payment_stock', 'payment_apply_id', 'stock_id'); } + + /** + * 查询 submit_files 不为空的记录 + */ + public function scopeWithNonEmptyFiles($query) + { + return $query->whereNotNull('submit_files') + ->whereRaw('JSON_LENGTH(submit_files) > 0'); + } } \ No newline at end of file diff --git a/app/common/model/FinReceipt.php b/app/common/model/FinReceipt.php index 7347be0..88d3090 100644 --- a/app/common/model/FinReceipt.php +++ b/app/common/model/FinReceipt.php @@ -40,7 +40,7 @@ class FinReceipt extends Model /** * 允许自动填充模型字段 */ - protected $fillable = ['id', 'receipt_no', 'payment_apply_id', 'country_id', 'remitter', 'account_id', 'currency_id', 'amount', 'discount_amount', 'total_amount_cny', 'exchange_rate', 'arrived_at', 'received_at', 'voucher_no', 'fx_receipt_file', 'fx_receipt_file_name', 'is_domestic_return', 'created_by', 'updated_by', 'created_at', 'updated_at']; + protected $fillable = ['id', 'receipt_no', 'payment_apply_id', 'country_id', 'remitter', 'account_id', 'currency_id', 'amount', 'discount_amount', 'total_amount_cny', 'exchange_rate', 'arrived_at', 'received_at', 'voucher_no', 'fx_receipt_file', 'fx_receipt_file_name', 'is_domestic_return', 'submit_files', 'created_by', 'updated_by', 'created_at', 'updated_at']; /** * 不允许自动填充模型字段 @@ -62,4 +62,20 @@ class FinReceipt extends Model { return $this->belongsToMany(StockOrders::class, 'fin_payment_stock', 'receipt_id', 'stock_id'); } + + /** + * 关联付款申请 + */ +// public function finPaymentApply() +// { +// return $this->hasOne(FinPaymentApply::class, 'receipt_id', 'id'); +// } + + /** + * 关联付款申请表 + */ + public function paymentApply() + { + return $this->belongsTo(FinPaymentApply::class, 'payment_apply_id', 'id'); + } } \ No newline at end of file diff --git a/app/common/service/FinPaymentApplyService.php b/app/common/service/FinPaymentApplyService.php index e55b6a7..42cae9a 100644 --- a/app/common/service/FinPaymentApplyService.php +++ b/app/common/service/FinPaymentApplyService.php @@ -127,6 +127,13 @@ class FinPaymentApplyService return $data; } + + public function afterServiceUpdate($res, Request $request) + { + if($res->submit_files != '' && $res->receipt_id != 0) + FinReceipt::where('id', $res->receipt_id)->update(['submit_files' => $res->submit_files]); + } + /** * 生成唯一的付款申请编码 * 格式:PA + 日期 + 6位字母数字组合 @@ -207,7 +214,19 @@ class FinPaymentApplyService $query->whereDoesntHave('receipt'); } - if ((isset($param['scene']) && $param['scene'] == 'list') || empty($param['receipt_id'])) { + if(isset($param['scene']) && $param['scene'] == 'pending'){ //待入账列表 + $query->where('receipt_id', '>' ,'0'); + $query->where('submit_files','!=',''); + $query->whereNull('received_at'); + } + + if($param['scene'] == 'delete'){ //查询作废数据 + $query->onlyTrashed(); + }elseif ($param['scene'] == 'all'){ //查询包含作废数据的全量数据 + $query->withTrashed(); + } + + if ((isset($param['scene']) && ($param['scene'] == 'list' || $param['scene'] == 'pending'))|| empty($param['receipt_id']) ) { return $query->orderBy('id','desc')->paginate($limit, ['*'], 'page', $page); } @@ -264,4 +283,11 @@ class FinPaymentApplyService return $query->paginate($limit, ['*'], 'page', $page); } + public function delete($id) + { + $res = FinPaymentApply::where('id', $id)->delete(); + return $res; + } + + } \ No newline at end of file diff --git a/app/common/service/FinReceiptService.php b/app/common/service/FinReceiptService.php index 6073cdf..df236ea 100644 --- a/app/common/service/FinReceiptService.php +++ b/app/common/service/FinReceiptService.php @@ -10,6 +10,7 @@ use app\common\validate\FinReceiptValidate; use app\common\traits\crud\CrudServiceTrait; use app\common\model\FinReceipt; use support\exception\BusinessException; +use support\Redis; use support\Request; class FinReceiptService @@ -194,6 +195,10 @@ class FinReceiptService $page = (int)$request->get('current', 1); $param = $request->all(); + if(isset($param['voucher_no_timeout_hour'])){ + redis::set('voucher_no_timeout_hour', $param['voucher_no_timeout_hour'], 'EX', 30); + } + $where = []; // 汇款国家 @@ -218,7 +223,7 @@ class FinReceiptService // 构建查询 $query = $this->modelClass::where($where); - // 没有付款记录的收款记录 + // 收款记录绑定情况 if (isset($param['payment_apply']) && $param['payment_apply'] == 'not') { $query->where('payment_apply_id', 0); } elseif (isset($param['payment_apply']) && $param['payment_apply'] == 'already') { @@ -228,7 +233,12 @@ class FinReceiptService $query->where('created_at', '<' ,time() - 24*60*60); } - if (isset($param['scene']) && $param['scene'] == 'list') { + if(isset($param['scene']) && $param['scene'] == 'pending'){ //待入账列表 + $query->where('payment_apply_id', '>' ,'0'); + $query->where('submit_files','!=',''); + } + + if (isset($param['scene']) && ($param['scene'] == 'list' ||$param['scene'] == 'pending')) { return $query->paginate($limit, ['*'], 'page', $page); } @@ -277,13 +287,20 @@ class FinReceiptService return $query->paginate($limit, ['*'], 'page', $page); } - public function credit($id) + public function credit($id, $exchange_rate) { $finReceipt = FinReceipt::find($id); if(!empty($finReceipt->received_at)){ throw new BusinessException('该记录已将确认入账'); } - $res = $this->logic->credit($id); + + $data = [ + 'received_at' => time(), + 'exchange_rate' => $exchange_rate, + 'total_amount_cny' => round($finReceipt['amount']*$exchange_rate,2), + ]; + $res = $this->logic->credit($id,$data); + FinPaymentApply::where('id', $finReceipt->payment_apply_id)->update(['received_at' => $data['received_at']]); $this->afterServiceUpdate(['id'=>$id]); return $res; } @@ -298,6 +315,7 @@ class FinReceiptService public function serviceUpdatePayment(Request $request, array $data = [], string $scene = 'payment') { $payment_apply_id = $request->input('payment_apply_id'); + $id = $request->input('id'); $data = $this->preProcess(ucfirst($scene), $data, $request); $config = $this->buildQueryConfig($request, $scene); @@ -308,8 +326,10 @@ class FinReceiptService $payment = FinPaymentApply::where('id', $payment_apply_id)->first(); $receipt = FinReceipt::find($id); $data['discount_amount'] = $payment->amount - $receipt->amount ?? 0; + $data['submit_files'] = $payment->submit_files ?? ''; $updated = $this->logicUpdateBy($config, $data); return $this->postProcess(ucfirst($scene), $data); } + } \ No newline at end of file diff --git a/app/common/service/StockOrdersService.php b/app/common/service/StockOrdersService.php index d4187a1..9d5e4df 100644 --- a/app/common/service/StockOrdersService.php +++ b/app/common/service/StockOrdersService.php @@ -73,6 +73,7 @@ class StockOrdersService case 'print': case 'approval': $config['where']['id'] = $p['id']; + $config['with'][] = 'files'; // 添加文件附件关联 break; case 'detail': case 'show': @@ -102,22 +103,14 @@ class StockOrdersService public function beforeServiceStore($data, Request $request) { $userId = $request->user->uid; - - $data['order_no'] = generateStockNo(); - $data['biz_date'] = strtotime($data['biz_date']); - $data['eta_ship_date'] = strtotime($data['eta_ship_date']); - $data['latest_arrival_date'] = strtotime($data['latest_arrival_date']); - $data['salesperson_id'] = $userId; - $data['salesperson_snapshot'] = User::where('id', $userId)->value('name') ?? ''; - $data['approval_status'] = $data['approval_status'] == 1 ? 1 : 0; - - // if ($data['is_final'] == 1) { - $data['pick_up_date'] = $data['pick_up_date'] ? strtotime($data['pick_up_date']) : 0; - $data['collect_all_date'] = $data['collect_all_date'] ? strtotime($data['collect_all_date']) : 0; - // } - + // 检查绑定 + (new UserService())->checkBind($userId); + // 保存不检查文件上传 + if ($data['approval_status'] != 0) { + $this->validateRequiredFiles($data); + } + // 终版备货单时,至少一个车辆填写了vin if ($data['is_final'] == 1) { - // 检查是否至少有一个车辆填写了vin $hasVin = false; foreach ($data['veh_list'] as $k => $v) { if ($v['vin'] != '') { @@ -125,17 +118,105 @@ class StockOrdersService break; } } - // 如果没有任何车辆填写vin,则抛出异常 if (!$hasVin) { throw new BusinessException('至少需要为一辆车辆填写vin,才能提交审核'); } } - - // 检查绑定 - (new UserService())->checkBind($userId); + $data['order_no'] = generateStockNo(); + $data['biz_date'] = strtotime($data['biz_date']); + $data['eta_ship_date'] = strtotime($data['eta_ship_date']); + $data['latest_arrival_date'] = strtotime($data['latest_arrival_date']); + $data['salesperson_id'] = $userId; + $data['salesperson_snapshot'] = User::where('id', $userId)->value('name') ?? ''; + $data['approval_status'] = $data['approval_status'] == 1 ? 1 : 0; + $data['pick_up_date'] = $data['pick_up_date'] ? strtotime($data['pick_up_date']) : 0; + $data['collect_all_date'] = $data['collect_all_date'] ? strtotime($data['collect_all_date']) : 0; return $data; } + /** + * 验证创建备货单时必需的文件是否已提供 + * + * @param array $data 提交的数据 + * @throws BusinessException 当必需的文件缺失或为空时抛出异常 + */ + private function validateRequiredFiles(array $data): void + { + $requiredFiles = [ + 'contract' => '合同', + 'transaction_slip' => '水单', + 'pi_doc' => 'PI' + ]; + + foreach ($requiredFiles as $fileType => $fileLabel) { + if (!isset($data[$fileType]) || !is_array($data[$fileType]) || empty($data[$fileType])) { + throw new BusinessException("创建备货单时{$fileLabel}文件不能为空"); + } + + // 检查文件数组中的每个条目是否都有有效的URL + $hasValidFile = false; + foreach ($data[$fileType] as $fileInfo) { + if (isset($fileInfo['url']) && !empty($fileInfo['url'])) { + $hasValidFile = true; + break; + } + } + + if (!$hasValidFile) { + throw new BusinessException("创建备货单时{$fileLabel}文件不能为空"); + } + } + } + + /** + * 验证备货单审批时必需的文件是否存在 + * + * @param StockOrders $item 备货单对象 + * @throws BusinessException 当必需的文件缺失或为空时抛出异常 + */ + private function validateRequiredFilesForApproval($item): void + { + // 确保已加载文件关联 + if (!isset($item->files)) { + $item->load('files'); + } + + $requiredFiles = [ + \app\common\model\StockOrdersFiles::CONTRACT => '合同', + \app\common\model\StockOrdersFiles::TRANSACTION_SLIP => '水单', + \app\common\model\StockOrdersFiles::PI_DOC => 'PI' + ]; + + // 按文件类型分组现有文件 + $existingFiles = []; + foreach ($item->files as $file) { + if (!isset($existingFiles[$file->file_code])) { + $existingFiles[$file->file_code] = []; + } + $existingFiles[$file->file_code][] = $file; + } + + // 检查每种必需文件是否存在 + foreach ($requiredFiles as $fileCode => $fileLabel) { + if (!isset($existingFiles[$fileCode]) || empty($existingFiles[$fileCode])) { + throw new BusinessException("提交审批前必须上传{$fileLabel}文件"); + } + + // 检查文件中是否有有效的URL + $hasValidFile = false; + foreach ($existingFiles[$fileCode] as $file) { + if (!empty($file->file_url)) { + $hasValidFile = true; + break; + } + } + + if (!$hasValidFile) { + throw new BusinessException("提交审批前必须上传有效的{$fileLabel}文件"); + } + } + } + protected function afterServiceStore($res) { // 处理原单 @@ -271,10 +352,14 @@ class StockOrdersService public function serviceApproval(Request $request) { $config = $this->buildQueryConfig($request, 'approval'); + $item = $this->logicShow($config); + + // 检查备货单是否包含必需的文件 + $this->validateRequiredFilesForApproval($item); + $this->logicUpdateBy($config, [ 'approval_status' => 1 ]); - $item = $this->logicShow($config); $this->startApproval($item); return $this->postProcess('Approval', $item); } @@ -366,6 +451,9 @@ class StockOrdersService */ public function beforeServiceUpdate($data, Request $request) { + // 检查必填文件是否已上传 + // $this->validateRequiredFiles($data); + $data['biz_date'] = strtotime($data['biz_date']); $data['eta_ship_date'] = strtotime($data['eta_ship_date']); $data['latest_arrival_date'] = strtotime($data['latest_arrival_date']); @@ -1024,4 +1112,7 @@ class StockOrdersService 'submit_note' => $submitNote ]; } + + + } \ No newline at end of file diff --git a/config/route.php b/config/route.php index 22b0862..c31e972 100644 --- a/config/route.php +++ b/config/route.php @@ -448,7 +448,7 @@ Route::group('/admin', function () { Route::any('/show', [app\common\controller\FinPaymentApplyController::class, 'show']); Route::any('/store', [app\common\controller\FinPaymentApplyController::class, 'store']); Route::any('/update', [app\common\controller\FinPaymentApplyController::class, 'update']); - Route::any('/destroy', [app\common\controller\FinPaymentApplyController::class, 'destroy']); + Route::any('/delete', [app\common\controller\FinPaymentApplyController::class, 'delete']); }); // 系统配置 From 6c59fdef106c5dfaccefc67485d8318693a2fbca Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 4 Nov 2025 15:43:42 +0800 Subject: [PATCH 4/6] Merge branch 'develop' # Conflicts: # app/common/service/StockOrdersService.php --- app/admin/service/BrandSeriesService.php | 254 ++++++++++++++---- .../controller/BrandSeriesController.php | 42 ++- app/common/service/CrawlService.php | 14 + app/common/service/StockOrdersService.php | 14 +- config/route.php | 2 + 5 files changed, 264 insertions(+), 62 deletions(-) diff --git a/app/admin/service/BrandSeriesService.php b/app/admin/service/BrandSeriesService.php index f1526d3..594605b 100644 --- a/app/admin/service/BrandSeriesService.php +++ b/app/admin/service/BrandSeriesService.php @@ -168,50 +168,8 @@ class BrandSeriesService throw new \Exception('抓取失败,未找到该系列车型'); } - $client = new Client([ - 'verify' => false, // 禁用 SSL 验证 - 'timeout' => 10 - ]); try { - $response = $client->get('https://www.dongchedi.com/motor/pc/car/series/car_list', [ - 'query' => ['series_id' => $seriesId], - 'headers' => [ - 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' - ] - ]); - $data = json_decode($response->getBody()->getContents(), true); - if($data['status'] != 0 || (!isset($data['data']) && !isset($data['data']['tab_list']))){ - Log::error('抓取车型数据失败' . $data['message'], $data); - throw new \Exception('抓取车型数据失败' . $data['message']); - } - $models = []; - foreach ($data['data']['tab_list'] as $item) { - if($item['tab_text'] == '在售' || $item['tab_text'] == '停售'){ - unset($item['data'][0]); - $models = array_merge($models, $item['data']); - } - } - foreach ($models as $key => $model) { - if(!isset($model["info"]['car_name']) || $model['type'] != "1115"){ - unset($models[$key]); - continue; - } - $official_price = 0; - if(!isset($model["info"]['official_price'])){ - $official_price = (int)(str_replace("万", "", $model["info"]['official_price_str']) * 10000) ?? 0; - }else{ - $official_price = $model["info"]['official_price'] * 10000; - } - $models[$key] = [ - 'title' => $model["info"]['year'] . '款 ' .$model["info"]['car_name'], - 'brand_name' => $model["info"]['brand_name'], - 'series_name' => $model["info"]['series_name'], - 'year' => $model["info"]['year'], - 'msrp' => $official_price, - 'car_id' => $model["info"]['id'], - ]; - } - return array_values($models); + return $this->crawlSeriesModel($seriesId); } catch (\Exception $e) { throw new \Exception($e->getMessage()); } @@ -296,7 +254,150 @@ class BrandSeriesService // 验证系列是否存在 $series = BrandSeries::where('id', $series_id)->first(); if (!$series) { - throw new \Exception("系列ID {$series_id} 不存在"); + throw new \Exception("车辆系列未收录,联系管理员处理"); + } + // 获取品牌信息 + $brand = Brand::where('id', $series->brand_id)->first(); + if (!$brand) { + throw new \Exception("车辆品牌未收录,联系管理员处理"); + } + + // 保存车型数据 + $createdCount = 0; + $repeat = 0; + $fail = 0; + $msg = ''; + + foreach ($models as $item) { + $modelData = [ + 'brand_code' => $brand->brand_code, + 'brand_name' => $brand->brand_name, + 'series_code' => $series->series_code, + 'series_name' => $series->series_name, + 'model_name' => $item['title'], + 'model_year' => $item['year'], + 'msrp' => $item['msrp'], + ]; + //指纹数组 + $fingerprintArr = [ + 'brand_name' => $modelData['brand_name'], + 'series_name' => $modelData['series_name'], + 'model_name' => $modelData['model_name'], + 'model_year' => $modelData['model_year'], + 'msrp' => $modelData['msrp'], + ]; + // 生成指纹 + $modelData['fingerprint'] = Fingerprint::makeFromArray($fingerprintArr); + + $modelData['model_code'] = ModelNaming::buildModelCode($modelData); + $modelData['sku_code'] = SkuCodeBuilder::build($modelData); + + try { + $model = VehVehicleSku::where('brand_name', $brand->brand_name)->where('series_name', $series->series_name)->where('model_name', $item['title'])->first(); + if (!empty($model)) { + $repeat++; + continue; + } + CrudUtil::create(VehVehicleSku::class, $modelData); + $createdCount++; + } catch (\Exception $e) { + // 记录错误但继续处理其他车型 + $msg .= "创建车型失败: " . $e->getMessage() . "\n"; + Log::error("创建车型失败:" . $msg, ['series_id' => $series_id, 'model' => $item]); + $fail++; + } + } + + Log::info('series-crawl-' . $series_id . ' ' . $msg); + $msg = '创建车型成功' . $createdCount . '条'; + $msg .= (!empty($repeat)) ? ',重复' . $repeat . '条' : ''; + $msg .= (!empty($fail)) ? ',失败' . $fail . '条' : ''; + return $msg; + } + + /** + * 抓取车型数据(通过接口) + * @param Request $request + * @return array + * @throws \Exception + */ + public function crawlKeyword(Request $request): array + { + $keyword = $request->get('keyword'); + + // 执行抓取 + $crawlService = new CrawlService(); + + //懂车帝模糊搜索返回车系地址 + $href = $crawlService->modelSearch($keyword); + $seriesId = $this->extractSeriesId($href); + + if (empty($seriesId)) { + throw new \Exception('抓取失败,未找到该系列车型'); + } + + //抓取品牌与系列 + $brandSeries = $crawlService->crawlBrandSeries($seriesId); + + + // 验证品牌是否存在 + $brand = Brand::where('brand_name', $brandSeries['brand'])->first(); + // 获取品牌信息 + $series = BrandSeries::where('series_name', $brandSeries['series'])->first(); + $result['brand'] = ['brand_name' => $brandSeries['brand'], 'exists' => $brand?1:0]; + if($brand){ + $result['brand']['id'] = $brand['id']; + } + $result['series'] = ['series_name' => $brandSeries['series'], 'exists' => $series?1:0]; + if($series){ + $result['series']['id'] = $series['id']; + if(empty($result['brand']['id'])){ + $result['brand']['id'] = $series['brand_id']; + } + } + + try { + //获取车型数据 + $result['model'] = $this->crawlSeriesModel($seriesId); + return $result; + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + } + + /** + * @param Request $request + * @return array|string + * @throws \Exception + */ + public function crawlKeywordCreate(Request $request) + { + $series_id = $request->input('series_id'); + $brand_id = $request->input('brand_id'); + $series_name = $request->input('series_name'); + $models = $request->input('models'); + + if(empty($brand_id)){ + throw new \Exception("请选择品牌"); + } + if(empty($series_id) && empty($series_name)){ + throw new \Exception("请选择车系"); + } + + if (empty($series_id)) { + $series = BrandSeries::where('brand_id', $brand_id)->where('series_name', $series_name)->first(); + if (empty($brandseries)) { //如果没有该车系则创建 + $seriesService = new \app\admin\service\BrandSeriesService(); + $seriesData = [ + 'brand_id' => $brand_id, + 'series_code' => $seriesService->generateUniqueModificationCode($brand_id), + 'series_name' => $series_name, + ]; + $series = CrudUtil::create(BrandSeries::class, $seriesData); + } + $series_id = $series->id; + }else{ + $series = BrandSeries::where('id', $series_id)->first(); } // 获取品牌信息 $brand = Brand::where('id', $series->brand_id)->first(); @@ -354,24 +455,61 @@ class BrandSeriesService return $msg; } - /** - * 从车型名中提取年份 - * @param string $carModel 车型名称 - * @return string|null 提取到的年份或null + * 获取车系下车型数据 + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException */ - function extractYearFromCarModel($carModel) + public function crawlSeriesModel($seriesId) { - // 匹配4位数字 + "款" 的模式 - if (preg_match('/(\d{4})款/', $carModel, $matches)) { - return $matches[1]; - } + $client = new Client([ + 'verify' => false, // 禁用 SSL 验证 + 'timeout' => 10 + ]); + try { + $response = $client->get('https://www.dongchedi.com/motor/pc/car/series/car_list', [ + 'query' => ['series_id' => $seriesId], + 'headers' => [ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + ] + ]); + $data = json_decode($response->getBody()->getContents(), true); + if($data['status'] != 0 || (!isset($data['data']) && !isset($data['data']['tab_list']))){ + Log::error('抓取车型数据失败' . $data['message'], $data); + throw new \Exception('抓取车型数据失败' . $data['message']); + } + $models = []; + foreach ($data['data']['tab_list'] as $item) { + if($item['tab_text'] == '在售' || $item['tab_text'] == '停售'){ + unset($item['data'][0]); + $models = array_merge($models, $item['data']); + } + } + foreach ($models as $key => $model) { + if(!isset($model["info"]['car_name']) || $model['type'] != "1115"){ + unset($models[$key]); + continue; + } + $official_price = 0; + if(!isset($model["info"]['official_price'])){ + $official_price = (int)(str_replace("万", "", $model["info"]['official_price_str']) * 10000) ?? 0; + }else{ + $official_price = $model["info"]['official_price'] * 10000; + } + $models[$key] = [ + 'title' => $model["info"]['year'] . '款 ' .$model["info"]['car_name'], + 'brand_name' => $model["info"]['brand_name'], + 'series_name' => $model["info"]['series_name'], + 'year' => $model["info"]['year'], + 'msrp' => $official_price, + 'car_id' => $model["info"]['id'], + ]; + } - // 匹配4位数字 + 空格或结尾的模式 - if (preg_match('/^(\d{4})(?=\s|$)/', $carModel, $matches)) { - return $matches[1]; - } + return array_values($models); - return null; + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } } } \ No newline at end of file diff --git a/app/common/controller/BrandSeriesController.php b/app/common/controller/BrandSeriesController.php index 00faa00..3cd32d3 100644 --- a/app/common/controller/BrandSeriesController.php +++ b/app/common/controller/BrandSeriesController.php @@ -48,7 +48,7 @@ class BrandSeriesController extends BaseController } /** - * 异步抓取系列车型 + * 抓取系列车型 * @param Request $request * @return \support\Response */ @@ -58,7 +58,7 @@ class BrandSeriesController extends BaseController if($result){ return $this->success($result,'抓取成功'); }else{ - return $this->error('抓取任务提交失败'); + return $this->error('抓取任务失败'); } } catch (\Exception $e) { return $this->error($e->getMessage()); @@ -66,7 +66,7 @@ class BrandSeriesController extends BaseController } /** - * 异步抓取系列车型 + * 保存抓取的系列车型 * @param Request $request * @return \support\Response */ @@ -82,4 +82,40 @@ class BrandSeriesController extends BaseController return $this->error($e->getMessage()); } } + + /** + * 关键词抓取品牌、车系、车型 + * @param Request $request + * @return \support\Response + */ + public function crawlKeyword(Request $request){ + try { + $result = $this->service->crawlKeyword($request); + if($result){ + return $this->success($result,'抓取成功'); + }else{ + return $this->error('抓取任务失败'); + } + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 保存抓取的系列车型 + * @param Request $request + * @return \support\Response + */ + public function crawlKeywordCreate(Request $request){ + try { + $result = $this->service->crawlKeywordCreate($request); + if($result){ + return $this->success($result,'成功'); + }else{ + return $this->error('创建失败'); + } + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/common/service/CrawlService.php b/app/common/service/CrawlService.php index a2c69d4..53e7026 100644 --- a/app/common/service/CrawlService.php +++ b/app/common/service/CrawlService.php @@ -73,4 +73,18 @@ class CrawlService }); return $href; } + + /** + * 关键词搜索的更多页面,通过面包屑抓取品牌与系列 + * @param $series + * @return mixed|string + */ + public function crawlBrandSeries($series) + { + $url = "https://www.dongchedi.com/auto/series/" . $series . "?anchorId=carModels"; + $ql = QueryList::get($url); + $result = $ql->find('div.breadcrumb_wrap__2GLeU span')->texts()->all(); + + return ['brand' => $result[2], 'series' => $result[3]]; + } } \ No newline at end of file diff --git a/app/common/service/StockOrdersService.php b/app/common/service/StockOrdersService.php index 9d5e4df..6da7a24 100644 --- a/app/common/service/StockOrdersService.php +++ b/app/common/service/StockOrdersService.php @@ -1114,5 +1114,17 @@ class StockOrdersService } - + public function serviceDestroy(Request $request, string $scene = 'destroy') + { + $config = $this->buildQueryConfig($request, $scene); + $item = $this->logicShow($config); + if (!$item) { + throw new BusinessException('数据不存在'); + } + if ($item->approval_status == 1 || $item->approval_status == 2) { + throw new BusinessException('审核中或者已审核的订单不能删除'); + } + $deleted = $this->logicDeleteBy($config); + return $this->postProcess(ucfirst($scene), $deleted); + } } \ No newline at end of file diff --git a/config/route.php b/config/route.php index c31e972..36492d9 100644 --- a/config/route.php +++ b/config/route.php @@ -109,6 +109,8 @@ Route::group('/common', function () { Route::any('/destroy', [app\common\controller\BrandSeriesController::class, 'destroy']); Route::any('/crawlModels', [app\common\controller\BrandSeriesController::class, 'crawlModels']); Route::any('/crawlModelCreate', [app\common\controller\BrandSeriesController::class, 'crawlModelCreate']); + Route::any('/crawlKeyword', [app\common\controller\BrandSeriesController::class, 'crawlKeyword']); + Route::any('/crawlKeywordCreate', [app\common\controller\BrandSeriesController::class, 'crawlKeywordCreate']); }); }); From 80fbf6c35a95dfcc60c47fe3fb551018b52eab6e Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 11 Nov 2025 16:46:22 +0800 Subject: [PATCH 5/6] Merge branch 'develop' # Conflicts: # config/route.php --- .gitignore | 5 +- .../FinanceReconDubaiFilesController.php | 33 ++ app/command/FinanceRecoCommand.php | 32 ++ app/common/controller/UploadController.php | 4 +- .../logic/FinanceReconDubaiFilesLogic.php | 17 + app/common/model/FinanceReconDubaiFiles.php | 49 +++ .../service/FinanceReconDubaiFilesService.php | 290 +++++++++++++++++ app/common/service/StockOrdersService.php | 40 +-- app/common/service/UploadService.php | 2 +- app/common/utils/excel/ExcelImportTrait.php | 133 ++++++++ app/common/utils/excel/ExcelReader.php | 292 ++++++++++++++++++ app/common/utils/pdf/template/stockOrder.stub | 22 +- .../FinanceReconDubaiFilesValidate.php | 29 ++ app/controller/IndexController.php | 30 +- config/oss.php | 10 + config/route.php | 10 + webman | 142 ++++----- 17 files changed, 1030 insertions(+), 110 deletions(-) create mode 100644 app/admin/controller/FinanceReconDubaiFilesController.php create mode 100644 app/command/FinanceRecoCommand.php create mode 100644 app/common/logic/FinanceReconDubaiFilesLogic.php create mode 100644 app/common/model/FinanceReconDubaiFiles.php create mode 100644 app/common/service/FinanceReconDubaiFilesService.php create mode 100644 app/common/utils/excel/ExcelImportTrait.php create mode 100644 app/common/utils/excel/ExcelReader.php create mode 100644 app/common/validate/FinanceReconDubaiFilesValidate.php create mode 100644 config/oss.php mode change 100644 => 100755 webman diff --git a/.gitignore b/.gitignore index fa572a1..c08e119 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ /tests/tmp /tests/.phpunit.result.cache /public/uploads -/webman \ No newline at end of file +/webman +/start.php +/windows.php +/bootstrap.php \ No newline at end of file diff --git a/app/admin/controller/FinanceReconDubaiFilesController.php b/app/admin/controller/FinanceReconDubaiFilesController.php new file mode 100644 index 0000000..cd85dce --- /dev/null +++ b/app/admin/controller/FinanceReconDubaiFilesController.php @@ -0,0 +1,33 @@ +service = new FinanceReconDubaiFilesService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + // protected function indexFormat($data) + // { + // if (isset($data['created_at'])) + // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + // if (isset($data['updated_at'])) + // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + // return $data; + // } +} \ No newline at end of file diff --git a/app/command/FinanceRecoCommand.php b/app/command/FinanceRecoCommand.php new file mode 100644 index 0000000..1406ab9 --- /dev/null +++ b/app/command/FinanceRecoCommand.php @@ -0,0 +1,32 @@ +generatePrivateUrl('fdesk_test/20251104/20251104_财务.xlsx');; + + $service = new \app\common\service\FinanceReconDubaiFilesService(); + $result = $service->dImport('fdesk_test/20251104/20251104_迪拜.xlsx'); + + return self::SUCCESS; + } +} \ No newline at end of file diff --git a/app/common/controller/UploadController.php b/app/common/controller/UploadController.php index a025666..800090a 100644 --- a/app/common/controller/UploadController.php +++ b/app/common/controller/UploadController.php @@ -91,7 +91,7 @@ class UploadController $mimes = $ossservice->getMimeTypes(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'zip', 'rar']); $result = Storage::adapter('oss') - ->path('x_server/' . $sceneUrl . date('Ymd')) + ->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd')) ->size(1024*1024*5) ->extYes($mimes) ->upload($file); @@ -132,7 +132,7 @@ class UploadController } $result = Storage::adapter('oss') - ->path('x_server/' . $sceneUrl . date('Ymd')) + ->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd')) ->size(1024*1024*5) ->extYes(['image/jpeg','image/png']) ->extNo(['image/gif']) diff --git a/app/common/logic/FinanceReconDubaiFilesLogic.php b/app/common/logic/FinanceReconDubaiFilesLogic.php new file mode 100644 index 0000000..360f23f --- /dev/null +++ b/app/common/logic/FinanceReconDubaiFilesLogic.php @@ -0,0 +1,17 @@ +generatePrivateUrl() 完成 + */ +class FinanceReconDubaiFilesService +{ + use CrudServiceTrait; + use ExcelImportTrait; // 复用通用导入方法 + + /** 指定当前要操作的模型(用于 CrudServiceTrait) */ + protected string $modelClass = FinanceReconDubaiFiles::class; + + /** 验证器 */ + protected $validator = FinanceReconDubaiFilesValidate::class; + + /** 获取验证器实例 */ + public function getValidator() + { + return new $this->validator; + } + + /** + * 写入前置:将上传组件的结构转为 *_url / *_name,并记录创建人 + */ + public function beforeServiceStore($data, Request $request) + { + $data['created_by'] = $request->user->uid ?? 0; + $data['caiwu_file_url'] = $data['caiwu_file']['url'] ?? ''; + $data['caiwu_file_name'] = $data['caiwu_file']['name'] ?? ''; + $data['dubai_file_url'] = $data['dubai_file']['url'] ?? ''; + $data['dubai_file_name'] = $data['dubai_file']['name'] ?? ''; + return $data; + } + + /* ============================================================ + * 一、M项目对账明细 导入配置(sheet: M项目对账明细) + * ============================================================ */ + + /** 中文表头 => 目标字段名 */ + protected array $headerMapM = [ + '车架号' => 'vin', + '开票价' => 'invoice_price', + '实际采购价' => 'actual_purchase_price', + '开票价和实际采购价差额' => 'invoice_vs_purchase_diff', + '车源中介费' => 'source_broker_fee', + '配件' => 'parts_fee', + '停车费' => 'parking_fee', + '保险费' => 'insurance_fee', + '车辆手续费' => 'vehicle_service_fee', + '检测费' => 'inspection_fee', + '购置税' => 'purchase_tax', + '汽车其他费用' => 'other_vehicle_fee', + '转移待出口' => 'transfer_to_export', + '国际运费' => 'freight_international', + '国内运费1' => 'freight_domestic_1', + '国内运费2' => 'freight_domestic_2', + '报关费及港杂费' => 'customs_port_fee', + '注销费' => 'cancellation_fee', + '车船税' => 'vehicle_vessel_tax', + '其他财务费用' => 'other_finance_fee', + '汽车小额杂费' => 'minor_vehicle_misc', + '上牌费' => 'registration_fee', + '其他保险费' => 'other_insurance_fee', + '油补' => 'fuel_subsidy', + '快递费' => 'express_fee', + '国内成本合计' => 'domestic_cost_total', + '退税额' => 'tax_refund_amount', + 'cif杰贝阿里成本价' => 'cif_jebel_ali_cost', + '范经理发车看板CIF' => 'fan_manager_cif', + '财务-范经理 差额' => 'finance_vs_fan_diff', + ]; + + /** 需要转为 decimal 的目标字段 */ + protected array $decimalFieldsM = [ + 'invoice_price','actual_purchase_price','invoice_vs_purchase_diff', + 'source_broker_fee','parts_fee','parking_fee','insurance_fee','vehicle_service_fee','inspection_fee', + 'purchase_tax','other_vehicle_fee','transfer_to_export', + 'freight_international','freight_domestic_1','freight_domestic_2','customs_port_fee', + 'cancellation_fee','vehicle_vessel_tax','other_finance_fee','minor_vehicle_misc', + 'registration_fee','other_insurance_fee','fuel_subsidy','express_fee', + 'domestic_cost_total','tax_refund_amount','cif_jebel_ali_cost','fan_manager_cif','finance_vs_fan_diff', + ]; + + /** + * M项目对账明细导入 + * + * @param string $file 可为“签名 URL”或“OSS 对象键” + * @param bool $isUrl 若传入的是已签名 URL,请设 true;若是对象键,设 false + * @param bool $insertToDb 是否立即入库 + * + * 返回:['sheet','total_rows','skipped','errors','to_import','inserted','preview_3',('error'?)] + */ + public function mImport(string $file, bool $isUrl = true, bool $insertToDb = true): array + { + $sheet = 'M项目对账明细'; + + // 允许传对象键:当 isUrl=false 且非 http(s) 时,自动用 OssService 签名 + if ($isUrl === false && !preg_match('#^https?://#i', $file)) { + $file = $this->ossSignedUrl($file); + $isUrl = true; + } + + $rows = $this->readSheetRows($file, $sheet, $isUrl, headerRow: 1); + $mapped = []; + $skipped = 0; + $errors = 0; + + foreach ($rows as $row) { + try { + if ($this->shouldSkipByVinAliases($row, ['车架号'])) { $skipped++; continue; } + $rec = $this->mapAndNormalizeRow($row, $this->headerMapM, $this->decimalFieldsM); + $this->stamp($rec); + $mapped[] = $rec; + } catch (\Throwable $e) { + // 单行异常不阻断全局导入 + $errors++; continue; + } + } + + $result = [ + 'sheet' => $sheet, + 'total_rows' => count($rows), + 'skipped' => $skipped, + 'errors' => $errors, + 'to_import' => count($mapped), + ]; + + if ($insertToDb) { + // 注意表名:按你的业务这里写入 finance_recon_m_detail + $ret = $this->batchInsert('finance_recon_m_detail', $mapped); + $result['inserted'] = $ret['inserted']; + if ($ret['error']) $result['error'] = 'Database insert error: ' . $ret['error']; + } else { + $result['inserted'] = 0; + } + + $result['preview_3'] = array_slice($mapped, 0, 3); + return $result; + } + + /** M项目对账明细:从 OSS 对象键导入(语义化便捷入口) */ + public function mImportFromOssKey(string $objectKey, bool $insertToDb = true): array + { + $signedUrl = $this->ossSignedUrl($objectKey); + return $this->mImport($signedUrl, true, $insertToDb); + } + + /** M项目对账明细:从已签名 URL 导入(语义化便捷入口) */ + public function mImportFromSignedUrl(string $signedUrl, bool $insertToDb = true): array + { + return $this->mImport($signedUrl, true, $insertToDb); + } + + /* ============================================================ + * 二、迪拜两表合并 导入配置(sheets: Stock in Jebel Ali / Under shipping) + * ============================================================ */ + + /** 表头映射(已兼容不一致列名:VIN NO/车架号、国际运费和杂费/SHIPPING COST) */ + protected array $headerMapDubai = [ + 'VIN NO' => 'vin', '车架号' => 'vin', + '开票价' => 'invoice_price', + '中介费' => 'broker_fee', + '购置税' => 'purchase_tax', + '退税' => 'tax_refund', + '不含税车价' => 'price_ex_vat', + '费用' => 'fee_total', + '国内运费' => 'domestic_freight', + 'FCA成本价' => 'fca_cost', + 'FCA成本价汇率' => 'fca_fx', + '国际运费和杂费' => 'shipping_cost', + 'SHIPPING COST' => 'shipping_cost', + '运费汇率' => 'shipping_fx', + 'CIF 杰贝阿里成本价' => 'cif_jebel_ali_cost', + 'CIF RMB' => 'cif_rmb', // Under shipping 没有该列,将在逻辑中推导 + ]; + + /** 需要转为 decimal 的目标字段(汇率字段使用 DECIMAL(18,6) 建表) */ + protected array $decimalFieldsDubai = [ + 'invoice_price','broker_fee','purchase_tax','tax_refund','price_ex_vat', + 'fee_total','domestic_freight','fca_cost','fca_fx', + 'shipping_cost','shipping_fx','cif_jebel_ali_cost','cif_rmb', + ]; + + /** + * 迪拜两表合并导入(Stock in Jebel Ali / Under shipping) + * + * 缺失值规则: + * - Under shipping 无 “CIF RMB”,尝试:cif_rmb = cif_jebel_ali_cost × shipping_fx + * 若运费汇率缺失则用 fca_fx 兜底,都没有则置 NULL。 + * + * 返回:['sheets','total_rows','skipped','to_import','inserted','preview_5',('error'?)] + */ + public function dImport(string $file, bool $isUrl = false, bool $insertToDb = true): array + { + // 允许传对象键:当 isUrl=false 且非 http(s) 时,自动用 OssService 签名 + if ($isUrl === false && !preg_match('#^https?://#i', $file)) { + $file = $this->ossSignedUrl($file); + $isUrl = true; + } + + $sheets = ['Stock in Jebel Ali', 'Under shipping']; + $parsed = []; + + // 逐 Sheet 读取 + foreach ($sheets as $sheet) { + $parsed[$sheet] = $this->readSheetRows($file, $sheet, $isUrl, headerRow: 1); + } + + // 行处理与合并 + $rowsAll = []; + $skipped = 0; + + foreach ($sheets as $sheet) { + foreach ($parsed[$sheet] as $row) { + if ($this->shouldSkipByVinAliases($row, ['VIN NO','车架号'])) { $skipped++; continue; } + + // 映射 + 数值规范化 + $rec = $this->mapAndNormalizeRow($row, $this->headerMapDubai, $this->decimalFieldsDubai); + + // Under shipping 无 CIF RMB:尝试用汇率推导 + if (empty($rec['cif_rmb'])) { + $cif = $rec['cif_jebel_ali_cost'] ?? null; + $fx = $rec['shipping_fx'] ?? null; + if ($cif !== null && $fx !== null) { + $rec['cif_rmb'] = round($cif * $fx, 2); + } elseif ($cif !== null && !empty($rec['fca_fx'])) { + $rec['cif_rmb'] = round($cif * (float)$rec['fca_fx'], 2); + } else { + $rec['cif_rmb'] = null; + } + } + + // 统一 VIN 与来源 + $rec['vin'] = $this->pickFirstNonEmpty($row, ['VIN NO','车架号']); + $rec['source_sheet'] = $sheet; + + $this->stamp($rec); + $rowsAll[] = $rec; + } + } + + // 结果与入库 + $result = [ + 'sheets' => $sheets, + 'total_rows' => array_sum(array_map('count', $parsed)), + 'skipped' => $skipped, + 'to_import' => count($rowsAll), + ]; + + if ($insertToDb) { + // 注意表名:按你的业务这里写入 finance_recon_d_detail + $ret = $this->batchInsert('finance_recon_d_detail', $rowsAll); + $result['inserted'] = $ret['inserted']; + if ($ret['error']) $result['error'] = 'Database insert error: ' . $ret['error']; + } else { + $result['inserted'] = 0; + } + + $result['preview_5'] = array_slice($rowsAll, 0, 5); + return $result; + } + + /** 迪拜两表:从 OSS 对象键导入(语义化便捷入口) */ + public function dImportFromOssKey(string $objectKey, bool $insertToDb = true): array + { + $signedUrl = $this->ossSignedUrl($objectKey); + return $this->dImport($signedUrl, true, $insertToDb); + } + + /** 迪拜两表:从已签名 URL 导入(语义化便捷入口) */ + public function dImportFromSignedUrl(string $signedUrl, bool $insertToDb = true): array + { + return $this->dImport($signedUrl, true, $insertToDb); + } +} \ No newline at end of file diff --git a/app/common/service/StockOrdersService.php b/app/common/service/StockOrdersService.php index 6da7a24..7ee30a4 100644 --- a/app/common/service/StockOrdersService.php +++ b/app/common/service/StockOrdersService.php @@ -655,7 +655,7 @@ class StockOrdersService */ private function generateVehicleUniqueId(array $veh): string { - $keysToRemove = ['vin', 'remark', 'modify_list', 'order_id']; + $keysToRemove = ['remark', 'modify_list', 'order_id']; $array = array_diff_key($veh, array_flip($keysToRemove)); $uniqueIdStr = json_encode($array); return md5($uniqueIdStr); @@ -884,6 +884,7 @@ class StockOrdersService 'num' => 1, 'mileage' => $v->mileage, 'remark' => $v->remark, + 'vin' => $v->vin, ] + $moneyValues; } return ['list' => $vehList]; @@ -925,25 +926,26 @@ class StockOrdersService { $vehicleData['num'] += 1; - $fieldList = [ - 'fob_transport_fee', - 'car_price', - 'ocean_freight', - 'commission', - 'retention_price', - 'port_misc_fee', - 'total_price', - 'clearance_price', - 'profit' - ]; + // ** 11.11 remove 价格*数量 + // $fieldList = [ + // 'fob_transport_fee', + // 'car_price', + // 'ocean_freight', + // 'commission', + // 'retention_price', + // 'port_misc_fee', + // 'total_price', + // 'clearance_price', + // 'profit' + // ]; - foreach ($vehicleData as $field => $value) { - if (in_array($field, $fieldList)) { - $amount = $vehicleData[$field]['amount'] * $vehicleData['num']; - $currencySymbol = SysCurrency::where('currency_code', $vehicleData[$field]['currency'])->value('currency_symbol'); - $vehicleData[$field]['currency_info'] = $currencySymbol . number_format($amount, 2, '.', ','); - } - } + // foreach ($vehicleData as $field => $value) { + // if (in_array($field, $fieldList)) { + // $amount = $vehicleData[$field]['amount'] * $vehicleData['num']; + // $currencySymbol = SysCurrency::where('currency_code', $vehicleData[$field]['currency'])->value('currency_symbol'); + // $vehicleData[$field]['currency_info'] = $currencySymbol . number_format($amount, 2, '.', ','); + // } + // } } /** diff --git a/app/common/service/UploadService.php b/app/common/service/UploadService.php index 43684cf..62b838c 100644 --- a/app/common/service/UploadService.php +++ b/app/common/service/UploadService.php @@ -50,7 +50,7 @@ class UploadService $size = filesize($filePath) ?: 0; $sceneUrl = $scene ? ($scene . '/') : ''; - $dir = 'x_server/' . $sceneUrl . date('Ymd') . '/'; + $dir = config('oss.oss_main_dir') . $sceneUrl . date('Ymd') . '/'; // 使用传入的 original_name 命名(缺少扩展名时补上推断的扩展名),并做简单安全处理 $safeBaseName = preg_replace('/[\\\\\/]+/', '_', $originalName); if ($extension && strtolower(pathinfo($safeBaseName, PATHINFO_EXTENSION)) !== strtolower($extension)) { diff --git a/app/common/utils/excel/ExcelImportTrait.php b/app/common/utils/excel/ExcelImportTrait.php new file mode 100644 index 0000000..79fffa4 --- /dev/null +++ b/app/common/utils/excel/ExcelImportTrait.php @@ -0,0 +1,133 @@ +ossSignedUrl($objectKey); + $ret = ExcelReader::readSheetFromUrl($signedUrl, $sheet, $headerRow, $startRow); + return $ret['rows'] ?? []; + } + + /** + * 使用你的 OssService 生成私有签名 URL + * - 兼容你的现有写法:(new \app\common\service\OssService)->generatePrivateUrl($objectKey) + */ + protected function ossSignedUrl(string $objectKey): string + { + return (new \app\common\service\OssService())->generatePrivateUrl($objectKey); + } + + /** + * 将一行做字段映射 + 基础类型规范化(decimal/bool/date) + * - $headerMap:表头映射(源列名 => 目标字段名) + * - $decimalFields/$boolFields/$dateFields:需要做类型规范化的目标字段名列表 + */ + protected function mapAndNormalizeRow( + array $row, + array $headerMap, + array $decimalFields = [], + array $boolFields = [], + array $dateFields = [] + ): array { + $rec = ExcelReader::remap($row, $headerMap); + + foreach ($decimalFields as $f) { + if (array_key_exists($f, $rec)) { + $rec[$f] = ExcelReader::toDecimal($rec[$f]); // 建议已升级为“去货币符号/千分位/括号负数”的版本 + } + } + foreach ($boolFields as $f) { + if (array_key_exists($f, $rec)) { + $rec[$f] = ExcelReader::toBool($rec[$f]); + } + } + foreach ($dateFields as $f) { + if (array_key_exists($f, $rec)) { + $rec[$f] = ExcelReader::toDate($rec[$f]); + } + } + return $rec; + } + + /** + * 基于 VIN 的跳过规则:VIN 为空、或是“合计/总计”行 + * - $vinAliases:VIN 列的候选表头(如 ['车架号','VIN NO']) + */ + protected function shouldSkipByVinAliases(array $row, array $vinAliases = ['车架号', 'VIN NO']): bool + { + $vin = $this->pickFirstNonEmpty($row, $vinAliases); + if ($vin === null) return true; + return (bool)preg_match('/^(合计|总计)/u', $vin); + } + + /** + * 在多个候选键里取第一个非空字符串 + */ + protected function pickFirstNonEmpty(array $row, array $keys): ?string + { + foreach ($keys as $k) { + if (isset($row[$k])) { + $v = trim((string)$row[$k]); + if ($v !== '') return $v; + } + } + return null; + } + + /** + * 统一补齐 created_at / updated_at 时间戳(int(11)) + */ + protected function stamp(array &$rec): void + { + $now = time(); + $rec['created_at'] = $now; + $rec['updated_at'] = $now; + } + + /** + * 批量入库 + * - 返回 ['inserted'=>N, 'error'=>null|string] + * - 发生异常时不抛出,返回 error 字符串,便于上层统一处理 + */ + protected function batchInsert(string $table, array $rows): array + { + if (!$rows) return ['inserted' => 0, 'error' => null]; + try { + Db::table($table)->insert($rows); + return ['inserted' => count($rows), 'error' => null]; + } catch (\Throwable $e) { + return ['inserted' => 0, 'error' => $e->getMessage()]; + } + } +} diff --git a/app/common/utils/excel/ExcelReader.php b/app/common/utils/excel/ExcelReader.php new file mode 100644 index 0000000..e5f002a --- /dev/null +++ b/app/common/utils/excel/ExcelReader.php @@ -0,0 +1,292 @@ +[], 'rows'=>[]] + * - $headerRow:从 1 开始计数(Excel 行号) + * - $startRow:数据起始行(默认 headerRow+1) + * - 注意:$filePath 必须是“本地文件路径”;若为 URL,请改用 readSheetFromUrl() + */ + public static function readSheet(string $filePath, string $sheetName, int $headerRow = 1, ?int $startRow = null): array + { + self::assertRequirements(); + + if (!is_file($filePath)) { + throw new \RuntimeException("File not found: {$filePath}"); + } + + $reader = IOFactory::createReaderForFile($filePath); + $reader->setReadDataOnly(true); + $spreadsheet = $reader->load($filePath); + + $sheet = $spreadsheet->getSheetByName($sheetName); + if (!$sheet) { + $names = implode(' / ', $spreadsheet->getSheetNames()); + $spreadsheet->disconnectWorksheets(); + throw new \RuntimeException("Sheet '{$sheetName}' not found. Available: {$names}"); + } + + $highestRow = $sheet->getHighestDataRow(); + $highestCol = $sheet->getHighestDataColumn(); + $highestColIndex = Coordinate::columnIndexFromString($highestCol); + + // 读取表头(去除可能的 UTF-8 BOM) + $headers = []; + for ($col = 1; $col <= $highestColIndex; $col++) { + $coord = Coordinate::stringFromColumnIndex($col) . $headerRow; + $cell = $sheet->getCell($coord); + $raw = $cell ? $cell->getCalculatedValue() : null; + $text = is_null($raw) ? '' : trim((string)$raw); + // 去掉 BOM + if ($col === 1) { + $text = preg_replace('/^\xEF\xBB\xBF/u', '', $text); + } + $headers[$col] = $text === '' ? "COL_{$col}" : $text; + } + + // 读取数据 + $rows = []; + $firstDataRow = $startRow ?? ($headerRow + 1); + for ($row = $firstDataRow; $row <= $highestRow; $row++) { + $record = []; + $isEmpty = true; + + for ($col = 1; $col <= $highestColIndex; $col++) { + $coord = Coordinate::stringFromColumnIndex($col) . $row; + $cell = $sheet->getCell($coord); + $value = $cell ? $cell->getCalculatedValue() : null; + + // 日期单元格转换(使用 PhpSpreadsheet 的日期检测) + if ($cell && \PhpOffice\PhpSpreadsheet\Shared\Date::isDateTime($cell) && is_numeric($value)) { + $dt = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value); + $value = $dt ? $dt->format('Y-m-d') : $value; + } + + if (is_string($value)) { + $value = trim($value); + } + if ($value !== null && $value !== '') { + $isEmpty = false; + } + $record[$headers[$col]] = $value; + } + + if (!$isEmpty) { + $rows[] = $record; + } + } + + $spreadsheet->disconnectWorksheets(); + return ['headers' => array_values($headers), 'rows' => $rows]; + } + + /** + * 从 URL 读取指定 sheet(内部先下载到临时文件) + */ + public static function readSheetFromUrl(string $url, string $sheetName, int $headerRow = 1, ?int $startRow = null): array + { + $tmp = self::downloadToTemp($url); + try { + return self::readSheet($tmp, $sheetName, $headerRow, $startRow); + } finally { + @unlink($tmp); + } + } + + /** + * 按映射表将“源表头”=>“目标字段名”(snake_case) + * $map 形如:['车架号' => 'vin', '实际采购价' => 'actual_purchase_price', ...] + */ + public static function remap(array $row, array $map): array + { + $out = []; + foreach ($map as $from => $to) { + $out[$to] = $row[$from] ?? null; + } + return $out; + } + + /** 将一批行做 remap */ + public static function remapRows(array $rows, array $map): array + { + $out = []; + foreach ($rows as $row) { + $out[] = self::remap($row, $map); + } + return $out; + } + + /** + * 将“金额字符串”解析成 float(适配多币种/多地区写法) + * - 支持:¥ ¥ $ € £ …、AED/Dirham:dh / dhs / aed、阿语 د.إ + * - 支持:中文/全角标点、阿拉伯小数/千分位、细空格、撇号千分位 + * - 支持:括号负数 (xxx) 与显式负号 + * - 返回 null 表示无法解析 + */ + public static function toDecimal($v): ?float + { + if ($v === null || $v === '') return null; + if (is_numeric($v)) return (float)$v; + + $s = trim((string)$v); + + // 1) 统一常见 unicode 标点(全角/阿拉伯)与空格 + $s = strtr($s, [ + ',' => ',', '.' => '.', + '٫' => '.', '٬' => ',', + "\xC2\xA0" => ' ', // NBSP + "\xE2\x80\xAF" => ' ', // NNBSP(窄不换行空格) + ]); + + // 2) 括号负数,如 (dh1,234.56) + $negative = false; + if (preg_match('/^\s*\((.*)\)\s*$/u', $s, $m)) { + $negative = true; + $s = $m[1]; + } + + // 3) 去币种/符号:AED/Dirham(dh/dhs/aed/د.إ)& 其它常见符号/字母 + $s = preg_replace('/\b(dhs?|aed)\b/i', '', $s); // dh / dhs / aed + $s = str_replace(['د.إ', 'د', 'إ'], '', $s); // 常见阿语形式简化处理 + $s = preg_replace('/[A-Za-z¥¥$€£₽₺₩₹﷼]+/u', '', $s); // 其它字母/货币符号 + + // 4) 只保留 数字/点/逗号/减号/空格/撇号,然后去掉空格与撇号(千分位) + $s = preg_replace('/[^\d\.,\-\s\']+/u', '', $s); + $s = str_replace([' ', "'"], '', $s); + + // 5) 统一小数分隔符(判断最后出现的分隔符) + $lastComma = strrpos($s, ','); + $lastDot = strrpos($s, '.'); + + if ($lastComma !== false && $lastDot !== false) { + // 同时存在,最后出现者视为小数点 + if ($lastComma > $lastDot) { + // 欧式 1.234,56 + $s = str_replace('.', '', $s); // 去千分点 + $s = substr_replace($s, '.', $lastComma, 1); // 最后一个逗号改为小数点 + $s = str_replace(',', '', $s); // 其余逗号清空 + } else { + // 美式 1,234.56 + $s = str_replace(',', '', $s); + } + } elseif ($lastComma !== false) { + // 只有逗号 + $digitsAfter = strlen($s) - $lastComma - 1; + if (substr_count($s, ',') > 1) { + // 多个逗号基本都是千分位 + $s = str_replace(',', '', $s); + } else { + // 一个逗号:若逗号后 1~2 位,视为小数点,否则千分位 + $s = ($digitsAfter >= 1 && $digitsAfter <= 2) + ? str_replace(',', '.', $s) + : str_replace(',', '', $s); + } + } elseif ($lastDot !== false && substr_count($s, '.') > 1) { + // 多个点:保留最后一个为小数点,其他去掉(当作千分位) + $digitsAfter = strlen($s) - $lastDot - 1; + if ($digitsAfter >= 1 && $digitsAfter <= 2) { + $s = preg_replace('/\.(?=.*\.)/', '', $s); // 移除除最后一个外的点 + } else { + $s = str_replace('.', '', $s); + } + } + + // 6) 处理显式负号 + if (strpos($s, '-') !== false) { + $negative = true; + $s = str_replace('-', '', $s); + } + + // 7) 最终校验并转 float + if ($s === '' || !preg_match('/^\d+(\.\d+)?$/', $s)) { + return null; // 无法解析 + } + + $num = (float)$s; + return $negative ? -$num : $num; + } + + /** 规范化布尔:'是/否'、'Y/N'、'1/0'、'true/false' */ + public static function toBool($v): ?int + { + if ($v === null || $v === '') return null; + $s = mb_strtolower(trim((string)$v)); + if (in_array($s, ['是','y','yes','true','1','√','✅'], true)) return 1; + if (in_array($s, ['否','n','no','false','0','×','❌'], true)) return 0; + return null; + } + + /** 规范化日期(readSheet 已对 Excel 日期做过处理,这里再兜底) */ + public static function toDate($v): ?string + { + if ($v === null || $v === '') return null; + if (is_numeric($v)) { + // Excel 序列号兜底 + $ts = ExcelDate::excelToTimestamp($v); + return $ts ? date('Y-m-d', $ts) : null; + } + // 简单清洗 + $s = trim((string)$v); + // 常见格式直接返回(如 2025-11-04 / 2025/11/04) + if (preg_match('/^\d{4}[-\/]\d{1,2}[-\/]\d{1,2}$/', $s)) { + return str_replace('/', '-', $s); + } + return $s; // 留给上层决定是否再严格校验 + } + + /** + * 把远程 URL 流式下载到临时文件;返回临时路径 + */ + protected static function downloadToTemp(string $url, ?string $ext = null): string + { + $ext = $ext ? ('.' . ltrim($ext, '.')) : ''; + $tmp = sys_get_temp_dir() . '/excel_' . uniqid() . $ext; + $fp = fopen($tmp, 'w'); + if (!$fp) { + throw new \RuntimeException("Cannot open temp file: {$tmp}"); + } + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_FILE => $fp, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_TIMEOUT => 300, + CURLOPT_CONNECTTIMEOUT => 15, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_USERAGENT => 'Webman-ExcelReader/1.0', + ]); + $ok = curl_exec($ch); + $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $err = curl_error($ch); + curl_close($ch); + fclose($fp); + if ($ok === false || $http >= 400) { + @unlink($tmp); + throw new \RuntimeException("Download failed: HTTP {$http} {$err}"); + } + return $tmp; + } +} diff --git a/app/common/utils/pdf/template/stockOrder.stub b/app/common/utils/pdf/template/stockOrder.stub index 697b8eb..807c3e3 100644 --- a/app/common/utils/pdf/template/stockOrder.stub +++ b/app/common/utils/pdf/template/stockOrder.stub @@ -89,7 +89,7 @@
-

合塑实业-汽车事业部-备货单(客户下定)

+

合塑实业-汽车事业部-备货单

备货单号: 业务员: @@ -126,7 +126,7 @@ 车型 数量 颜色 - 费用明细 + 费用明细 @@ -142,9 +142,12 @@ - +

0): ?> - (km) + 里程:km +
+ + VIN: @@ -160,6 +163,7 @@ + 手续费 改装费 配件费 收款价 @@ -171,6 +175,7 @@ + @@ -191,6 +196,7 @@ + 加价 报关价 FOB运费 车价 @@ -202,6 +208,7 @@ + @@ -214,7 +221,7 @@ 备注 - + @@ -231,7 +238,7 @@ 合同汇率: - + 合计到岸价: @@ -261,7 +268,8 @@ 出运方式: 国内收货港: - 贸易条款/清关目的港: / + 贸易条款: + 清关目的港: 提车日期: diff --git a/app/common/validate/FinanceReconDubaiFilesValidate.php b/app/common/validate/FinanceReconDubaiFilesValidate.php new file mode 100644 index 0000000..193b230 --- /dev/null +++ b/app/common/validate/FinanceReconDubaiFilesValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 72532c7..c3c75ae 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -15,9 +15,9 @@ use GuzzleHttp\Client; class IndexController { - protected $noNeedLogin = ['index', 'view', 'perm', 'createResource', 'test']; + protected $noNeedLogin = ['index', 'view', 'perm', 'createResource', 'pdf', 'test']; - protected $noNeedHeaderData = ['index', 'view', 'perm', 'createResource', 'test']; + protected $noNeedHeaderData = ['index', 'view', 'perm', 'createResource', 'pdf', 'test']; public function index(Request $request) { @@ -74,25 +74,25 @@ class IndexController $options = [ 'controller' => [ 'module' => 'common', - 'name' => 'stock_orders_files' + 'name' => 'finance_recon_dubai_files' ], 'model' => [ 'module' => 'common', - 'name' => 'stock_orders_files' + 'name' => 'finance_recon_dubai_files' ], 'service' => [ 'module' => 'common', - 'name' => 'stock_orders_files' + 'name' => 'finance_recon_dubai_files' ], 'logic' => [ 'module' => 'common', - 'name' => 'stock_orders_files' + 'name' => 'finance_recon_dubai_files' ], 'validate' => [ 'module' => 'common', - 'name' => 'stock_orders_files' + 'name' => 'finance_recon_dubai_files' ], - 'table' => 'stock_orders_files' + 'table' => 'finance_recon_dubai_files' ]; $result = (new Generate($options))->doGenerate(); @@ -114,7 +114,7 @@ class IndexController } - public function test(Request $request) + public function pdf(Request $request) { $id = $request->get('id'); $service = new \app\common\service\StockOrdersService(); @@ -154,4 +154,16 @@ class IndexController $data = array('header' => $header,'body' => $response); return $data; } + + + + public function test(Request $request) + { + $tmp = (new \app\common\service\OssService)->generatePrivateUrl('fdesk_test/20251104/20251104_财务.xlsx');; + + $service = new \app\common\service\FinanceRecoDubaiFilesService(); + $result = $service->import($tmp, true); + + return json(['code' => 0, 'msg' => 'ok', 'data' => $result]); + } } \ No newline at end of file diff --git a/config/oss.php b/config/oss.php new file mode 100644 index 0000000..bb03fc7 --- /dev/null +++ b/config/oss.php @@ -0,0 +1,10 @@ + env('OSS_ACCESSID'), + 'oss_accesssecret' => env('OSS_ACCESSSECRET'), + 'oss_bucket' => env('OSS_BUCKET'), + 'oss_endpoint' => env('OSS_ENDPOINT'), + 'oss_static_url' => env('OSS_STATIC_URL'), + 'oss_main_dir' => env('OSS_MAIN_DIR') +]; \ No newline at end of file diff --git a/config/route.php b/config/route.php index 36492d9..c49f9dc 100644 --- a/config/route.php +++ b/config/route.php @@ -472,6 +472,16 @@ Route::group('/admin', function () { Route::any('/claim', [app\admin\controller\dashboard\OfficeClerkController::class, 'claim']); }); }); + + Route::group('/finance', function () { + Route::group('/dubai', function () { + Route::any('/list', [app\admin\controller\FinanceReconDubaiFilesController::class, 'index']); + Route::any('/show', [app\admin\controller\FinanceReconDubaiFilesController::class, 'show']); + Route::any('/store', [app\admin\controller\FinanceReconDubaiFilesController::class, 'store']); + Route::any('/update', [app\admin\controller\FinanceReconDubaiFilesController::class, 'update']); + Route::any('/destroy', [app\admin\controller\FinanceReconDubaiFilesController::class, 'destroy']); + }); + }); }); diff --git a/webman b/webman old mode 100644 new mode 100755 index c853676..3f55062 --- a/webman +++ b/webman @@ -1,71 +1,71 @@ -#!/usr/bin/env php -load(); - } else { - Dotenv::createMutable(run_path())->load(); - } -} - -$appConfig = require $appConfigFile; -if ($timezone = $appConfig['default_timezone'] ?? '') { - date_default_timezone_set($timezone); -} - -if ($errorReporting = $appConfig['error_reporting'] ?? '') { - error_reporting($errorReporting); -} - -if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) { - require_once __DIR__ . '/support/bootstrap.php'; -} else { - if (class_exists('Support\App')) { - Support\App::loadAllConfig(['route']); - } else { - Config::reload(config_path(), ['route', 'container']); - } -} - -$cli = new Command(); -$cli->setName('webman cli'); -$cli->installInternalCommands(); -if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) { - $cli->installCommands($command_path); -} - -foreach (config('plugin', []) as $firm => $projects) { - if (isset($projects['app'])) { - foreach (['', '/app'] as $app) { - if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) { - $command_path = base_path() . "/plugin/$firm{$app}/$command_str"; - $cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str"); - } - } - } - foreach ($projects as $name => $project) { - if (!is_array($project)) { - continue; - } - $project['command'] ??= []; - array_walk($project['command'], [$cli, 'createCommandInstance']); - } -} - -$cli->run(); +#!/usr/bin/env php +load(); + } else { + Dotenv::createMutable(run_path())->load(); + } +} + +$appConfig = require $appConfigFile; +if ($timezone = $appConfig['default_timezone'] ?? '') { + date_default_timezone_set($timezone); +} + +if ($errorReporting = $appConfig['error_reporting'] ?? '') { + error_reporting($errorReporting); +} + +if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) { + require_once __DIR__ . '/support/bootstrap.php'; +} else { + if (class_exists('Support\App')) { + Support\App::loadAllConfig(['route']); + } else { + Config::reload(config_path(), ['route', 'container']); + } +} + +$cli = new Command(); +$cli->setName('webman cli'); +$cli->installInternalCommands(); +if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) { + $cli->installCommands($command_path); +} + +foreach (config('plugin', []) as $firm => $projects) { + if (isset($projects['app'])) { + foreach (['', '/app'] as $app) { + if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) { + $command_path = base_path() . "/plugin/$firm{$app}/$command_str"; + $cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str"); + } + } + } + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + $project['command'] ??= []; + array_walk($project['command'], [$cli, 'createCommandInstance']); + } +} + +$cli->run(); From 8b3778e83e4b4fe203331e380976e13745875ab4 Mon Sep 17 00:00:00 2001 From: mzl Date: Thu, 13 Nov 2025 10:06:19 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/service/UserService.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 6ea31bd..5bd60cf 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -159,14 +159,10 @@ class UserService /** 分页列表 */ public function serviceTeam(Request $request) { - if (method_exists($this, 'complexQuery')) { - $result = $this->complexQuery($request->get()); - } else { - $config = $this->buildQueryConfig($request, 'team'); - $limit = (int)$request->get('size', 15); - $page = (int)$request->get('current', 1); - $result = $this->logicList($config, $limit, $page); - } + $config = $this->buildQueryConfig($request, 'team'); + $limit = (int)$request->get('size', 15); + $page = (int)$request->get('current', 1); + $result = $this->logicList($config, $limit, $page); foreach ($result as &$data){ $data['customer_count'] = Customer::where('user_id', $data['id'])->count();