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..6901082 --- /dev/null +++ b/app/admin/controller/dashboard/FinStatisticsController.php @@ -0,0 +1,134 @@ +get(); + $query = FinReceipt::where('payment_apply_id','>',0); + + $result = []; + //已认领数量 + $result['claimed_num'] = $query->count(); + //已认领金额 +// $result['claimed_amount'] = $query->sum('amount'); + $result['claimed_amount'] = array_values($this->receiptStatisticsSum($query, $currency)); //各币种的已认领金额 + $result['claimed_amount_sum'] =$this->amountSum($result['claimed_amount']); //已认领的人民币总金额 + + //未入账数量 + $query2 = FinReceipt::whereNull('received_at'); + $result['unreceived_num'] = $query2->count(); + //未入账金额 +// $result['unreceived_amount'] = $query2->sum('amount'); + $result['unreceived_amount'] = array_values($this->receiptStatisticsSum($query2, $currency)); //各币种的未入账金额 + $result['unreceived_amount_sum'] = $this->amountSum($result['unreceived_amount']); //未入账的已认领的人民币总金额 + + //今日入账 + $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'] = $query3->count(); + //未认领金额 +// $result['unclaimed_amount'] = $query3->sum('amount'); + $result['unclaimed_amount'] = $this->receiptStatisticsSum($query3, $currency); //各币种的未认领金额 + $result['unclaimed_amount_sum'] = $this->amountSum($result['unclaimed_amount']); //未认领的已认领的人民币总金额 + + //已超时未认领数量 + $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/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..98c49c2 --- /dev/null +++ b/app/common/controller/FinPaymentApplyController.php @@ -0,0 +1,181 @@ +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 = []; + $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; + } + + /** + * 数据格式化 + * + * @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; + } + + /** + * 作废 + * @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 new file mode 100644 index 0000000..4c73b7a --- /dev/null +++ b/app/common/controller/FinReceiptController.php @@ -0,0 +1,195 @@ +service = new FinReceiptService(); + } + + /** + * 确认入账 + * @param Request $request + * @return \support\Response + */ + public function credit(Request $request) + { + try { + $id = $request->input('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()); + } + } + + 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['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'])){ + $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')??''; + } + 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/controller/UploadController.php b/app/common/controller/UploadController.php index d90c84a..a025666 100644 --- a/app/common/controller/UploadController.php +++ b/app/common/controller/UploadController.php @@ -87,12 +87,15 @@ class UploadController } $originalName = $file->getUploadName(); + $ossservice = new OssService(); + $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')) ->size(1024*1024*5) + ->extYes($mimes) ->upload($file); - - $ossservice = new OssService(); + $data = [ 'original_name' => $originalName, 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($data); + } +} \ 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..af52c85 --- /dev/null +++ b/app/common/model/FinPaymentApply.php @@ -0,0 +1,76 @@ +hasOne(FinReceipt::class, 'payment_apply_id', 'id'); + } + + /** + * 关联的备货单(通过中间表) + */ + public function stocks() + { + 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/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..88d3090 --- /dev/null +++ b/app/common/model/FinReceipt.php @@ -0,0 +1,81 @@ +belongsTo(FinPaymentApply::class, 'payment_apply_id', 'id'); + } + + /** + * 关联的备货单(通过中间表) + */ + public function stocks() + { + 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/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; + } + + + 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位字母数字组合 + * + * @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'] == '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); + } + + $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); + } + + public function delete($id) + { + $res = FinPaymentApply::where('id', $id)->delete(); + return $res; + } + + +} \ 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..df236ea --- /dev/null +++ b/app/common/service/FinReceiptService.php @@ -0,0 +1,335 @@ +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(); + + if(isset($param['voucher_no_timeout_hour'])){ + redis::set('voucher_no_timeout_hour', $param['voucher_no_timeout_hour'], 'EX', 30); + } + + $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'] == '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); + } + + // 计算七天前的时间戳 + $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, $exchange_rate) + { + $finReceipt = FinReceipt::find($id); + if(!empty($finReceipt->received_at)){ + throw new BusinessException('该记录已将确认入账'); + } + + $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; + } + + /** + * 更新:绑定收款记录,计算折让金额 + * @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; + $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/OssService.php b/app/common/service/OssService.php index 72a956e..1aacc1c 100644 --- a/app/common/service/OssService.php +++ b/app/common/service/OssService.php @@ -76,4 +76,38 @@ class OssService throw new BusinessException('Delete Error: ' . $th->getMessage()); } } + + /** + * 获取对应的 MIME 类型 + */ + public function getMimeTypes($extensions) + { + $mimeMap = [ + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'png' => 'image/png', + 'gif' => 'image/gif', + 'bmp' => 'image/bmp', + 'webp' => 'image/webp', + 'pdf' => 'application/pdf', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'txt' => 'text/plain', + 'zip' => 'application/zip', + 'rar' => 'application/x-rar-compressed' + ]; + + $mimes = []; + foreach ($extensions as $ext) { + if (isset($mimeMap[$ext])) { + $mimes[] = $mimeMap[$ext]; + } + } + + return array_unique($mimes); + } } \ No newline at end of file diff --git a/app/common/service/StockOrdersService.php b/app/common/service/StockOrdersService.php index 76d2ab5..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) { // 处理原单 @@ -161,7 +242,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]); } @@ -270,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); } @@ -295,7 +381,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); } /** @@ -364,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']); @@ -479,14 +569,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 +1000,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 +1074,45 @@ 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 @@ + + +
+ + +
+ 付款单提醒+ |
+
|
+ 您好, +您有新的付款单待处理。请及时查看: + +
请点击下方按钮立即查看详情: + +
+ 立即查看
+
+
+
+ |
+
|
+ © 2025 河南新友合科技有限公司. 保留所有权利。 +这是系统自动发送的邮件,请勿直接回复。 + |
+