Merge branch 'develop'
This commit is contained in:
@@ -136,6 +136,7 @@ class ApprovalRecordsService
|
||||
$bizInfo = [];
|
||||
$service = match ($data['flow_type']) {
|
||||
'stock_orders' => new \app\common\service\StockOrdersService(),
|
||||
'stock_orders_nullify' => new \app\common\service\StockOrdersService(),
|
||||
'purchase_orders' => new \app\common\service\PurchaseOrdersService(),
|
||||
default => new \app\common\service\StockOrdersService()
|
||||
};
|
||||
@@ -154,6 +155,14 @@ class ApprovalRecordsService
|
||||
'instance.bizInfo.moneyValues',
|
||||
'instance.bizInfo.salesperson',
|
||||
],
|
||||
'stock_orders_nullify' => [
|
||||
'instance.bizInfo.items',
|
||||
'instance.bizInfo.items.options',
|
||||
'instance.bizInfo.items.moneyValues',
|
||||
'instance.bizInfo.accounts',
|
||||
'instance.bizInfo.moneyValues',
|
||||
'instance.bizInfo.salesperson',
|
||||
],
|
||||
'purchase_orders' => [
|
||||
'instance.bizInfo.items',
|
||||
'instance.bizInfo.items.stockOrder',
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\logic\BrandLogic;
|
||||
use QL\QueryList;
|
||||
|
||||
class CrawlService
|
||||
{
|
||||
public function brandSeries($brand)
|
||||
{
|
||||
$url = "https://www.dongchedi.com/search?currTab=6&keyword=".$brand;
|
||||
$ql = QueryList::get($url );
|
||||
$series = [];
|
||||
$ql->find('div.card-name')->texts()->map(function($item) use(&$series) {
|
||||
$series[] = $item;
|
||||
});
|
||||
return $series;
|
||||
}
|
||||
|
||||
public function seriesModel($series)
|
||||
{
|
||||
$href = $this->modelSearch($series);
|
||||
|
||||
//例如 $seriesUrl = "https://www.dongchedi.com/auto/series/4865?anchorId=carModels"
|
||||
if (empty($href)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$seriesUrl = (strpos($href, 'http') === 0) ? $href : ("https://www.dongchedi.com" . $href);
|
||||
$ql = QueryList::get($seriesUrl);
|
||||
|
||||
$nodes = $ql->find('div.tw-grid div.tw-relative div.tw-items-center>a');
|
||||
$texts = $nodes->texts()->all();
|
||||
$links = $nodes->attrs('href')->all();
|
||||
$nodes = $ql->find('div.tw-grid div.tw-text-color-gray-800');
|
||||
$price = $nodes->texts()->all();
|
||||
|
||||
$results = [];
|
||||
foreach ($texts as $index => $text) {
|
||||
$link = $links[$index] ?? '';
|
||||
if (!empty($link) && strpos($link, 'http') !== 0) {
|
||||
$link = 'https://www.dongchedi.com' . $link;
|
||||
}
|
||||
$results[] = [
|
||||
'title' => trim($text),
|
||||
'link' => $link,
|
||||
'msrp' => (int)(str_replace("万", "", $price[$index]) * 10000) ?? 0,
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
// public function modelDesc(array $models)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
|
||||
/**
|
||||
* 返回车系地址
|
||||
* @param $series
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function modelSearch($series)
|
||||
{
|
||||
$url = "https://www.dongchedi.com/search?keyword=".$series;
|
||||
$ql = QueryList::get($url );
|
||||
$href = '';
|
||||
$ql->find('a.fold-btn')->attrs('href')
|
||||
->map(function ($item) use(&$href) {
|
||||
$href = $item;
|
||||
});
|
||||
return $href;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
namespace app\common\service;
|
||||
|
||||
use support\Db;
|
||||
|
||||
class MenuBadgeService
|
||||
{
|
||||
/**
|
||||
* 构造某个节点向上的角标链路(根->叶),每一级返回“完整url + 角标”
|
||||
* @param int $menuId 叶子或中间节点ID(directory/menu均可)
|
||||
* @param int $leafCount 该节点需要向上累加的消息数(正数可表示新增)
|
||||
* @param bool $includeHidden 是否包含隐藏/禁用菜单(默认 false:过滤 status != 1)
|
||||
* @return array<int, array{id:int,name:string,type:string,url:string,badge:int}>
|
||||
*/
|
||||
public static function buildBadgeChain(
|
||||
int $menuId,
|
||||
int $leafCount,
|
||||
bool $includeHidden = false,
|
||||
bool $forceIncludeSelf = true
|
||||
): array {
|
||||
// 取到从“叶->根”的节点列表
|
||||
$nodes = self::getAncestorNodesIterative($menuId, $includeHidden, $forceIncludeSelf);
|
||||
if (empty($nodes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 调整为“根->叶”
|
||||
$nodes = array_reverse($nodes);
|
||||
$segments = [];
|
||||
$result = [];
|
||||
$leafId = $menuId;
|
||||
|
||||
foreach ($nodes as $n) {
|
||||
$isLeaf = ((int)$n['id'] === $leafId);
|
||||
|
||||
// 中间层的 permission 不展示(但若有内部url段,仍需累计到segments)
|
||||
if (($n['type'] ?? '') === 'permission' && !$isLeaf) {
|
||||
// 若这是 internal 且有 url 段,仍拼到段里,便于子级正确拼接
|
||||
$openType = strtolower((string)($n['open_type'] ?? 'internal'));
|
||||
if ($openType === 'internal') {
|
||||
$seg = trim((string)($n['url'] ?? ''), '/');
|
||||
if ($seg !== '') {
|
||||
$segments[] = $seg;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算完整URL(internal 按段拼接;external/iframe 直接用 link/url)
|
||||
$fullUrl = self::composeFullUrl($segments, $n);
|
||||
|
||||
// 叶子(传入ID)必须返回;其他节点照常
|
||||
if ($isLeaf || ($n['type'] ?? '') !== 'permission') {
|
||||
$result[] = [
|
||||
'id' => (int)$n['id'],
|
||||
'name' => (string)$n['name'],
|
||||
'type' => (string)$n['type'],
|
||||
'url' => $fullUrl,
|
||||
'badge' => (int)$leafCount,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量聚合多个叶子节点的角标,把同一路径处的角标相加(父级=所有子级之和)
|
||||
* @param array<int, array{menu_id:int,count:int}> $items
|
||||
* @param bool $includeHidden
|
||||
* @return array<int, array{url:string,badge:int}>
|
||||
*/
|
||||
public static function aggregateChains(array $items, bool $includeHidden = false): array
|
||||
{
|
||||
$acc = []; // key = url
|
||||
|
||||
foreach ($items as $it) {
|
||||
$menuId = (int)($it['menu_id'] ?? 0);
|
||||
$leafCount = (int)($it['count'] ?? 0);
|
||||
if ($menuId <= 0 || $leafCount === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$chain = self::buildBadgeChain($menuId, $leafCount, $includeHidden);
|
||||
foreach ($chain as $node) {
|
||||
$key = $node['url'];
|
||||
if (!isset($acc[$key])) {
|
||||
$acc[$key] = ['url' => $node['url'], 'badge' => 0];
|
||||
}
|
||||
$acc[$key]['badge'] += $node['badge'];
|
||||
}
|
||||
}
|
||||
|
||||
// 父级在前(更短的路径在前),便于前端逐级渲染
|
||||
usort($acc, function ($a, $b) {
|
||||
return substr_count($a['url'], '/') <=> substr_count($b['url'], '/');
|
||||
});
|
||||
|
||||
return array_values($acc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迭代法:从叶子一路查到根(MySQL版本无关)
|
||||
* $forceIncludeSelf=true 时,无论叶子 status 是否为1,都会把叶子纳入;
|
||||
* 但祖先节点仍按 $includeHidden 规则过滤。
|
||||
*/
|
||||
private static function getAncestorNodesIterative(
|
||||
int $id,
|
||||
bool $includeHidden = false,
|
||||
bool $forceIncludeSelf = true
|
||||
): array {
|
||||
$cols = ['id', 'parent_id', 'name', 'type', 'url', 'open_type', 'link', 'is_hide', 'status'];
|
||||
$nodes = [];
|
||||
$seen = [];
|
||||
$cur = $id;
|
||||
$maxHop = 32; // 防环
|
||||
|
||||
$isFirst = true;
|
||||
for ($i = 0; $i < $maxHop && $cur > 0; $i++) {
|
||||
/** @var object|null $row */
|
||||
$row = Db::table('resources')->select($cols)->where('id', $cur)->first();
|
||||
if (!$row) break;
|
||||
|
||||
$rowArr = (array)$row;
|
||||
|
||||
if (!$includeHidden) {
|
||||
// 叶子节点:若 $forceIncludeSelf=true 则无视 status 过滤;祖先照常过滤
|
||||
if (!$isFirst && (int)$rowArr['status'] !== 1) {
|
||||
break;
|
||||
}
|
||||
if ($isFirst && !$forceIncludeSelf && (int)$rowArr['status'] !== 1) {
|
||||
// 如果明确不强制包含叶子,叶子也按规则过滤
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$nodes[] = $rowArr;
|
||||
|
||||
if (in_array($cur, $seen, true)) {
|
||||
break; // 防环
|
||||
}
|
||||
$seen[] = $cur;
|
||||
|
||||
$cur = (int)$rowArr['parent_id'];
|
||||
$isFirst = false;
|
||||
}
|
||||
|
||||
return $nodes; // 叶 -> 根
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装“完整路由”
|
||||
* - internal:把父级段 + 自身 url 段拼成形如 /a/b/c
|
||||
* - external/iframe:直接用 link(若无则退回自身 url)
|
||||
* - 忽略空段(避免出现重复斜杠)
|
||||
*/
|
||||
private static function composeFullUrl(array &$segments, array $node): string
|
||||
{
|
||||
$openType = strtolower((string)($node['open_type'] ?? 'internal'));
|
||||
|
||||
// 外链或 iframe:直接返回 link/url,不再参与分段拼接
|
||||
if ($openType !== 'internal') {
|
||||
$target = trim((string)($node['link'] ?? ''), " \t\r\n");
|
||||
if ($target === '') {
|
||||
$target = (string)($node['url'] ?? '');
|
||||
}
|
||||
return $target;
|
||||
}
|
||||
|
||||
// internal:把自身 url 作为一个“段”拼到前面来的 segments
|
||||
$seg = trim((string)($node['url'] ?? ''), '/');
|
||||
if ($seg !== '') {
|
||||
$segments[] = $seg;
|
||||
}
|
||||
|
||||
return '/' . implode('/', $segments);
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,7 @@ class MessageNotificationsService
|
||||
];
|
||||
|
||||
// 执行推送
|
||||
(new \app\common\utils\WsEvents())->pushWsNotice($data, $userId);
|
||||
(new \app\common\utils\WsEvents())->pushWebsocket('notify.receive', $userId, $data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -393,6 +393,44 @@ class StockOrdersService
|
||||
return $this->postProcess('Approval', $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 作废审批
|
||||
*/
|
||||
public function serviceApprovalNullify(Request $request)
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, 'approval');
|
||||
$item = $this->logicShow($config);
|
||||
$this->approvalNullify($item);
|
||||
return $this->postProcess('Approval', $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批作废
|
||||
*/
|
||||
public function approvalNullify($item)
|
||||
{
|
||||
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
|
||||
$appro->startNullify('stock_orders_nullify', 'stock_orders', $item->id, [], $item->salesperson_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销审批
|
||||
*/
|
||||
public function serviceApprovalRevoke(Request $request)
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, 'approval');
|
||||
$item = $this->logicShow($config);
|
||||
$this->approvalRevoke($item);
|
||||
return $this->postProcess('Approval', $item);
|
||||
}
|
||||
|
||||
public function approvalRevoke($item)
|
||||
{
|
||||
$instance = \app\common\model\ApprovalInstances::where('biz_id', $item->id)->where('flow_type', 'stock_orders')->first();
|
||||
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
|
||||
$appro->revoke($instance->id, $item->salesperson_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建
|
||||
*
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\validate\UserGroupMembersValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\UserGroupMembers;
|
||||
use app\common\model\UserGroup;
|
||||
use support\Request;
|
||||
|
||||
class UserGroupMembersService
|
||||
{
|
||||
|
||||
use CrudServiceTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = UserGroupMembers::class;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
*/
|
||||
protected $validator = UserGroupMembersValidate::class;
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return new $this->validator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\logic\UserGroupLogic;
|
||||
use app\common\logic\UserGroupMembersLogic;
|
||||
use app\common\validate\UserGroupValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\UserGroup;
|
||||
use support\Request;
|
||||
|
||||
class UserGroupService
|
||||
{
|
||||
|
||||
use CrudServiceTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = UserGroup::class;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
*/
|
||||
protected $validator = UserGroupValidate::class;
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return new $this->validator;
|
||||
}
|
||||
|
||||
|
||||
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
|
||||
{
|
||||
$p = $request->all();
|
||||
|
||||
$config = [];
|
||||
$config['model'] = $this->modelClass;
|
||||
switch ($scene) {
|
||||
case 'default':
|
||||
$config['where']['status'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
|
||||
$config['with'] = ['groupMembers', 'groupMembers.user'];
|
||||
break;
|
||||
case 'index':
|
||||
$config['where']['status'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
|
||||
$config['with'] = ['groupMembers', 'groupMembers.user'];
|
||||
break;
|
||||
case 'show':
|
||||
$config['where']['id'] = $p['id'];
|
||||
$config['where']['status'] = 1;
|
||||
break;
|
||||
case 'store':
|
||||
break;
|
||||
case 'update':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
case 'destroy':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
case 'userGroup':
|
||||
$config['where']['status'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
$config['fields'] = ['id', 'title'];
|
||||
break;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function serviceStore(Request $request, array $data = [], string $scene = 'store')
|
||||
{
|
||||
$data = $this->preProcess(ucfirst($scene), $data, $request);
|
||||
$created = (new UserGroupLogic())->createGroup($data);
|
||||
return $this->postProcess(ucfirst($scene), $created);
|
||||
}
|
||||
|
||||
public function serviceUpdate(Request $request, array $data = [], string $scene = 'update')
|
||||
{
|
||||
$data = $this->preProcess(ucfirst($scene), $data, $request);
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$userGroup = $this->logicShow($config);
|
||||
$updated = (new UserGroupLogic())->updateGroup($userGroup, $data);
|
||||
return $this->postProcess(ucfirst($scene), $updated);
|
||||
}
|
||||
|
||||
public function serviceDestroy(Request $request, string $scene = 'destroy')
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$deleted = $this->logicDeleteBy($config);
|
||||
(new UserGroupMembersLogic())->deleteUserGroupMembers($config['where']['id']);
|
||||
return $this->postProcess(ucfirst($scene), $deleted);
|
||||
}
|
||||
|
||||
|
||||
public function userGroup(Request $request, string $scene = 'userGroup')
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$result = $this->logicListAll($config);
|
||||
return $this->postProcess(ucfirst($scene), $result);
|
||||
}
|
||||
|
||||
public function userGroupScene(Request $request, string $scene = 'userGroup')
|
||||
{
|
||||
$data = (new \app\common\service\SysDictService())->pluckKeyValueByCode('user_group_scene');
|
||||
$result = [];
|
||||
foreach($data as $key => $value) {
|
||||
$result[] = [
|
||||
'name' => $key,
|
||||
'id' => $value,
|
||||
];
|
||||
}
|
||||
return $this->postProcess(ucfirst($scene), $result);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ namespace app\common\service;
|
||||
use app\common\logic\Fingerprint;
|
||||
use app\common\logic\ModelNaming;
|
||||
use app\common\logic\SkuCodeBuilder;
|
||||
use app\common\logic\VehVehicleSkuLogic;
|
||||
use app\common\model\Brand;
|
||||
use app\common\model\BrandSeries;
|
||||
use app\common\validate\VehVehicleSkuValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\VehVehicleSku;
|
||||
@@ -25,6 +28,13 @@ class VehVehicleSkuService
|
||||
*/
|
||||
protected $validator = VehVehicleSkuValidate::class;
|
||||
|
||||
protected $logic;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->logic = (new VehVehicleSkuLogic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
@@ -111,6 +121,15 @@ class VehVehicleSkuService
|
||||
$config['orWhere'][] = [$col, 'like', $k];
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($p['brand_id']) && $p['brand_id']){
|
||||
$brand_code = Brand::where('id', $p['brand_id'])->value('brand_code');
|
||||
$config['where']['brand_code'] = $brand_code;
|
||||
}
|
||||
if(isset($p['series_id']) && $p['series_id']){
|
||||
$series_code = BrandSeries::where('id', $p['series_id'])->value('series_code');
|
||||
$config['where']['series_code'] = $series_code;
|
||||
}
|
||||
break;
|
||||
case 'show':
|
||||
$config['where']['id'] = $p['id'];
|
||||
@@ -138,6 +157,33 @@ class VehVehicleSkuService
|
||||
if (empty($data['model_name'])) {
|
||||
$data['model_name'] = ModelNaming::buildModelName($data);
|
||||
}
|
||||
if (empty($data['engine_disp_l'])) {
|
||||
$data['engine_disp_l'] = 0;
|
||||
}
|
||||
if (empty($data['engine_turbo'])) {
|
||||
$data['engine_turbo'] = 0;
|
||||
}
|
||||
if (empty($data['engine_power_kw'])) {
|
||||
$data['engine_power_kw'] = 0;
|
||||
}
|
||||
if (empty($data['battery_kwh'])) {
|
||||
$data['battery_kwh'] = 0;
|
||||
}
|
||||
if (empty($data['gears'])) {
|
||||
$data['gears'] = 1;
|
||||
}
|
||||
if (empty($data['range_km'])) { //续航
|
||||
$data['range_km'] = 0;
|
||||
}
|
||||
if (empty($data['doors'])) { //车位数
|
||||
$data['doors'] = 0;
|
||||
}
|
||||
if (empty($data['seats'])) { //座位数
|
||||
$data['seats'] = 0;
|
||||
}
|
||||
if (empty($data['weight_kg'])) { //整车质量
|
||||
$data['weight_kg'] = 0;
|
||||
}
|
||||
$data['sku_code'] = SkuCodeBuilder::build($data);
|
||||
$data['corp_id'] = 0;
|
||||
$data['created_by'] = $request->user->uid;
|
||||
@@ -177,4 +223,12 @@ class VehVehicleSkuService
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取车型list
|
||||
*/
|
||||
public function getList($data)
|
||||
{
|
||||
return $this->logic->getList($data);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user