调整备货单
This commit is contained in:
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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')
|
|
||||||
]
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 询单
|
// 询单
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Reference in New Issue
Block a user