Merge branch 'develop' into feature/mzl_dev

This commit is contained in:
mzl
2025-09-10 13:39:06 +08:00
parent 7a8c0d6cc3
commit 17efdf01df
10 changed files with 760 additions and 765 deletions
@@ -47,7 +47,9 @@ class ApprovalRecordsController extends BaseController
'step_index' => $record['step_index'],
'action' => $record['action'],
'status' => SysDictItem::where('item_value', $record['status'])->where('status', 1)->value('item_label'),
'action_desc' => SysDictItem::where('item_value', $record['action'])->where('status', 1)->value('item_label'),
'status' => $record['status'],
'status_desc' => SysDictItem::where('item_value', $record['status'])->where('status', 1)->value('item_label'),
'comment' => $record['comment'],
'approved_at' => $record['approved_at'],
+4 -1
View File
@@ -11,7 +11,6 @@ use app\common\model\StockOrders;
use app\common\model\SysCurrency;
use support\Request;
use app\common\model\User;
use support\exception\BusinessException;
use support\Db;
class StockOrdersService
@@ -63,6 +62,10 @@ class StockOrdersService
}
$config['order']['id'] = 'desc';
// $config['dataLimit']['type'] = 'relation';
// $config['dataLimit']['userId'] = $request->user->uid;
// $config['dataLimit']['userField'] = 'salesperson_id';
break;
case 'print':
$config['where']['id'] = $p['id'];
@@ -112,7 +112,7 @@ class ApprovalFlowService
if ((int)$instance->current_step !== $idx) {
// return ['status' => 'noop', 'reason' => 'not_current_step', 'current_step' => (int)$instance->current_step];
// throw new BusinessException("非当前步骤");
return ['status' => 'noop', 'msg' => '非当前步骤'];
return ['status' => 'noop', 'msg' => '非当前审批'];
}
// return ['status' => 'noop', 'reason' => 'no_pending_record'];
return ['status' => 'noop', 'msg' => '无待处理记录'];
@@ -136,7 +136,7 @@ class ApprovalFlowService
if ($outcome === 'pending') {
// return ['status' => 'step_pending'];
return ['status' => 'step_pending', 'msg' => '步骤待定'];
return ['status' => 'step_pending', 'msg' => '审批待定'];
}
// 3) 有结论:关闭本步其余 pending
@@ -386,10 +386,11 @@ class ApprovalFlowService
$resolved = array_unique(array_filter(array_map('intval', $resolved)));
if ($avoidSelf) {
$resolved = array_values(array_filter($resolved, fn($id) => $id !== (int)$instance->initiator_id));
// $resolved = array_values(array_filter($resolved, fn($id) => $id !== (int)$instance->initiator_id));
$resolved = array_values($resolved);
}
if (empty($resolved)) {
throw new BusinessException("步骤未解析到任何审批人");
throw new BusinessException("未解析到任何审批人");
}
return $resolved;
}
@@ -1,751 +0,0 @@
<?php
namespace app\common\utils\approval\service;
use support\exception\BusinessException;
use support\Db;
/**
* 审批流程服务(Webman + Illuminate DB)
* - 支持条件步骤、会签(any/all)、并发安全(事务+锁)、抄送、超时处理
* - 统一状态:
* 实例 status: processing | approved | rejected | revoked | timed_out
* 记录 status: pending | approved | rejected | skipped | timeout
* 记录 action: approve | reject | null
*/
class ApprovalFlowService_bak
{
// === 对外方法 ===
/**
* 启动审批流程
*/
public function start(string $flowType, string $bizType, string $bizId, array $formData, int $initiatorId): array
{
return Db::transaction(function () use ($flowType, $bizType, $bizId, $formData, $initiatorId) {
// 1) 流程定义
$flow = Db::table('approval_flows')
->where('type', $flowType)
->where('is_active', 1)
->first();
if (!$flow) {
throw new BusinessException("审批流程未找到或已停用: {$flowType}");
}
$steps = json_decode($flow->steps, true);
if (!is_array($steps) || empty($steps)) {
throw new BusinessException("流程步骤配置错误");
}
// 2) 寻找首个有效步骤索引(考虑条件)
$firstIndex = $this->getNextStepIndex($steps, $formData, -1);
if ($firstIndex === null) {
throw new BusinessException("未找到可执行的审批步骤");
}
$firstStep = $steps[$firstIndex];
// 3) 计算实例截止时间(实例级,可扩展到步骤级)
$deadlineAt = null;
if (($flow->timeout_hours ?? 0) > 0) {
$deadlineAt = date('Y-m-d H:i:s', time() + $flow->timeout_hours * 3600);
}
// 4) 创建审批实例
$now = date('Y-m-d H:i:s');
$instanceId = Db::table('approval_instances')->insertGetId([
'flow_type' => $flowType,
'biz_type' => $bizType,
'biz_id' => $bizId,
'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'initiator_id' => $initiatorId,
'status' => 'processing',
'current_step' => $firstIndex,
'deadline_at' => $deadlineAt,
'created_at' => $now,
'updated_at' => $now,
]);
// 5) 创建首步待办
$instance = (object)[
'id' => $instanceId,
'flow_type' => $flowType,
'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'initiator_id' => $initiatorId,
'current_step' => $firstIndex,
'status' => 'processing',
];
$approverIds = $this->resolveApprovers($firstStep, $instance, true);
foreach ($approverIds as $uid) {
// 唯一键会防重复
$this->insertApprovalRecord($instanceId, $firstIndex, $uid);
}
// 6) 通知审批人 & 抄送
$this->sendNotification($approverIds, $instance, 'pending', false);
$this->notifyCCs($firstStep, $instance, 'pending');
return ['instance_id' => $instanceId];
});
}
/**
* 执行审批
*/
public function approve(int $instanceId, int $approverId, string $action, string $comment = '')
{
if (!in_array($action, ['approve', 'reject'], true)) {
throw new BusinessException("无效的审批操作");
}
return Db::transaction(function () use ($instanceId, $approverId, $action, $comment) {
// 锁实例,保证并发安全
$instance = Db::table('approval_instances')->where('id', $instanceId)->lockForUpdate()->first();
if (!$instance) {
throw new BusinessException("审批实例未找到");
}
if ($instance->status !== 'processing') {
throw new BusinessException("流程已结束或不可审批");
}
// 读取流程与当前步
$flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first();
if (!$flow) throw new BusinessException("审批流程定义未找到");
$steps = json_decode($flow->steps, true);
$idx = (int)$instance->current_step;
$step = $steps[$idx] ?? null;
if (!$step) throw new BusinessException("当前步骤配置错误");
// 锁当前步所有记录,避免同时推进
$pendingRecord = Db::table('approval_records')
->where('instance_id', $instanceId)
->where('approver_id', $approverId)
->where('step_index', $idx)
->where('status', 'pending')
->lockForUpdate()
->first();
if (!$pendingRecord) {
throw new BusinessException("未找到待审批记录");
}
$now = date('Y-m-d H:i:s');
// 1) 更新当前人的审批记录
Db::table('approval_records')->where('id', $pendingRecord->id)->update([
'action' => $action,
'status' => ($action === 'approve') ? 'approved' : 'rejected',
'comment' => $comment,
'approved_at' => $now,
'updated_at' => $now,
]);
// 2) 聚合判断本步骤结果
$outcome = $this->getStepOutcome($instanceId, $idx); // 'pending'|'approve'|'reject'
$type = $step['type'] ?? 'all';
$stepDone = false; $finalReject = false; $finalApprove = false;
if ($type === 'any') {
if ($outcome === 'reject') { $stepDone = true; $finalReject = true; }
elseif ($outcome === 'approve') { $stepDone = true; $finalApprove = true; }
} else { // all
if ($outcome === 'reject') { $stepDone = true; $finalReject = true; }
elseif ($outcome === 'approve') { $stepDone = true; $finalApprove = true; }
}
if (!$stepDone) {
return ['status' => 'step_pending'];
}
// 3) 本步已有结论:关闭其余 pending 待办为 skipped
$this->closeOtherPending($instanceId, $idx, [$approverId]);
// 4) 步骤结论为拒绝 -> 结束
if ($finalReject) {
Db::table('approval_instances')->where('id', $instanceId)->update([
'status' => 'rejected',
'finished_at' => $now,
'updated_at' => $now,
]);
$this->sendNotification([$instance->initiator_id], $instance, 'reject', false);
return ['status' => 'completed', 'result' => 'reject'];
}
// 5) 进入下一步(基于表单快照与条件继续寻找)
$formData = json_decode($instance->form_data, true) ?: [];
$nextIndex = $this->getNextStepIndex($steps, $formData, $idx);
if ($nextIndex === null) {
// 流程结束(全通过)
Db::table('approval_instances')->where('id', $instanceId)->update([
'status' => 'approved',
'finished_at' => $now,
'updated_at' => $now,
]);
$this->sendNotification([$instance->initiator_id], $instance, 'approve', false);
return ['status' => 'completed', 'result' => 'approve'];
}
// 6) 推进实例 current_step(乐观条件)
$affected = Db::table('approval_instances')
->where('id', $instanceId)
->where('current_step', $idx)
->where('status', 'processing')
->update([
'current_step' => $nextIndex,
'updated_at' => $now,
]);
if (!$affected) {
throw new BusinessException('流程已被他人推进,请刷新后重试');
}
// 7) 生成下一步待办 + 通知
$nextStep = $steps[$nextIndex];
$nextApprovers = $this->resolveApprovers($nextStep, $instance, true);
foreach ($nextApprovers as $uid) {
$this->insertApprovalRecord($instanceId, $nextIndex, $uid);
}
$this->sendNotification($nextApprovers, $instance, 'pending', false);
$this->notifyCCs($nextStep, $instance, 'pending');
return ['status' => 'next_step', 'step_name' => $nextStep['name'] ?? ('Step '.$nextIndex)];
});
}
/**
* 撤回流程(可选)
* 仅限发起人在第一条“已处理记录”出现前撤回
*/
public function revoke(int $instanceId, int $initiatorId, string $reason = '')
{
return Db::transaction(function () use ($instanceId, $initiatorId, $reason) {
$instance = Db::table('approval_instances')->where('id', $instanceId)->lockForUpdate()->first();
if (!$instance) throw new BusinessException('审批实例未找到');
if ((int)$instance->initiator_id !== (int)$initiatorId) throw new BusinessException('无权撤回');
if ($instance->status !== 'processing') throw new BusinessException('流程已结束或不可撤回');
// 已有处理记录则不允许撤回
$handled = Db::table('approval_records')
->where('instance_id', $instanceId)
->whereIn('status', ['approved','rejected','timeout','skipped'])
->count() > 0;
if ($handled) throw new BusinessException('当前进度不可撤回');
$now = date('Y-m-d H:i:s');
Db::table('approval_instances')->where('id', $instanceId)->update([
'status' => 'revoked',
'finished_at' => $now,
'updated_at' => $now,
]);
// 关闭所有 pending 待办
Db::table('approval_records')
->where('instance_id', $instanceId)
->where('status', 'pending')
->update(['status' => 'skipped', 'approved_at' => $now, 'updated_at' => $now]);
$this->sendNotification([$initiatorId], $instance, 'revoked', false);
return ['status' => 'completed', 'result' => 'revoked'];
});
}
/**
* 我的待办
*/
public function getPendingApprovals(int $userId): array
{
return Db::table('approval_records as ar')
->join('approval_instances as ai', 'ar.instance_id', '=', 'ai.id')
->where('ar.approver_id', $userId)
->where('ar.status', 'pending')
->where('ai.status', 'processing')
->select('ai.*')
->distinct()
->get()
->toArray();
}
/**
* 审批历史
*/
public function getApprovalHistory(int $instanceId): array
{
return Db::table('approval_records')
->where('instance_id', $instanceId)
->orderBy('step_index')
->orderByRaw('COALESCE(approved_at, created_at) asc')
->get()
->toArray();
}
/**
* 流程图数据(识别 skipped)
*/
public function getFlowDiagramData(int $instanceId): array
{
$instance = Db::table('approval_instances')->find($instanceId);
if (!$instance) return [];
$flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first();
if (!$flow) return [];
$steps = json_decode($flow->steps, true) ?: [];
$records = Db::table('approval_records')
->where('instance_id', $instanceId)
->orderBy('step_index')->orderByRaw('COALESCE(approved_at, created_at) asc')
->get()
->groupBy('step_index');
$formData = json_decode($instance->form_data, true) ?: [];
$data = [];
foreach ($steps as $i => $step) {
$actions = ($records->get($i) ?? collect())->toArray();
$status = 'pending';
if (!empty($actions)) {
$hasReject = false; $allApproved = true;
foreach ($actions as $a) {
if ($a->status === 'rejected') $hasReject = true;
if ($a->status !== 'approved') $allApproved = false;
}
if ($hasReject) $status = 'rejected';
elseif ($allApproved) $status = 'approved';
else $status = 'processing';
} else {
// 无记录:可能被跳过、未到达、已过
if ($i < (int)$instance->current_step) {
// 如果条件不满足,认为 skipped,否则视为 approved(历史兼容)
if (isset($step['condition']) && !$this->evaluateCondition($step['condition'], $formData)) {
$status = 'skipped';
} else {
$status = 'approved';
}
} elseif ($i === (int)$instance->current_step && $instance->status === 'processing') {
$status = 'processing';
} else {
// 还未到达
$status = 'pending';
}
}
$data[] = [
'index' => $i,
'name' => $step['name'] ?? ('Step '.$i),
'type' => $step['type'] ?? 'all',
'status' => $status,
'actions' => $actions,
];
}
return $data;
}
/**
* 定时任务:处理超时实例(实例级)
*/
public function handleTimeouts()
{
$now = date('Y-m-d H:i:s');
$expired = Db::table('approval_instances')
->where('status', 'processing')
->whereNotNull('deadline_at')
->where('deadline_at', '<=', $now)
->get();
foreach ($expired as $instance) {
Db::transaction(function () use ($instance, $now) {
$flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first();
if (!$flow) return;
$final = ($flow->timeout_action === 'auto_approve') ? 'approved' : 'rejected';
$finalAction = ($flow->timeout_action === 'auto_approve') ? 'approve' : 'reject';
// 更新实例
Db::table('approval_instances')->where('id', $instance->id)->update([
'status' => ($final === 'approved') ? 'approved' : 'rejected',
'finished_at' => $now,
'updated_at' => $now,
]);
// 关闭当前步所有 pending 记录为 timeout
Db::table('approval_records')
->where('instance_id', $instance->id)
->where('step_index', $instance->current_step)
->where('status', 'pending')
->update(['status' => 'timeout', 'approved_at' => $now, 'updated_at' => $now]);
$this->notifyInitiator($instance, $finalAction);
});
}
}
// === 内部工具 ===
/**
* 解析审批人
* $avoidSelf:是否过滤发起人本人
*/
private function resolveApprovers(array $step, object $instance, bool $avoidSelf = true): array
{
$resolved = [];
$formData = json_decode($instance->form_data ?? '{}', true) ?: [];
$confs = $step['approvers'] ?? [];
foreach ($confs as $cfg) {
$typ = $cfg['type'] ?? '';
$val = $cfg['value'] ?? null;
$ids = [];
switch ($typ) {
case 'role':
// $ids = $this->getUserIdsByRole($val);
// 支持:
// - {"type":"role","value":1} // 按 role_id
// - {"type":"role","value":"销售经理"} // 按 roles.name
// - {"type":"role","value":"销售经理","system_id": 2} // 限定系统;默认也包含全局(system_id=0)
$val = $cfg['value'] ?? null;
$sys = isset($cfg['system_id']) ? (int)$cfg['system_id'] : null;
// include_global 默认 true(把 system_id=0 的“全局角色”也算上)
$includeGlobal = array_key_exists('include_global', $cfg) ? (bool)$cfg['include_global'] : true;
$ids = $this->getUserIdsByRole($val, $sys, true, $includeGlobal);
break;
case 'user':
$ids = [(int)$val];
break;
case 'initiator_leader':
$leaderId = $this->getLeaderIdByUserId((int)$instance->initiator_id);
if ($leaderId) $ids = [(int)$leaderId];
break;
case 'field_user':
if (isset($formData[$val])) $ids = [(int)$formData[$val]];
break;
case 'dept_head':
$head = $this->getDeptHeadId((int)$val);
if ($head) $ids = [(int)$head];
break;
default:
throw new BusinessException("不支持的审批人类型: {$typ}");
}
$resolved = array_merge($resolved, $ids);
}
$resolved = array_unique(array_filter(array_map('intval', $resolved)));
if ($avoidSelf) {
$resolved = array_values(array_filter($resolved, fn($id) => $id !== (int)$instance->initiator_id));
}
if (empty($resolved)) {
throw new BusinessException("步骤未解析到任何审批人");
}
return $resolved;
}
/**
* 通知抄送人
*/
private function notifyCCs(array $step, object $instance, string $action = 'pending'): void
{
$ccConfig = $step['cc'] ?? [];
if (empty($ccConfig)) return;
$formData = json_decode($instance->form_data ?? '{}', true) ?: [];
$uids = [];
foreach ($ccConfig as $cc) {
$typ = $cc['type'] ?? '';
$val = $cc['value'] ?? null;
switch ($typ) {
case 'role':
$uids = array_merge($uids, $this->getUserIdsByRole($val));
break;
case 'user':
$uids[] = (int)$val;
break;
case 'initiator_leader':
$leaderId = $this->getLeaderIdByUserId((int)$instance->initiator_id);
if ($leaderId) $uids[] = (int)$leaderId;
break;
case 'field_user':
if (isset($formData[$val])) $uids[] = (int)$formData[$val];
break;
case 'dept_head':
$head = $this->getDeptHeadId((int)$val);
if ($head) $uids[] = (int)$head;
break;
default:
// ignore
break;
}
}
$uids = array_unique(array_filter(array_map('intval', $uids)));
if (empty($uids)) return;
$now = date('Y-m-d H:i:s');
foreach ($uids as $uid) {
// 唯一键防重;这里仍做一次 exists 判断
$exists = Db::table('approval_ccs')
->where('instance_id', $instance->id)
->where('user_id', $uid)
->count() > 0;
if (!$exists) {
Db::table('approval_ccs')->insert([
'instance_id' => $instance->id,
'user_id' => $uid,
'notified_at' => $now,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
$this->sendNotification($uids, $instance, $action, true);
}
/**
* 条件求值:支持 {field} {op} {value}
* op: ==, !=, >, <, >=, <=
*/
private function evaluateCondition(?string $condition, array $formData): bool
{
if (!$condition) return true;
$pattern = '/^(\w+)\s*(==|!=|>|<|>=|<=)\s*(.+)$/';
if (!preg_match($pattern, $condition, $m)) {
throw new BusinessException("条件格式错误: {$condition}");
}
$field = $m[1];
$op = $m[2];
$value = trim($m[3], "\"'");
$actual = $formData[$field] ?? null;
// 数值比较
if (is_numeric($value) && is_numeric($actual)) {
$value = (float)$value;
$actual = (float)$actual;
}
switch ($op) {
case '==': return $actual == $value;
case '!=': return $actual != $value;
case '>': return $actual > $value;
case '<': return $actual < $value;
case '>=': return $actual >= $value;
case '<=': return $actual <= $value;
}
throw new BusinessException("不支持的操作符: {$op}");
}
/**
* 获取下一个有效步骤索引(考虑 condition)
* @param int $currentStepIndex 从该索引之后开始找;如果传 -1 则从开头找“首个有效”
*/
private function getNextStepIndex(array $steps, array $formData, int $currentStepIndex): ?int
{
for ($i = $currentStepIndex + 1; $i < count($steps); $i++) {
$s = $steps[$i];
if (!isset($s['condition']) || $this->evaluateCondition($s['condition'], $formData)) {
return $i;
}
}
return null;
}
/**
* 聚合本步骤结论
* 返回:'pending' | 'approve' | 'reject'
*/
private function getStepOutcome(int $instanceId, int $stepIndex): string
{
$records = Db::table('approval_records')
->where('instance_id', $instanceId)
->where('step_index', $stepIndex)
->get();
$hasPending = false; $hasReject = false; $allApproved = true;
foreach ($records as $r) {
if ($r->status === 'pending') { $hasPending = true; $allApproved = false; }
elseif ($r->status === 'rejected') { $hasReject = true; $allApproved = false; }
elseif ($r->status !== 'approved') { $allApproved = false; }
}
if ($hasPending) return 'pending';
if ($hasReject) return 'reject';
return $allApproved ? 'approve' : 'reject';
}
/**
* 关闭本步其余待办(标记为 skipped)
*/
private function closeOtherPending(int $instanceId, int $stepIndex, array $keepApproverIds = []): void
{
$now = date('Y-m-d H:i:s');
$q = Db::table('approval_records')
->where('instance_id', $instanceId)
->where('step_index', $stepIndex)
->where('status', 'pending');
if (!empty($keepApproverIds)) {
$q->whereNotIn('approver_id', $keepApproverIds);
}
$q->update([
'status' => 'skipped',
'approved_at' => $now,
'updated_at' => $now,
]);
}
private function insertApprovalRecord(int $instanceId, int $stepIndex, int $approverId): void
{
$now = date('Y-m-d H:i:s');
// 防重复:唯一键兜底,这里先查一次
$exists = Db::table('approval_records')
->where('instance_id', $instanceId)
->where('step_index', $stepIndex)
->where('approver_id', $approverId)
->count() > 0;
if (!$exists) {
Db::table('approval_records')->insert([
'instance_id' => $instanceId,
'approver_id' => $approverId,
'step_index' => $stepIndex,
'action' => null, // 待办阶段无动作
'status' => 'pending', // 明确 pending
'comment' => null,
'approved_at' => null, // 未审批为 NULL
'created_at' => $now,
'updated_at' => $now,
]);
}
}
private function sendNotification(array $userIds, object $instance, string $action, bool $isCC): void
{
$map = [
'pending' => '待审批',
'approve' => '已通过',
'reject' => '已拒绝',
'revoked' => '已撤回',
];
$prefix = $isCC ? '[抄送]' : '';
$text = $map[$action] ?? $action;
$msg = "{$prefix}您有一条审批 {$text}:流程ID {$instance->id},类型 {$instance->flow_type}";
// TODO: 接入你的消息系统(站内信/企微/飞书…)
// MessageService::send($userIds, $msg);
// echo "通知 ".implode(',', $userIds).":{$msg}\n";
}
private function notifyInitiator(object $instance, string $result): void
{
$this->sendNotification([(int)$instance->initiator_id], $instance, $result, false);
}
// === 数据访问 ===
// private function getUserIdsByRole(string $roleCode): array
// {
// // 兼容 pluck 行为(不同版本可能返回 array 或 Collection)
// $rows = Db::table('user_roles as ur')
// ->join('roles as r', 'ur.role_id', '=', 'r.id')
// ->where('r.code', $roleCode)
// ->select('ur.user_id')
// ->get();
// $ids = [];
// foreach ($rows as $row) {
// $ids[] = (int)$row->user_id;
// }
// return array_values(array_unique($ids));
// }
/**
* 从角色取用户ID列表
*
* @param mixed $roleIdOrName 数字 => role_id,字符串 => roles.name 精确匹配
* @param int|null $systemId 可选:限定 roles.system_id;默认不传=不过滤
* @param bool $onlyActive 是否仅取 status=1 的角色(默认 true)
* @param bool $includeGlobal 是否包含全局角色(system_id=0),默认 true
* @return int[]
*/
private function getUserIdsByRole($roleIdOrName, ?int $systemId = null, bool $onlyActive = true, bool $includeGlobal = true): array
{
// 查询构造
// 情况A:纯数字 => 当作 role_id,优先直接走 user_roles(role_id) 索引
if (is_int($roleIdOrName) || (is_string($roleIdOrName) && ctype_digit($roleIdOrName))) {
$roleId = (int)$roleIdOrName;
// 如果不需要校验 roles 的 system/status,就不 join,性能更好
$needJoin = $onlyActive || !is_null($systemId) || $includeGlobal === false;
if (!$needJoin) {
$rows = Db::table('user_roles')
->where('role_id', $roleId)
->select('user_id')
->get();
} else {
$q = Db::table('user_roles as ur')
->join('roles as r', 'ur.role_id', '=', 'r.id')
->where('ur.role_id', $roleId)
->select('ur.user_id');
if ($onlyActive) {
$q->where('r.status', 1);
}
if (!is_null($systemId)) {
if ($includeGlobal) {
$q->whereIn('r.system_id', [0, $systemId]);
} else {
$q->where('r.system_id', $systemId);
}
}
$rows = $q->get();
}
}
// 情况B:字符串 => 当作 roles.name
else {
$name = (string)$roleIdOrName;
$q = Db::table('user_roles as ur')
->join('roles as r', 'ur.role_id', '=', 'r.id')
->where('r.name', $name)
->select('ur.user_id');
if ($onlyActive) {
$q->where('r.status', 1);
}
if (!is_null($systemId)) {
if ($includeGlobal) {
$q->whereIn('r.system_id', [0, $systemId]);
} else {
$q->where('r.system_id', $systemId);
}
}
$rows = $q->get();
}
$ids = [];
foreach ($rows as $row) {
$ids[] = (int)$row->user_id;
}
return array_values(array_unique($ids));
}
private function getLeaderIdByUserId(int $userId): ?int
{
$id = Db::table('user')->where('id', $userId)->value('report_to');
return $id ? (int)$id : null;
}
private function getDeptHeadId(int $deptId): ?int
{
$id = Db::table('departments')->where('id', $deptId)->value('head_user_id');
return $id ? (int)$id : null;
}
}
+52 -6
View File
@@ -240,16 +240,62 @@
</tbody>
</table>
<div class="" style="text-align:right;">
<div style="padding-top: 6mm; padding-bottom: 6mm; display: table; margin: 0 0 0 auto;">
<div style="display: table-cell; vertical-align: middle;">
<span>下单人:</span>
<img src="<?php echo htmlspecialchars($data['sign_img_url'] ?? ' - '); ?>" style="width: 21mm; height: 12mm; vertical-align: middle;">
<!-- PDF 底部签名区 -->
<!-- PDF 底部签名区(无表格版本) -->
<div style="margin-top: 6mm; font-size: 12px;">
<!-- 左侧:业务员签字 / 备发货负责人签字 -->
<div style="width: 50%; float: left; text-align: left;">
<!-- 行距通过 margin-bottom 控制;行内元素通过固定行高和 vertical-align: middle 垂直居中 -->
<div style="height: 14mm; line-height: 14mm; margin-bottom: 5mm;">
<span>业务员签字:</span>
<?php if (!empty($data['sign_img_url'])): ?>
<img src="<?php echo htmlspecialchars($data['sign_img_url']); ?>" style="height: 12mm; vertical-align: middle;">
<?php else: ?>
<span style="display:inline-block; width:21mm; height:12mm; vertical-align: middle; border-bottom:0.2mm solid #000;"></span>
<?php endif; ?>
</div>
<div style="height: 14mm; line-height: 14mm;">
<span>备发货负责人签字:</span>
<?php if (!empty($data['sign_img_url'])): ?>
<img src="<?php echo htmlspecialchars($data['sign_img_url']); ?>" style="height: 12mm; vertical-align: middle;">
<?php else: ?>
<span style="display:inline-block; width:21mm; height:12mm; vertical-align: middle; border-bottom:0.2mm solid #000;"></span>
<?php endif; ?>
</div>
</div>
<div>下单时间:<?php echo htmlspecialchars($data['created_at'] ?? ' - '); ?></div>
<!-- 右侧:组长签字 / 日期 -->
<div style="width: 50%; float: right; text-align: left;">
<div style="height: 14mm; line-height: 14mm; margin-bottom: 5mm;">
<span>组长签字:</span>
<?php if (!empty($data['sign_img_url'])): ?>
<img src="<?php echo htmlspecialchars($data['sign_img_url']); ?>" style="height: 12mm; vertical-align: middle;">
<?php else: ?>
<span style="display:inline-block; width:21mm; height:12mm; vertical-align: middle; border-bottom:0.2mm solid #000;"></span>
<?php endif; ?>
</div>
<div style="height: 14mm; line-height: 14mm;">
<span>日期:</span>
<?php
$_dt = $data['signed_date'] ?? ($data['created_at'] ?? '');
if (is_numeric($_dt)) {
echo htmlspecialchars(date('Y-m-d', (int)$_dt));
} elseif (!empty($_dt)) {
$t = strtotime($_dt);
echo $t ? htmlspecialchars(date('Y-m-d', $t)) : ' - ';
} else {
echo ' - ';
}
?>
</div>
</div>
<div style="clear: both;"></div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,255 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>合塑实业-汽车事业部-备货单(客户下定)</title>
<style>
/* 横版:A4 297mm,左右各 12mm => 可用宽度 273mm */
@page { margin: 12mm; }
body {
font-family: 'notosanssc', 'DejaVu Sans', Arial, sans-serif;
font-size: 10pt;
line-height: 1.3;
color: #000;
background: #fff;
}
.container {
width: 273mm; /* 横向版心 */
margin: 0 auto;
background: #fff;
}
.header {
text-align: center;
margin-bottom: 10pt;
padding-bottom: 6pt;
}
.title {
font-size: 14pt;
font-weight: bold;
margin-bottom: 6pt;
}
.sub-title {
font-size: 9pt;
color: #000;
margin: 0 6pt;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 10pt;
font-size: 9pt;
table-layout: fixed; /* 横向固定布局,配合 colgroup 更稳 */
}
th, td {
border: 0.5pt solid #000;
padding: 6pt 6pt;
text-align: center;
vertical-align: middle;
word-break: break-all;
}
th {
background-color: #f0f0f0;
font-weight: bold;
color: #000;
}
/* 跨页友好 */
thead { display: table-header-group; }
tfoot { display: table-row-group; }
tr, img { page-break-inside: avoid; }
/* 信息类表格左对齐 */
.info-table td,
.logistics-table td { text-align: left; }
.info-label { font-weight: bold; color: #000; }
.info-value { color: #000; }
.summary-row {
background-color: #f0f0f0;
font-weight: bold;
}
.payment-title {
background-color: #f0f0f0;
font-weight: bold;
text-align: center;
vertical-align: middle;
white-space: nowrap;
}
.nowrap { white-space: nowrap; }
</style>
</head>
<body>
<div class="container">
<!-- 头部 -->
<div class="header">
<h1 class="title">合塑实业-汽车事业部-备货单(客户下定)</h1>
<div>
<span class="sub-title">备货单号: <?php echo htmlspecialchars($data['order_no'] ?? 'N/A'); ?></span>
<span class="sub-title">业务员: <?php echo htmlspecialchars($data['salesperson_snapshot'] ?? ' - '); ?></span>
</div>
</div>
<!-- 基本信息 -->
<table class="info-table">
<colgroup>
<col style="width:18%">
<col style="width:15%">
<col style="width:14%">
<col style="width:14%">
<col style="width:19%">
<col style="width:20%">
</colgroup>
<tbody>
<tr>
<td><span class="info-label">阿里订单号/PI号:</span> <span class="info-value"><?php echo htmlspecialchars($data['pi_no'] ?? ' - '); ?></span></td>
<td><span class="info-label">日期:</span> <span class="info-value nowrap"><?php echo htmlspecialchars($data['biz_date'] ?? ' - '); ?></span></td>
<td><span class="info-label">关目的国:</span> <span class="info-value"><?php echo htmlspecialchars($data['clearance_country_snapshot'] ?? ' - '); ?></span></td>
<td><span class="info-label">客户来源:</span> <span class="info-value"><?php echo htmlspecialchars($data['customer_source_snapshot'] ?? ' - '); ?></span></td>
<td><span class="info-label">客户名称:</span> <span class="info-value"><?php echo htmlspecialchars($data['customer_snapshot'] ?? ' - '); ?></span></td>
<td><span class="info-label">客户所在国:</span> <span class="info-value"><?php echo htmlspecialchars($data['customer_country_snapshot'] ?? ' - '); ?></span></td>
</tr>
</tbody>
</table>
<!-- 产品列表 -->
<table class="product-table">
<colgroup>
<col style="width:6%">
<col style="width:34%">
<col style="width:6%">
<col style="width:6%">
<col style="width:6%">
<col style="width:14%">
<col style="width:8%">
<col style="width:8%">
<col style="width:6%">
<col style="width:6%">
</colgroup>
<thead>
<tr>
<th>编号</th>
<th>车型</th>
<th>数量/台</th>
<th>颜色/外</th>
<th>内饰</th>
<th>改装</th>
<th class="nowrap">单价</th>
<th class="nowrap">总价</th>
<th class="nowrap">官方指导价/二手挂牌价</th>
<th class="nowrap">优惠/二手减价</th>
</tr>
</thead>
<tbody>
<?php foreach (($data['veh_list'] ?? []) as $vehicle): ?>
<tr>
<td><?php echo htmlspecialchars($vehicle['index'] ?? ' - '); ?></td>
<td><?php echo htmlspecialchars($vehicle['model_snapshot'] ?? ' - '); ?></td>
<td><?php echo htmlspecialchars($vehicle['num'] ?? ' - '); ?></td>
<td><?php echo htmlspecialchars($vehicle['color_exterior_snapshot'] ?? ' - '); ?></td>
<td><?php echo htmlspecialchars($vehicle['color_interior_snapshot'] ?? ' - '); ?></td>
<td><?php echo htmlspecialchars($vehicle['modify'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['unit_price_text'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['total_price_text'] ?? ' - '); ?></td>
<?php if ($vehicle['mileage'] > 0): ?>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['listing_price'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['reduction_price'] ?? ' - '); ?></td>
<?php else: ?>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['official_price'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['discount_price'] ?? ' - '); ?></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
<tr class="summary-row">
<td colspan="6" style="text-align:left;">
<span class="info-label">合同汇率:</span> <span class="info-value"><?php echo htmlspecialchars($data['contract_exchange_rate'] ?? ' - '); ?></span>
</td>
<td colspan="4" style="text-align:left;">
<span class="info-label">合计到岸价:</span> <span class="info-value"><?php echo htmlspecialchars($data['cif_total_amount'] ?? ' - '); ?></span>
</td>
</tr>
</tbody>
</table>
<!-- 物流信息 -->
<table class="logistics-table">
<colgroup>
<col style="width:16%">
<col style="width:18%">
<col style="width:12%">
<col style="width:18%">
<col style="width:22%">
<col style="width:14%">
</colgroup>
<tbody>
<tr>
<td><span class="info-label">清关人:</span> <span class="info-value"><?php echo htmlspecialchars($data['declarant_name'] ?? ' - '); ?></span></td>
<td><span class="info-label">预计发运日期:</span> <span class="info-value nowrap"><?php echo htmlspecialchars($data['eta_ship_date'] ?? ' - '); ?></span></td>
<td><span class="info-label">出运方式:</span> <span class="info-value"><?php echo htmlspecialchars($data['shipping_method_snapshot'] ?? ' - '); ?></span></td>
<td><span class="info-label">国内收货港:</span> <span class="info-value"><?php echo htmlspecialchars($data['domestic_port_snapshot'] ?? ' - '); ?></span></td>
<td><span class="info-label">贸易条款/清关目的港:</span> <span class="info-value"><?php echo htmlspecialchars($data['trade_term_snapshot'] ?? ' - '); ?>/<?php echo htmlspecialchars($data['clearance_port_snapshot'] ?? ' - '); ?></span></td>
<td><span class="info-label">最迟到港时间:</span> <span class="info-value nowrap"><?php echo htmlspecialchars($data['latest_arrival_date'] ?? ' - '); ?></span></td>
</tr>
</tbody>
</table>
<!-- 计划收款信息 -->
<table class="payment-table">
<colgroup>
<col style="width:12%">
<col style="width:10%">
<col style="width:14%">
<col style="width:12%">
<col style="width:12%">
<col style="width:20%">
<col style="width:10%">
</colgroup>
<thead>
<tr>
<th style="width:24mm" rowspan="<?php echo htmlspecialchars($data['acc_index']); ?>">计划收款信息</th>
<th>序号</th>
<th>汇款国</th>
<th>日期</th>
<th>币种</th>
<th>账户</th>
<th>汇款人</th>
<th>金额</th>
</tr>
</thead>
<tbody>
<?php foreach (($data['account_list'] ?? []) as $account): ?>
<tr>
<td class="nowrap"><?php echo htmlspecialchars($account['index'] ?? ' - '); ?></td>
<td><?php echo htmlspecialchars($account['remit_country_name_snapshot'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($account['remit_date'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($account['currency_name_snapshot'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($account['account_name_snapshot'] ?? ' - '); ?></td>
<td style="text-align:left;"><?php echo htmlspecialchars($account['remitter_name'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($account['amount'] ?? ' - '); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="" style="text-align:right;">
<div style="padding-top: 6mm; padding-bottom: 6mm; display: table; margin: 0 0 0 auto;">
<div style="display: table-cell; vertical-align: middle;">
<span>下单人:</span>
<img src="<?php echo htmlspecialchars($data['sign_img_url'] ?? ' - '); ?>" style="width: 21mm; height: 12mm; vertical-align: middle;">
</div>
</div>
<div>下单时间:<?php echo htmlspecialchars($data['created_at'] ?? ' - '); ?></div>
</div>
</div>
</body>
</html>
+3
View File
@@ -127,5 +127,8 @@ class IndexController
// $up = new UploadService();
// $res = $up->uploadLocalFile($param);
// var_dump($res);
$service = new \app\common\service\StockOrdersService();
$service->createdPdfFile(60);
}
}
@@ -46,7 +46,9 @@ class ApprovalRecordsController extends BaseController
'step_index' => $record['step_index'],
'action' => $record['action'],
'status' => SysDictItem::where('item_value', $record['status'])->where('status', 1)->value('item_label'),
'action_desc' => SysDictItem::where('item_value', $record['action'])->where('status', 1)->value('item_label'),
'status' => $record['status'],
'status_desc' => SysDictItem::where('item_value', $record['status'])->where('status', 1)->value('item_label'),
'comment' => $record['comment'],
'approved_at' => $record['approved_at'],
+1 -1
View File
@@ -3,7 +3,7 @@ namespace app\crm\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\StockOrdersService;
use app\crm\service\StockOrdersService;
use support\Request;
class StockOrdersController extends BaseController
+434
View File
@@ -0,0 +1,434 @@
<?php
namespace app\crm\service;
use app\common\model\StockOrderAccounts;
use app\common\model\StockOrderItemOptions;
use app\common\model\StockOrderItems;
use app\common\validate\StockOrdersValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\StockOrders;
use app\common\model\SysCurrency;
use support\Request;
use app\common\model\User;
use support\exception\BusinessException;
use support\Db;
class StockOrdersService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = StockOrders::class;
/**
* 验证器
*/
protected $validator = StockOrdersValidate::class;
/**
* 获取验证器
*/
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':
// ====== ====== 筛选 ====== ======
// 业务id
if (isset($p['salesperson_id']) && $p['salesperson_id']) {
$config['where']['salesperson_id'] = $p['salesperson_id'];
}
// 初、终版
$config['where']['is_final'] = $p['is_final'] ?? 0;
// 关键字
if (isset($p['keyword']) && $p['keyword']) {
$k = '%' . $p['keyword'] . '%';
foreach (['pi_no', 'order_no', 'customer_snapshot'] as $col) {
$config['orWhere'][] = [$col, 'like', $k];
}
}
$config['order']['id'] = 'desc';
$config['where']['salesperson_id'] = $request->user->uid;
break;
case 'print':
$config['where']['id'] = $p['id'];
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['with'][] = 'items';
$config['with'][] = 'items.options';
$config['with'][] = 'items.moneyValues';
$config['with'][] = 'accounts';
$config['with'][] = 'moneyValues';
$config['with'][] = 'salesperson';
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
public function serviceStore(Request $request, array $data = [])
{
// 关联表较多,增加事务处理
return Db::transaction(function () use ($request, $data) {
$data = $this->preProcess('Store', $data, $request);
$dl = $this->getDataLimitFromRequest($request);
$created = $this->logicCreate($data, $dl); // 表1
// 车辆
$cifTotalAmount = 0;
$cifTotalAmountRate = 0;
$cifTotalAmountCurrency = 'CNY';
// $profit = 0;
foreach ($data['veh_list'] as $veh) {
$veh['order_id'] = $created->id;
// 唯一标识
$uniqueIdStr = "";
$uniqueIdStr .= $veh['sku_id'];
$uniqueIdStr .= $veh['model_snapshot'];
$uniqueIdStr .= $veh['color_interior_code'];
$uniqueIdStr .= $veh['color_exterior_code'];
$uniqueIdStr .= $veh['mileage'];
$uniqueIdStr .= json($veh['modify_list']);
$veh['unique_id'] = md5($uniqueIdStr);
$item = StockOrderItems::create($veh); // 表2
// 金额 // 表3
// $item->addMoney('official_price', $veh['official_price']['currency'], $veh['official_price']['amount'], $veh['official_price']['rate'], '官方指导价');
// $item->addMoney('discount_price', $veh['discount_price']['currency'], $veh['discount_price']['amount'], $veh['discount_price']['rate'], '优惠价');
if ($veh['unit_price']['amount']) {
$item->addMoney('unit_price', $veh['unit_price']['currency'], $veh['unit_price']['amount'], $veh['unit_price']['rate'], '单价');
}
if ($veh['total_price']['amount']) {
$item->addMoney('total_price', $veh['total_price']['currency'], $veh['total_price']['amount'], $veh['total_price']['rate'], '总价');
}
if ($veh['handling_fee']['amount']) {
$item->addMoney('handling_fee', $veh['handling_fee']['currency'], $veh['handling_fee']['amount'], $veh['handling_fee']['rate'], '手续费');
}
if ($veh['fob_transport_fee']['amount']) {
$item->addMoney('fob_transport_fee', $veh['fob_transport_fee']['currency'], $veh['fob_transport_fee']['amount'], $veh['fob_transport_fee']['rate'], 'FOB运输费');
}
if ($veh['port_misc_fee']['amount']) {
$item->addMoney('port_misc_fee', $veh['port_misc_fee']['currency'], $veh['port_misc_fee']['amount'], $veh['port_misc_fee']['rate'], '港杂');
}
if ($veh['retrofit_fee']['amount']) {
$item->addMoney('retrofit_fee', $veh['retrofit_fee']['currency'], $veh['retrofit_fee']['amount'], $veh['retrofit_fee']['rate'], '改装费');
}
// 二手车 or 新车
if ($veh['mileage'] > 0) {
if ($veh['listing_price']['amount']) {
$item->addMoney('listing_price', $veh['listing_price']['currency'], $veh['listing_price']['amount'], $veh['listing_price']['rate'], '二手挂牌价');
}
if ($veh['reduction_price']['amount']) {
$item->addMoney('reduction_price', $veh['reduction_price']['currency'], $veh['reduction_price']['amount'], $veh['reduction_price']['rate'], '二手减价');
}
$ttPrice = ($veh['listing_price']['amount'] / $created->contract_exchange_rate);
$ddPrice = $veh['reduction_price']['amount'];
} else {
if ($veh['official_price']['amount']) {
$item->addMoney('official_price', $veh['official_price']['currency'], $veh['official_price']['amount'], $veh['official_price']['rate'], '官方指导价');
}
if ($veh['discount_price']['amount'] ) {
$item->addMoney('discount_price', $veh['discount_price']['currency'], $veh['discount_price']['amount'], $veh['discount_price']['rate'], '优惠价');
}
$ttPrice = ($veh['official_price']['amount'] / $created->contract_exchange_rate);
$ddPrice = $veh['discount_price']['amount'];
}
// 利润
$profit = (float)$veh['unit_price']['amount'] - (float)$ttPrice - (float)$ddPrice - (float)$veh['handling_fee']['amount'] - (float)$veh['fob_transport_fee']['amount'] - (float)$veh['port_misc_fee']['amount'] - (float)$veh['retrofit_fee']['amount'];
$item->addMoney('profit', $veh['unit_price']['currency'], $profit, $veh['unit_price']['rate'], '利润');
// cif_total_amount
$cifTotalAmount += $veh['total_price']['amount'];
$cifTotalAmountRate = $veh['retrofit_fee']['rate'];
$cifTotalAmountCurrency = $veh['retrofit_fee']['currency'];
// 改装
if (count($veh['modify_list']) > 0) {
foreach ($veh['modify_list'] as $modify) {
$modify['order_item_id'] = $item->id;
StockOrderItemOptions::create($modify); // 表4
}
}
}
// 计算&&插入 表1
$created->addMoney('cif_total_amount', $cifTotalAmountCurrency, $cifTotalAmount, $cifTotalAmountRate, '合计到岸价');
// 账户
if (count($data['account_list']) > 0) {
foreach ($data['account_list'] as $account) {
$account['order_id'] = $created->id;
$account['remit_date'] = strtotime($account['remit_date']);
StockOrderAccounts::create($account); // 表5
}
}
return $this->postProcess('Store', $created);
});
}
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') ?? '';
return $data;
}
protected function afterServiceStore($res)
{
// 处理原单
if ($res->is_final == 1 && $res->object_id > 0) {
$order = StockOrders::find($res->object_id);
$order->is_use = 1;
$order->object_id = $res->id;
if (!$order->save()) {
flg('备货单【'.$order->order_no.'】创建终版成功,但更新原单失败');
}
}
// 启动审批
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
$result = $appro->start('stock_order_flow', 'stock_orders', $res->id, $res->toArray(), $res->salesperson_id);
var_dump($result);
// 异步生成pdf文件
\Webman\RedisQueue\Redis::send('stock-order-pdf', ['id'=>$res->id]);
return $res;
}
public function createdPdfFile($id)
{
$config = [];
$config['model'] = $this->modelClass;
$config['where']['id'] = $id;
$config['with'][] = 'items';
$config['with'][] = 'items.options';
$config['with'][] = 'items.moneyValues';
$config['with'][] = 'accounts';
$config['with'][] = 'moneyValues';
$config['with'][] = 'salesperson';
$item = $this->logicShow($config);
$pdfData = $this->pdfData($item);
// 渲染模板为最终HTML
$tplPath = app_path('common/utils/pdf/template/stockOrder.stub');
$html = \app\common\utils\pdf\service\PdfHelper::render($tplPath, ['data' => $pdfData]);
// 生成PDF
$driver = new \app\common\utils\pdf\service\MpdfDriver();
$pdfBin = $driver->htmlToPdf($html);
// 存储
$dir = public_path('pdf').'/';
$fileName = ($pdfData['order_no'] ?? date('YmdHis')) . '.pdf';
// 本地保存
\app\common\utils\pdf\service\PdfStorage::saveBinary(
$pdfBin,
$dir,
$fileName
);
// // 在线预览
// return new \support\Response(200, [
// 'Content-Type' => 'application/pdf',
// 'Content-Disposition' => 'inline; filename="stock-order.pdf"',
// ], $pdfBin);
// $url = [
// 'file_name' => $fileName,
// 'file_url' => getenv('MAIN_DOMAIN').'/pdf/'.$fileName
// ];
// 上传云端
$param = [
'path' => $dir . $fileName,
'scene' => 'stock_orders',
'category' => 'pdf',
'original_name' => $fileName
];
$up = new \app\common\service\UploadService();
$res = $up->uploadLocalFile($param);
@unlink($dir . $fileName);
// 更新文件地址
$item->file_url = $res['file_name'];
$item->save();
return $res;
}
public function servicePrint(Request $request)
{
$config = $this->buildQueryConfig($request, 'show');
$item = $this->logicShow($config);
return $this->postProcess('Print', $item);
}
public function pdfData($item)
{
$vehList = [];
$index = 0;
foreach ($item->items as $k => $v) {
// 存在则数量加1
if (count($vehList) > 0 && isset($vehList[$v->unique_id])) {
$vehList[$v->unique_id]['num'] += 1;
$vehList[$v->unique_id]['total_price'] += $vehList[$v->unique_id]['unit_price'];
$vehList[$v->unique_id]['total_price_text'] = $vehList[$v->unique_id]['total_price_symbol'].number_format($vehList[$v->unique_id]['total_price'], 2, '.', ',');
continue;
}
$index += 1;
// 处理金额
$mv = [];
foreach ($v->moneyValues as $money) {
$mv[$money->field] = [
'currency' => $money->currency,
'amount' => $money->amount,
'rate' => $money->exchange_rate,
'currency_symbol' => SysCurrency::where('currency_code', $money->currency)->value('currency_symbol'),
];
}
// 处理改装
$mfy = '';
foreach ($v->options as $my) {
$mfy .= $my->option_name_snapshot.' ';
}
// 处理结果集
$vehList[$v->unique_id] = [
'index' => $index,
'model_snapshot' => $v->model_snapshot,
'color_interior_snapshot' => $v->color_interior_snapshot,
'color_exterior_snapshot' => $v->color_exterior_snapshot,
'num' => 1,
'modify' => $mfy,
'mileage' => $v->mileage,
'unit_price' => $mv['unit_price']['amount'],
'total_price' => $mv['total_price']['amount'],
'unit_price_symbol' => $mv['unit_price']['currency_symbol'],
'total_price_symbol' => $mv['total_price']['currency_symbol'],
'unit_price_text' => $mv['unit_price']['currency_symbol'].number_format($mv['unit_price']['amount'], 2, '.', ','),
'total_price_text' => $mv['total_price']['currency_symbol'].number_format($mv['total_price']['amount'], 2, '.', ',')
];
// 根据里程处理 新车or二手车
if ($v->mileage > 0) {
if (isset($mv['reduction_price'])) {
$vehList[$v->unique_id]['reduction_price'] = $mv['reduction_price']['amount'];
$vehList[$v->unique_id]['reduction_price_symbol'] = $mv['reduction_price']['currency_symbol'];
$vehList[$v->unique_id]['reduction_price_text'] = $mv['reduction_price']['currency_symbol'].number_format($mv['reduction_price']['amount'], 2, '.', ',');
}
$vehList[$v->unique_id]['listing_price'] = $mv['listing_price']['amount'];
$vehList[$v->unique_id]['listing_price_symbol'] = $mv['listing_price']['currency_symbol'];
$vehList[$v->unique_id]['listing_price_text'] = $mv['listing_price']['currency_symbol'].number_format($mv['listing_price']['amount'], 2, '.', ',');
} else {
if (isset($mv['discount_price'])) {
$vehList[$v->unique_id]['discount_price'] = $mv['discount_price']['amount'];
$vehList[$v->unique_id]['discount_price_symbol'] = $mv['discount_price']['currency_symbol'];
$vehList[$v->unique_id]['discount_price_text'] = $mv['discount_price']['currency_symbol'].number_format($mv['discount_price']['amount'], 2, '.', ',');
}
$vehList[$v->unique_id]['official_price'] = $mv['official_price']['amount'];
$vehList[$v->unique_id]['official_price_symbol'] = $mv['official_price']['currency_symbol'];
$vehList[$v->unique_id]['official_price_text'] = $mv['official_price']['currency_symbol'].number_format($mv['official_price']['amount'], 2, '.', ',');
}
}
$accList = [];
$accIndex = 1;
foreach ($item->accounts as $k => $v) {
$accList[] = [
'index' => '收款'.$k + 1,
'remit_country_name_snapshot' => $v->remit_country_name_snapshot,
'remit_date' => date('Y-m-d', $v->remit_date),
'currency_name_snapshot' => $v->currency_name_snapshot,
'account_name_snapshot' => $v->account_name_snapshot,
'remitter_name' => $v->remitter_name,
'amount' => $v->amount,
];
$accIndex += 1;
}
$cifTotalAmount = 0.00;
if ($item->moneyValues) {
$cifTotalAmountSysboml = SysCurrency::where('currency_code', $item->moneyValues->currency)->value('currency_symbol');
$cifTotalAmount = $cifTotalAmountSysboml.sprintf("%.2f", $item->moneyValues->amount, $item->moneyValues->exchange_rate);
}
$veh = [
'order_no' => $item->order_no,
'salesperson_snapshot' => $item->salesperson_snapshot,
// 'sign_img' => $item->salesperson->sign_img,
'sign_img_url' => (!empty($item->salesperson->sign_img))?(new \app\common\service\OssService)->generatePrivateUrl($item->salesperson->sign_img):'',
'created_at' => date('Y-m-d', strtotime($item->created_at)),
'pi_no' => $item->pi_no,
'biz_date' => date('Y-m-d', $item->biz_date),
'clearance_country_snapshot' => $item->clearance_country_snapshot,
'customer_source_snapshot' => $item->customer_source_snapshot,
'customer_snapshot' => $item->customer_snapshot,
'customer_country_snapshot' => $item->customer_country_snapshot,
// 订单项
'veh_list' => $vehList,
'contract_exchange_rate' => $item->contract_exchange_rate,
'cif_total_amount' => $item->cif_total_amount,
'declarant_name' => $item->declarant_name,
'eta_ship_date' => date('Y-m-d', $item->eta_ship_date),
'shipping_method_snapshot' => $item->shipping_method_snapshot,
'domestic_port_snapshot' => $item->domestic_port_snapshot,
'trade_term_snapshot' => $item->trade_term_snapshot,
'clearance_port_snapshot' => $item->clearance_port_snapshot,
'latest_arrival_date' => date('Y-m-d', $item->latest_arrival_date),
// 账户
'account_list' => $accList,
'acc_index' => $accIndex,
'cif_total_amount' => $cifTotalAmount
];
return $veh;
}
}