调整备货单
This commit is contained in:
@@ -154,4 +154,16 @@ class UserController extends BaseController
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,8 +80,17 @@ class SysDictService
|
||||
break;
|
||||
case 'scope':
|
||||
$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'])) {
|
||||
$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'][] = 'item_label';
|
||||
@@ -95,7 +104,7 @@ class SysDictService
|
||||
$config['with']['sysDictItem'][] = 'item_value';
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ class UserService
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
case 'edit':
|
||||
case 'updateSign':
|
||||
$config['where']['id'] = $request->user->uid;
|
||||
break;
|
||||
}
|
||||
@@ -154,4 +155,13 @@ class UserService
|
||||
$user = $this->logicUpdateBy($config,$data);
|
||||
return $user;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function serviceUpdateSign(Request $request, array $data)
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, 'updateSign');
|
||||
$user = $this->logicUpdateBy($config,$data);
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,8 @@ class StockOrderItems extends Model
|
||||
protected $fillable = [
|
||||
'id', 'order_id', 'product_code', 'sku_id', 'sku_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'
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,6 +87,6 @@ class StockOrders extends Model
|
||||
*/
|
||||
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');
|
||||
}
|
||||
}
|
||||
@@ -103,20 +103,65 @@ class StockOrdersService
|
||||
// $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'], '优惠价');
|
||||
$item->addMoney('unit_price', $veh['unit_price']['currency'], $veh['unit_price']['amount'], $veh['unit_price']['rate'], '单价');
|
||||
$item->addMoney('total_price', $veh['total_price']['currency'], $veh['total_price']['amount'], $veh['total_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运输费');
|
||||
$item->addMoney('port_misc_fee', $veh['port_misc_fee']['currency'], $veh['port_misc_fee']['amount'], $veh['port_misc_fee']['rate'], '港杂');
|
||||
$item->addMoney('retrofit_fee', $veh['retrofit_fee']['currency'], $veh['retrofit_fee']['amount'], $veh['retrofit_fee']['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'], '优惠价');
|
||||
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 = $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'], '利润');
|
||||
|
||||
// cif_total_amount
|
||||
@@ -193,8 +238,8 @@ class StockOrdersService
|
||||
$pdfBin = $driver->htmlToPdf($html);
|
||||
|
||||
// 存储
|
||||
$dir = public_path('pdf');
|
||||
$fileName = '/'.($pdfData['order_no'] ?? date('YmdHis')) . '.pdf';
|
||||
$dir = public_path('pdf').'/';
|
||||
$fileName = ($pdfData['order_no'] ?? date('YmdHis')) . '.pdf';
|
||||
|
||||
// 本地保存
|
||||
$filePath = \app\common\utils\pdf\service\PdfStorage::saveBinary(
|
||||
@@ -209,7 +254,11 @@ class StockOrdersService
|
||||
// 'Content-Disposition' => 'inline; filename="stock-order.pdf"',
|
||||
// ], $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;
|
||||
foreach ($item->items as $k => $v) {
|
||||
// 存在则数量加1
|
||||
if (count($vehList) > 0 && isset($vehList[$v->sku_id])) {
|
||||
$vehList[$v->sku_id]['num'] += 1;
|
||||
$vehList[$v->sku_id]['total_price'] += $vehList[$v->sku_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, '.', ',');
|
||||
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;
|
||||
@@ -232,7 +281,7 @@ class StockOrdersService
|
||||
$mv[$money->field] = [
|
||||
'currency' => $money->currency,
|
||||
'amount' => $money->amount,
|
||||
'rate' => $money->rate,
|
||||
'rate' => $money->exchange_rate,
|
||||
'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,
|
||||
'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'],
|
||||
'official_price' => $mv['official_price']['amount'],
|
||||
'discount_price' => $mv['discount_price']['amount'],
|
||||
|
||||
'unit_price_symbol' => $mv['unit_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, '.', ','),
|
||||
'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, '.', ','),
|
||||
'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 = [];
|
||||
@@ -283,6 +345,12 @@ class StockOrdersService
|
||||
$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,
|
||||
@@ -310,7 +378,8 @@ class StockOrdersService
|
||||
|
||||
// 账户
|
||||
'account_list' => $accList,
|
||||
'acc_index' => $accIndex
|
||||
'acc_index' => $accIndex,
|
||||
'cif_total_amount' => $cifTotalAmount
|
||||
];
|
||||
|
||||
return $veh;
|
||||
|
||||
@@ -52,6 +52,9 @@ class UserService
|
||||
'sn' => $user->sn,
|
||||
'corp_id' => $user->corp_id,
|
||||
'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,
|
||||
// 'permissions' => $permissionCodes
|
||||
@@ -84,7 +87,9 @@ class UserService
|
||||
'email' => $user->email,
|
||||
'sex' => $user->sex,
|
||||
'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("未找到可执行的审批步骤");
|
||||
}
|
||||
|
||||
// 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([
|
||||
'flow_type' => $flowType,
|
||||
'biz_type' => $bizType,
|
||||
'biz_id' => $bizId,
|
||||
'form_data' => json_encode($formData, JSON_UNESCAPED_UNICODE),
|
||||
'initiator_id' => $initiatorId,
|
||||
'status' => 'processing',
|
||||
'status' => 'processing', // processing 表示正在进行中
|
||||
'current_step' => 0,
|
||||
'deadline_at' => $deadlineAt,
|
||||
'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]);
|
||||
// 5. 创建审批记录(待办)
|
||||
$approverIds = $this->resolveApprovers($firstStep, (object)[
|
||||
'initiator_id' => $initiatorId,
|
||||
'form_data' => json_encode($formData)
|
||||
]);
|
||||
|
||||
foreach ($approverIds as $approverId) {
|
||||
Db::table('approval_records')->insert([
|
||||
'instance_id' => $instanceId,
|
||||
'step_name' => $firstStep['name'],
|
||||
'approver_id' => $approverId,
|
||||
'step_index' => 0,
|
||||
'action' => '', // 未操作时为空
|
||||
'status' => 'pending',
|
||||
'approved_at' => date('Y-m-d H:i:s', 0), // 未审批时设置为初始时间
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
|
||||
// 5. 通知审批人
|
||||
$this->sendNotification($approverIds, (object)['id' => $instanceId, 'flow_type' => $flowType], 'pending', false);
|
||||
// 6. 通知审批人
|
||||
$instance = (object)[
|
||||
'id' => $instanceId,
|
||||
'flow_type' => $flowType,
|
||||
'form_data' => json_encode($formData),
|
||||
'initiator_id' => $initiatorId
|
||||
];
|
||||
$this->sendNotification($approverIds, $instance, 'pending', false);
|
||||
|
||||
// 6. 通知抄送人(当前步骤)
|
||||
$this->notifyCCs($firstStep, (object)['id' => $instanceId, 'flow_type' => $flowType, 'form_data' => json_encode($formData)], 'pending');
|
||||
// 7. 通知抄送人(当前步骤)
|
||||
$this->notifyCCs($firstStep, $instance, 'pending');
|
||||
|
||||
return ['instance_id' => $instanceId];
|
||||
}
|
||||
@@ -73,13 +93,28 @@ class ApprovalFlowService
|
||||
*/
|
||||
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();
|
||||
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;
|
||||
// 获取流程定义
|
||||
$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) {
|
||||
throw new BusinessException("当前步骤配置错误");
|
||||
@@ -90,20 +125,33 @@ class ApprovalFlowService
|
||||
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')
|
||||
->where('instance_id', $instanceId)
|
||||
->where('approver_id', $approverId)
|
||||
->where('step_index', $currentStepIndex)
|
||||
->where('status', 'pending')
|
||||
->update([
|
||||
'status' => $action,
|
||||
'action' => $action,
|
||||
'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']);
|
||||
// 检查当前步骤是否完成(根据 type: any, all)
|
||||
$stepCompleted = $this->isStepCompleted($instanceId, $currentStepIndex, $currentStep['type']);
|
||||
|
||||
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([
|
||||
'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'),
|
||||
]);
|
||||
|
||||
@@ -127,21 +176,26 @@ class ApprovalFlowService
|
||||
}
|
||||
|
||||
// 进入下一步
|
||||
$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'),
|
||||
]);
|
||||
|
||||
// 获取下一步信息
|
||||
$nextStep = $steps[$nextStepIndex];
|
||||
|
||||
// 创建下一步的审批记录
|
||||
$nextApprovers = $this->resolveApprovers($nextStep, $instance);
|
||||
foreach ($nextApprovers as $approverId) {
|
||||
Db::table('approval_records')->insert([
|
||||
'instance_id' => $instanceId,
|
||||
'step_name' => $nextStep['name'],
|
||||
'approver_id' => $approverId,
|
||||
'step_index' => $nextStepIndex,
|
||||
'action' => '',
|
||||
'status' => 'pending',
|
||||
'approved_at' => date('Y-m-d H:i:s', 0),
|
||||
'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));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -264,7 +338,7 @@ class ApprovalFlowService
|
||||
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;
|
||||
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];
|
||||
if (!isset($step['condition']) || $this->evaluateCondition($step['condition'], $formData)) {
|
||||
return $step;
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -299,20 +373,38 @@ class ApprovalFlowService
|
||||
*/
|
||||
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('status', 'pending')
|
||||
->count();
|
||||
->where('step_index', $stepIndex)
|
||||
->get();
|
||||
|
||||
if ($type === 'any') {
|
||||
return $pendingCount === 0; // 任一通过即可
|
||||
if ($records->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($type === 'all' || $type === 'sequential') {
|
||||
return $pendingCount === 0; // 所有人都必须审批
|
||||
switch ($type) {
|
||||
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
|
||||
{
|
||||
// 简化查询:找到用户需要审批的实例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')
|
||||
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.*')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
@@ -406,12 +489,7 @@ class ApprovalFlowService
|
||||
if ($hasReject) {
|
||||
$status = 'rejected';
|
||||
} else {
|
||||
if ($step['type'] === 'parallel') {
|
||||
// 简化处理
|
||||
$status = 'approved';
|
||||
} else {
|
||||
$status = 'approved';
|
||||
}
|
||||
$status = 'approved';
|
||||
}
|
||||
} elseif ($index < $instance->current_step) {
|
||||
$status = 'approved';
|
||||
@@ -449,172 +527,40 @@ class ApprovalFlowService
|
||||
'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');
|
||||
$updateData['status'] = 'reject';
|
||||
} elseif ($flow->timeout_action === 'auto_approve') {
|
||||
$updateData['status'] = 'approved';
|
||||
Db::table('approval_instances')->where('id', $instance->id)->update($updateData);
|
||||
$this->notifyInitiator($instance, 'approved_timeout');
|
||||
$updateData['status'] = 'approve';
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// 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 字段
|
||||
->where('r.code', $roleCode)
|
||||
->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";
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -140,10 +140,10 @@
|
||||
<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>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -157,8 +157,15 @@
|
||||
<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>
|
||||
<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>
|
||||
<?php endforeach; ?>
|
||||
|
||||
@@ -232,6 +239,17 @@
|
||||
<?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="https://mzl.dev.hnxinyouhe.com/01.jpg" style="width: 21mm; height: 12mm; vertical-align: middle;">
|
||||
</div>
|
||||
</div>
|
||||
<div>下单时间:2025-09-08</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -107,13 +107,23 @@ class IndexController
|
||||
|
||||
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,
|
||||
'msg' => 'success',
|
||||
'data' => [
|
||||
'time' => time(),
|
||||
'date' => date('Y-m-d H:i:s')
|
||||
]
|
||||
'msg' => 'success'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -51,16 +51,13 @@ class StockOrdersController extends BaseController
|
||||
}
|
||||
|
||||
if (count($data['moneyValues']) > 0) {
|
||||
foreach ($data['moneyValues'] as $mvs) {
|
||||
$data['cif_total_amount'] = [
|
||||
'exchange_rate' => $mvs['exchange_rate'],
|
||||
'amount' => $mvs['amount'],
|
||||
'currency' => $mvs['currency'],
|
||||
'id' => $mvs['id']
|
||||
];
|
||||
}
|
||||
$data['cif_total_amount'] = [
|
||||
'exchange_rate' => $data['moneyValues']['exchange_rate'],
|
||||
'amount' => $data['moneyValues']['amount'],
|
||||
'currency' => $data['moneyValues']['currency'],
|
||||
'id' => $data['moneyValues']['id']
|
||||
];
|
||||
}
|
||||
unset($data['moneyValues']);
|
||||
|
||||
foreach ($data['items'] as &$item) {
|
||||
foreach ($item['moneyValues'] as $mv) {
|
||||
|
||||
Reference in New Issue
Block a user