调整备货单

This commit is contained in:
mzl
2025-09-08 20:00:12 +08:00
parent 28368e27a7
commit 84c588496b
14 changed files with 945 additions and 247 deletions
+12
View File
@@ -154,4 +154,16 @@ class UserController extends BaseController
return $this->error($e->getMessage()); return $this->error($e->getMessage());
} }
} }
public function updateSign(Request $request)
{
try {
$data = $request->post();
$this->service->serviceUpdateSign($request, $data);
return $this->success([], '签名成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
} }
+11 -2
View File
@@ -80,8 +80,17 @@ class SysDictService
break; break;
case 'scope': case 'scope':
$config['order']['id'] = 'desc'; $config['order']['id'] = 'desc';
// 决定查id还是code
$scopeType = 0;
if (isset($p['scope_type']) && $p['scope_type'] > 0) {
$scopeType = 1;
}
if (isset($p['scope']) && !empty($p['scope'])) { if (isset($p['scope']) && !empty($p['scope'])) {
$config['where']['id'] = ['in', explode(',', $p['scope'])]; if ($scopeType == 1) {
$config['where']['dict_code'] = ['in', explode(',', $p['scope'])];
} else {
$config['where']['id'] = ['in', explode(',', $p['scope'])];
}
} }
$config['with']['sysDictItem'][] = 'dict_id'; $config['with']['sysDictItem'][] = 'dict_id';
$config['with']['sysDictItem'][] = 'item_label'; $config['with']['sysDictItem'][] = 'item_label';
@@ -95,7 +104,7 @@ class SysDictService
$config['with']['sysDictItem'][] = 'item_value'; $config['with']['sysDictItem'][] = 'item_value';
break; break;
} }
return $config; return $config;
} }
+10
View File
@@ -113,6 +113,7 @@ class UserService
$config['where']['id'] = $p['id']; $config['where']['id'] = $p['id'];
break; break;
case 'edit': case 'edit':
case 'updateSign':
$config['where']['id'] = $request->user->uid; $config['where']['id'] = $request->user->uid;
break; break;
} }
@@ -154,4 +155,13 @@ class UserService
$user = $this->logicUpdateBy($config,$data); $user = $this->logicUpdateBy($config,$data);
return $user; return $user;
} }
public function serviceUpdateSign(Request $request, array $data)
{
$config = $this->buildQueryConfig($request, 'updateSign');
$user = $this->logicUpdateBy($config,$data);
return $user;
}
} }
+2 -1
View File
@@ -44,7 +44,8 @@ class StockOrderItems extends Model
protected $fillable = [ protected $fillable = [
'id', 'order_id', 'product_code', 'sku_id', 'sku_snapshot', 'id', 'order_id', 'product_code', 'sku_id', 'sku_snapshot',
'model_snapshot', 'quantity', 'color_interior_code', 'color_interior_snapshot', 'model_snapshot', 'quantity', 'color_interior_code', 'color_interior_snapshot',
'color_exterior_code', 'color_exterior_snapshot', 'remark', 'vin', 'created_at', 'updated_at' 'color_exterior_code', 'color_exterior_snapshot', 'remark', 'vin', 'created_at', 'updated_at',
'mileage', 'unique_id'
]; ];
/** /**
+1 -1
View File
@@ -87,6 +87,6 @@ class StockOrders extends Model
*/ */
public function moneyValues() public function moneyValues()
{ {
return $this->hasMany(VehMoneyValue::class, 'object_id', 'id')->where('object_scene', 'stock_orders'); return $this->hasOne(VehMoneyValue::class, 'object_id', 'id')->where('object_scene', 'stock_orders');
} }
} }
+98 -29
View File
@@ -103,20 +103,65 @@ class StockOrdersService
// $profit = 0; // $profit = 0;
foreach ($data['veh_list'] as $veh) { foreach ($data['veh_list'] as $veh) {
$veh['order_id'] = $created->id; $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 $item = StockOrderItems::create($veh); // 表2
// 金额 // 表3 // 金额 // 表3
$item->addMoney('official_price', $veh['official_price']['currency'], $veh['official_price']['amount'], $veh['official_price']['rate'], '官方指导价'); // $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'], '优惠价'); // $item->addMoney('discount_price', $veh['discount_price']['currency'], $veh['discount_price']['amount'], $veh['discount_price']['rate'], '优惠价');
$item->addMoney('unit_price', $veh['unit_price']['currency'], $veh['unit_price']['amount'], $veh['unit_price']['rate'], '单价'); if ($veh['unit_price']['amount']) {
$item->addMoney('total_price', $veh['total_price']['currency'], $veh['total_price']['amount'], $veh['total_price']['rate'], '总价'); $item->addMoney('unit_price', $veh['unit_price']['currency'], $veh['unit_price']['amount'], $veh['unit_price']['rate'], '单价');
$item->addMoney('handling_fee', $veh['handling_fee']['currency'], $veh['handling_fee']['amount'], $veh['handling_fee']['rate'], '手续费'); }
$item->addMoney('fob_transport_fee', $veh['fob_transport_fee']['currency'], $veh['fob_transport_fee']['amount'], $veh['fob_transport_fee']['rate'], 'FOB运输费'); if ($veh['total_price']['amount']) {
$item->addMoney('port_misc_fee', $veh['port_misc_fee']['currency'], $veh['port_misc_fee']['amount'], $veh['port_misc_fee']['rate'], '港杂'); $item->addMoney('total_price', $veh['total_price']['currency'], $veh['total_price']['amount'], $veh['total_price']['rate'], '总价');
$item->addMoney('retrofit_fee', $veh['retrofit_fee']['currency'], $veh['retrofit_fee']['amount'], $veh['retrofit_fee']['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 = $veh['unit_price']['amount'] - ($veh['official_price']['amount'] / $created->contract_exchange_rate) - $veh['discount_price']['amount'] - $veh['handling_fee']['amount'] - $veh['fob_transport_fee']['amount'] - $veh['port_misc_fee']['amount'] - $veh['retrofit_fee']['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'], '利润'); $item->addMoney('profit', $veh['unit_price']['currency'], $profit, $veh['unit_price']['rate'], '利润');
// cif_total_amount // cif_total_amount
@@ -193,8 +238,8 @@ class StockOrdersService
$pdfBin = $driver->htmlToPdf($html); $pdfBin = $driver->htmlToPdf($html);
// 存储 // 存储
$dir = public_path('pdf'); $dir = public_path('pdf').'/';
$fileName = '/'.($pdfData['order_no'] ?? date('YmdHis')) . '.pdf'; $fileName = ($pdfData['order_no'] ?? date('YmdHis')) . '.pdf';
// 本地保存 // 本地保存
$filePath = \app\common\utils\pdf\service\PdfStorage::saveBinary( $filePath = \app\common\utils\pdf\service\PdfStorage::saveBinary(
@@ -209,7 +254,11 @@ class StockOrdersService
// 'Content-Disposition' => 'inline; filename="stock-order.pdf"', // 'Content-Disposition' => 'inline; filename="stock-order.pdf"',
// ], $pdfBin); // ], $pdfBin);
return ['file_url' => $dir.$fileName]; $url = [
'file_name' => $fileName,
'file_url' => getenv('MAIN_DOMAIN').'/pdf/'.$fileName
];
return $url;
} }
@@ -219,10 +268,10 @@ class StockOrdersService
$index = 0; $index = 0;
foreach ($item->items as $k => $v) { foreach ($item->items as $k => $v) {
// 存在则数量加1 // 存在则数量加1
if (count($vehList) > 0 && isset($vehList[$v->sku_id])) { if (count($vehList) > 0 && isset($vehList[$v->unique_id])) {
$vehList[$v->sku_id]['num'] += 1; $vehList[$v->unique_id]['num'] += 1;
$vehList[$v->sku_id]['total_price'] += $vehList[$v->sku_id]['unit_price']; $vehList[$v->unique_id]['total_price'] += $vehList[$v->unique_id]['unit_price'];
$vehList[$v->sku_id]['total_price_text'] = $vehList[$v->sku_id]['total_price_symbol'].number_format($vehList[$v->sku_id]['total_price'], 2, '.', ','); $vehList[$v->unique_id]['total_price_text'] = $vehList[$v->unique_id]['total_price_symbol'].number_format($vehList[$v->unique_id]['total_price'], 2, '.', ',');
continue; continue;
} }
$index += 1; $index += 1;
@@ -232,7 +281,7 @@ class StockOrdersService
$mv[$money->field] = [ $mv[$money->field] = [
'currency' => $money->currency, 'currency' => $money->currency,
'amount' => $money->amount, 'amount' => $money->amount,
'rate' => $money->rate, 'rate' => $money->exchange_rate,
'currency_symbol' => SysCurrency::where('currency_code', $money->currency)->value('currency_symbol'), 'currency_symbol' => SysCurrency::where('currency_code', $money->currency)->value('currency_symbol'),
]; ];
} }
@@ -243,29 +292,42 @@ class StockOrdersService
} }
// 处理结果集 // 处理结果集
$vehList[$v->sku_id] = [ $vehList[$v->unique_id] = [
'index' => $index, 'index' => $index,
'model_snapshot' => $v->model_snapshot, 'model_snapshot' => $v->model_snapshot,
'color_interior_snapshot' => $v->color_interior_snapshot, 'color_interior_snapshot' => $v->color_interior_snapshot,
'color_exterior_snapshot' => $v->color_exterior_snapshot, 'color_exterior_snapshot' => $v->color_exterior_snapshot,
'num' => 1, 'num' => 1,
'modify' => $mfy, 'modify' => $mfy,
'mileage' => $v->mileage,
'unit_price' => $mv['unit_price']['amount'], 'unit_price' => $mv['unit_price']['amount'],
'total_price' => $mv['total_price']['amount'], 'total_price' => $mv['total_price']['amount'],
'official_price' => $mv['official_price']['amount'],
'discount_price' => $mv['discount_price']['amount'],
'unit_price_symbol' => $mv['unit_price']['currency_symbol'], 'unit_price_symbol' => $mv['unit_price']['currency_symbol'],
'total_price_symbol' => $mv['total_price']['currency_symbol'], 'total_price_symbol' => $mv['total_price']['currency_symbol'],
'official_price_symbol' => $mv['official_price']['currency_symbol'],
'discount_price_symbol' => $mv['discount_price']['currency_symbol'],
'unit_price_text' => $mv['unit_price']['currency_symbol'].number_format($mv['unit_price']['amount'], 2, '.', ','), '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, '.', ','), 'total_price_text' => $mv['total_price']['currency_symbol'].number_format($mv['total_price']['amount'], 2, '.', ',')
'official_price_text' => $mv['official_price']['currency_symbol'].number_format($mv['official_price']['amount'], 2, '.', ','),
'discount_price_text' => $mv['discount_price']['currency_symbol'].number_format($mv['discount_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 = []; $accList = [];
@@ -283,6 +345,12 @@ class StockOrdersService
$accIndex += 1; $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 = [ $veh = [
'order_no' => $item->order_no, 'order_no' => $item->order_no,
'salesperson_snapshot' => $item->salesperson_snapshot, 'salesperson_snapshot' => $item->salesperson_snapshot,
@@ -310,7 +378,8 @@ class StockOrdersService
// 账户 // 账户
'account_list' => $accList, 'account_list' => $accList,
'acc_index' => $accIndex 'acc_index' => $accIndex,
'cif_total_amount' => $cifTotalAmount
]; ];
return $veh; return $veh;
+6 -1
View File
@@ -52,6 +52,9 @@ class UserService
'sn' => $user->sn, 'sn' => $user->sn,
'corp_id' => $user->corp_id, 'corp_id' => $user->corp_id,
'corp_name' => $corp_name, 'corp_name' => $corp_name,
'report_to' => $user->report_to,
'sign_img' => $user->sign_img,
'sign_img_url' => (!empty($user->sign_img))?(new \app\common\service\OssService)->generatePrivateUrl($user->sign_img):'',
], ],
// 'menu' => $menuTree, // 'menu' => $menuTree,
// 'permissions' => $permissionCodes // 'permissions' => $permissionCodes
@@ -84,7 +87,9 @@ class UserService
'email' => $user->email, 'email' => $user->email,
'sex' => $user->sex, 'sex' => $user->sex,
'status' => $user->status, 'status' => $user->status,
'roles' => $user->roles->pluck('name') 'roles' => $user->roles->pluck('name'),
'sign_img' => $user->sign_img,
'sign_img_url' => (!empty($user->sign_img))?$ossservice->generatePrivateUrl($user->sign_img):'',
]; ];
} }
@@ -34,36 +34,56 @@ class ApprovalFlowService
throw new BusinessException("未找到可执行的审批步骤"); throw new BusinessException("未找到可执行的审批步骤");
} }
// 3. 创建审批实例 // 3. 计算截止时间
$deadlineAt = null;
if ($flow->timeout_hours > 0) {
$deadlineAt = date('Y-m-d H:i:s', time() + $flow->timeout_hours * 3600);
}
// 4. 创建审批实例
$instanceId = Db::table('approval_instances')->insertGetId([ $instanceId = Db::table('approval_instances')->insertGetId([
'flow_type' => $flowType, 'flow_type' => $flowType,
'biz_type' => $bizType, 'biz_type' => $bizType,
'biz_id' => $bizId, 'biz_id' => $bizId,
'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE), 'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE),
'initiator_id' => $initiatorId, 'initiator_id' => $initiatorId,
'status' => 'processing', 'status' => 'processing', // processing 表示正在进行中
'current_step' => 0, 'current_step' => 0,
'deadline_at' => $deadlineAt,
'created_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'),
]); ]);
// 4. 创建审批记录(待办) // 5. 创建审批记录(待办)
$approverIds = $this->resolveApprovers($firstStep, (object)['initiator_id' => $initiatorId]); $approverIds = $this->resolveApprovers($firstStep, (object)[
'initiator_id' => $initiatorId,
'form_data' => json_encode($formData)
]);
foreach ($approverIds as $approverId) { foreach ($approverIds as $approverId) {
Db::table('approval_records')->insert([ Db::table('approval_records')->insert([
'instance_id' => $instanceId, 'instance_id' => $instanceId,
'step_name' => $firstStep['name'],
'approver_id' => $approverId, 'approver_id' => $approverId,
'step_index' => 0,
'action' => '', // 未操作时为空
'status' => 'pending', 'status' => 'pending',
'approved_at' => date('Y-m-d H:i:s', 0), // 未审批时设置为初始时间
'created_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]); ]);
} }
// 5. 通知审批人 // 6. 通知审批人
$this->sendNotification($approverIds, (object)['id' => $instanceId, 'flow_type' => $flowType], 'pending', false); $instance = (object)[
'id' => $instanceId,
'flow_type' => $flowType,
'form_data' => json_encode($formData),
'initiator_id' => $initiatorId
];
$this->sendNotification($approverIds, $instance, 'pending', false);
// 6. 通知抄送人(当前步骤) // 7. 通知抄送人(当前步骤)
$this->notifyCCs($firstStep, (object)['id' => $instanceId, 'flow_type' => $flowType, 'form_data' => json_encode($formData)], 'pending'); $this->notifyCCs($firstStep, $instance, 'pending');
return ['instance_id' => $instanceId]; return ['instance_id' => $instanceId];
} }
@@ -73,13 +93,28 @@ class ApprovalFlowService
*/ */
public function approve(int $instanceId, int $approverId, string $action, string $comment = '') public function approve(int $instanceId, int $approverId, string $action, string $comment = '')
{ {
// 验证操作是否有效
if (!in_array($action, ['approve', 'reject'])) {
throw new BusinessException("无效的审批操作");
}
$instance = Db::table('approval_instances')->where('id', $instanceId)->first(); $instance = Db::table('approval_instances')->where('id', $instanceId)->first();
if (!$instance) { if (!$instance) {
throw new BusinessException("审批实例未找到"); throw new BusinessException("审批实例未找到");
} }
$steps = json_decode($instance->steps, true) ?: json_decode(Db::table('approval_flows')->where('type', $instance->flow_type)->value('steps'), true); // 获取流程定义
$currentStep = $steps[$instance->current_step] ?? null; $flow = Db::table('approval_flows')
->where('type', $instance->flow_type)
->first();
if (!$flow) {
throw new BusinessException("审批流程定义未找到");
}
$steps = json_decode($flow->steps, true);
$currentStepIndex = $instance->current_step;
$currentStep = $steps[$currentStepIndex] ?? null;
if (!$currentStep) { if (!$currentStep) {
throw new BusinessException("当前步骤配置错误"); throw new BusinessException("当前步骤配置错误");
@@ -90,20 +125,33 @@ class ApprovalFlowService
throw new BusinessException("您无权审批此步骤"); throw new BusinessException("您无权审批此步骤");
} }
// 检查用户是否已经审批过
$existingRecord = Db::table('approval_records')
->where('instance_id', $instanceId)
->where('approver_id', $approverId)
->where('step_index', $currentStepIndex)
->first();
if ($existingRecord && $existingRecord->status !== 'pending') {
throw new BusinessException("您已经审批过此步骤");
}
// 更新审批记录 // 更新审批记录
Db::table('approval_records') Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('approver_id', $approverId) ->where('approver_id', $approverId)
->where('step_index', $currentStepIndex)
->where('status', 'pending') ->where('status', 'pending')
->update([ ->update([
'status' => $action, 'action' => $action,
'status' => $action, // 状态与操作一致
'comment' => $comment, 'comment' => $comment,
'approved_at' => date('Y-m-d H:i:s'), 'approved_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'),
]); ]);
// 检查当前步骤是否完成(根据 type: any, all, sequential) // 检查当前步骤是否完成(根据 type: any, all)
$stepCompleted = $this->isStepCompleted($instanceId, $instance->current_step, $currentStep['type']); $stepCompleted = $this->isStepCompleted($instanceId, $currentStepIndex, $currentStep['type']);
if (!$stepCompleted) { if (!$stepCompleted) {
// 步骤未完成,不进入下一步 // 步骤未完成,不进入下一步
@@ -111,12 +159,13 @@ class ApprovalFlowService
} }
// 步骤已完成,进入下一步或结束 // 步骤已完成,进入下一步或结束
$nextStep = $this->getNextValidStep($steps, json_decode($instance->form_data, true), $instance->current_step + 1); $nextStepIndex = $this->getNextStepIndex($steps, json_decode($instance->form_data, true), $currentStepIndex);
if ($nextStep === null) { if ($nextStepIndex === null) {
// 流程结束 // 流程结束
Db::table('approval_instances')->where('id', $instanceId)->update([ Db::table('approval_instances')->where('id', $instanceId)->update([
'status' => $action === 'approve' ? 'approved' : 'rejected', 'status' => $action, // 最终状态与最后一次操作一致
'finished_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'),
]); ]);
@@ -127,21 +176,26 @@ class ApprovalFlowService
} }
// 进入下一步 // 进入下一步
$nextStepIndex = array_search($nextStep, $steps);
Db::table('approval_instances')->where('id', $instanceId)->update([ Db::table('approval_instances')->where('id', $instanceId)->update([
'current_step' => $nextStepIndex, 'current_step' => $nextStepIndex,
'updated_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'),
]); ]);
// 获取下一步信息
$nextStep = $steps[$nextStepIndex];
// 创建下一步的审批记录 // 创建下一步的审批记录
$nextApprovers = $this->resolveApprovers($nextStep, $instance); $nextApprovers = $this->resolveApprovers($nextStep, $instance);
foreach ($nextApprovers as $approverId) { foreach ($nextApprovers as $approverId) {
Db::table('approval_records')->insert([ Db::table('approval_records')->insert([
'instance_id' => $instanceId, 'instance_id' => $instanceId,
'step_name' => $nextStep['name'],
'approver_id' => $approverId, 'approver_id' => $approverId,
'step_index' => $nextStepIndex,
'action' => '',
'status' => 'pending', 'status' => 'pending',
'approved_at' => date('Y-m-d H:i:s', 0),
'created_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]); ]);
} }
@@ -233,6 +287,26 @@ class ApprovalFlowService
$ccUserIds = array_unique(array_filter($ccUserIds)); $ccUserIds = array_unique(array_filter($ccUserIds));
if (!empty($ccUserIds)) { if (!empty($ccUserIds)) {
// 记录抄送信息到数据库
foreach ($ccUserIds as $userId) {
// 检查是否已存在抄送记录
$exists = Db::table('approval_ccs')
->where('instance_id', $instance->id)
->where('user_id', $userId)
->exists();
if (!$exists) {
Db::table('approval_ccs')->insert([
'instance_id' => $instance->id,
'user_id' => $userId,
'notified_at' => date('Y-m-d H:i:s'),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
// 发送通知
$this->sendNotification($ccUserIds, $instance, $action, true); $this->sendNotification($ccUserIds, $instance, $action, true);
} }
} }
@@ -264,7 +338,7 @@ class ApprovalFlowService
case '==': return $actualValue == $value; case '==': return $actualValue == $value;
case '!=': return $actualValue != $value; case '!=': return $actualValue != $value;
case '>': return $actualValue > $value; case '>': return $actualValue > $value;
case '<': $actualValue < $value; case '<': return $actualValue < $value;
case '>=': return $actualValue >= $value; case '>=': return $actualValue >= $value;
case '<=': return $actualValue <= $value; case '<=': return $actualValue <= $value;
default: throw new BusinessException("不支持的操作符: {$op}"); default: throw new BusinessException("不支持的操作符: {$op}");
@@ -272,14 +346,14 @@ class ApprovalFlowService
} }
/** /**
* 获取下一个有效步骤(考虑 condition) * 获取下一个有效步骤索引(考虑 condition)
*/ */
private function getNextValidStep(array $steps, array $formData, int $startIndex = 0): ?array private function getNextStepIndex(array $steps, array $formData, int $currentStepIndex): ?int
{ {
for ($i = $startIndex; $i < count($steps); $i++) { for ($i = $currentStepIndex + 1; $i < count($steps); $i++) {
$step = $steps[$i]; $step = $steps[$i];
if (!isset($step['condition']) || $this->evaluateCondition($step['condition'], $formData)) { if (!isset($step['condition']) || $this->evaluateCondition($step['condition'], $formData)) {
return $step; return $i;
} }
} }
return null; return null;
@@ -299,20 +373,38 @@ class ApprovalFlowService
*/ */
private function isStepCompleted(int $instanceId, int $stepIndex, string $type): bool private function isStepCompleted(int $instanceId, int $stepIndex, string $type): bool
{ {
$pendingCount = Db::table('approval_records') // 获取当前步骤的所有审批记录
$records = Db::table('approval_records')
->where('instance_id', $instanceId) ->where('instance_id', $instanceId)
->where('status', 'pending') ->where('step_index', $stepIndex)
->count(); ->get();
if ($type === 'any') { if ($records->isEmpty()) {
return $pendingCount === 0; // 任一通过即可 return false;
} }
if ($type === 'all' || $type === 'sequential') { switch ($type) {
return $pendingCount === 0; // 所有人都必须审批 case 'any':
// 任意一人审批即可(approve或reject都算完成)
foreach ($records as $record) {
if ($record->status !== 'pending') {
return true;
}
}
return false;
case 'all':
// 所有人都必须审批
foreach ($records as $record) {
if ($record->status === 'pending') {
return false;
}
}
return true;
default:
return false;
} }
return false;
} }
/** /**
@@ -341,21 +433,12 @@ class ApprovalFlowService
*/ */
public function getPendingApprovals(int $userId): array public function getPendingApprovals(int $userId): array
{ {
// 简化查询:找到用户需要审批的实例ID return Db::table('approval_records as ar')
// 更复杂的逻辑(如并行会签)需要更精细的查询 ->join('approval_instances as ai', 'ar.instance_id', '=', 'ai.id')
$instanceIds = Db::table('approval_records') ->where('ar.approver_id', $userId)
->where('approver_id', $userId) ->where('ar.status', 'pending')
->pluck('instance_id') ->where('ai.status', 'processing')
->unique() ->select('ai.*')
->toArray();
if (empty($instanceIds)) {
return [];
}
return Db::table('approval_instances')
->whereIn('id', $instanceIds)
->where('status', 'processing')
->get() ->get()
->toArray(); ->toArray();
} }
@@ -406,12 +489,7 @@ class ApprovalFlowService
if ($hasReject) { if ($hasReject) {
$status = 'rejected'; $status = 'rejected';
} else { } else {
if ($step['type'] === 'parallel') { $status = 'approved';
// 简化处理
$status = 'approved';
} else {
$status = 'approved';
}
} }
} elseif ($index < $instance->current_step) { } elseif ($index < $instance->current_step) {
$status = 'approved'; $status = 'approved';
@@ -449,172 +527,40 @@ class ApprovalFlowService
'updated_at' => $now 'updated_at' => $now
]; ];
if ($flow->timeout_action === 'auto_reject') { if ($flow->timeout_action === 'auto_reject') {
$updateData['status'] = 'rejected'; $updateData['status'] = 'reject';
Db::table('approval_instances')->where('id', $instance->id)->update($updateData);
$this->notifyInitiator($instance, 'rejected_timeout');
} elseif ($flow->timeout_action === 'auto_approve') { } elseif ($flow->timeout_action === 'auto_approve') {
$updateData['status'] = 'approved'; $updateData['status'] = 'approve';
Db::table('approval_instances')->where('id', $instance->id)->update($updateData);
$this->notifyInitiator($instance, 'approved_timeout');
} }
Db::table('approval_instances')->where('id', $instance->id)->update($updateData);
$this->notifyInitiator($instance, $flow->timeout_action);
} }
} }
} }
// --- 辅助方法 --- // --- 辅助方法 ---
// private function canUserApproveStep(array $step, int $userId, object $instance): bool
// {
// $approvers = $this->resolveApprovers($step, $instance);
// return in_array($userId, $approvers);
// }
// private function resolveApprovers(array $step, object $instance): array
// {
// $resolved = [];
// foreach ($step['approvers'] as $approver) {
// if ($approver['type'] === 'role') {
// // 示例:根据角色查找用户ID,你需要替换为实际逻辑
// if ($approver['value'] === 'admin') {
// $resolved[] = 1; // 假设 admin 角色的用户ID是1
// } elseif ($approver['value'] === 'manager') {
// $resolved[] = 2; $resolved[] = 3; // 假设有多个manager
// }
// } elseif ($approver['type'] === 'user') {
// $resolved[] = (int)$approver['value'];
// } elseif ($approver['type'] === 'initiator_leader') {
// // 示例:获取发起人的领导
// $resolved[] = 4; // 假设领导ID是4
// }
// }
// return array_unique($resolved);
// }
// private function resolveApprovers(array $step, object $instance): array
// {
// $resolved = [];
// foreach ($step['approvers'] as $approverConfig) {
// $approverIds = [];
// switch ($approverConfig['type']) {
// // 1. 按角色查找
// case 'role':
// $roleCode = $approverConfig['value'];
// // 所有拥有该角色的用户ID
// $approverIds = $this->getUserIdsByRole($roleCode);
// break;
// // 2. 按具体用户ID
// case 'user':
// $approverIds = [(int)$approverConfig['value']];
// break;
// // 3. 按发起人的直属领导 (业务一般是组长)
// case 'initiator_leader':
// // 用户表中 report_to 字段
// $approverIds = $this->getLeaderIdByUserId($instance->initiator_id);
// if ($approverIds) {
// $approverIds = [$approverIds];
// } else {
// $approverIds = []; // 没有领导,跳过
// }
// break;
// // 4. 按业务字段关联的用户 (高级功能)
// case 'field_user':
// $fieldName = $approverConfig['value']; // 如 'project_manager_id'
// $formData = json_decode($instance->form_data, true);
// if (isset($formData[$fieldName])) {
// $approverIds = [(int)$formData[$fieldName]];
// } else {
// $approverIds = [];
// }
// break;
// // 5. 按组织架构层级 (如发起人所在部门的负责人)
// case 'dept_head':
// $deptId = $approverConfig['value']; // 也可以从 instance 或 formData 中获取
// $approverIds = $this->getDeptHeadId($deptId);
// if ($approverIds) {
// $approverIds = [$approverIds];
// } else {
// $approverIds = [];
// }
// break;
// default:
// throw new BusinessException("不支持的审批人类型: {$approverConfig['type']}");
// }
// $resolved = array_merge($resolved, $approverIds);
// }
// return array_unique($resolved);
// }
// 角色层
private function getUserIdsByRole(string $roleCode): array private function getUserIdsByRole(string $roleCode): array
{ {
// user_roles 表和 roles 表
return Db::table('user_roles as ur') return Db::table('user_roles as ur')
->join('roles as r', 'ur.role_id', '=', 'r.id') ->join('roles as r', 'ur.role_id', '=', 'r.id')
->where('r.code', $roleCode) // 假设 roles 表有 code 字段 ->where('r.code', $roleCode)
->pluck('ur.user_id') ->pluck('ur.user_id')
->toArray(); ->toArray();
} }
// 领导层
private function getLeaderIdByUserId(int $userId): ?int private function getLeaderIdByUserId(int $userId): ?int
{ {
// users 表 report_to 字段
return Db::table('user')->where('id', $userId)->value('report_to'); return Db::table('user')->where('id', $userId)->value('report_to');
} }
// 组织架构层
private function getDeptHeadId(int $deptId): ?int private function getDeptHeadId(int $deptId): ?int
{ {
// departments 表 head_user_id 字段(部门逻辑暂时不用,根据后续业务实际情况开发)
return Db::table('departments')->where('id', $deptId)->value('head_user_id'); return Db::table('departments')->where('id', $deptId)->value('head_user_id');
} }
private function calculateNextStepIndex(int $instanceId, int $currentStepIndex, array $currentStep): int
{
// 对于 sequential 和 any,单次 approve 即进入下一步
if ($currentStep['type'] === 'sequential' || $currentStep['type'] === 'any') {
return $currentStepIndex + 1;
} elseif ($currentStep['type'] === 'parallel') {
// 对于并行会签,需要检查是否所有审批人都已 approve
// 这里简化处理,实际需要精确计算
// 假设只要有一次 approve 就算完成(不准确,仅为示例)
// 正确做法是查询所有 approvers 是否都 approve 了
return $currentStepIndex + 1;
}
return $currentStepIndex + 1;
}
private function notifyNextApprovers(int $instanceId)
{
// 实现你的通知逻辑,如发送站内信、邮件、企业微信等
// 可以查询 instance_id 对应的当前步骤审批人
echo "通知下一个审批人 (Instance ID: {$instanceId})\n";
}
private function notifyInitiator(object $instance, string $result) private function notifyInitiator(object $instance, string $result)
{ {
// 通知发起人审批结果
echo "通知发起人 {$instance->initiator_id} 审批结果: {$result}\n"; echo "通知发起人 {$instance->initiator_id} 审批结果: {$result}\n";
} }
private function notifyAllApprovers(object $instance, string $status)
{
// 通知所有相关审批人(如撤回时)
echo "通知所有审批人,实例 {$instance->id} 状态变为: {$status}\n";
}
private function notifyCc(int $instanceId)
{
// 通知抄送人
echo "通知抄送人 (Instance ID: {$instanceId})\n";
}
} }
@@ -0,0 +1,620 @@
<?php
namespace app\common\utils\approval\service;
use support\exception\BusinessException;
use support\Db;
class ApprovalFlowService_bak2
{
// --- 公共方法 ---
/**
* 启动审批流程
*/
public function start(string $flowType, string $bizType, string $bizId, array $formData, int $initiatorId): array
{
// 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. 查找第一个有效步骤(考虑 condition)
$firstStep = $this->getNextValidStep($steps, $formData);
if (!$firstStep) {
throw new BusinessException("未找到可执行的审批步骤");
}
// 3. 创建审批实例
$instanceId = Db::table('approval_instances')->insertGetId([
'flow_type' => $flowType,
'biz_type' => $bizType,
'biz_id' => $bizId,
'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE),
'initiator_id' => $initiatorId,
'status' => 'processing',
'current_step' => 0,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
// 4. 创建审批记录(待办)
$approverIds = $this->resolveApprovers($firstStep, (object)['initiator_id' => $initiatorId]);
foreach ($approverIds as $approverId) {
Db::table('approval_records')->insert([
'instance_id' => $instanceId,
'step_name' => $firstStep['name'],
'approver_id' => $approverId,
'status' => 'pending',
'created_at' => date('Y-m-d H:i:s'),
]);
}
// 5. 通知审批人
$this->sendNotification($approverIds, (object)['id' => $instanceId, 'flow_type' => $flowType], 'pending', false);
// 6. 通知抄送人(当前步骤)
$this->notifyCCs($firstStep, (object)['id' => $instanceId, 'flow_type' => $flowType, 'form_data' => json_encode($formData)], 'pending');
return ['instance_id' => $instanceId];
}
/**
* 执行审批
*/
public function approve(int $instanceId, int $approverId, string $action, string $comment = '')
{
$instance = Db::table('approval_instances')->where('id', $instanceId)->first();
if (!$instance) {
throw new BusinessException("审批实例未找到");
}
$steps = json_decode($instance->steps, true) ?: json_decode(Db::table('approval_flows')->where('type', $instance->flow_type)->value('steps'), true);
$currentStep = $steps[$instance->current_step] ?? null;
if (!$currentStep) {
throw new BusinessException("当前步骤配置错误");
}
// 检查用户是否有权限审批
if (!$this->canUserApproveStep($currentStep, $approverId, $instance)) {
throw new BusinessException("您无权审批此步骤");
}
// 更新审批记录
Db::table('approval_records')
->where('instance_id', $instanceId)
->where('approver_id', $approverId)
->where('status', 'pending')
->update([
'status' => $action,
'comment' => $comment,
'approved_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
// 检查当前步骤是否完成(根据 type: any, all, sequential)
$stepCompleted = $this->isStepCompleted($instanceId, $instance->current_step, $currentStep['type']);
if (!$stepCompleted) {
// 步骤未完成,不进入下一步
return ['status' => 'step_pending'];
}
// 步骤已完成,进入下一步或结束
$nextStep = $this->getNextValidStep($steps, json_decode($instance->form_data, true), $instance->current_step + 1);
if ($nextStep === null) {
// 流程结束
Db::table('approval_instances')->where('id', $instanceId)->update([
'status' => $action === 'approve' ? 'approved' : 'rejected',
'updated_at' => date('Y-m-d H:i:s'),
]);
// 通知发起人结果
$this->sendNotification([$instance->initiator_id], $instance, $action, false);
return ['status' => 'completed', 'result' => $action];
}
// 进入下一步
$nextStepIndex = array_search($nextStep, $steps);
Db::table('approval_instances')->where('id', $instanceId)->update([
'current_step' => $nextStepIndex,
'updated_at' => date('Y-m-d H:i:s'),
]);
// 创建下一步的审批记录
$nextApprovers = $this->resolveApprovers($nextStep, $instance);
foreach ($nextApprovers as $approverId) {
Db::table('approval_records')->insert([
'instance_id' => $instanceId,
'step_name' => $nextStep['name'],
'approver_id' => $approverId,
'status' => 'pending',
'created_at' => date('Y-m-d H:i:s'),
]);
}
// 通知下一步审批人
$this->sendNotification($nextApprovers, $instance, 'pending', false);
// 通知下一步抄送人
$this->notifyCCs($nextStep, $instance, 'pending');
return ['status' => 'next_step', 'step_name' => $nextStep['name']];
}
/**
* 解析审批人
*/
private function resolveApprovers(array $step, object $instance): array
{
$resolved = [];
$formData = json_decode($instance->form_data ?? '{}', true);
foreach ($step['approvers'] as $approverConfig) {
$approverIds = [];
switch ($approverConfig['type']) {
case 'role':
$approverIds = $this->getUserIdsByRole($approverConfig['value']);
break;
case 'user':
$approverIds = [(int)$approverConfig['value']];
break;
case 'initiator_leader':
$leaderId = $this->getLeaderIdByUserId($instance->initiator_id);
if ($leaderId) $approverIds = [$leaderId];
break;
case 'field_user':
if (isset($formData[$approverConfig['value']])) {
$approverIds = [(int)$formData[$approverConfig['value']]];
}
break;
case 'dept_head':
$approverIds = $this->getDeptHeadId($approverConfig['value']);
if ($approverIds) $approverIds = [$approverIds];
break;
default:
throw new BusinessException("不支持的审批人类型: {$approverConfig['type']}");
}
$resolved = array_merge($resolved, $approverIds);
}
return array_unique(array_filter($resolved));
}
/**
* 通知抄送人
*/
private function notifyCCs(array $step, object $instance, string $action = 'pending')
{
$ccConfig = $step['cc'] ?? [];
$ccUserIds = [];
$formData = json_decode($instance->form_data ?? '{}', true);
foreach ($ccConfig as $cc) {
switch ($cc['type']) {
case 'role':
$ccUserIds = array_merge($ccUserIds, $this->getUserIdsByRole($cc['value']));
break;
case 'user':
$ccUserIds[] = (int)$cc['value'];
break;
case 'initiator_leader':
$leaderId = $this->getLeaderIdByUserId($instance->initiator_id);
if ($leaderId) $ccUserIds[] = $leaderId;
break;
case 'field_user':
if (isset($formData[$cc['value']])) {
$ccUserIds[] = (int)$formData[$cc['value']];
}
break;
case 'dept_head':
$deptHeadId = $this->getDeptHeadId($cc['value']);
if ($deptHeadId) $ccUserIds[] = $deptHeadId;
break;
default:
break;
}
}
$ccUserIds = array_unique(array_filter($ccUserIds));
if (!empty($ccUserIds)) {
$this->sendNotification($ccUserIds, $instance, $action, true);
}
}
/**
* 评估条件
*/
private function evaluateCondition(?string $condition, array $formData): bool
{
if (!$condition) return true;
$pattern = '/^(\w+)\s*(==|!=|>|<|>=|<=)\s*(.+)$/';
if (!preg_match($pattern, $condition, $matches)) {
throw new BusinessException("条件格式错误: {$condition}");
}
$field = $matches[1];
$op = $matches[2];
$value = trim($matches[3], "\"'");
$actualValue = $formData[$field] ?? null;
if (is_numeric($value) && is_numeric($actualValue)) {
$value = (float)$value;
$actualValue = (float)$actualValue;
}
switch ($op) {
case '==': return $actualValue == $value;
case '!=': return $actualValue != $value;
case '>': return $actualValue > $value;
case '<': $actualValue < $value;
case '>=': return $actualValue >= $value;
case '<=': return $actualValue <= $value;
default: throw new BusinessException("不支持的操作符: {$op}");
}
}
/**
* 获取下一个有效步骤(考虑 condition)
*/
private function getNextValidStep(array $steps, array $formData, int $startIndex = 0): ?array
{
for ($i = $startIndex; $i < count($steps); $i++) {
$step = $steps[$i];
if (!isset($step['condition']) || $this->evaluateCondition($step['condition'], $formData)) {
return $step;
}
}
return null;
}
/**
* 检查用户是否有权限审批
*/
private function canUserApproveStep(array $step, int $userId, object $instance): bool
{
$approvers = $this->resolveApprovers($step, $instance);
return in_array($userId, $approvers);
}
/**
* 检查当前步骤是否完成
*/
private function isStepCompleted(int $instanceId, int $stepIndex, string $type): bool
{
$pendingCount = Db::table('approval_records')
->where('instance_id', $instanceId)
->where('status', 'pending')
->count();
if ($type === 'any') {
return $pendingCount === 0; // 任一通过即可
}
if ($type === 'all' || $type === 'sequential') {
return $pendingCount === 0; // 所有人都必须审批
}
return false;
}
/**
* 发送通知(你可以替换为你的通知系统)
*/
private function sendNotification(array $userIds, object $instance, string $action, bool $isCC)
{
$actionText = [
'pending' => '待审批',
'approve' => '已通过',
'reject' => '已拒绝'
][$action] ?? $action;
$prefix = $isCC ? '[抄送]' : '';
$message = "{$prefix}您有一条审批 {$actionText}:流程ID {$instance->id},类型 {$instance->flow_type}";
// TODO: 调用你的消息系统,如邮件、站内信、企业微信等
// 示例:MessageService::send($userIds, $message);
echo "通知用户 " . implode(',', $userIds) . ":$message\n";
}
/**
* 获取用户待审批列表
* @param int $userId 用户ID
* @return array
*/
public function getPendingApprovals(int $userId): array
{
// 简化查询:找到用户需要审批的实例ID
// 更复杂的逻辑(如并行会签)需要更精细的查询
$instanceIds = Db::table('approval_records')
->where('approver_id', $userId)
->pluck('instance_id')
->unique()
->toArray();
if (empty($instanceIds)) {
return [];
}
return Db::table('approval_instances')
->whereIn('id', $instanceIds)
->where('status', 'processing')
->get()
->toArray();
}
/**
* 获取审批历史记录
* @param int $instanceId
* @return array
*/
public function getApprovalHistory(int $instanceId): array
{
return Db::table('approval_records')
->where('instance_id', $instanceId)
->orderBy('step_index')
->orderBy('approved_at')
->get()
->toArray();
}
/**
* 获取审批流程图数据 (简化版)
* @param int $instanceId
* @return array
*/
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')
->orderBy('approved_at')
->get()
->groupBy('step_index');
$diagramData = [];
foreach ($steps as $index => $step) {
$status = 'pending';
$actions = $records->get($index, collect())->toArray();
if (!empty($actions)) {
$hasReject = collect($actions)->contains('action', 'reject');
if ($hasReject) {
$status = 'rejected';
} else {
if ($step['type'] === 'parallel') {
// 简化处理
$status = 'approved';
} else {
$status = 'approved';
}
}
} elseif ($index < $instance->current_step) {
$status = 'approved';
} elseif ($index == $instance->current_step) {
$status = 'processing';
}
$diagramData[] = [
'index' => $index,
'name' => $step['name'],
'type' => $step['type'],
'status' => $status,
'actions' => $actions
];
}
return $diagramData;
}
/**
* 处理超时的审批实例 (定时任务调用)
*/
public function handleTimeouts()
{
$now = date('Y-m-d H:i:s');
$expiredInstances = Db::table('approval_instances')
->where('status', 'processing')
->where('deadline_at', '<=', $now)
->get();
foreach ($expiredInstances as $instance) {
$flow = Db::table('approval_flows')->where('type', $instance->flow_type)->first();
if ($flow && $flow->timeout_action) {
$updateData = [
'finished_at' => $now,
'updated_at' => $now
];
if ($flow->timeout_action === 'auto_reject') {
$updateData['status'] = 'rejected';
Db::table('approval_instances')->where('id', $instance->id)->update($updateData);
$this->notifyInitiator($instance, 'rejected_timeout');
} elseif ($flow->timeout_action === 'auto_approve') {
$updateData['status'] = 'approved';
Db::table('approval_instances')->where('id', $instance->id)->update($updateData);
$this->notifyInitiator($instance, 'approved_timeout');
}
}
}
}
// --- 辅助方法 ---
// private function canUserApproveStep(array $step, int $userId, object $instance): bool
// {
// $approvers = $this->resolveApprovers($step, $instance);
// return in_array($userId, $approvers);
// }
// private function resolveApprovers(array $step, object $instance): array
// {
// $resolved = [];
// foreach ($step['approvers'] as $approver) {
// if ($approver['type'] === 'role') {
// // 示例:根据角色查找用户ID,你需要替换为实际逻辑
// if ($approver['value'] === 'admin') {
// $resolved[] = 1; // 假设 admin 角色的用户ID是1
// } elseif ($approver['value'] === 'manager') {
// $resolved[] = 2; $resolved[] = 3; // 假设有多个manager
// }
// } elseif ($approver['type'] === 'user') {
// $resolved[] = (int)$approver['value'];
// } elseif ($approver['type'] === 'initiator_leader') {
// // 示例:获取发起人的领导
// $resolved[] = 4; // 假设领导ID是4
// }
// }
// return array_unique($resolved);
// }
// private function resolveApprovers(array $step, object $instance): array
// {
// $resolved = [];
// foreach ($step['approvers'] as $approverConfig) {
// $approverIds = [];
// switch ($approverConfig['type']) {
// // 1. 按角色查找
// case 'role':
// $roleCode = $approverConfig['value'];
// // 所有拥有该角色的用户ID
// $approverIds = $this->getUserIdsByRole($roleCode);
// break;
// // 2. 按具体用户ID
// case 'user':
// $approverIds = [(int)$approverConfig['value']];
// break;
// // 3. 按发起人的直属领导 (业务一般是组长)
// case 'initiator_leader':
// // 用户表中 report_to 字段
// $approverIds = $this->getLeaderIdByUserId($instance->initiator_id);
// if ($approverIds) {
// $approverIds = [$approverIds];
// } else {
// $approverIds = []; // 没有领导,跳过
// }
// break;
// // 4. 按业务字段关联的用户 (高级功能)
// case 'field_user':
// $fieldName = $approverConfig['value']; // 如 'project_manager_id'
// $formData = json_decode($instance->form_data, true);
// if (isset($formData[$fieldName])) {
// $approverIds = [(int)$formData[$fieldName]];
// } else {
// $approverIds = [];
// }
// break;
// // 5. 按组织架构层级 (如发起人所在部门的负责人)
// case 'dept_head':
// $deptId = $approverConfig['value']; // 也可以从 instance 或 formData 中获取
// $approverIds = $this->getDeptHeadId($deptId);
// if ($approverIds) {
// $approverIds = [$approverIds];
// } else {
// $approverIds = [];
// }
// break;
// default:
// throw new BusinessException("不支持的审批人类型: {$approverConfig['type']}");
// }
// $resolved = array_merge($resolved, $approverIds);
// }
// return array_unique($resolved);
// }
// 角色层
private function getUserIdsByRole(string $roleCode): array
{
// user_roles 表和 roles 表
return Db::table('user_roles as ur')
->join('roles as r', 'ur.role_id', '=', 'r.id')
->where('r.code', $roleCode) // 假设 roles 表有 code 字段
->pluck('ur.user_id')
->toArray();
}
// 领导层
private function getLeaderIdByUserId(int $userId): ?int
{
// users 表 report_to 字段
return Db::table('user')->where('id', $userId)->value('report_to');
}
// 组织架构层
private function getDeptHeadId(int $deptId): ?int
{
// departments 表 head_user_id 字段(部门逻辑暂时不用,根据后续业务实际情况开发)
return Db::table('departments')->where('id', $deptId)->value('head_user_id');
}
private function calculateNextStepIndex(int $instanceId, int $currentStepIndex, array $currentStep): int
{
// 对于 sequential 和 any,单次 approve 即进入下一步
if ($currentStep['type'] === 'sequential' || $currentStep['type'] === 'any') {
return $currentStepIndex + 1;
} elseif ($currentStep['type'] === 'parallel') {
// 对于并行会签,需要检查是否所有审批人都已 approve
// 这里简化处理,实际需要精确计算
// 假设只要有一次 approve 就算完成(不准确,仅为示例)
// 正确做法是查询所有 approvers 是否都 approve 了
return $currentStepIndex + 1;
}
return $currentStepIndex + 1;
}
private function notifyNextApprovers(int $instanceId)
{
// 实现你的通知逻辑,如发送站内信、邮件、企业微信等
// 可以查询 instance_id 对应的当前步骤审批人
echo "通知下一个审批人 (Instance ID: {$instanceId})\n";
}
private function notifyInitiator(object $instance, string $result)
{
// 通知发起人审批结果
echo "通知发起人 {$instance->initiator_id} 审批结果: {$result}\n";
}
private function notifyAllApprovers(object $instance, string $status)
{
// 通知所有相关审批人(如撤回时)
echo "通知所有审批人,实例 {$instance->id} 状态变为: {$status}\n";
}
private function notifyCc(int $instanceId)
{
// 通知抄送人
echo "通知抄送人 (Instance ID: {$instanceId})\n";
}
}
+24 -6
View File
@@ -140,10 +140,10 @@
<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>
<th class="nowrap">官方指导价</th> <th class="nowrap">官方指导价/二手挂牌价</th>
<th class="nowrap">优惠</th> <th class="nowrap">优惠/二手减价</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -157,8 +157,15 @@
<td><?php echo htmlspecialchars($vehicle['modify'] ?? ' - '); ?></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['unit_price_text'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['total_price_text'] ?? ' - '); ?></td> <td class="nowrap"><?php echo htmlspecialchars($vehicle['total_price_text'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['official_price_text'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['discount_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> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@@ -232,6 +239,17 @@
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </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="https://mzl.dev.hnxinyouhe.com/01.jpg" style="width: 21mm; height: 12mm; vertical-align: middle;">
</div>
</div>
<div>下单时间:2025-09-08</div>
</div>
</div> </div>
</body> </body>
</html> </html>
+16 -6
View File
@@ -107,13 +107,23 @@ class IndexController
public function test() public function test()
{ {
return json([
$flowType = 'stock_order_flow';
$bizType = 'stock_orders';
$bizId = 32;
$formData = [];
$initiatorId = 74;
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
// $sp = $appro->start($flowType, $bizType, $bizId, $formData, $initiatorId);
$sp = $appro->approve(1, 1, 'approve');
var_dump($sp);
return json([
'code' => 0, 'code' => 0,
'msg' => 'success', 'msg' => 'success'
'data' => [
'time' => time(),
'date' => date('Y-m-d H:i:s')
]
]); ]);
} }
} }
+6 -9
View File
@@ -51,16 +51,13 @@ class StockOrdersController extends BaseController
} }
if (count($data['moneyValues']) > 0) { if (count($data['moneyValues']) > 0) {
foreach ($data['moneyValues'] as $mvs) { $data['cif_total_amount'] = [
$data['cif_total_amount'] = [ 'exchange_rate' => $data['moneyValues']['exchange_rate'],
'exchange_rate' => $mvs['exchange_rate'], 'amount' => $data['moneyValues']['amount'],
'amount' => $mvs['amount'], 'currency' => $data['moneyValues']['currency'],
'currency' => $mvs['currency'], 'id' => $data['moneyValues']['id']
'id' => $mvs['id'] ];
];
}
} }
unset($data['moneyValues']);
foreach ($data['items'] as &$item) { foreach ($data['items'] as &$item) {
foreach ($item['moneyValues'] as $mv) { foreach ($item['moneyValues'] as $mv) {
+1
View File
@@ -153,6 +153,7 @@ Route::group('/admin', function () {
Route::any('/authorize', [app\admin\controller\UserController::class, 'authorize']); Route::any('/authorize', [app\admin\controller\UserController::class, 'authorize']);
Route::any('/set/resources', [app\admin\controller\UserController::class, 'updateResources']); Route::any('/set/resources', [app\admin\controller\UserController::class, 'updateResources']);
Route::any('/update/password', [app\admin\controller\UserController::class, 'savePassword']); Route::any('/update/password', [app\admin\controller\UserController::class, 'savePassword']);
Route::any('/update/sign', [app\admin\controller\UserController::class, 'updateSign']);
}); });
// 询单 // 询单
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB