perf(database): 优化查询性能通过改进关联查询方式
- 在SplitExecutor.php中将with('receivers')改为get($splitOrderId, ['receivers'])
- 在HuifuSplitService.php中将with('receivers')改为where('id', $splitOrderId)->find()
- 在Settlement控制器中将with('items')改为get($ids, ['items'])
这些改动避免了N+1查询问题,提高了数据库查询效率。
```
505 行
19 KiB
PHP
505 行
19 KiB
PHP
<?php
|
|
|
|
namespace app\admin\controller\shopro\operating_company;
|
|
|
|
use app\admin\controller\shopro\Base;
|
|
use app\admin\model\shopro\operating_company\Company as CompanyModel;
|
|
use app\admin\model\shopro\operating_company\SettlementItem;
|
|
use app\admin\model\shopro\operating_company\SettlementLog;
|
|
use addons\shopro\library\Export;
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
|
use think\Db;
|
|
|
|
class Settlement extends Base
|
|
{
|
|
protected $model = null;
|
|
protected $companyModel = null;
|
|
|
|
public function _initialize()
|
|
{
|
|
parent::_initialize();
|
|
$this->model = new \app\admin\model\shopro\operating_company\Settlement;
|
|
$this->companyModel = new CompanyModel;
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
$this->request->filter(['strip_tags']);
|
|
if ($this->request->isAjax()) {
|
|
$nobuildfields = ['company_name', 'period_range'];
|
|
list($where, $sort, $order, $offset, $limit) = $this->custombuildparams(null, $nobuildfields);
|
|
$query = $this->buildSearch()->where($where);
|
|
$total = $query->count();
|
|
$list = $this->buildSearch()
|
|
->with(['company'])
|
|
->where($where)
|
|
->order($sort, $order)
|
|
->limit($offset, $limit)
|
|
->select();
|
|
return $this->success('结算列表', null, ['total' => $total, 'rows' => $list]);
|
|
}
|
|
return $this->view->fetch();
|
|
}
|
|
|
|
protected function buildSearch()
|
|
{
|
|
$filter = $this->request->get('filter', '');
|
|
$filter = (array) json_decode($filter, true);
|
|
$query = $this->model;
|
|
|
|
if (!empty($filter['company_name'])) {
|
|
$query = $query->where('company_name', 'like', '%' . $filter['company_name'] . '%');
|
|
}
|
|
if (!empty($filter['period_range'])) {
|
|
$range = explode(' - ', $filter['period_range']);
|
|
if (count($range) === 2) {
|
|
$query = $query->where('period_start', '>=', $range[0])
|
|
->where('period_end', '<=', $range[1]);
|
|
}
|
|
}
|
|
return $query;
|
|
}
|
|
|
|
public function add()
|
|
{
|
|
if ($this->request->isPost()) {
|
|
$params = $this->request->post('row/a');
|
|
$this->validateSettlement($params);
|
|
Db::startTrans();
|
|
try {
|
|
$company = $this->companyModel->get($params['company_id']);
|
|
if (!$company) {
|
|
throw new \Exception('运营公司不存在');
|
|
}
|
|
$settlement = $this->model->create([
|
|
'settlement_sn' => $this->generateSn(),
|
|
'company_id' => $company->id,
|
|
'company_name' => $company->name,
|
|
'amount' => $params['amount'],
|
|
'period_start' => $params['period_start'],
|
|
'period_end' => $params['period_end'],
|
|
'audit_status' => isset($params['submit']) ? 1 : 0,
|
|
'pay_status' => 0,
|
|
'admin_id' => $this->auth->id,
|
|
'source' => 'manual',
|
|
]);
|
|
$this->saveItems($settlement->id, $params);
|
|
SettlementLog::record($settlement->id, 'create', $this->auth->id, $settlement->toArray());
|
|
if (isset($params['submit'])) {
|
|
SettlementLog::record($settlement->id, 'submit', $this->auth->id);
|
|
}
|
|
Db::commit();
|
|
} catch (\Exception $e) {
|
|
Db::rollback();
|
|
$this->error($e->getMessage());
|
|
}
|
|
$this->success('添加成功');
|
|
}
|
|
return $this->view->fetch();
|
|
}
|
|
|
|
public function edit($ids = null)
|
|
{
|
|
$row = $this->model->get($ids, ['items']);
|
|
if (!$row) {
|
|
$this->error('记录不存在');
|
|
}
|
|
if (!in_array($row->audit_status, [0, 3])) {
|
|
$this->error('当前状态不可编辑');
|
|
}
|
|
if ($this->request->isPost()) {
|
|
$params = $this->request->post('row/a');
|
|
$this->validateSettlement($params);
|
|
Db::startTrans();
|
|
try {
|
|
$company = $this->companyModel->get($params['company_id']);
|
|
if (!$company) {
|
|
throw new \Exception('运营公司不存在');
|
|
}
|
|
$row->save([
|
|
'company_id' => $company->id,
|
|
'company_name' => $company->name,
|
|
'amount' => $params['amount'],
|
|
'period_start' => $params['period_start'],
|
|
'period_end' => $params['period_end'],
|
|
'audit_status' => isset($params['submit']) ? 1 : ($row->audit_status == 3 ? 0 : $row->audit_status),
|
|
]);
|
|
SettlementItem::where('settlement_id', $row->id)->delete();
|
|
$this->saveItems($row->id, $params);
|
|
SettlementLog::record($row->id, 'edit', $this->auth->id);
|
|
if (isset($params['submit'])) {
|
|
SettlementLog::record($row->id, 'submit', $this->auth->id);
|
|
}
|
|
Db::commit();
|
|
} catch (\Exception $e) {
|
|
Db::rollback();
|
|
$this->error($e->getMessage());
|
|
}
|
|
$this->success('保存成功');
|
|
}
|
|
$this->view->assign('row', $row);
|
|
return $this->view->fetch();
|
|
}
|
|
|
|
public function del($ids = null)
|
|
{
|
|
$row = $this->model->get($ids);
|
|
if (!$row) {
|
|
$this->error('记录不存在');
|
|
}
|
|
if (!in_array($row->audit_status, [0, 3])) {
|
|
$this->error('仅草稿或已驳回状态可删除');
|
|
}
|
|
Db::startTrans();
|
|
try {
|
|
SettlementItem::where('settlement_id', $row->id)->delete();
|
|
SettlementLog::where('settlement_id', $row->id)->delete();
|
|
$row->delete();
|
|
Db::commit();
|
|
} catch (\Exception $e) {
|
|
Db::rollback();
|
|
$this->error($e->getMessage());
|
|
}
|
|
$this->success('删除成功');
|
|
}
|
|
|
|
public function submit()
|
|
{
|
|
$id = $this->request->post('id');
|
|
$row = $this->model->get($id);
|
|
if (!$row) {
|
|
$this->error('记录不存在');
|
|
}
|
|
if (!in_array($row->audit_status, [0, 3])) {
|
|
$this->error('当前状态不可提交');
|
|
}
|
|
$row->audit_status = 1;
|
|
$row->save();
|
|
SettlementLog::record($row->id, 'submit', $this->auth->id);
|
|
$this->success('已提交审核');
|
|
}
|
|
|
|
public function audit()
|
|
{
|
|
$id = $this->request->post('id');
|
|
$action = $this->request->post('action');
|
|
$remark = $this->request->post('remark', '');
|
|
|
|
$row = $this->model->get($id);
|
|
if (!$row) {
|
|
$this->error('记录不存在');
|
|
}
|
|
if ($row->audit_status != 1) {
|
|
$this->error('当前状态不可审核');
|
|
}
|
|
|
|
if ($action === 'approve') {
|
|
$row->audit_status = 2;
|
|
$row->audit_remark = $remark;
|
|
$row->audit_admin_id = $this->auth->id;
|
|
$row->save();
|
|
SettlementLog::record($row->id, 'approve', $this->auth->id, ['remark' => $remark]);
|
|
$this->success('审核通过');
|
|
} elseif ($action === 'reject') {
|
|
$row->audit_status = 3;
|
|
$row->audit_remark = $remark;
|
|
$row->audit_admin_id = $this->auth->id;
|
|
$row->save();
|
|
SettlementLog::record($row->id, 'reject', $this->auth->id, ['remark' => $remark]);
|
|
$this->success('已驳回');
|
|
}
|
|
$this->error('无效操作');
|
|
}
|
|
|
|
public function markPay()
|
|
{
|
|
$id = $this->request->post('id');
|
|
$payStatus = (int) $this->request->post('pay_status');
|
|
$remark = $this->request->post('remark', '');
|
|
|
|
$row = $this->model->get($id);
|
|
if (!$row) {
|
|
$this->error('记录不存在');
|
|
}
|
|
if ($row->audit_status != 2) {
|
|
$this->error('仅审核通过的结算单可标记打款');
|
|
}
|
|
if (!in_array($payStatus, [1, 2])) {
|
|
$this->error('无效的打款状态');
|
|
}
|
|
|
|
$row->pay_status = $payStatus;
|
|
$row->pay_remark = $remark;
|
|
$row->pay_time = $payStatus == 1 ? time() : null;
|
|
$row->save();
|
|
|
|
SettlementLog::record($row->id, $payStatus == 1 ? 'pay' : 'pay_fail', $this->auth->id, [
|
|
'remark' => $remark,
|
|
]);
|
|
$this->success('操作成功');
|
|
}
|
|
|
|
public function import()
|
|
{
|
|
if (!$this->request->isPost()) {
|
|
return $this->view->fetch();
|
|
}
|
|
$file = $this->request->request('file');
|
|
if (!$file) {
|
|
$this->error('请上传文件');
|
|
}
|
|
$filePath = ROOT_PATH . 'public' . DS . $file;
|
|
if (!is_file($filePath)) {
|
|
$this->error('文件不存在');
|
|
}
|
|
|
|
$ext = pathinfo($filePath, PATHINFO_EXTENSION);
|
|
if (!in_array($ext, ['csv', 'xls', 'xlsx'])) {
|
|
$this->error('不支持的文件格式');
|
|
}
|
|
|
|
$rows = $this->readImportFile($filePath, $ext);
|
|
if (count($rows) < 2) {
|
|
$this->error('导入数据为空');
|
|
}
|
|
|
|
$header = array_map('trim', $rows[0]);
|
|
$map = $this->getImportColumnMap($header);
|
|
$batch = 'IMP' . date('YmdHis');
|
|
$success = 0;
|
|
$errors = [];
|
|
|
|
Db::startTrans();
|
|
try {
|
|
for ($i = 1; $i < count($rows); $i++) {
|
|
$line = $rows[$i];
|
|
if (!$line || !array_filter($line)) {
|
|
continue;
|
|
}
|
|
try {
|
|
$data = $this->parseImportRow($line, $map);
|
|
$company = $this->companyModel->where('name', $data['company_name'])->find();
|
|
if (!$company) {
|
|
$company = $this->companyModel->create([
|
|
'name' => $data['company_name'],
|
|
'status' => 'normal',
|
|
]);
|
|
}
|
|
$settlement = $this->model->create([
|
|
'settlement_sn' => $this->generateSn(),
|
|
'company_id' => $company->id,
|
|
'company_name' => $company->name,
|
|
'amount' => $data['amount'],
|
|
'period_start' => $data['period_start'],
|
|
'period_end' => $data['period_end'],
|
|
'audit_status' => 1,
|
|
'pay_status' => 0,
|
|
'admin_id' => $this->auth->id,
|
|
'source' => 'import',
|
|
'import_batch' => $batch,
|
|
]);
|
|
if (!empty($data['order_sn']) || !empty($data['remark'])) {
|
|
SettlementItem::create([
|
|
'settlement_id' => $settlement->id,
|
|
'order_sn' => $data['order_sn'] ?? '',
|
|
'item_name' => '分成导入',
|
|
'amount' => $data['amount'],
|
|
'remark' => $data['remark'] ?? '',
|
|
]);
|
|
}
|
|
SettlementLog::record($settlement->id, 'create', $this->auth->id, ['source' => 'import']);
|
|
SettlementLog::record($settlement->id, 'submit', $this->auth->id);
|
|
$success++;
|
|
} catch (\Exception $e) {
|
|
$errors[] = '第' . ($i + 1) . '行:' . $e->getMessage();
|
|
}
|
|
}
|
|
Db::commit();
|
|
} catch (\Exception $e) {
|
|
Db::rollback();
|
|
$this->error($e->getMessage());
|
|
}
|
|
|
|
$msg = '成功导入 ' . $success . ' 条';
|
|
if ($errors) {
|
|
$msg .= ',失败 ' . count($errors) . ' 条:' . implode(';', array_slice($errors, 0, 5));
|
|
}
|
|
$this->success($msg);
|
|
}
|
|
|
|
public function export()
|
|
{
|
|
list($where, $sort, $order) = $this->custombuildparams(null, ['company_name', 'period_range']);
|
|
$list = $this->buildSearch()->where($where)->order($sort, $order)->select();
|
|
$list = collection($list)->toArray();
|
|
|
|
$expCellName = [
|
|
'settlement_sn' => '结算单号',
|
|
'company_name' => '结算对象',
|
|
'amount' => '金额',
|
|
'period_start' => '周期开始',
|
|
'period_end' => '周期结束',
|
|
'audit_status_text' => '审核状态',
|
|
'pay_status_text' => '打款状态',
|
|
'audit_remark' => '审核备注',
|
|
'pay_remark' => '打款备注',
|
|
'createtime' => '创建时间',
|
|
];
|
|
|
|
$newList = [];
|
|
foreach ($list as $item) {
|
|
$newList[] = [
|
|
'settlement_sn' => $item['settlement_sn'],
|
|
'company_name' => $item['company_name'],
|
|
'amount' => $item['amount'],
|
|
'period_start' => $item['period_start'],
|
|
'period_end' => $item['period_end'],
|
|
'audit_status_text' => $this->model->getAuditStatusList()[$item['audit_status']] ?? '',
|
|
'pay_status_text' => $this->model->getPayStatusList()[$item['pay_status']] ?? '',
|
|
'audit_remark' => $item['audit_remark'],
|
|
'pay_remark' => $item['pay_remark'],
|
|
'createtime' => date('Y-m-d H:i:s', $item['createtime']),
|
|
];
|
|
}
|
|
|
|
$export = new Export();
|
|
$export->exportExcel('运营公司结算-' . date('Y-m-d'), $expCellName, $newList);
|
|
}
|
|
|
|
public function downloadTemplate()
|
|
{
|
|
$spreadsheet = new Spreadsheet();
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
$headers = ['运营公司名称', '结算金额', '周期开始', '周期结束', '订单号', '备注'];
|
|
foreach ($headers as $col => $title) {
|
|
$sheet->setCellValueByColumnAndRow($col + 1, 1, $title);
|
|
}
|
|
$sheet->setCellValueByColumnAndRow(1, 2, '示例运营公司');
|
|
$sheet->setCellValueByColumnAndRow(2, 2, '1000.00');
|
|
$sheet->setCellValueByColumnAndRow(3, 2, date('Y-m-01'));
|
|
$sheet->setCellValueByColumnAndRow(4, 2, date('Y-m-d'));
|
|
|
|
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
header('Content-Disposition: attachment;filename="运营公司结算导入模板.xlsx"');
|
|
header('Cache-Control: max-age=0');
|
|
$writer = new Xlsx($spreadsheet);
|
|
$writer->save('php://output');
|
|
exit;
|
|
}
|
|
|
|
public function logs()
|
|
{
|
|
$id = $this->request->get('id');
|
|
$logs = SettlementLog::where('settlement_id', $id)->order('id desc')->select();
|
|
return $this->success('操作成功', null, $logs);
|
|
}
|
|
|
|
protected function generateSn()
|
|
{
|
|
$prefix = 'OCS' . date('Ymd');
|
|
$last = $this->model->where('settlement_sn', 'like', $prefix . '%')
|
|
->order('id desc')->value('settlement_sn');
|
|
$seq = $last ? ((int) substr($last, -6)) + 1 : 1;
|
|
return $prefix . str_pad($seq, 6, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
protected function validateSettlement($params)
|
|
{
|
|
if (empty($params['company_id']) || empty($params['amount'])
|
|
|| empty($params['period_start']) || empty($params['period_end'])) {
|
|
throw new \Exception('请填写完整结算信息');
|
|
}
|
|
if ($params['period_start'] > $params['period_end']) {
|
|
throw new \Exception('结算周期开始不能晚于结束');
|
|
}
|
|
if ($params['amount'] <= 0) {
|
|
throw new \Exception('结算金额必须大于0');
|
|
}
|
|
}
|
|
|
|
protected function saveItems($settlementId, $params)
|
|
{
|
|
if (empty($params['items']) || !is_array($params['items'])) {
|
|
return;
|
|
}
|
|
foreach ($params['items'] as $item) {
|
|
if (empty($item['amount'])) {
|
|
continue;
|
|
}
|
|
SettlementItem::create([
|
|
'settlement_id' => $settlementId,
|
|
'order_sn' => $item['order_sn'] ?? '',
|
|
'item_name' => $item['item_name'] ?? '',
|
|
'amount' => $item['amount'],
|
|
'remark' => $item['remark'] ?? '',
|
|
]);
|
|
}
|
|
}
|
|
|
|
protected function getImportColumnMap($header)
|
|
{
|
|
$aliases = [
|
|
'company_name' => ['运营公司名称', '结算对象'],
|
|
'amount' => ['结算金额', '金额'],
|
|
'period_start' => ['周期开始', '结算周期起'],
|
|
'period_end' => ['周期结束', '结算周期止'],
|
|
'order_sn' => ['订单号'],
|
|
'remark' => ['备注'],
|
|
];
|
|
$map = [];
|
|
foreach ($header as $idx => $title) {
|
|
foreach ($aliases as $key => $names) {
|
|
if (in_array($title, $names)) {
|
|
$map[$key] = $idx;
|
|
}
|
|
}
|
|
}
|
|
if (!isset($map['company_name'], $map['amount'], $map['period_start'], $map['period_end'])) {
|
|
throw new \Exception('模板表头不正确');
|
|
}
|
|
return $map;
|
|
}
|
|
|
|
protected function parseImportRow($line, $map)
|
|
{
|
|
$companyName = trim($line[$map['company_name']] ?? '');
|
|
$amount = trim($line[$map['amount']] ?? '');
|
|
$periodStart = trim($line[$map['period_start']] ?? '');
|
|
$periodEnd = trim($line[$map['period_end']] ?? '');
|
|
if (!$companyName || !$amount || !$periodStart || !$periodEnd) {
|
|
throw new \Exception('必填项不能为空');
|
|
}
|
|
if (!is_numeric($amount) || $amount <= 0) {
|
|
throw new \Exception('结算金额无效');
|
|
}
|
|
return [
|
|
'company_name' => $companyName,
|
|
'amount' => $amount,
|
|
'period_start' => date('Y-m-d', strtotime($periodStart)),
|
|
'period_end' => date('Y-m-d', strtotime($periodEnd)),
|
|
'order_sn' => isset($map['order_sn']) ? trim($line[$map['order_sn']] ?? '') : '',
|
|
'remark' => isset($map['remark']) ? trim($line[$map['remark']] ?? '') : '',
|
|
];
|
|
}
|
|
|
|
protected function readImportFile($filePath, $ext)
|
|
{
|
|
if ($ext === 'csv') {
|
|
$rows = [];
|
|
$handle = fopen($filePath, 'r');
|
|
while (($data = fgetcsv($handle)) !== false) {
|
|
$rows[] = $data;
|
|
}
|
|
fclose($handle);
|
|
return $rows;
|
|
}
|
|
|
|
$reader = $ext === 'xls'
|
|
? new \PhpOffice\PhpSpreadsheet\Reader\Xls()
|
|
: new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
|
|
$spreadsheet = $reader->load($filePath);
|
|
return $spreadsheet->getActiveSheet()->toArray();
|
|
}
|
|
}
|