This commit is contained in:
2026-01-05 14:33:21 +08:00
commit ee348b4925
451 changed files with 39954 additions and 0 deletions
@@ -0,0 +1,62 @@
<?php
namespace app\common\service;
use app\common\validate\ApprovalFlowsValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\ApprovalFlows;
use support\Request;
class ApprovalFlowsService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = ApprovalFlows::class;
/**
* 验证器
*/
protected $validator = ApprovalFlowsValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
protected function buildQueryConfig(Request $request, string $scene = 'index'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'index':
$config['order']['id'] = 'desc';
break;
case 'show':
$config['where']['id'] = $p['id'];
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\ApprovalInstancesValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\ApprovalInstances;
class ApprovalInstancesService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = ApprovalInstances::class;
/**
* 验证器
*/
protected $validator = ApprovalInstancesValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,194 @@
<?php
namespace app\common\service;
use app\common\validate\ApprovalRecordsValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\ApprovalRecords;
use support\Request;
class ApprovalRecordsService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = ApprovalRecords::class;
/**
* 验证器
*/
protected $validator = ApprovalRecordsValidate::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 'index':
$config['order']['id'] = 'desc';
$config['with'] = [
'instance',
'approver',
'instance.flows',
'instance.initiator',
'instance.bizInfo'
];
// $config['dataLimit']['type'] = 'relation';
// $config['dataLimit']['userId'] = $request->user->uid;
// $config['dataLimit']['userField'] = 'approver_id';
break;
case 'todo':
if ($p['scene'] === 'todo') {
if (!in_array(1, $request->user->role_ids)) {
$config['where']['approver_id'] = $request->user->uid;
}
$config['where']['status'] = 'pending';
$config['where']['instance.status'] = 'processing';
}
if ($p['scene'] === 'history') {
if (!in_array(1, $request->user->role_ids)) {
$config['where']['approver_id'] = $request->user->uid;
}
$config['where']['status'] = ['!=', 'pending'];
}
$config['order']['id'] = 'desc';
$config['with'] = [
'instance',
'approver',
'instance.flows',
'instance.initiator',
'instance.bizInfo'
];
break;
case 'show':
$config['where']['id'] = $p['id'];
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
case 'logs':
$config['where']['instance_id'] = $p['instance_id'];
$config['order']['id'] = 'desc';
break;
case 'border':
$config['where']['id'] = $p['id'];
$config['order']['id'] = 'desc';
$config['with'] = [
'instance',
'approver',
'instance.flows',
'instance.initiator',
'instance.bizInfo'
];
break;
}
return $config;
}
public function serviceApprove(Request $request)
{
$param = $request->all();
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
return $appro->approve($param['instance_id'], $request->user->uid, $param['action'], $param['comment']);
}
public function serviceLogs(Request $request)
{
$config = $this->buildQueryConfig($request, 'logs');
$item = $this->logicListAll($config);
return $this->postProcess('Logs', $item);
}
public function serviceBusinessOrder(Request $request, array $data = [])
{
$config = $this->buildQueryConfig($request, 'border');
$config = $this->borderConfig($config, $data['flow_type']);
$item = $this->logicShow($config);
$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()
};
$bizInfo = $service->formatData($item->instance->bizInfo);
return $this->postProcess('Border', $bizInfo);
}
public function borderConfig($config, $type = 'stock_orders')
{
$with = match ($type) {
'stock_orders' => [
'instance.bizInfo.items',
'instance.bizInfo.items.options',
'instance.bizInfo.items.moneyValues',
'instance.bizInfo.accounts',
'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',
'instance.bizInfo.items.stockOrderItems'
]
};
$config['with'] = array_merge($config['with'], $with);
return $config;
}
public function transferApproval(Request $request, array $data = [])
{
$service = new \app\common\utils\approval\service\ApprovalFlowService();
$result = $service->transferApproval($data['instance_id'], $data['step_index'], $request->user->uid, $data['to_uid'], $data['reason'], 'name');
return $result;
}
public function serviceApprovalTimeline(Request $request, array $data = [])
{
$instance = \app\common\model\ApprovalInstances::where('biz_id', $data['id'])->where('flow_type', $data['flow_type'])->first();
if (!$instance) {
return [];
}
$result = \app\common\model\ApprovalRecords::where('instance_id', $instance->id)->with('approver')->orderBy('id', 'desc')->get();
return $result;
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace app\common\service;
use app\common\logic\AuthLogic;
use app\common\model\User;
use app\common\utils\Feishu;
use support\Db;
class AuthService
{
/**
* 飞书授权
*/
public function feishuAuthorize(string $app_id, string $systemCcode, $mobile)
{
$logic = new AuthLogic();
$certificate = $logic->feishuAppInfo($app_id, $systemCcode);
$feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], 'xinyouhe');
return ['auth_url' => $feishu->authorize($certificate, $mobile)];
}
/**
* 登录,支持多类型
*/
public function login(array $data): array
{
$loginType = $data['login_type'] ?? 'feishu';
$logic = new AuthLogic();
return match ($loginType) {
'feishu' => $logic->feishuLogin($data),
// 'qrcode' => $logic->qrCodeLogin($data),
default => $logic->uPwdLogin($data),
};
}
/**
* 用户注册
*/
public function register(array $data)
{
$logic = new AuthLogic();
try {
Db::beginTransaction();
$result = $logic->register($data);
Db::commit();
return $result;
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
}
/**
* 校验token
*/
public function verifyToken(string $token, string $systemCode): array
{
return (new AuthLogic())->verifyToken($token, $systemCode);
}
/**
* 刷新token
* @param string $refreshToken
* @param string|null $systemCode
*/
public function refreshToken(string $refreshToken, string $systemCode): array
{
return (new AuthLogic())->refreshToken($refreshToken, $systemCode);
}
/**
* 登出(失效token)
*/
public function logout(?string $token, string $systemCode): void
{
(new AuthLogic())->logout($token, $systemCode);
}
/**
* 强制拉黑token
*/
public function blockToken(string $token, string $systemCode): void
{
(new AuthLogic())->blockToken($token, $systemCode);
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
namespace app\common\service;
use app\common\model\Corp;
use app\common\model\Systems;
use support\Redis;
use support\exception\BusinessException;
class BasicDataService
{
// 定义Redis键名前缀常量
const LOGIN_TYPE_KEY = 'login_types';
const SYSTEM_CODE_KEY = 'system_codes';
const CORP_ABBR_KEY = 'corp_abbrs';
/**
* 检查并缓存独立的基础参数
*/
public function checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr)
{
if (!$loginType && !$systemCode) {
throw new BusinessException('header缺少必要参数');
}
// 检查并缓存登录类型
if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) {
$this->cacheLoginTypes();
if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) {
throw new BusinessException('无效的登录方式');
}
}
return true;
}
/**
* 检查参数是否在Redis集合中
*/
private function checkParamInCache($cacheKey, $param)
{
return Redis::sIsMember($cacheKey, $param);
}
/**
* 缓存所有登录类型到集合
*/
private function cacheLoginTypes()
{
$cacheKey = self::LOGIN_TYPE_KEY;
// 如果集合已存在,直接返回
if (Redis::exists($cacheKey)) {
return;
}
$loginTypes = $this->getAllLoginTypes();
if (empty($loginTypes)) {
throw new BusinessException('未获取到登录类型数据');
}
// 批量添加值到集合
Redis::sAdd($cacheKey, ...$loginTypes);
Redis::expire($cacheKey, 3600); // 1小时有效期
}
/**
* 缓存所有系统码到集合
*/
private function cacheSystemCodes()
{
$cacheKey = self::SYSTEM_CODE_KEY;
// 如果集合已存在,直接返回
if (Redis::exists($cacheKey)) {
return;
}
$systemCodes = $this->getAllSystemCodes();
if (empty($systemCodes)) {
throw new BusinessException('未获取到系统码数据');
}
// 批量添加值到集合
Redis::sAdd($cacheKey, ...$systemCodes);
Redis::expire($cacheKey, 3600); // 1小时有效期
}
/**
* 缓存所有公司标识到集合
*/
private function cacheCorpAbbrs()
{
$cacheKey = self::CORP_ABBR_KEY;
// 如果集合已存在,直接返回
if (Redis::exists($cacheKey)) {
return;
}
$corpAbbrs = $this->getAllCorpAbbrs();
if (empty($corpAbbrs)) {
throw new BusinessException('未获取到公司标识数据');
}
// 批量添加值到集合
Redis::sAdd($cacheKey, ...$corpAbbrs);
Redis::expire($cacheKey, 3600); // 1小时有效期
}
/**
* 获取所有有效的登录类型
*/
private function getAllLoginTypes()
{
return ['upwd', 'feishu', 'qrcode']; // 示例数据
}
/**
* 获取所有有效的系统码
*/
private function getAllSystemCodes()
{
return Systems::pluck('code')->toArray();
}
/**
* 获取所有有效的公司标识
*/
private function getAllCorpAbbrs()
{
return Corp::pluck('abbr')->toArray();
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace app\common\service;
use app\admin\validate\BrandSeriesValidate;
use app\common\logic\BrandSeriesLogic;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\BrandSeries;
use support\exception\BusinessException;
use support\Request;
class BrandSeriesService
{
protected $logic;
public function __construct()
{
$this->logic = (new BrandSeriesLogic());
}
/**
* 获取系统list
*/
public function getList($params)
{
if (empty($params['brand_id']))
throw new BusinessException('品牌id不能为空');
$where = [];
if(isset($params['brand_id']) && $params['brand_id'])
$where[] = ['brand_id', '=', $params['brand_id']];
return $this->logic->getList($where);
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace app\common\service;
use app\common\logic\BrandLogic;
class BrandService
{
protected $logic;
public function __construct()
{
$this->logic = (new BrandLogic());
}
/**
* 获取系统list
*/
public function getList()
{
return $this->logic->getList();
}
}
@@ -0,0 +1,21 @@
<?php
namespace app\common\service;
use app\common\logic\CorpFeishuAppLogic;
class CorpFeishuAppService
{
protected $logic;
public function __construct()
{
$this->logic = (new CorpFeishuAppLogic());
}
public function getFeishuApp(int $corpId, int $systemId)
{
return $this->logic->getFeishuApp($corpId, $systemId);
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace app\common\service;
use app\common\logic\CorpLogic;
class CorpService
{
protected $logic;
public function __construct()
{
$this->logic = (new CorpLogic());
}
/**
* 获取公司list
*/
public function getList()
{
return $this->logic->getList();
}
}
+90
View File
@@ -0,0 +1,90 @@
<?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.'&from=search_tab_er&api_param={"en_qc":+0}';
$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;
}
/**
* 关键词搜索的更多页面,通过面包屑抓取品牌与系列
* @param $series
* @return mixed|string
*/
public function crawlBrandSeries($series)
{
$url = "https://www.dongchedi.com/auto/series/" . $series . "?anchorId=carModels";
$ql = QueryList::get($url);
$result = $ql->find('div.breadcrumb_wrap__2GLeU span')->texts()->all();
return ['brand' => $result[2], 'series' => $result[3]];
}
}
@@ -0,0 +1,293 @@
<?php
namespace app\common\service;
use app\common\model\FinPaymentStock;
use app\common\model\FinReceipt;
use app\common\validate\FinPaymentApplyValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\FinPaymentApply;
use support\Redis;
use support\Request;
class FinPaymentApplyService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = FinPaymentApply::class;
/**
* 验证器
*/
protected $validator = FinPaymentApplyValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构建查询条件
*/
protected function buildQueryConfig(Request $request, string $scene = 'index'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'index':
// ====== ====== 排序 ====== ======
$config['order']['id'] = 'desc';
// ====== ====== 筛选 ====== ======
// 汇款国家
if (isset($p['country_id']) && !empty($p['country_id'])) {
$config['where']['country_id'] = $p['country_id'];
}
// 收款账户筛选
if (isset($p['account_id']) && !empty($p['account_id'])) {
$config['where']['account_id'] = $p['account_id'];
}
// 入账资料超时
if (isset($p['scene']) && $p['scene'] == 'file_timeout') {
$config['where']['claimed_at'] = ['between', [1, time()-24*60*60]];
}
// 关键字
if (isset($p['keyword']) && $p['keyword']) {
$k = '%' . $p['keyword'] . '%';
$config['orWhere'][] = ['remitter', 'like', $k];
}
break;
case 'show':
$config['where']['id'] = $p['id'];
break;
case 'update':
case 'destroy':
$config['where']['id'] = $p['id'];
break;
case 'store':
break;
}
return $config;
}
public function beforeServiceStore($data, Request $request)
{
$data['apply_no'] = $this->generateUniqueApplyNo();
$data['doc_user_id'] = $request->user->uid;
$data['voucher_file'] = json_encode($data['voucher_file']);
$data['submit_files'] = json_encode($data['submit_files']);
redis::set('stock_' . $data['doc_user_id'], json_encode($request->post('stock')), 'EX', 30);
unset($data['stock']);
if($data['voucher_file'] == '[]' || $data['voucher_file'] == '{}' || $data['voucher_file'] == '""')
$data['voucher_file'] = '';
if($data['submit_files'] == '[]' || $data['submit_files'] == '{}' || $data['submit_files'] == '""')
$data['submit_files'] = '';
return $data;
}
public function afterServiceStore($res)
{
$data = [
'payment_apply_id' => $res['id'],
];
$piStock = json_decode(redis::get('stock_' . $res['doc_user_id']), true);
redis::del('stock' . $res['doc_user_id']);
foreach ($piStock as $stock) {
foreach ($stock['stock_id'] as $v) {
$data['stock_id'] = $v;
$data['pi_no'] = $stock['pi_on'];
FinPaymentStock::create($data);
}
}
}
public function beforeServiceUpdate($data, Request $request)
{
unset($data['stock']);
$data['submit_files'] = json_encode($data['submit_files']);
if($data['submit_files'] == '[]' || $data['submit_files'] == '{}' || $data['submit_files'] == '""')
$data['submit_files'] = '';
$data['voucher_file'] = json_encode($data['voucher_file']);
if($data['voucher_file'] == '[]' || $data['voucher_file'] == '{}' || $data['voucher_file'] == '""')
$data['voucher_file'] = '';
return $data;
}
public function afterServiceUpdate($res, Request $request)
{
if($res->submit_files != '' && $res->receipt_id != 0)
FinReceipt::where('id', $res->receipt_id)->update(['submit_files' => $res->submit_files]);
}
/**
* 生成唯一的付款申请编码
* 格式:PA + 日期 + 6位字母数字组合
*
* @return string 唯一编码
*/
public function generateUniqueApplyNo()
{
$chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$maxAttempts = 10; // 防止无限循环
$secondChar = 'PA' . date('Ymd');
for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
// 生成后6位随机字符
$suffix = '';
for ($i = 0; $i < 6; $i++) {
$suffix .= $chars[rand(0, strlen($chars) - 1)];
}
$applyNo = $secondChar . $suffix;
// 检查是否已存在
$existing = FinPaymentApply::where('apply_no', $applyNo)->first();
if (!$existing) {
return $applyNo;
}
}
// 如果1000次尝试后仍然重复,使用时间戳确保唯一性
$timestamp = substr(time(), -6);
return $secondChar . $timestamp;
}
/**
* 根据相似度排序付款记录
* @param Request $request
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|\Illuminate\Pagination\LengthAwarePaginator
*/
public function complexQueryIndex(Request $request)
{
$limit = (int)$request->get('size', 15);
$page = (int)$request->get('current', 1);
$param = $request->all();
$where = [];
$orWhere = [];
// 汇款国家
if (isset($param['country_id']) && !empty($param['country_id'])) {
$where[] = ['country_id', $param['country_id']];
}
// 收款账户筛选
if (isset($param['account_id']) && !empty($param['account_id'])) {
$where[] = ['account_id', $param['account_id']];
}
// 入账资料超时
if (isset($param['scene']) && $param['scene'] == 'file_timeout') {
$where[] = ['submit_files', ''];
$where[] = ['claimed_at', '<', time()-24*60*60];
$where[] = ['claimed_at', '>', 1];
}
// 关键字
if (isset($param['keyword']) && $param['keyword']) {
$k = '%' . $param['keyword'] . '%';
$where[] = ['remitter', 'like', $k];
}
// 构建查询
$query = $this->modelClass::where($where);
// 是否已关联收款记录的筛选
$correlation = $param['correlation']??'';
if ($correlation === 'receipt') { //已关联
$query->whereHas('receipt');
} elseif ($correlation === 'unbind') { //未关联
$query->whereDoesntHave('receipt');
}
if(isset($param['scene']) && $param['scene'] == 'pending'){ //待入账列表
$query->where('receipt_id', '>' ,'0');
$query->where('submit_files','!=','');
$query->whereNull('received_at');
}
if($param['scene'] == 'delete'){ //查询作废数据
$query->onlyTrashed();
}elseif ($param['scene'] == 'all'){ //查询包含作废数据的全量数据
$query->withTrashed();
}
if ((isset($param['scene']) && ($param['scene'] == 'list' || $param['scene'] == 'pending'))|| empty($param['receipt_id']) ) {
return $query->orderBy('id','desc')->paginate($limit, ['*'], 'page', $page);
}
$receipt = FinReceipt::where('id', $param['receipt_id'])->first();
$account_id = $receipt->account_id;
$currency_id = $receipt->currency_id;
$remitter = $receipt->remitter;
$amount = $receipt->amount;
// 计算十天前的时间戳
$sevenDaysAgo = time() - (10 * 24 * 60 * 60);
// 构建相似度排序逻辑
$similarityOrder = [];
// 如果有传入参数,计算相似度
if (!empty($account_id)) {
$similarityOrder[] = "CASE WHEN account_id = {$account_id} THEN 0 ELSE 1 END";
}
if (!empty($currency_id)) {
$similarityOrder[] = "CASE WHEN currency_id = {$currency_id} THEN 0 ELSE 1 END";
}
if (!empty($remitter)) {
$similarityOrder[] = "CASE WHEN remitter = '{$remitter}' THEN 0 ELSE 1 END";
}
if (!empty($amount)) {
$amount = (float)$amount;
$similarityOrder[] = "ABS(amount - {$amount})";
$similarityOrder[] = "CASE WHEN (ABS(amount - {$amount}) / {$amount} * 100) <= 5 THEN 0 ELSE 1 END";
}
// 构建排序SQL
$orderByClause = "";
if (!empty($similarityOrder)) {
$orderByClause = "CASE
WHEN created_at >= {$sevenDaysAgo} THEN
(" . implode(' + ', $similarityOrder) . ")
ELSE 0
END ASC, ";
}
$orderByClause .= "CASE
WHEN created_at > {$sevenDaysAgo} THEN id
ELSE 999999
END DESC, ";
$orderByClause .= "id DESC";
$query->orderByRaw($orderByClause);
return $query->paginate($limit, ['*'], 'page', $page);
}
public function delete($id)
{
$res = FinPaymentApply::where('id', $id)->delete();
return $res;
}
}
@@ -0,0 +1,62 @@
<?php
namespace app\common\service;
use app\common\validate\FinReceiptHisValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\FinReceiptHis;
use support\exception\BusinessException;
use support\Request;
class FinReceiptHisService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = FinReceiptHis::class;
/**
* 验证器
*/
protected $validator = FinReceiptHisValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构建查询条件
*/
protected function buildQueryConfig(Request $request, string $scene = 'index'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'index':
if(empty($p['receipt_id'])){
throw new BusinessException('receipt_id不能为空');
}
// ====== ====== 排序 ====== ======
$config['order']['id'] = 'desc';
// ====== ====== 筛选 ====== ======
$config['where']['receipt_id'] = $p['receipt_id'];
break;
case 'show':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
}
+335
View File
@@ -0,0 +1,335 @@
<?php
namespace app\common\service;
use app\common\logic\FinReceiptLogic;
use app\common\model\FinPaymentApply;
use app\common\model\FinPaymentStock;
use app\common\model\FinReceiptHis;
use app\common\validate\FinReceiptValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\FinReceipt;
use support\exception\BusinessException;
use support\Redis;
use support\Request;
class FinReceiptService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = FinReceipt::class;
/**
* 验证器
*/
protected $validator = FinReceiptValidate::class;
protected $logic;
public function __construct()
{
$this->logic = (new FinReceiptLogic());
}
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构建查询条件
*/
protected function buildQueryConfig(Request $request, string $scene = 'index'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'index':
// ====== ====== 排序 ====== ======
$config['order']['id'] = 'desc';
// ====== ====== 筛选 ====== ======
// 汇款国家
if (isset($p['country_id']) && !empty($p['country_id'])) {
$config['where']['country_id'] = $p['country_id'];
}
// 没有付款记录的收款记录
if (isset($p['payment_apply']) && $p['payment_apply'] == 'not') { //未认领
$config['where']['payment_apply_id'] = 0;
} elseif (isset($p['payment_apply']) && $p['payment_apply'] == 'already') { //已认领
$config['where']['payment_apply_id'] = ['>', 0];
} elseif (isset($p['payment_apply']) && $p['payment_apply'] == 'timeout') { //超时未认领
$config['where']['payment_apply_id'] = 0;
$config['where']['created_at'] = ['<' ,time() - 24*60*60];
}
// 收款申请筛选
if (isset($p['payment_apply_id']) && !empty($p['payment_apply_id'])) {
$config['where']['payment_apply_id'] = $p['payment_apply_id'];
}
// 收款账户筛选
if (isset($p['account_id']) && !empty($p['account_id'])) {
$config['where']['account_id'] = $p['account_id'];
}
// 关键字
if (isset($p['keyword']) && $p['keyword']) {
$k = '%' . $p['keyword'] . '%';
$config['orWhere'][] = ['remitter', 'like', $k];
}
break;
case 'show':
$config['where']['id'] = $p['id'];
break;
case 'update':
case 'payment':
case 'destroy':
$config['where']['id'] = $p['id'];
break;
case 'store':
break;
}
return $config;
}
public function beforeServiceStore($data, Request $request)
{
if(isset($data['payment_apply_id']) && empty($data['payment_apply_id'])){
unset($data['payment_apply_id']);
}
$data['receipt_no'] = $this->generateUniqueReceipt();
$data['arrived_at'] = strtotime( $data['arrived_at']);
$data['created_by'] = $request->user->uid;
$data['updated_by'] = $request->user->uid;
$data['fx_receipt_file'] = json_encode($data['fx_receipt_file']);
if($data['fx_receipt_file'] == '[]' || $data['fx_receipt_file'] == '{}' || $data['fx_receipt_file'] == '""')
$data['fx_receipt_file'] = '';
return $data;
}
public function afterServiceStore($res)
{
$receipt = FinReceipt::where('id',$res->id)->first()->toArray();
$receipt = array_merge($receipt,['receipt_id' => $res->id]);
unset($receipt['id']);
FinReceiptHis::create($receipt);
}
public function beforeServiceUpdate($data, Request $request)
{
$data['updated_by'] = $request->user->uid;
if(isset($data['arrived_at']))
$data['arrived_at'] = strtotime($data['arrived_at']);
if(isset($data['received_at']))
$data['received_at'] = strtotime($data['received_at']);
$data['fx_receipt_file'] = json_encode($data['fx_receipt_file']);
if($data['fx_receipt_file'] == '[]' || $data['fx_receipt_file'] == '{}' || $data['fx_receipt_file'] == '""')
$data['fx_receipt_file'] = '';
return $data;
}
public function afterServiceUpdate($res)
{
$receipt = FinReceipt::where('id',$res['id'])->first()->toArray();
$receipt = array_merge($receipt,['receipt_id' => $res['id']]);
unset($receipt['id']);
FinReceiptHis::create($receipt);
}
/**
* 生成唯一的付款申请编码
* 格式:PA + 日期 + 6位字母数字组合
*
* @return string 唯一编码
*/
public function generateUniqueReceipt()
{
$chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$maxAttempts = 10; // 防止无限循环
$secondChar = 'RE' . date('Ymd');
for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
// 生成后6位随机字符
$suffix = '';
for ($i = 0; $i < 6; $i++) {
$suffix .= $chars[rand(0, strlen($chars) - 1)];
}
$ReceiptNo = $secondChar . $suffix;
// 检查是否已存在
$existing = FinReceipt::where('receipt_no', $ReceiptNo)->first();
if (!$existing) {
return $ReceiptNo;
}
}
// 如果1000次尝试后仍然重复,使用时间戳确保唯一性
$timestamp = substr(time(), -6);
return $secondChar . $timestamp;
}
/**
* 根据相似度排序付款记录
* @param Request $request
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|\Illuminate\Pagination\LengthAwarePaginator
*/
public function complexQueryIndex(Request $request)
{
$limit = (int)$request->get('size', 15);
$page = (int)$request->get('current', 1);
$param = $request->all();
if(isset($param['voucher_no_timeout_hour'])){
redis::set('voucher_no_timeout_hour', $param['voucher_no_timeout_hour'], 'EX', 30);
}
$where = [];
// 汇款国家
if (isset($param['country_id']) && !empty($param['country_id'])) {
$where[] = ['country_id', $param['country_id']];
}
// 收款账户筛选
if (isset($param['account_id']) && !empty($param['account_id'])) {
$where[] = ['account_id', $param['account_id']];
}
// 收款申请筛选
if (isset($param['payment_apply_id']) && !empty($param['payment_apply_id'])) {
$where[]['payment_apply_id'] = $param['payment_apply_id'];
}
// 关键字
if (isset($param['keyword']) && $param['keyword']) {
$k = '%' . $param['keyword'] . '%';
$where[] = ['remitter', 'like', $k];
}
// 构建查询
$query = $this->modelClass::where($where);
// 收款记录绑定情况
if (isset($param['payment_apply']) && $param['payment_apply'] == 'not') {
$query->where('payment_apply_id', 0);
} elseif (isset($param['payment_apply']) && $param['payment_apply'] == 'already') {
$query->where('payment_apply_id', '>', 0);
} elseif (isset($param['payment_apply']) && $param['payment_apply'] == 'timeout') { //超时未认领
$query->where('payment_apply_id', 0);
$query->where('created_at', '<' ,time() - 24*60*60);
}
if(isset($param['scene']) && $param['scene'] == 'pending'){ //待入账列表
$query->where('payment_apply_id', '>' ,'0');
$query->where('submit_files','!=','');
}
if (isset($param['scene']) && ($param['scene'] == 'list' ||$param['scene'] == 'pending')) {
return $query->paginate($limit, ['*'], 'page', $page);
}
// 计算七天前的时间戳
$sevenDaysAgo = time() - (10 * 24 * 60 * 60);
// 构建相似度排序逻辑
$similarityOrder = [];
// 如果有传入参数,计算相似度
if (isset($param['account_id']) && !empty($param['account_id'])) {
$similarityOrder[] = "CASE WHEN account_id = {$param['account_id']} THEN 0 ELSE 1 END";
}
if (isset($param['country_id']) && !empty($param['country_id'])) {
$similarityOrder[] = "CASE WHEN country_id = {$param['country_id']} THEN 0 ELSE 1 END";
}
if (isset($param['keyword']) && $param['keyword']) {
$keyword = addslashes($param['keyword']);
$similarityOrder[] = "CASE WHEN remitter LIKE '%{$keyword}%' THEN 0 ELSE 1 END";
}
if (isset($param['amount']) && !empty($param['amount'])) {
$amount = (float)$param['amount'];
$similarityOrder[] = "ABS(amount - {$amount})";
}
// 构建排序SQL
$orderByClause = "";
if (!empty($similarityOrder)) {
$orderByClause = "CASE
WHEN created_at >= {$sevenDaysAgo} THEN
(" . implode(' + ', $similarityOrder) . ")
ELSE 999999
END ASC, ";
}
$orderByClause .= "CASE
WHEN created_at < {$sevenDaysAgo} THEN id
ELSE 0
END DESC";
$query->orderByRaw($orderByClause);
return $query->paginate($limit, ['*'], 'page', $page);
}
public function credit($id, $exchange_rate)
{
$finReceipt = FinReceipt::find($id);
if(!empty($finReceipt->received_at)){
throw new BusinessException('该记录已将确认入账');
}
$data = [
'received_at' => time(),
'exchange_rate' => $exchange_rate,
'total_amount_cny' => round($finReceipt['amount']*$exchange_rate,2),
];
$res = $this->logic->credit($id,$data);
FinPaymentApply::where('id', $finReceipt->payment_apply_id)->update(['received_at' => $data['received_at']]);
$this->afterServiceUpdate(['id'=>$id]);
return $res;
}
/**
* 更新:绑定收款记录,计算折让金额
* @param Request $request
* @param array $data
* @param string $scene
* @return mixed
*/
public function serviceUpdatePayment(Request $request, array $data = [], string $scene = 'payment')
{
$payment_apply_id = $request->input('payment_apply_id');
$id = $request->input('id');
$data = $this->preProcess(ucfirst($scene), $data, $request);
$config = $this->buildQueryConfig($request, $scene);
FinPaymentApply::where('id', $payment_apply_id)->update(['receipt_id' => $id, 'claimed_at' => time(), 'claimed_by' => $request->user->uid]);
FinPaymentStock::where('payment_apply_id', $payment_apply_id)->update(['receipt_id' => $id]);
//折让金额
$payment = FinPaymentApply::where('id', $payment_apply_id)->first();
$receipt = FinReceipt::find($id);
$data['discount_amount'] = $payment->amount - $receipt->amount ?? 0;
$data['submit_files'] = $payment->submit_files ?? '';
$updated = $this->logicUpdateBy($config, $data);
return $this->postProcess(ucfirst($scene), $data);
}
}
@@ -0,0 +1,290 @@
<?php
namespace app\common\service;
use app\common\validate\FinanceReconDubaiFilesValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\FinanceReconDubaiFiles;
use app\common\utils\excel\ExcelImportTrait;
use support\Request;
/**
* FinanceReconDubaiFilesService
*
* - 提供两类导入:
* 1) M项目对账明细(sheet: M项目对账明细)
* 2) 迪拜两表合并(sheets: Stock in Jebel Ali / Under shipping)
*
* - 公共导入流程复用 ExcelImportTrait
* - OSS 读取通过你的 OssService->generatePrivateUrl() 完成
*/
class FinanceReconDubaiFilesService
{
use CrudServiceTrait;
use ExcelImportTrait; // 复用通用导入方法
/** 指定当前要操作的模型(用于 CrudServiceTrait) */
protected string $modelClass = FinanceReconDubaiFiles::class;
/** 验证器 */
protected $validator = FinanceReconDubaiFilesValidate::class;
/** 获取验证器实例 */
public function getValidator()
{
return new $this->validator;
}
/**
* 写入前置:将上传组件的结构转为 *_url / *_name,并记录创建人
*/
public function beforeServiceStore($data, Request $request)
{
$data['created_by'] = $request->user->uid ?? 0;
$data['caiwu_file_url'] = $data['caiwu_file']['url'] ?? '';
$data['caiwu_file_name'] = $data['caiwu_file']['name'] ?? '';
$data['dubai_file_url'] = $data['dubai_file']['url'] ?? '';
$data['dubai_file_name'] = $data['dubai_file']['name'] ?? '';
return $data;
}
/* ============================================================
* 一、M项目对账明细 导入配置(sheet: M项目对账明细)
* ============================================================ */
/** 中文表头 => 目标字段名 */
protected array $headerMapM = [
'车架号' => 'vin',
'开票价' => 'invoice_price',
'实际采购价' => 'actual_purchase_price',
'开票价和实际采购价差额' => 'invoice_vs_purchase_diff',
'车源中介费' => 'source_broker_fee',
'配件' => 'parts_fee',
'停车费' => 'parking_fee',
'保险费' => 'insurance_fee',
'车辆手续费' => 'vehicle_service_fee',
'检测费' => 'inspection_fee',
'购置税' => 'purchase_tax',
'汽车其他费用' => 'other_vehicle_fee',
'转移待出口' => 'transfer_to_export',
'国际运费' => 'freight_international',
'国内运费1' => 'freight_domestic_1',
'国内运费2' => 'freight_domestic_2',
'报关费及港杂费' => 'customs_port_fee',
'注销费' => 'cancellation_fee',
'车船税' => 'vehicle_vessel_tax',
'其他财务费用' => 'other_finance_fee',
'汽车小额杂费' => 'minor_vehicle_misc',
'上牌费' => 'registration_fee',
'其他保险费' => 'other_insurance_fee',
'油补' => 'fuel_subsidy',
'快递费' => 'express_fee',
'国内成本合计' => 'domestic_cost_total',
'退税额' => 'tax_refund_amount',
'cif杰贝阿里成本价' => 'cif_jebel_ali_cost',
'范经理发车看板CIF' => 'fan_manager_cif',
'财务-范经理 差额' => 'finance_vs_fan_diff',
];
/** 需要转为 decimal 的目标字段 */
protected array $decimalFieldsM = [
'invoice_price','actual_purchase_price','invoice_vs_purchase_diff',
'source_broker_fee','parts_fee','parking_fee','insurance_fee','vehicle_service_fee','inspection_fee',
'purchase_tax','other_vehicle_fee','transfer_to_export',
'freight_international','freight_domestic_1','freight_domestic_2','customs_port_fee',
'cancellation_fee','vehicle_vessel_tax','other_finance_fee','minor_vehicle_misc',
'registration_fee','other_insurance_fee','fuel_subsidy','express_fee',
'domestic_cost_total','tax_refund_amount','cif_jebel_ali_cost','fan_manager_cif','finance_vs_fan_diff',
];
/**
* M项目对账明细导入
*
* @param string $file 可为“签名 URL”或“OSS 对象键”
* @param bool $isUrl 若传入的是已签名 URL,请设 true;若是对象键,设 false
* @param bool $insertToDb 是否立即入库
*
* 返回:['sheet','total_rows','skipped','errors','to_import','inserted','preview_3',('error'?)]
*/
public function mImport(string $file, bool $isUrl = true, bool $insertToDb = true): array
{
$sheet = 'M项目对账明细';
// 允许传对象键:当 isUrl=false 且非 http(s) 时,自动用 OssService 签名
if ($isUrl === false && !preg_match('#^https?://#i', $file)) {
$file = $this->ossSignedUrl($file);
$isUrl = true;
}
$rows = $this->readSheetRows($file, $sheet, $isUrl, headerRow: 1);
$mapped = [];
$skipped = 0;
$errors = 0;
foreach ($rows as $row) {
try {
if ($this->shouldSkipByVinAliases($row, ['车架号'])) { $skipped++; continue; }
$rec = $this->mapAndNormalizeRow($row, $this->headerMapM, $this->decimalFieldsM);
$this->stamp($rec);
$mapped[] = $rec;
} catch (\Throwable $e) {
// 单行异常不阻断全局导入
$errors++; continue;
}
}
$result = [
'sheet' => $sheet,
'total_rows' => count($rows),
'skipped' => $skipped,
'errors' => $errors,
'to_import' => count($mapped),
];
if ($insertToDb) {
// 注意表名:按你的业务这里写入 finance_recon_m_detail
$ret = $this->batchInsert('finance_recon_m_detail', $mapped);
$result['inserted'] = $ret['inserted'];
if ($ret['error']) $result['error'] = 'Database insert error: ' . $ret['error'];
} else {
$result['inserted'] = 0;
}
$result['preview_3'] = array_slice($mapped, 0, 3);
return $result;
}
/** M项目对账明细:从 OSS 对象键导入(语义化便捷入口) */
public function mImportFromOssKey(string $objectKey, bool $insertToDb = true): array
{
$signedUrl = $this->ossSignedUrl($objectKey);
return $this->mImport($signedUrl, true, $insertToDb);
}
/** M项目对账明细:从已签名 URL 导入(语义化便捷入口) */
public function mImportFromSignedUrl(string $signedUrl, bool $insertToDb = true): array
{
return $this->mImport($signedUrl, true, $insertToDb);
}
/* ============================================================
* 二、迪拜两表合并 导入配置(sheets: Stock in Jebel Ali / Under shipping)
* ============================================================ */
/** 表头映射(已兼容不一致列名:VIN NO/车架号、国际运费和杂费/SHIPPING COST) */
protected array $headerMapDubai = [
'VIN NO' => 'vin', '车架号' => 'vin',
'开票价' => 'invoice_price',
'中介费' => 'broker_fee',
'购置税' => 'purchase_tax',
'退税' => 'tax_refund',
'不含税车价' => 'price_ex_vat',
'费用' => 'fee_total',
'国内运费' => 'domestic_freight',
'FCA成本价' => 'fca_cost',
'FCA成本价汇率' => 'fca_fx',
'国际运费和杂费' => 'shipping_cost',
'SHIPPING COST' => 'shipping_cost',
'运费汇率' => 'shipping_fx',
'CIF 杰贝阿里成本价' => 'cif_jebel_ali_cost',
'CIF RMB' => 'cif_rmb', // Under shipping 没有该列,将在逻辑中推导
];
/** 需要转为 decimal 的目标字段(汇率字段使用 DECIMAL(18,6) 建表) */
protected array $decimalFieldsDubai = [
'invoice_price','broker_fee','purchase_tax','tax_refund','price_ex_vat',
'fee_total','domestic_freight','fca_cost','fca_fx',
'shipping_cost','shipping_fx','cif_jebel_ali_cost','cif_rmb',
];
/**
* 迪拜两表合并导入(Stock in Jebel Ali / Under shipping)
*
* 缺失值规则:
* - Under shipping 无 “CIF RMB”,尝试:cif_rmb = cif_jebel_ali_cost × shipping_fx
* 若运费汇率缺失则用 fca_fx 兜底,都没有则置 NULL。
*
* 返回:['sheets','total_rows','skipped','to_import','inserted','preview_5',('error'?)]
*/
public function dImport(string $file, bool $isUrl = false, bool $insertToDb = true): array
{
// 允许传对象键:当 isUrl=false 且非 http(s) 时,自动用 OssService 签名
if ($isUrl === false && !preg_match('#^https?://#i', $file)) {
$file = $this->ossSignedUrl($file);
$isUrl = true;
}
$sheets = ['Stock in Jebel Ali', 'Under shipping'];
$parsed = [];
// 逐 Sheet 读取
foreach ($sheets as $sheet) {
$parsed[$sheet] = $this->readSheetRows($file, $sheet, $isUrl, headerRow: 1);
}
// 行处理与合并
$rowsAll = [];
$skipped = 0;
foreach ($sheets as $sheet) {
foreach ($parsed[$sheet] as $row) {
if ($this->shouldSkipByVinAliases($row, ['VIN NO','车架号'])) { $skipped++; continue; }
// 映射 + 数值规范化
$rec = $this->mapAndNormalizeRow($row, $this->headerMapDubai, $this->decimalFieldsDubai);
// Under shipping 无 CIF RMB:尝试用汇率推导
if (empty($rec['cif_rmb'])) {
$cif = $rec['cif_jebel_ali_cost'] ?? null;
$fx = $rec['shipping_fx'] ?? null;
if ($cif !== null && $fx !== null) {
$rec['cif_rmb'] = round($cif * $fx, 2);
} elseif ($cif !== null && !empty($rec['fca_fx'])) {
$rec['cif_rmb'] = round($cif * (float)$rec['fca_fx'], 2);
} else {
$rec['cif_rmb'] = null;
}
}
// 统一 VIN 与来源
$rec['vin'] = $this->pickFirstNonEmpty($row, ['VIN NO','车架号']);
$rec['source_sheet'] = $sheet;
$this->stamp($rec);
$rowsAll[] = $rec;
}
}
// 结果与入库
$result = [
'sheets' => $sheets,
'total_rows' => array_sum(array_map('count', $parsed)),
'skipped' => $skipped,
'to_import' => count($rowsAll),
];
if ($insertToDb) {
// 注意表名:按你的业务这里写入 finance_recon_d_detail
$ret = $this->batchInsert('finance_recon_d_detail', $rowsAll);
$result['inserted'] = $ret['inserted'];
if ($ret['error']) $result['error'] = 'Database insert error: ' . $ret['error'];
} else {
$result['inserted'] = 0;
}
$result['preview_5'] = array_slice($rowsAll, 0, 5);
return $result;
}
/** 迪拜两表:从 OSS 对象键导入(语义化便捷入口) */
public function dImportFromOssKey(string $objectKey, bool $insertToDb = true): array
{
$signedUrl = $this->ossSignedUrl($objectKey);
return $this->dImport($signedUrl, true, $insertToDb);
}
/** 迪拜两表:从已签名 URL 导入(语义化便捷入口) */
public function dImportFromSignedUrl(string $signedUrl, bool $insertToDb = true): array
{
return $this->dImport($signedUrl, true, $insertToDb);
}
}
@@ -0,0 +1,67 @@
<?php
namespace app\common\service;
use app\common\validate\InquiryTransmitConfigValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\InquiryTransmitConfig;
use support\Request;
class InquiryTransmitConfigService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = InquiryTransmitConfig::class;
/**
* 验证器
*/
protected $validator = InquiryTransmitConfigValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构造 CrudUtil 查询配置
*/
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']['id'] = 'desc';
break;
case 'index':
$config['where']['status'] = 1;
$config['order']['id'] = 'desc';
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;
}
return $config;
}
}
@@ -0,0 +1,92 @@
<?php
namespace app\common\service;
use app\common\validate\InquiryTransmitValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\InquiryTransmit;
use support\Request;
class InquiryTransmitService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = InquiryTransmit::class;
/**
* 验证器
*/
protected $validator = InquiryTransmitValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/** 创建 */
public function serviceStore(Request $request, array $data = [], string $domain = '')
{
$data['email'] = $data['Email'];
$data['name'] = $data['Name'];
$data['tel'] = $data['Tel'];
$data['company'] = $data['Company'];
$data['country'] = $data['Country'];
$data['form_Url'] = $data['Form Url'];
$data['message'] = $data['Message'];
$data['ip_location'] = $data['IpLocation'];
$data['ip'] = $data['Ip'];
$data['domain'] = $domain;
return InquiryTransmit::create($data);
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['order']['id'] = 'desc';
break;
case 'index':
// 域名
if (isset($p['domain']) && !empty($p['domain'])) {
$config['where']['domain'] = $p['domain'];
}
// 关键字
if (isset($p['keyword']) && $p['keyword']) {
$k = '%' . $p['keyword'] . '%';
foreach (['email', 'name', 'tel', 'country', 'message', 'form_url'] as $col) {
$config['orWhere'][] = [$col, 'like', $k];
}
}
$config['order']['id'] = 'desc';
break;
case 'show':
$config['where']['id'] = $p['id'];
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
}
+199
View File
@@ -0,0 +1,199 @@
<?php
namespace app\common\service;
use app\common\logic\InvItemLogic;
use app\common\validate\InvItemValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\InvItem;
use support\Request;
use support\exception\BusinessException;
class InvItemService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = InvItem::class;
/**
* 验证器
*/
protected $validator = InvItemValidate::class;
protected $logic;
public function __construct()
{
$this->logic = (new InvItemLogic());
}
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构造 CrudUtil 查询配置
*/
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';
break;
case 'index':
$config['where']['status'] = 1;
// 关键字
if (isset($p['keyword']) && $p['keyword']) {
$k = '%' . $p['keyword'] . '%';
foreach (['item_name'] as $col) {
$config['orWhere'][] = [$col, 'like', $k];
}
}
$config['order']['sort'] = 'desc';
$config['order']['id'] = 'desc';
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;
}
return $config;
}
/**
* 生成 8-12 位的物品编码(默认10位)
*
* @param int $length 编码长度,默认为10(范围 8-12)
* @return string 返回大写的字母数字组合编码
*/
private function generateItemCode($length = 10)
{
// 排除易混淆字符:0, O, o, 1, I, l
$chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
$code = '';
$maxIndex = strlen($chars) - 1;
for ($i = 0; $i < $length; $i++) {
$code .= $chars[mt_rand(0, $maxIndex)];
}
return $code;
}
public function beforeServiceStore($data, Request $request): array
{
$data['item_code'] = $this->generateItemCode();
$data['category_id'] = 0;
$data['spec_model'] = '';
$data['unit'] = '件';
$data['is_consumable'] = 0;
$data['stock_qty'] = $data['stock_qty']??1;
$data['location'] = '';
return $data;
}
/**
* 变动物品库存数量
*
* @param int $itemId 物品ID
* @param int $changeQty 变动数量:>0 入库,<0 出库
* @param bool $allowNegative 是否允许库存变成负数,默认不允许
*
* @return InvItem 最新的物品记录
* @throws BusinessException
*/
public function changeStock(int $itemId, int $changeQty, bool $allowNegative = false): InvItem
{
if ($changeQty === 0) {
throw new BusinessException('库存变动数量不能为 0');
}
$now = time();
// 构建查询
$query = InvItem::query()->where('id', $itemId);
// 如果不允许负数,并且是出库场景(变动为负)
if (! $allowNegative && $changeQty < 0) {
// 要保证:当前 stock_qty >= 出库数量
$query->where('stock_qty', '>=', -$changeQty);
}
// 使用 increment 实现:UPDATE inv_item SET stock_qty = stock_qty + ?, updated_at = ? WHERE ...
$affected = $query->increment('stock_qty', $changeQty, [
'updated_at' => $now,
]);
if ($affected === 0) {
// 更新不到行:
// 1) 物品不存在
// 2) 或者库存不足(不允许负数时)
$exists = InvItem::query()->where('id', $itemId)->exists();
if (! $exists) {
throw new BusinessException('物品不存在');
}
throw new BusinessException('库存不足,无法完成库存变动');
}
// 再查一次最新数据返回(在外层事务中也会保持一致)
/** @var InvItem $item */
$item = InvItem::query()->find($itemId);
return $item;
}
/**
* 出库(减库存,默认不允许负数)
*/
public function stockOut(int $itemId, int $qty, bool $allowNegative = false): InvItem
{
if ($qty <= 0) {
throw new BusinessException('出库数量必须大于 0');
}
return $this->changeStock($itemId, -$qty, $allowNegative);
}
/**
* 入库(加库存)
*/
public function stockIn(int $itemId, int $qty): InvItem
{
if ($qty <= 0) {
throw new BusinessException('入库数量必须大于 0');
}
return $this->changeStock($itemId, $qty, true);
}
/**
* 获取车型list
*/
public function getList()
{
return $this->logic->getList();
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\InvRequisitionApproveValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\InvRequisitionApprove;
class InvRequisitionApproveService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = InvRequisitionApprove::class;
/**
* 验证器
*/
protected $validator = InvRequisitionApproveValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\InvRequisitionAttachmentValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\InvRequisitionAttachment;
class InvRequisitionAttachmentService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = InvRequisitionAttachment::class;
/**
* 验证器
*/
protected $validator = InvRequisitionAttachmentValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\InvRequisitionItemReturnAttachmentValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\InvRequisitionItemReturnAttachment;
class InvRequisitionItemReturnAttachmentService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = InvRequisitionItemReturnAttachment::class;
/**
* 验证器
*/
protected $validator = InvRequisitionItemReturnAttachmentValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,449 @@
<?php
namespace app\common\service;
use app\common\model\InvRequisition;
use app\common\model\InvRequisitionItem;
use app\common\validate\InvRequisitionItemReturnValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\InvRequisitionItemReturn;
use support\Request;
use support\Db;
use support\exception\BusinessException;
use app\common\service\UserService;
use Illuminate\Database\Eloquent\Model;
use app\common\model\InvRequisitionItemReturnAttachment;
class InvRequisitionItemReturnService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = InvRequisitionItemReturn::class;
/**
* 验证器
*/
protected $validator = InvRequisitionItemReturnValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 将二维数组按指定 key 重组为关联数组,该 key 的值作为新数组的键,
* 其余元素组成子数组作为值。
*
* @param array $array 原始二维数组
* @param string $keyField 用作新键的字段名(如 'id')
* @return array 重组后的数组
*/
private function groupArrayByKey($array, $keyField = 'id')
{
$result = [];
foreach ($array as $item) {
if (!isset($item[$keyField])) {
continue; // 跳过没有该 key 的项
}
$key = $item[$keyField];
// 移除用作键的字段
unset($item[$keyField]);
$result[$key] = $item;
}
return $result;
}
/**
* 创建物品归还申请前置处理
*
* @param array $data 归还申请数据
* @param Request $request 请求对象
* @return array 处理后的数据
*/
public function beforeServiceStore($data, Request $request): array
{
try {
// // 模拟数据
// $data = [
// [
// 'requisition_item_id' => 9,
// 'return_qty' => 1,
// 'apply_remark' => '申请备注',
// 'attachment' => [
// [
// 'original_name' => '附件1.jpg',
// 'file_path' => '/uploads/2023/07/01/attachment1.jpg'
// ],
// [
// 'original_name' => '附件2.jpg',
// 'file_path' => '/uploads/2023/07/01/attachment2.jpg'
// ]
// ]
// ]
// ];
// 获取所有申请项ID
$reqItemIds = array_column($data, 'requisition_item_id');
// 参数校验
if (empty($reqItemIds)) {
throw new BusinessException('未提供有效的申请项ID');
}
// 将申请项按 ID 重组为关联数组
$newReqItems = $this->groupArrayByKey($data, 'requisition_item_id');
// 获取申请项明细
$reqItemsArr = InvRequisitionItem::whereIn('id', $reqItemIds)->get()->toArray();
// 检查是否获取到有效数据
if (empty($reqItemsArr)) {
throw new BusinessException('未找到对应的申请项');
}
// 获取用户信息
$userService = new UserService();
$user = $userService->getUserInfo($request->user->uid, '');
$arr = [];
foreach ($reqItemsArr as $reqItem) {
$reqItemId = $reqItem['id'];
// 检查是否有对应的新申请项数据
if (!isset($newReqItems[$reqItemId])) {
continue;
}
$arr[] = [
'requisition_id' => $reqItem['requisition_id'],
'requisition_item_id' => $reqItemId,
'item_id' => $reqItem['item_id'],
'apply_user_id' => $user['user']['id'],
'apply_user_name' => $user['user']['name'],
'return_qty' => $newReqItems[$reqItemId]['return_qty'],
'apply_remark' => $newReqItems[$reqItemId]['apply_remark'] ?? '',
'attachment' => $newReqItems[$reqItemId]['attachment'],
'return_at' => time()
];
}
return $arr;
} catch (BusinessException $e) {
throw $e;
} catch (\Throwable $th) {
throw new BusinessException('创建物品归还申请失败:' . $th->getMessage());
}
}
/**
* 处理物品申请
*
* @param Request $request
* @param array $data
* @return array
*/
public function serviceStore(Request $request, array $data, string $scene = '')
{
try {
// 关联表增加事务处理
return Db::transaction(function () use ($request, $data) {
$data = $this->preProcess('Store', $data, $request);
$dl = $this->getDataLimitFromRequest($request);
foreach ($data as $item) {
$created = $this->logicCreate($item, $dl);
$this->processRequisitionItemReturnAttachment($created, $item['attachment']);
}
return $this->postProcess('Store', $created);
});
} catch (\Throwable $e) {
throw new BusinessException("创建物品申请失败:" . $e->getMessage());
}
}
/**
* 更新物品归还申请前置处理
*
* @param array $data 归还申请数据
* @param Request $request 请求对象
* @return array 处理后的数据
*/
public function beforeServiceUpdate($data, Request $request): array
{
// // 模拟数据
// $data = [
// 'id' => 1,
// 'requisition_item_id' => 9,
// 'return_qty' => 1,
// 'apply_remark' => '申请备注22',
// 'attachment' => [
// [
// 'original_name' => '附件1.jpg',
// 'file_path' => '/uploads/2023/07/01/attachment1.jpg'
// ],
// [
// 'original_name' => '附件2.jpg',
// 'file_path' => '/uploads/2023/07/01/attachment2.jpg'
// ],
// [
// 'original_name' => '附件3.jpg',
// 'file_path' => '/uploads/2023/07/01/attachment3.jpg'
// ]
// ]
// ];
$data['return_at'] = time();
return $data;
}
/**
* 更新物品归还申请
*
* @param Request $request
* @param array $data
* @return array
*/
public function serviceUpdate(Request $request, array $data, string $scene = '')
{
try {
// 关联表增加事务处理
return Db::transaction(function () use ($request, $data) {
$data = $this->preProcess('Update', $data, $request);
$config = $this->buildQueryConfig($request, 'update');
$attachment = $data['attachment'];
unset($data['attachment']);
$this->logicUpdateBy($config, $data);
// 获取更新后的完整记录
$updated = $this->logicShow($config);
// 处理附件更新
$this->processRequisitionItemReturnAttachment($updated, $attachment, true);
return $this->postProcess('Update', $updated);
});
} catch (\Throwable $e) {
throw new BusinessException("更新物品归还申请失败:" . $e->getMessage());
}
}
/**
* 处理物品申请附件
*
* @param Model $created
* @param array $items
* @param bool $isUpdate
* @return void
*/
private function processRequisitionItemReturnAttachment(Model $created, array $attachment, bool $isUpdate = false): void
{
if ($isUpdate) {
$this->clearRequisitionItemReturnAttachment($created->id);
}
foreach ($attachment as $file) {
$attachmentData = [
'return_id' => $created->id,
'file_name' => $file['original_name'],
'file_path' => $file['file_path']
];
InvRequisitionItemReturnAttachment::create($attachmentData);
}
}
/**
* 清空物品申请附件
*
* @param int $returnId
* @return void
*/
private function clearRequisitionItemReturnAttachment(int $returnId): void
{
InvRequisitionItemReturnAttachment::where('return_id', $returnId)->delete();
}
/**
* 物品领用归还确认
*
* @param Request $request
* @param array $data
* @param string $scene
* @return array|bool
* @throws BusinessException
*/
public function serviceReturnConfirm(Request $request, array $data, string $scene = 'returnConfirm')
{
try {
// 归还确认是业务顶层方法,这里统一使用事务
return Db::transaction(function () use ($request, $data, $scene) {
// 1. 当前操作人信息
$userInfo = (new UserService())->getUserInfo($request->user->uid, '');
if (empty($userInfo['user']['id']) || empty($userInfo['user']['name'])) {
throw new BusinessException('无法获取当前用户信息');
}
$handlerId = (int)$userInfo['user']['id'];
$handlerName = (string)$userInfo['user']['name'];
$data['handler_id'] = $handlerId;
$data['handler_name'] = $handlerName;
// 确认状态:1=通过,2=拒绝 (按你现在用法)
$status = isset($data['status']) ? (int)$data['status'] : null;
if (!in_array($status, [1, 2], true)) {
throw new BusinessException('非法的归还处理状态');
}
// 2. 构建查询配置,定位到这条归还记录
$config = $this->buildQueryConfig($request, $scene);
// 更新时不允许前端覆盖主键
unset($data['requisition_item_return_id']);
// 3. 获取现有记录
/** @var \app\model\InvRequisitionItemReturn|null $existingRecord */
$existingRecord = $this->logicShow($config);
if (!$existingRecord) {
throw new BusinessException('未找到对应的归还申请记录');
}
// 已处理校验:只要归还记录的 status 已经是「通过或拒绝」,就不能再处理
$existingStatus = (int)($existingRecord->status ?? 0);
if (in_array($existingStatus, [1, 2], true) || !empty($existingRecord->handler_id)) {
throw new BusinessException('该归还申请已经被处理过了');
}
// 4. 更新归还记录(状态 + 审核人 + 备注等)
$updateResult = $this->logicUpdateBy($config, $data);
if ($updateResult === 0) {
throw new BusinessException('更新归还申请失败,可能记录不存在或已被处理');
}
// 5. 若是拒绝,直接返回(不改库存、不改已归还数量)
if ($status === 2) {
// 拒绝:这里你返回 true 或者返回更新后的记录都可以
return true;
}
// 6. 审核通过:继续处理领用明细 & 库存 & 领用单状态
// 注意:requisition_item_id、item_id、return_qty 等字段在本次确认流程中不会被修改,
// 所以可以直接使用 $existingRecord 中的值(都是只读字段)
$requisitionItemId = (int)$existingRecord->requisition_item_id;
$itemId = (int)$existingRecord->item_id;
$returnQty = (int)$existingRecord->return_qty;
$requisitionId = (int)$existingRecord->requisition_id;
if ($requisitionItemId <= 0 || $itemId <= 0 || $returnQty <= 0 || $requisitionId <= 0) {
throw new BusinessException('归还申请记录缺少必要字段,无法处理');
}
// 6.1 处理领用明细
/** @var \app\model\InvRequisitionItem|null $item */
$item = InvRequisitionItem::find($requisitionItemId);
if (!$item) {
throw new BusinessException('未找到对应的领用明细记录');
}
$approvedQty = (int)($item->approved_qty ?? 0);
$returnedQty = (int)($item->returned_qty ?? 0);
if ($approvedQty <= 0) {
throw new BusinessException('该物品尚未审批出库,无法归还');
}
if ($returnedQty + $returnQty > $approvedQty) {
throw new BusinessException('归还数量超过已借数量,无法确认');
}
$item->returned_qty = $returnedQty + $returnQty;
if ($item->returned_qty >= $approvedQty) {
$item->is_finished = 1;
}
$item->updated_at = time();
if ($item->save() === false) {
throw new BusinessException('更新领用明细失败');
}
// 6.2 处理库存(入库)
(new InvItemService())->stockIn($itemId, $returnQty);
// 6.3 检查整张单是否已全部归还(只看非耗材)
$notFinishedCount = InvRequisitionItem::query()
->where('requisition_id', $requisitionId)
->where('is_consumable_snap', 0)
->where('is_finished', 0)
->count();
if ($notFinishedCount === 0) {
// 全部非耗材都已归还
$affected = InvRequisition::where('id', $requisitionId)->update([
'is_returned' => 1,
'updated_at' => time(),
]);
// Eloquent::update 返回的是影响行数,0 表示未更新到任何行
if ($affected === 0) {
throw new BusinessException('更新领用单状态失败,可能记录不存在');
}
}
// 7. 返回处理结果(保持你原来的 postProcess 习惯)
return $this->postProcess(ucfirst($scene), $existingRecord);
});
} catch (BusinessException $e) {
throw $e;
} catch (\Throwable $e) {
throw new BusinessException("确认归还物品失败:" . $e->getMessage());
}
}
/**
* 构建查询配置
*
* @param Request $request
* @param string $scene
* @return array
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'returnConfirm':
$config['where']['id'] = $p['requisition_item_return_id'];
break;
case 'index':
$config['where']['status'] = 1;
$config['order']['sort'] = 'desc';
$config['order']['id'] = 'desc';
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;
}
return $config;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\InvRequisitionItemValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\InvRequisitionItem;
class InvRequisitionItemService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = InvRequisitionItem::class;
/**
* 验证器
*/
protected $validator = InvRequisitionItemValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,212 @@
<?php
namespace app\common\service;
use app\common\validate\InvRequisitionValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\InvRequisition;
use app\common\model\InvRequisitionAttachment;
use app\common\model\InvRequisitionItem;
use Illuminate\Database\Eloquent\Model;
use support\Request;
use support\Db;
use support\exception\BusinessException;
class InvRequisitionService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = InvRequisition::class;
/**
* 验证器
*/
protected $validator = InvRequisitionValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 创建物品申请 前置
*
* @param array $data
* @param Request $request
* @return array
*/
public function beforeServiceStore($data, Request $request): array
{
$user = (new UserService())->getUserInfo($request->user->uid, '');
if (empty($user['user']['id']) || empty($user['user']['name'])) {
throw new BusinessException('无法获取当前用户信息');
}
$data['order_no'] = $this->generateOrderNo();
$data['applicant_id'] = (int)$user['user']['id'];
$data['applicant_name'] = (string)$user['user']['name'];
$data['leader_id'] = $user['user']['report_to'];
$data['status'] = InvRequisition::STATUS_LEADER;
return $data;
}
/**
* 处理物品申请
*
* @param Request $request
* @param array $data
* @return array
*/
public function serviceStore(Request $request, array $data, string $scene = '')
{
try {
// 关联表增加事务处理
return Db::transaction(function () use ($request, $data) {
$data = $this->preProcess('Store', $data, $request);
$dl = $this->getDataLimitFromRequest($request);
$created = $this->logicCreate($data, $dl);
// 处理物品申请项
$this->processRequisitionItems($created, $data['items']);
// 处理物品申请附件
$this->processRequisitionAttachmentItems($created, $data['attachment']);
return $this->postProcess('Store', $created);
});
} catch (\Throwable $e) {
throw new BusinessException("创建物品申请失败:" . $e->getMessage());
}
}
/**
* 更新物品申请
*
* @param Request $request
* @param array $data
* @return array
*/
public function serviceUpdate(Request $request, array $data, string $scene = '')
{
try {
// 关联表增加事务处理
return Db::transaction(function () use ($request, $data) {
$data = $this->preProcess('Update', $data, $request);
$config = $this->buildQueryConfig($request, 'update');
// 获取申请项/附件
$items = $data['items'];
$attachment = $data['attachment'];
unset($data['items']);
unset($data['attachment']);
// 执行更新操作
$this->logicUpdateBy($config, $data);
// 获取更新后的完整记录
$updated = $this->logicShow($config);
// 重新创建新的申请项
$this->processRequisitionItems((object)$updated, $items, true);
// 重新创建新的附件
$this->processRequisitionAttachmentItems((object)$updated, $attachment, true);
return $this->postProcess('Update', $updated);
});
} catch (\Throwable $e) {
throw new BusinessException("更新物品申请失败:" . $e->getMessage()."|".$e->getLine()."|".$e->getFile());
}
}
/**
* 处理物品申请项
*
* @param Model $created
* @param array $items
* @return void
*/
private function processRequisitionItems(Model $created, array $items, bool $isUpdate = false): void
{
if ($isUpdate) {
$this->clearRequisitionItems($created->id);
}
foreach ($items as $item) {
$itemData = [
'requisition_id' => $created->id,
'item_id' => $item['item_id'],
'item_name_snap' => $item['item_name_snap'], // 修改:使用 snap 字段
'spec_model_snap' => $item['spec_model_snap'], // 修改:使用 snap 字段
'unit_snap' => $item['unit_snap'], // 修改:使用 snap 字段
'is_consumable_snap' => $item['is_consumable_snap'], // 修改:使用 snap 字段
'request_qty' => (int) $item['request_qty'] ?? 1, // 动态获取数量,转为整型
'approved_qty' => (int) $item['request_qty'] ?? 1, // 初始审批数量等于申请数量
'remark' => $item['remark'] ?? ''
];
InvRequisitionItem::create($itemData);
}
}
/**
* 处理物品申请项
*
* @param Model $created
* @param array $items
* @return void
*/
private function processRequisitionAttachmentItems(Model $created, array $attachment, bool $isUpdate = false): void
{
if ($isUpdate) {
$this->clearRequisitionAttachmentItems($created->id);
}
foreach ($attachment as $file) {
$attachmentData = [
'requisition_id' => $created->id,
'file_name' => $file['original_name'],
'file_path' => $file['file_path']
];
InvRequisitionAttachment::create($attachmentData);
}
}
/**
* 删除申请项
*
* @param int $requisitionId
* @return void
*/
public function clearRequisitionItems(int $requisitionId): void
{
InvRequisitionItem::where('requisition_id', $requisitionId)->delete();
}
/**
* 删除申请项
*
* @param int $requisitionId
* @return void
*/
public function clearRequisitionAttachmentItems(int $requisitionId): void
{
InvRequisitionAttachment::where('requisition_id', $requisitionId)->delete();
}
/**
* 生成申请单号
*
* @return string
*/
private function generateOrderNo(): string
{
return 'RQ' . date('Ymd') . strtoupper(substr(uniqid(), -6));
}
}
+178
View File
@@ -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);
}
}
@@ -0,0 +1,286 @@
<?php
namespace app\common\service;
use app\common\logic\MessageNotificationsLogic;
use app\common\validate\MessageNotificationsValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\MessageNotifications;
use app\common\model\MessageUserStatus;
use app\common\model\User;
use support\Db;
use support\Request;
class MessageNotificationsService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = MessageNotifications::class;
/**
* 验证器
*/
protected $validator = MessageNotificationsValidate::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 'index':
$config['order']['id'] = 'desc';
break;
case 'show':
$config['where']['id'] = $p['id'];
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
public function serviceDestroy(Request $request)
{
$config = $this->buildQueryConfig($request, 'destroy');
$deleted = (new MessageNotificationsLogic())->logicDeleteBy($config);
return $this->postProcess('Destroy', $deleted);
}
/**
* 添加前置处理
*/
public function servicePush(Request $request, $data)
{
$pushData = [
'sender_id' => $request->user->uid,
'sender_type' => 'admin',
'title' => $data['title'],
'content' => $data['content'],
'scene' => 1,
'recipient' => $data['recipient'],
'push_method' => 1
];
$msgId = (new MessageNotificationsService())->pushNotice($pushData);
return ['msg_id' => $msgId];
}
public function afterServiceIndex($data)
{
$data->each(function (&$item) {
$temp = explode(',', $item->recipient);
$item->recipient_list = User::whereIn('id', $temp)->pluck('name')->toArray();
$item->scene_name = MessageNotifications::SCENE[$item->scene];
$item->push_method_name = MessageNotifications::PUSH_METHOD[$item->push_method];
});
unset($item);
return $data;
}
public function afterServiceShow($data)
{
$temp = explode(',', $data->recipient);
$data->recipient_list = User::whereIn('id', $temp)->pluck('name')->toArray();
$data->scene_name = MessageNotifications::SCENE[$data->scene];
$data->push_method_name = MessageNotifications::PUSH_METHOD[$data->push_method];
return $data;
}
// ====== ====== ====== 通知操作的处理 ====== ====== ======
/**
* 推送消息
*/
public function pushNotice(array $data = [], string $scene = "Default"): int
{
// 拼装数据(可能需要处理 消息标题、内容等等数据,可自行处理)
$pushData = $this->assemblyProcess($scene, $data);
// 写入消息
$msgId = $this->insertMessage($pushData);
// 推送
$this->pushWs($msgId, $pushData);
return $msgId;
}
/**
* 执行推送
*/
public function pushWs(int $msgId, array $pushData)
{
foreach ($pushData['recipient'] as $userId) {
// 查询消息数据
$config['model'] = MessageUserStatus::class;
$config['order']['id'] = 'desc';
$config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at'];
$config['where']['uid'] = $userId;
$config['where']['msg_id'] = $msgId;
$config['where']['status'] = 0;
$wsData = (new MessageUserStatusService())->logicShow($config)->toArray();
// 组装数据
if (isset($wsData['message']) && $wsData['message']) {
$wsData['message']['sender_name'] = User::where('id', $wsData['message']['sender_id'])->value('name') ?: '系统';
$wsData['message']['scene_name'] = MessageNotifications::SCENE[$wsData['message']['scene']];
$wsData['message']['created_at'] = date('Y-m-d H:i:s', $wsData['message']['created_at']);
}
// 获取未读消息数
$counts = MessageUserStatus::where('uid', $userId)->where('status', 0)->count('id');
$data = [
'notice_data' => $wsData,
'unread_counts' => $counts
];
// 执行推送
(new \app\common\utils\WsEvents())->pushWebsocket('notify.receive', $userId, $data);
}
return true;
}
/**
* 插入消息
* @param array $data
* @return int
*/
public function insertMessage(array $data): int
{
// 写消息
$msgId = $this->createMessage([
'sender_id' => $data['sender_id'],
'sender_type' => $data['sender_type'],
'title' => $data['title'],
'content' => $data['content'],
'scene' => $data['scene'],
'recipient' => implode(',', $data['recipient']),
'push_method' => $data['push_method'],
]);
// 写用户状态
$this->addUserStatus($msgId, $data['recipient']);
return $msgId;
}
/**
* 数据拼装方法调用
*/
protected function assemblyProcess(string $op, mixed $data): mixed
{
$hook = "assembly{$op}";
return method_exists($this, $hook)
? $this->{$hook}($data)
: $data;
}
/**
* 默认数据拼装
*/
public function assemblyDefault(array $data)
{
return [
'sender_id' => isset($data['sender_id']) && $data['sender_id'] ? $data['sender_id'] : 0,
'sender_type' => isset($data['sender_type']) && $data['sender_type'] ? $data['sender_type'] : 'system',
'title' => isset($data['title']) && $data['title'] ? $data['title'] : '系统通知',
'content' => isset($data['content']) && $data['content'] ? $data['content'] : '系统通知-默认',
'scene' => isset($data['scene']) && $data['scene'] ? $data['scene'] : 1,
'recipient' => isset($data['recipient']) && count($data['recipient']) > 0 ? $data['recipient'] : $this->getAllUserIds(),
'push_method' => isset($data['push_method']) && $data['push_method'] ? $data['push_method'] : 0,
];
}
// *** *** 可以扩展数据组拼装方法 *** ***
// ====== ====== ====== 通知操作的基础方法 ====== ====== ======
/**
* 创建一条消息
*/
public function createMessage(array $data): int
{
$msgId = Db::table('message_notifications')->insertGetId([
'msg_no' => generateMsgId(),
'sender_id' => $data['sender_id'],
'sender_type' => $data['sender_type'],
'title' => $data['title'],
'content' => $data['content'],
'scene' => $data['scene'],
'recipient' => $data['recipient'],
'push_method' => $data['push_method'],
'created_at' => time(),
]);
return $msgId;
}
/**
* 批量插入用户消息状态
*/
public function addUserStatus(int $msgId, array $uids)
{
$rows = [];
foreach ($uids as $uid) {
$rows[] = [
'msg_id' => $msgId,
'uid' => $uid,
'status' => 0,
'read_at' => null
];
}
if ($rows) {
Db::table('message_user_status')->insert($rows);
}
}
/**
* 标记消息已读
*/
public function markAsRead(array|string $msgIds, int $uid)
{
$query = Db::table('message_user_status')->where('uid', $uid)->where('status', 0);
if (is_array($msgIds)) {
$query->whereIn('msg_id', $msgIds);
}
return $query->update([
'status' => 1,
'read_at' => time()
]);
}
/**
* 获取所有用户ID
*/
public function getAllUserIds(): array
{
return User::where('status', 1)->pluck('id')->toArray();
}
public function getUserEmailsById(array $con = []): array
{
$user = User::where('status', 1);
if (count($con) > 0) {
return $user->whereIn('id', $con)->where('email', '!=', '')->pluck('email')->toArray();
}
return $user->where('email', '!=', '')->pluck('email')->toArray();
}
}
@@ -0,0 +1,136 @@
<?php
namespace app\common\service;
use app\common\model\MessageNotifications;
use app\common\validate\MessageUserStatusValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\MessageUserStatus;
use support\Request;
use app\common\model\User;
class MessageUserStatusService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = MessageUserStatus::class;
/**
* 验证器
*/
protected $validator = MessageUserStatusValidate::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 'unread':
$config['order']['id'] = 'desc';
$config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at'];
$config['where']['uid'] = $request->user->uid;
$config['where']['status'] = 0;
break;
case 'index':
$config['order']['id'] = 'desc';
$config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at'];
$config['where']['uid'] = $request->user->uid;
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at'];
$config['where']['uid'] = $request->user->uid;
break;
case 'store':
break;
case 'update':
case 'read':
$config['where']['id'] = $p['id'];
$config['where']['msg_id'] = $p['msg_id'];
$config['where']['uid'] = $request->user->uid;
break;
case 'destroy':
$config['where']['id'] = $p['id'];
$config['where']['uid'] = $request->user->uid;
break;
}
return $config;
}
public function serviceListUnRead(Request $request)
{
$config = $this->buildQueryConfig($request, 'unread');
$result = $this->logicListAll($config);
return $this->postProcess('ListUnRead', $result);
}
public function serviceRead(Request $request, array $data = [])
{
return $this->noticeRead($request->post('msg_ids'), $request->user->uid);
}
public function noticeRead(array|string $msgIds, int $uid): bool
{
$model = MessageUserStatus::where('uid', $uid)->where('status', 0);
if (is_array($msgIds)) {
$model->whereIn('msg_id', $msgIds);
}
$udata['read_at'] = time();
$udata['status'] = 1;
return $model->update($udata);
}
protected function afterServiceIndex($res)
{
$res->each(function (&$item) {
if (isset($item->message)) {
$item->message->sender_name = User::where('id', $item->message->sender_id)->value('name') ?: '系统';
$item->message->scene_name = MessageNotifications::SCENE[$item->message->scene];
}
});
unset($item);
return $res;
}
protected function afterServiceShow($res)
{
if (isset($res->message)) {
$res->message->sender_name = User::where('id', $res->message->sender_id)->value('name') ?: '系统';
$res->message->scene_name = MessageNotifications::SCENE[$res->message->scene];
}
// 标记已读
if ($res->status == 0) {
$this->noticeRead([$res->msg_id], $res->uid);
}
return $res;
}
protected function afterServiceListUnRead($res)
{
$res->each(function (&$item) {
if (isset($item->message)) {
$item->message->sender_name = User::where('id', $item->message->sender_id)->value('name') ?: '系统';
$item->message->scene_name = MessageNotifications::SCENE[$item->message->scene];
}
});
unset($item);
return $res;
}
}
+113
View File
@@ -0,0 +1,113 @@
<?php
namespace app\common\service;
use OSS\OssClient;
use OSS\Core\OssException;
use support\exception\BusinessException;
class OssService
{
private $accessKeyId;
private $accessKeySecret;
private $bucket;
private $endpoint;
private $ossClient;
public function __construct()
{
$this->accessKeyId = getenv('OSS_ACCESSID');
$this->accessKeySecret = getenv('OSS_ACCESSSECRET');
$this->bucket = getenv('OSS_BUCKET');
$this->endpoint = getenv('OSS_ENDPOINT');
$this->ossClient = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint);
}
/**
* 上传图片到 OSS
* @param string $filePath 本地文件路径
* @param string $objectKey OSS上存储的对象名称
* @return string 图片的 URL
*/
public function uploadImage($filePath, $objectKey)
{
try {
// 上传文件到 OSS
$this->ossClient->uploadFile($this->bucket, $objectKey, $filePath);
// 构造图片的私有 URL
$url = $this->generatePrivateUrl($objectKey);
return $url;
} catch (OssException $e) {
// 错误处理
throw new \RuntimeException('OSS Error: ' . $e->getMessage());
} catch (\Throwable $th) {
// 错误处理
throw new BusinessException('Error: ' . $th->getMessage());
}
}
/**
* 生成图片的私有访问 URL
* @param string $objectKey
* @return string
*/
public function generatePrivateUrl($objectKey, $expireTime = 3600)
{
// 生成 OSS 私有 URL(有效期为 3600 秒)
return $this->ossClient->signUrl($this->bucket, $objectKey, $expireTime);
}
/**
* 删除 OSS 上的文件
* @param string $objectKey OSS上存储的对象名称
* @return bool 删除是否成功
*/
public function deleteFile($objectKey)
{
try {
// 删除 OSS 上的文件
$this->ossClient->deleteObject($this->bucket, $objectKey);
return true;
} catch (OssException $e) {
// 错误处理
throw new \RuntimeException('OSS Delete Error: ' . $e->getMessage());
} catch (\Throwable $th) {
// 错误处理
throw new BusinessException('Delete Error: ' . $th->getMessage());
}
}
/**
* 获取对应的 MIME 类型
*/
public function getMimeTypes($extensions)
{
$mimeMap = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'bmp' => 'image/bmp',
'webp' => 'image/webp',
'pdf' => 'application/pdf',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls' => 'application/vnd.ms-excel',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'ppt' => 'application/vnd.ms-powerpoint',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'txt' => 'text/plain',
'zip' => 'application/zip',
'rar' => 'application/x-rar-compressed'
];
$mimes = [];
foreach ($extensions as $ext) {
if (isset($mimeMap[$ext])) {
$mimes[] = $mimeMap[$ext];
}
}
return array_unique($mimes);
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace app\common\service;
use app\common\logic\PermLogic;
use support\Cache;
class PermService
{
protected $logic;
public function __construct()
{
$this->logic = (new PermLogic());
}
// 用户资源缓存key
protected static function cacheKey(int $userId, string $systemCode): string
{
return "user_resources_{$userId}_{$systemCode}";
}
/**
* 获取用户所有资源(合并角色+直接分配),结果缓存
*/
public function getUserResources(int $userId, string $systemCode)
{
$cacheKey = self::cacheKey($userId, $systemCode);
$cached = Cache::get($cacheKey);
if ($cached !== null) {
return $cached;
}
// 调用 Logic 层获取资源
$resources = $this->logic->getUserResources($userId, $systemCode);
// 缓存结果
Cache::set($cacheKey, $resources, 3600);
return $resources;
}
/**
* 构建目录/菜单树,节点包含权限与打开方式
*/
public function getMenuTree(int $userId, string $systemCode): array
{
// 调用 Logic 层处理菜单构建
$resources = $this->getUserResources($userId, $systemCode);
return $this->logic->buildTree($resources);
}
/**
* 获取用户所有权限code
*/
public function getPermissionCodes(int $userId, string $systemCode): array
{
return $this->logic->getPermissionCodes($userId, $systemCode);
}
/**
* 清除资源相关缓存
*/
public function clearCache(int $userId, string $systemCode): void
{
Cache::delete(self::cacheKey($userId, $systemCode));
}
}
+109
View File
@@ -0,0 +1,109 @@
<?php
namespace app\common\service;
use app\common\model\User;
use support\Cache;
class PermService_bak
{
// 用户资源缓存key
protected static function cacheKey(int $userId): string
{
return "user_resources_{$userId}";
}
/**
* 获取用户所有资源(合并角色+直接分配),结果缓存
*/
public static function getUserResources(int $userId)
{
$cacheKey = self::cacheKey($userId);
$cached = Cache::get($cacheKey);
if ($cached !== null) {
return $cached;
}
$user = User::with(['roles.resources', 'resources'])->find($userId);
if (!$user) {
return collect();
}
$all = collect();
foreach ($user->roles as $role) {
$all = $all->merge($role->resources);
}
$all = $all->merge($user->resources);
$unique = $all->unique('id')->values();
Cache::set($cacheKey, $unique, 3600);
return $unique;
}
/**
* 构建目录/菜单树,节点包含权限与打开方式
*/
public static function getMenuTree(int $userId): array
{
$resources = self::getUserResources($userId);
$menus = $resources
->filter(fn($r) => in_array($r->type, ['directory','menu']))
->values();
return self::buildTree($menus, $resources);
}
protected static function buildTree($nodes, $allResources, int $parentId = 0): array
{
$tree = [];
foreach ($nodes as $node) {
if ($node->parent_id !== $parentId) continue;
$children = self::buildTree($nodes, $allResources, $node->id);
$authList = $allResources
->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id)
->map(fn($r) => [
'id' => $r->id,
'title' => $r->name,
'auth_mark' => $r->code,
])->values()->all();
$tree[] = [
'id' => $node->id,
'name' => $node->name,
'path' => $node->url,
'meta' => [
'title' => $node->name,
'icon' => $node->icon,
'keepAlive' => true,
'authList' => $authList,
'openType' => $node->open_type, // 'internal' | 'external' | 'iframe'
],
'children' => $children,
];
}
return $tree;
}
/**
* 获取用户所有权限code
*/
public static function getPermissionCodes(int $userId): array
{
return self::getUserResources($userId)
->filter(fn($res) => $res->type === 'permission')
->pluck('code')
->unique()
->values()
->all();
}
/**
* 清除资源相关缓存
*/
public static function clearCache(int $userId): void
{
Cache::delete(self::cacheKey($userId));
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\PurchaseOrderItemsValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\PurchaseOrderItems;
class PurchaseOrderItemsService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PurchaseOrderItems::class;
/**
* 验证器
*/
protected $validator = PurchaseOrderItemsValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,357 @@
<?php
namespace app\common\service;
use app\common\validate\PurchaseOrdersValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\PurchaseOrders;
use app\common\logic\PurchaseOrdersLogic;
use app\common\model\PurchaseOrderItems;
use app\common\model\User;
use support\Db;
use support\exception\BusinessException;
use support\Request;
class PurchaseOrdersService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PurchaseOrders::class;
/**
* 验证器
*/
protected $validator = PurchaseOrdersValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
public function getLogic()
{
return new PurchaseOrdersLogic();
}
/**
* 获取前置预处理数据
*
* @param array $data 原始返回值
* @return array 处理后的返回值
*/
public function preQueryData(array $data)
{
$result = match ($data['scene']) {
'pi_no' => $this->getLogic()->getPiList(),
'order_no' => $this->getLogic()->getOrderNoListByPiNo($data['pi_no']),
'order_items' => $this->getLogic()->getOrderItemsListByOrderNo($data['order_id']),
default => $data,
};
return $this->postProcess('PreData', $result->toArray());
}
public function afterServicePreData(array $data)
{
foreach ($data as $key => &$item) {
if (isset($item['biz_date'])) {
$item['biz_date'] = date('Y-m-d', $item['biz_date']);
}
}
unset($item);
return $data;
}
public function beforeServiceStore(array $data, Request $request)
{
$data['order_no'] = generateStockNo('CG');
$data['buyer_id'] = $request->user->uid;
$data['buyer_name'] = User::where('id', $request->user->uid)->value('name');
if ($data['is_draft'] == 0) {
$data['status'] = 1;
}
return $data;
}
public function serviceStore(Request $request, array $data = [], string $scene = 'store')
{
// 使用事务确保数据一致性
return Db::transaction(function () use ($request, $data, $scene) {
$data = $this->preProcess(ucfirst($scene), $data, $request);
$dl = $this->getDataLimitFromRequest($request);
// 创建采购单
$created = $this->logicCreate($data, $dl);
if (isset($data['stock_order'])) {
foreach ($data['stock_order'] as $order) {
foreach ($order['stock_items'] as $item) {
// 创建采购单明细
PurchaseOrderItems::create([
'purchase_order_id' => $created->id,
'stock_id' => $order['stock_orders_id'],
'stock_items_id' => $item['stock_order_items_id'],
'stock_items_uqid' => $item['unique_id'],
]);
}
}
}
return $this->postProcess(ucfirst($scene), $created);
});
}
/**
* 构建查询配置
*
* @param Request $request
* @param string $scene
* @return array
*/
protected function buildQueryConfig(Request $request, string $scene = 'index'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'index':
$config['order']['id'] = 'desc';
break;
case 'update':
$config['where']['id'] = $p['id'] ?? 0;
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['with'] = [
'items', 'items.stockOrder', 'items.stockOrderItems'
];
break;
}
return $config;
}
/**
* 删除采购订单
*
* @param Request $request
* @return mixed
*/
public function serviceDestroy(Request $request)
{
// 使用事务确保数据一致性
return Db::transaction(function () use ($request) {
// 构建查询条件
$config = $this->buildQueryConfig($request, 'show');
// 软删除采购单明细
$order = $this->logicShow($config);
if ($order && $order->items) {
foreach ($order->items as $item) {
$item->delete();
}
}
// 软删除采购单主表
$res = $order->delete();
return $this->postProcess('Destroy', $res);
});
}
/**
* 创建采购单成功后处理
*/
protected function afterServiceStore($res)
{
if ($res->is_draft == 0) {
$this->startApproval($res, ['resubmitted'=>0]);
}
return $res;
}
public function startApproval($item)
{
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
$appro->start('purchase_orders', 'purchase_orders', $item->id, ['resubmitted'=>0], $item->buyer_id);
}
public function beforeServiceUpdate(array $data, Request $request)
{
if ($data['is_draft'] == 0) {
$data['status'] = 1;
}
return $data;
}
/**
* 更新采购单
*
* @param Request $request
* @param array $data
* @return mixed
*/
public function serviceUpdate(Request $request, array $data = [], string $scene = 'update')
{
// 使用事务确保数据一致性
return Db::transaction(function () use ($request, $data, $scene) {
$data = $this->preProcess(ucfirst($scene), $data, $request);
// 构建查询条件
$config = $this->buildQueryConfig($request, $scene);
// 更新主表信息
$udata = $data;
unset($udata['stock_order']);
unset($udata['is_resubmit']);
unset($udata['approval_desc']);
$this->logicUpdateBy($config, $udata);
// 获取更新后的数据
$order = $this->logicShow($config);
// 更新采购单明细
if (isset($data['stock_order'])) {
// 先删除原有的明细
PurchaseOrderItems::where('purchase_order_id', $order->id)->delete();
// 重新创建明细
foreach ($data['stock_order'] as $orderData) {
foreach ($orderData['stock_items'] as $item) {
PurchaseOrderItems::create([
'purchase_order_id' => $order->id,
'stock_id' => $orderData['stock_orders_id'],
'stock_items_id' => $item['stock_order_items_id'],
'stock_items_uqid' => $item['unique_id'],
]);
}
}
}
return $this->postProcess(
ucfirst($scene),
$order,
[
'is_resubmit'=>$data['is_resubmit'],
'approval_desc'=>$data['approval_desc']
]
);
});
}
protected function afterServiceUpdate($res, $data)
{
if ($data['is_resubmit'] == 0 && $res->status == 1) {
$this->startApproval($res);
}
if ($data['is_resubmit'] == 1) {
$instance = \app\common\model\ApprovalInstances::where('biz_id', $res->id)->where('flow_type', 'purchase_orders')->first();
if ( $instance === null) {
throw new BusinessException('未找到审批实例');
}
// 审批服务
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
// 重新提交
$appro->resubmit($instance->id, $instance->initiator_id, ['resubmitted'=>1], $data['approval_desc']);
// 添加到讨论
if (!empty($data['approval_desc'])) {
$appro->postDiscussion($instance->id, $instance->initiator_id, $data['approval_desc'], false, [], 'name');
}
}
return $res;
}
public function serviceResubmit(Request $request, array $data = [])
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
$config['where']['id'] = $p['id'];
$config['where']['buyer_id'] = $request->user->uid;
$info = $this->logicShow($config);
if ($info === null) {
throw new BusinessException('未找到采购单');
}
if ($info->status != 3) {
throw new BusinessException('该状态不能重新提交');
}
$instance = \app\common\model\ApprovalInstances::where('biz_id', $info->id)->where('flow_type', 'purchase_orders')->first();
if ( $instance === null) {
throw new BusinessException('未找到审批实例');
}
// 审批服务
$appro = new \app\common\utils\approval\service\ApprovalFlowService();
// 重新提交
$result = $appro->resubmit($instance->id, $instance->initiator_id, ['resubmitted'=>1], $data['remark']);
// 添加到讨论
if (!empty($data['remark'])) {
$appro->postDiscussion($instance->id, $instance->initiator_id, $data['remark'], false, [], 'name');
}
$info->status = 1;
$info->save();
return $result;
}
public function updateStatus(array $data = [])
{
$info = PurchaseOrders::where('id', $data['id'])->first();
if ($info === null) {
throw new BusinessException('未找到采购单');
}
$info->status = $data['status'];
return $info->save();
}
public function formatData($data)
{
$contractFile = $data['contract_file'] ? explode(',', $data['contract_file']) : [];
$contractFileUrl = [];
if (count($contractFile) > 0) {
foreach ($contractFile as $file) {
$contractFileUrl[] = (new \app\common\service\OssService)->generatePrivateUrl($file);
}
}
$result = [
'id' => $data['id'],
'order_no' => $data['order_no'],
'pi_no' => $data['pi_no'],
'buyer_id' => $data['buyer_id'],
'buyer_name' => $data['buyer_name'],
'contract_file' => $contractFile,
'contract_file_url' => $contractFileUrl,
'status' => $data['status'],
'remark' => $data['remark']
];
$temps = [];
if (isset($data['items']) && count($data['items']) > 0) {
foreach ($data['items'] as $record) {
if (!in_array($record['stockOrder']['id'], array_keys($temps))) {
$temps[$record['stockOrder']['id']] = [
'id' => $record['stockOrder']['id'],
'order_no' => $record['stockOrder']['order_no'],
'salesperson_snapshot' => $record['stockOrder']['salesperson_snapshot'],
'biz_date' => date('Y-m-d', $record['stockOrder']['biz_date'])
];
}
$temps[$record['stockOrder']['id']]['stock_order_items'][] =[
'id' => $record['stockOrderItems']['id'],
'model_snapshot' => $record['stockOrderItems']['model_snapshot'],
'unique_id' => $record['stockOrderItems']['unique_id'],
];
}
}
$result['stock_order'] = array_values($temps);
return $result;
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace app\common\service;
use app\common\logic\ResourceLogic;
use app\common\validate\ResourceValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Resource;
use app\common\utils\Tree;
use support\Request;
class ResourceService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Resource::class;
/**
* 验证器
*/
protected $validator = ResourceValidate::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']['system_id'] = ['in', [0, $p['system_id']]];
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'menuList':
$config['where']['status'] = 1;
$config['where']['type'] = ['!=', 'permission'];
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'menuTreeList':
$config['where']['system_id'] = ['in', [0, $p['system_id']]];
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'index':
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
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;
}
return $config;
}
public function serviceMenuList(Request $request)
{
$config = $this->buildQueryConfig($request, 'menuList');
$result = $this->logicListAll($config);
return $this->postProcess('MenuList', $result);
}
public function serviceMenuTreeList(Request $request)
{
$config = $this->buildQueryConfig($request, 'menuTreeList');
$result = (new ResourceLogic())->logicListAllResources($request, $config);
return $this->postProcess('MenuTreeList', $result);
}
protected function afterServiceDefault($data)
{
$data = Tree::buildMenuTreeWithReference($data->toArray());
return $data;
}
protected function afterServiceMenuList($data)
{
$data = Tree::buildMenuTree($data->toArray());
return $data;
}
protected function afterServiceMenuTreeList($data)
{
$data = Tree::buildMenuTree($data->toArray());
return $data;
}
public function beforeServiceStore($data, Request $request)
{
$data['parent_id'] = $data['parent_id'] != '' ? $data['parent_id'] : 0;
$data['system_id'] = $data['system_id'] != '' ? $data['system_id'] : 0;
return $data;
}
public function beforeServiceUpdate($data, Request $request)
{
$data['parent_id'] = $data['parent_id'] != '' ? $data['parent_id'] : 0;
$data['system_id'] = $data['system_id'] != '' ? $data['system_id'] : 0;
return $data;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\StockOrderAccountsValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\StockOrderAccounts;
class StockOrderAccountsService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = StockOrderAccounts::class;
/**
* 验证器
*/
protected $validator = StockOrderAccountsValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\StockOrderItemOptionsValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\StockOrderItemOptions;
class StockOrderItemOptionsService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = StockOrderItemOptions::class;
/**
* 验证器
*/
protected $validator = StockOrderItemOptionsValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\StockOrderItemsValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\StockOrderItems;
class StockOrderItemsService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = StockOrderItems::class;
/**
* 验证器
*/
protected $validator = StockOrderItemsValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\StockOrdersFilesValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\StockOrdersFiles;
class StockOrdersFilesService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = StockOrdersFiles::class;
/**
* 验证器
*/
protected $validator = StockOrdersFilesValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace app\common\service;
use app\admin\validate\SysAccountValidate;
use app\common\logic\SysAccountLogic;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\SysAccount;
use support\Request;
class SysAccountService
{
protected $logic;
public function __construct()
{
$this->logic = (new SysAccountLogic());
}
/**
* 获取系统list
*/
public function getList()
{
return $this->logic->getList();
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace app\common\service;
use app\common\logic\SysConfigLogic;
use app\common\logic\SysCurrencyLogic;
use app\common\logic\SystemsLogic;
use support\Redis;
class SysConfigService
{
protected $logic;
public function __construct()
{
$this->logic = (new SysConfigLogic());
}
/**
* 获取系统list
*/
public static function getConfig(string $config_key)
{
$value = Redis::get($config_key);
if (empty($value)) {
$config = (new SysConfigLogic())->findByKey($config_key);
if (empty($config)) {
return null;
}
$value = $config->config_value;
Redis::set($config->config_key, $value, 'EX', 7200);
}
return $value;
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace app\common\service;
use app\common\logic\SysCurrencyLogic;
use app\common\logic\SystemsLogic;
class SysCurrencyService
{
protected $logic;
public function __construct()
{
$this->logic = (new SysCurrencyLogic());
}
/**
* 获取系统list
*/
public function getList()
{
return $this->logic->getList();
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace app\common\service;
use app\admin\validate\SysDictItemValidate;
use app\common\logic\SysDictItemLogic;
use app\common\logic\SysDictLogic;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\SysDictItem;
use support\exception\BusinessException;
use support\Request;
class SysDictItemService
{
protected $logic;
public function __construct()
{
$this->logic = (new SysDictItemLogic());
}
/**
* 获取系统list
*/
public function getList($params)
{
if (empty($params['dict_id']) && empty($params['dict_code']))
throw new BusinessException('未明确要查询的字典组');
$where = [];
if(isset($params['dict_id']) && $params['dict_id'])
$where[] = ['dict_id', '=', $params['dict_id']];
if(isset($params['dict_code']) && $params['dict_code']){
$dict = (new SysDictLogic)->getDictByCode($params['dict_code']);
if (empty($dict['id']))
throw new BusinessException('字典组code错误');
$where[] = ['dict_id', '=', $dict['id']];
}
return $this->logic->getList($where);
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace app\common\service;
use app\admin\validate\SysDictValidate;
use app\common\logic\SysDictLogic;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\SysDict;
use support\Request;
class SysDictService
{
protected $logic;
public function __construct()
{
$this->logic = (new SysDictLogic());
}
/**
* 获取系统list
*/
public function getList()
{
return $this->logic->getList();
}
public static function pluckValuesByCode(string $dictCode): array
{
return SysDict::where('dict_code', $dictCode)
->where('status', 1)
->first()?->items()
->where('status', 1)
->orderBy('sort')
->pluck('item_value')
->toArray() ?? [];
}
public static function pluckKeyValueByCode(string $dictCode): array
{
return SysDict::where('dict_code', $dictCode)
->where('status', 1)
->first()?->items()
->where('status', 1)
->orderBy('sort')
->pluck('item_value', 'item_label')
->toArray() ?? [];
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace app\common\service;
use app\common\validate\SysOpLogValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\SysOpLog;
use support\Request;
class SysOpLogService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = SysOpLog::class;
/**
* 验证器
*/
protected $validator = SysOpLogValidate::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 'index':
// ====== ====== 排序 ====== ======
$config['order']['id'] = 'desc';
// ====== ====== 筛选 ====== ======
// 状态
if (isset($p['user_id']) && $p['user_id']) {
$config['where']['user_id'] = $p['user_id'];
}
// ip
if (isset($p['ip']) && $p['ip']) {
$config['where']['ip'] = $p['ip'];
}
// ip
if (isset($p['method']) && $p['method']) {
$config['where']['method'] = $p['method'];
}
// 关键字
if (isset($p['keyword']) && $p['keyword']) {
$k = '%' . $p['keyword'] . '%';
foreach (['user_name', 'route', 'params_brief'] as $col) {
$config['orWhere'][] = [$col, 'like', $k];
}
}
//$config['where']['status'] = 1;
break;
case 'show':
$config['where']['id'] = $p['id'];
//$config['where']['status'] = 1;
break;
}
return $config;
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace app\common\service;
use app\admin\validate\SysPortValidate;
use app\common\logic\SysPortLogic;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\SysPort;
use support\Request;
class SysPortService
{
protected $logic;
public function __construct()
{
$this->logic = (new SysPortLogic());
}
/**
* 获取系统list
*/
public function getList($params)
{
$where = [];
if(isset($params['scene']) && $params['scene']) {
if($params['scene'] == 'domestic'){
$where['country_id'] = 45;
}
}
return $this->logic->getList($where);
}
}
@@ -0,0 +1,77 @@
<?php
namespace app\common\service;
use app\common\validate\SysUpdateLogValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\SysUpdateLog;
use support\Request;
class SysUpdateLogService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = SysUpdateLog::class;
/**
* 验证器
*/
protected $validator = SysUpdateLogValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'index':
$config['order']['id'] = 'desc';
break;
case 'show':
$config['where']['id'] = $p['id'];
break;
case 'store':
break;
case 'update':
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
/**
* 添加前置处理
*/
public function beforeServiceStore($data, Request $request)
{
$data['update_time'] = !empty($data['update_time'])?strtotime($data['update_time']):0;
return $data;
}
/**
* 添加前置处理
*/
public function beforeServiceupdate($data, Request $request)
{
$data['update_time'] = !empty($data['update_time'])?strtotime($data['update_time']):0;
return $data;
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace app\common\service;
use app\common\logic\SystemsLogic;
class SystemsService
{
protected $logic;
public function __construct()
{
$this->logic = (new SystemsLogic());
}
/**
* 获取系统list
*/
public function getList()
{
return $this->logic->getList();
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
namespace app\common\service;
use support\exception\BusinessException;
use support\Log;
use support\Request;
class UploadService
{
/**
* 从服务器本地路径上传文件到 OSS
*
* @param Request $request
* - path: 本地文件绝对路径
* - scene: 业务分区/场景
* - category: 文件分类(可选)
* - original_name: 原始文件名(可选)
*/
public function uploadLocalFile($param)
{
try {
$filePath = $param['path'];
$scene = $param['scene'];
$category = $param['category']??'general';
$providedOriginalName = $param['original_name']??'';
if (empty($filePath)) {
Log::error('本地文件路径不能为空',$param);
throw new BusinessException('本地文件路径不能为空');
}
if (!file_exists($filePath) || !is_readable($filePath)) {
Log::error('本地文件不存在或不可读',$param);
throw new BusinessException('本地文件不存在或不可读');
}
if (empty($scene)) {
Log::error('上传场景不能为空',$param);
throw new BusinessException('上传场景不能为空');
}
$basename = basename($filePath);
$originalName = $providedOriginalName ?: $basename;
$extensionFromName = pathinfo($originalName, PATHINFO_EXTENSION);
$extensionFromPath = pathinfo($basename, PATHINFO_EXTENSION);
$extension = $extensionFromName ?: $extensionFromPath;
$mime = mime_content_type($filePath) ?: 'application/octet-stream';
$size = filesize($filePath) ?: 0;
$sceneUrl = $scene ? ($scene . '/') : '';
$dir = config('oss.oss_main_dir') . $sceneUrl . date('Ymd') . '/';
// 使用传入的 original_name 命名(缺少扩展名时补上推断的扩展名),并做简单安全处理
$safeBaseName = preg_replace('/[\\\\\/]+/', '_', $originalName);
if ($extension && strtolower(pathinfo($safeBaseName, PATHINFO_EXTENSION)) !== strtolower($extension)) {
// 若提供的名称没有扩展名或扩展名不一致,则以推断扩展名为准
$safeBaseName = pathinfo($safeBaseName, PATHINFO_FILENAME) . '.' . $extension;
} elseif (!$extension && !pathinfo($safeBaseName, PATHINFO_EXTENSION)) {
// 实在没有扩展名信息时,保持原名
}
// 重名检测:同一目录、同名且同类型则在文件名后追加序号
$uploadedFilesService = new UploadedFilesService();
$candidateName = $safeBaseName;
$nameBody = pathinfo($safeBaseName, PATHINFO_FILENAME);
$nameExt = pathinfo($safeBaseName, PATHINFO_EXTENSION);
$counter = 1;
while (true) {
$candidatePath = $dir . $candidateName;
$existing = $uploadedFilesService->findFile(['file_path' => $candidatePath]);
if (!$existing) {
break;
}
// 若存在同路径同名,且类型也相同,则追加序号
if (isset($existing->file_type) && strtolower($existing->file_type) === strtolower($mime)) {
$suffix = ' (' . $counter . ')';
$candidateName = $nameBody . $suffix . ($nameExt !== '' ? ('.' . $nameExt) : '');
$counter++;
continue;
}
// 类型不同则允许同名,结束循环
break;
}
$objectKey = $dir . $candidateName;
$ossService = new OssService();
$ossService->uploadImage($filePath, $objectKey);
$privateUrl = $ossService->generatePrivateUrl($objectKey);
// 记录文件信息到数据库
$uploadedFilesService = new UploadedFilesService();
$fileInfo = [
'file_name' => basename($objectKey),
'file_path' => $objectKey,
'file_size' => $size,
'file_type' => $mime,
'original_name' => $originalName,
];
$recordResult = $uploadedFilesService->recordFileInfo($fileInfo, null, $scene, $category);
$data = [
'original_name' => $originalName,
'file_name' => $objectKey,
'file_url' => $privateUrl,
'file_id' => $recordResult['id'] ?? null,
'partition' => $scene,
'category' => $category,
];
return $data;
} catch (\Throwable $e) {
Log::error($e->getMessage(),$param);
throw new BusinessException($e->getMessage());
}
}
/**
* 删除 OSS 上的文件
*
* @param array $param
* - object_key: OSS 对象键名-oss路径(必需)
* - file_id: 数据库文件记录ID(可选,用于删除数据库记录)
* @return bool 删除是否成功
*
* 示例
* $uploadService = new UploadService();
*
* 只删除 OSS 文件
* $uploadService->deleteOssFile([
* 'object_key' => 'vehicle_album/documents/20241201/abc123.pdf'
* ]);
*
* 同时删除 OSS 文件和数据库记录
* $uploadService->deleteOssFile([
* 'object_key' => 'vehicle_album/images/20241201/abc123.jpg',
* 'file_id' => 123
* ]);
*/
public function deleteOssFile($objectKey,$fileId = null)
{
try {
if (empty($objectKey)) {
Log::error('OSS对象键名不能为空', $objectKey);
throw new BusinessException('OSS对象键名不能为空');
}
$uploadedFilesService = new UploadedFilesService();
// 如果提供了文件ID,同时删除数据库记录
if ($fileId) {
$uploadedFilesService->deleteFile($fileId);
}else{
$uploadedFilesService = new UploadedFilesService();
if (empty($fileId)) {
$where = ['file_path' => $objectKey];
$file = $uploadedFilesService->getFile($where);
$fileId = $file->id;
}
if(!empty($fileId)){
$uploadedFilesService->deleteFile($fileId);
}
}
// 删除 OSS 上的文件
$ossService = new OssService();
$ossService->deleteFile($objectKey);
Log::info('OSS文件删除成功', ['object_key' => $objectKey, 'file_id' => $fileId]);
return true;
} catch (\Throwable $e) {
Log::error('删除OSS文件失败: ' . $e->getMessage());
throw new BusinessException('删除OSS文件失败: ' . $e->getMessage());
}
}
}
+238
View File
@@ -0,0 +1,238 @@
<?php
namespace app\common\service;
use app\common\validate\UploadedFilesValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\UploadedFiles;
use support\Request;
use support\exception\BusinessException;
class UploadedFilesService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = UploadedFiles::class;
/**
* 文件上传分区配置
*/
protected array $uploadPartitions = [
'image' => [
'path' => 'images',
'max_size' => 5 * 1024 * 1024, // 5MB
'allowed_types' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp']
],
'document' => [
'path' => 'documents',
'max_size' => 10 * 1024 * 1024, // 10MB
'allowed_types' => ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
'extensions' => ['pdf', 'doc', 'docx']
],
'audio' => [
'path' => 'audios',
'max_size' => 20 * 1024 * 1024, // 20MB
'allowed_types' => ['audio/mp3', 'audio/wav', 'audio/ogg'],
'extensions' => ['mp3', 'wav', 'ogg']
],
'other' => [
'path' => 'others',
'max_size' => 5 * 1024 * 1024, // 5MB
'allowed_types' => [],
'extensions' => []
]
];
/**
* 记录文件信息到数据库
*
* @param array $fileInfo 文件信息
* @param Request|null $request 请求对象(可选)
* @param string $partition 分区名称
* @param string $category 文件分类
* @return array
* @throws BusinessException
*/
public function recordFileInfo(array $fileInfo, ?Request $request = null, string $partition = 'other', string $category = 'general'): array
{
// 验证必需的文件信息
$requiredFields = ['file_name', 'file_path', 'file_size', 'file_type'];
foreach ($requiredFields as $field) {
if (!isset($fileInfo[$field])) {
throw new BusinessException("缺少必需的文件信息字段: {$field}");
}
}
// 获取文件扩展名
$fileExtension = pathinfo($fileInfo['file_name'], PATHINFO_EXTENSION);
// 记录到数据库
$uploadIp = $request ? $request->getRealIp() : '0.0.0.0';
$userAgent = $request ? $request->header('User-Agent', '') : '';
$fileData = [
'file_name' => $fileInfo['file_name'],
'file_path' => $fileInfo['file_path'],
'file_size' => $fileInfo['file_size'],
'file_type' => $fileInfo['file_type'],
'category' => $category,
'original_name' => $fileInfo['original_name'] ?? '',
'extension' => $fileExtension,
'partition' => $partition,
'upload_ip' => $uploadIp,
'user_agent' => $userAgent,
];
$uploadedFile = $this->logicCreate($fileData);
return [
'id' => $uploadedFile->id,
'file_name' => $fileInfo['file_name'],
'file_path' => $fileInfo['file_path'],
'file_url' => getenv('MAIN_DOMAIN') . $fileInfo['file_path'],
'file_size' => $fileInfo['file_size'],
'file_type' => $fileInfo['file_type'],
'category' => $category,
'partition' => $partition,
'original_name' => $fileInfo['original_name'] ?? '',
'extension' => $fileExtension,
];
}
/**
* 获取文件信息
*
* @param int $fileId
* @return array|null
*/
public function getFileInfo(int $fileId): ?array
{
$file = $this->logicShow(['where' => ['id' => $fileId]]);
if (!$file) {
return null;
}
return [
'id' => $file->id,
'file_name' => $file->file_name,
'file_path' => $file->file_path,
'file_url' => getenv('MAIN_DOMAIN') . $file->file_path,
'file_size' => $file->file_size,
'file_type' => $file->file_type,
'category' => $file->category,
'partition' => $file->partition ?? 'other',
'original_name' => $file->original_name ?? '',
'extension' => $file->extension ?? '',
'created_at' => $file->created_at,
];
}
/**
* 获取文件
*
* @param string $path
* @return bool|\Illuminate\Database\Eloquent\Model
*/
public function getFile(array $where)
{
$config['model'] = $this->modelClass;
$config['where'] = $where;
$file = $this->logicShow($config);
if (!$file) {
return false;
}
return $file;
}
/**
* 获取文件
*
* @param string $path
* @return bool|\Illuminate\Database\Eloquent\Model
*/
public function findFile(array $where)
{
$file = UploadedFiles::where(['file_path' => $where['file_path']]);
if (!$file) {
return false;
}
return $file;
}
/**
* 删除文件
*
* @param int $fileId
* @return bool
*/
public function deleteFile(int $fileId): bool
{
$config['model'] = $this->modelClass;
$config['where'] = ['id' => $fileId];
$file = $this->logicShow($config);
if (!$file) {
return false;
}
// 删除数据库记录
$config =[
'model' => $this->modelClass,
'where' => ['id' => $fileId],
];
return $this->logicDeleteBy($config);
}
/**
* 获取分区配置
*
* @return array
*/
public function getPartitionConfig(): array
{
return $this->uploadPartitions;
}
/**
* 生成文件名
*
* @param $file
* @return string
*/
protected function generateFileName($file): string
{
$extension = $file->getUploadExtension();
$timestamp = time();
$random = substr(md5(uniqid()), 0, 8);
return $timestamp . '_' . $random . '.' . $extension;
}
/**
* 格式化文件大小
*
* @param int $bytes
* @return string
*/
protected function formatFileSize(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, 2) . ' ' . $units[$pow];
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\UsedcarCrawlResultsImagesValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\UsedcarCrawlResultsImages;
class UsedcarCrawlResultsImagesService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = UsedcarCrawlResultsImages::class;
/**
* 验证器
*/
protected $validator = UsedcarCrawlResultsImagesValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\UsedcarCrawlResultsValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\UsedcarCrawlResults;
class UsedcarCrawlResultsService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = UsedcarCrawlResults::class;
/**
* 验证器
*/
protected $validator = UsedcarCrawlResultsValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -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;
}
}
+124
View File
@@ -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);
}
}
+253
View File
@@ -0,0 +1,253 @@
<?php
namespace app\common\service;
use app\common\logic\UserLogic;
use app\common\model\Corp;
use app\common\model\User;
use app\common\model\UserGroup;
use app\common\model\UserGroupMembers;
use app\common\traits\crud\CrudLogicTrait;
use app\common\traits\crud\CrudServiceTrait;
use app\common\utils\CrudUtil;
use app\crm\model\Country;
use app\crm\model\Customer;
use support\exception\BusinessException;
use support\Request;
class UserService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = User::class;
protected $logic;
public function __construct()
{
$this->logic = (new UserLogic());
}
/**
* 获取用户信息(包括菜单和权限)
*/
public function getUserInfo(int $uid, string $systemCode)
{
$user = $this->logic->userInfo($uid);
// $permService = new PermService();
// $menuTree = $permService->getMenuTree($uid, $systemCode);
// $permissionCodes = $permService->getPermissionCodes($uid, $systemCode);
$corp_name = Corp::where('abbr', $user->corp_id)->value('name');
return [
'user' => [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
'phone' => $user->phone,
'email' => $user->email,
'status' => $user->status,
'roles' => $user->roles->pluck('name'),
'sex' => $user->sex,
'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
];
}
/**
* 获取用户信息(包括菜单和权限)
*/
public function getUser(int $uid, $corp_abbr)
{
$user = $this->logic->userInfo($uid);
// 查找直属上级 10.13 add
$reportToInfo = [];
if ($user->reportToInfo) {
$reportToInfo = [
'id' => $user->reportToInfo->id,
'name' => $user->reportToInfo->name
];
}
// 查找绑定内勤 10.13 add
$officeClerkInfo = [];
$ugm = UserGroupMembers::where('user_id', $uid)->where('status', 1)->first();
if ($ugm) {
$lugm = UserGroupMembers::where('group_id', $ugm->group_id)->where('is_leader', 1)->first();
if ($lugm) {
$officeClerkInfo['id'] = $lugm->user_id;
$officeClerkInfo['name'] = User::where('id', $lugm->user_id)->value('name');
}
}
$corp_name = Corp::where('abbr', $corp_abbr)->value('name');
$ossservice = new OssService();
return [
'id' => $user->id,
'sn' => $user->sn,
'name' => $user->name,
'position' => $user->position,
'socialize' => $user->socialize,
'socialize_pic' => $user->socialize_pic,
'socialize_pic_full' => (!empty($user->socialize_pic))?$ossservice->generatePrivateUrl($user->socialize_pic):'',
'country_id' => $user->country_id,
'country_txt' => Country::where('id',$user->country_id)->value('cn_addr'),
'avatar' => $user->avatar,
'avatar_full' => $user->avatar,
'phone' => $user->phone,
'corp_name' => $corp_name,
'email' => $user->email,
'sex' => $user->sex,
'status' => $user->status,
'roles' => $user->roles->pluck('name'),
'sign_img' => $user->sign_img,
'sign_img_url' => (!empty($user->sign_img))?$ossservice->generatePrivateUrl($user->sign_img):'',
'report_to_info' => $reportToInfo,
'office_clerk_info' => $officeClerkInfo
];
}
/**
* 编辑用户信息
*/
public function edit(int $uid, array $data)
{
unset($data['password']);
unset($data['phone']);
unset($data['email']);
unset($data['sn']);
$user = $this->logic->edit($uid,$data);
return $user;
}
/**
* 修改用户密码
*/
public function savePassword(int $uid, array $data)
{
// 对密码进行哈希处理
$password = $data['password'];
$user = User::find($uid);
if (!password_verify($password, $user->password)) {
throw new BusinessException('密码错误');
}
$user = $this->logic->savePassword($uid,$data);
return $user;
}
/**
* 获取用户列表
*/
public function getList(Request $request)
{
return $this->logic->getList($request);
}
/** 分页列表 */
public function serviceTeam(Request $request)
{
$config = $this->buildQueryConfig($request, 'team');
$limit = (int)$request->get('size', 15);
$page = (int)$request->get('current', 1);
$result = $this->logicList($config, $limit, $page);
foreach ($result as &$data){
$data['customer_count'] = Customer::where('user_id', $data['id'])->count();
unset($data['password']);
unset($data['open_id']);
unset($data['tenant_key']);
unset($data['union_id']);
}
return $this->postProcess('List', $result);
}
/** 分页列表 */
public function resignMember(Request $request)
{
$uid = $request->user->uid;
$userId = $request->get('user_id');
$user = User::find($userId);
if ($user->report_to != $uid) {
throw new BusinessException('禁止操作非组员');
}
$data = [
'status' => 2,
];
$user = $this->logic->edit($userId,$data);
$user = $user->toArray();
unset($user['password']);
unset($user['open_id']);
unset($user['tenant_key']);
unset($user['union_id']);
return $user;
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'team':
// $config['where']['status'] = 1; // 关键字
$config['order']['id'] = 'desc';
if (isset($p['status']) && $p['status']) {
$config['where']['status'] = $p['status'];
}
$config['with']= ['reportToInfo'];
if (!in_array(1, $request->user->role_ids)) {
$config['where']['reportToInfo.id'] = $request->user->uid;
}
// $config['dataLimit']['type'] = 'relation';
// $config['dataLimit']['userId'] = $request->user->uid;
// $config['dataLimit']['userField'] = 'report_to';
break;
}
return $config;
}
public function checkBind($userId)
{
$user = \app\common\model\User::find($userId);
if (!$user->report_to) {
throw new BusinessException('请先前往个人中心-绑定直属上级');
}
if ($user->sign_img === null || $user->sign_img === '') {
throw new BusinessException('请先前往个人中心-操作签名');
}
$ug = \app\common\model\UserGroupMembers::with(['group' => function($query) {
$query->where('scenario_code', 'stock_orders');
}])->where('user_id', $userId)->first();
if (!$ug) {
throw new BusinessException('请先前往个人中心-绑定内勤');
}
return true;
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace app\common\service;
use app\admin\validate\VehColorValidate;
use app\common\logic\VehColorLogic;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\VehColor;
use support\exception\BusinessException;
class VehColorService
{
protected $logic;
public function __construct()
{
$this->logic = (new VehColorLogic());
}
/**
* 获取系统list
*/
public function getList($params)
{
if (empty($params['type']))
throw new BusinessException('类型不能为空');
$where = [];
if(isset($params['type']) && $params['type'])
$where[] = ['type', '=', $params['type']];
return $this->logic->getList($where);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace app\common\service;
use app\common\validate\VehDictI18nValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\VehDictI18n;
class VehDictI18nService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = VehDictI18n::class;
/**
* 验证器
*/
protected $validator = VehDictI18nValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/** 本进程缓存:[dict][code] => labels array */
protected static array $labelsCache = [];
public static function label(string $dict=null, ?string $code=null, string $locale='zh-CN', ?string $fallbackZh=null): ?string
{
if (!$dict || !$code) return $fallbackZh;
$labels = self::labels($dict, $code);
if (!$labels) return $fallbackZh ?? $code;
return $labels[$locale] ?? $labels['zh-CN'] ?? $code;
}
/** 返回 labels 数组,命中缓存 */
public static function labels(string $dict, string $code): ?array
{
$dict = strtoupper($dict);
$code = (string)$code;
if (isset(self::$labelsCache[$dict][$code])) {
return self::$labelsCache[$dict][$code];
}
$term = DictTerm::where('dict',$dict)->where('code',$code)->first(['labels']);
if (!$term) return null;
$labels = $term->labels ?? null;
if (!is_array($labels)) return null;
self::$labelsCache[$dict][$code] = $labels;
return $labels;
}
/** 校验 code 是否存在于字典(用于必填维度) */
public static function ensureExists(string $dict, ?string $code, string $fieldNameForError): void
{
if ($code === null || $code === '') {
throw new \InvalidArgumentException("缺少必填字段:{$fieldNameForError}");
}
$ok = VehDictI18n::where('dict',$dict)->where('code',$code)->where('status', 1)->exists();
if (!$ok) throw new \InvalidArgumentException("字典不存在或未启用:{$dict}.{$code}");
}
}
@@ -0,0 +1,57 @@
<?php
namespace app\common\service;
use app\common\validate\VehDictMappingValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\VehDictMapping;
class VehDictMappingService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = VehDictMapping::class;
/**
* 验证器
*/
protected $validator = VehDictMappingValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
public static function all(): array
{
$rows = VehDictMapping::where('status', 1)->orderBy('sort')->orderBy('id')->get()->toArray();
$out = [];
foreach ($rows as $r) {
$out[$r['dict']] = [
'field' => $r['field'],
'dict' => $r['dict'],
'required' => (int)$r['required'] === 1,
];
}
return $out;
}
public static function requiredDicts(): array
{
return array_keys(array_filter(self::all(), fn($x)=>$x['required']===true));
}
public static function enabled(string $dict): bool
{
$all = self::all();
return isset($all[$dict]);
}
}
@@ -0,0 +1,28 @@
<?php
namespace app\common\service;
use app\admin\validate\VehModificationValidate;
use app\common\logic\VehModificationLogic;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\VehModification;
use support\Request;
class VehModificationService
{
protected $logic;
public function __construct()
{
$this->logic = (new VehModificationLogic());
}
/**
* 获取系统list
*/
public function getList()
{
return $this->logic->getList();
}
}
@@ -0,0 +1,77 @@
<?php
namespace app\common\service;
use app\common\validate\VehMoneyValueValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\VehMoneyValue;
use support\Db;
class VehMoneyValueService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = VehMoneyValue::class;
/**
* 验证器
*/
protected $validator = VehMoneyValueValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 复制金额记录
*
* @param string $fromScene 源场景
* @param int $fromId 源对象ID
* @param string $toScene 目标场景
* @param int $toId 目标对象ID
* @return int 插入条数
* @throws BusinessException
*/
public function copyMoneyValues(string $fromScene, int $fromId, string $toScene, int $toId): int
{
// 1. 查询源数据
$rows = Db::table('veh_money_value')
->where('object_scene', $fromScene)
->where('object_id', $fromId)
->get()
->toArray();
if (empty($rows)) {
return 0;
}
$now = time();
$insertData = [];
foreach ($rows as $row) {
$insertData[] = [
'object_scene' => $toScene,
'object_id' => $toId,
'field' => $row->field,
'currency' => $row->currency,
'amount' => $row->amount,
'exchange_rate' => $row->exchange_rate,
'remark' => $row->remark,
'created_at' => $now,
'updated_at' => $now,
];
}
// 2. 插入新数据
return Db::table('veh_money_value')->insert($insertData);
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\VehVehicleListingValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\VehVehicleListing;
class VehVehicleListingService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = VehVehicleListing::class;
/**
* 验证器
*/
protected $validator = VehVehicleListingValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
+250
View File
@@ -0,0 +1,250 @@
<?php
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;
use support\Request;
class VehVehicleSkuService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = VehVehicleSku::class;
/**
* 验证器
*/
protected $validator = VehVehicleSkuValidate::class;
protected $logic;
public function __construct()
{
$this->logic = (new VehVehicleSkuLogic());
}
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
// // 枚举
// private const MAPS = [
// 'BODY' => [
// 'SEDAN' => '三厢轿车', 'SUV'=>'SUV', 'HB'=>'两厢/掀背', 'MPV'=>'MPV', 'PICKUP'=>'皮卡'
// ],
// 'DRIVE' => [
// 'FWD'=>'前驱', 'RWD'=>'后驱', 'AWD'=>'四驱(AWD)', '4WD'=>'四驱(4WD)'
// ],
// 'MOTOR_LAYOUT' => [
// 'FWD'=>'前置单电机', 'RWD'=>'后置单电机', 'AWD'=>'双电机四驱', 'DM-I'=>'DM-i', 'DM-P'=>'DM-p'
// ],
// 'FUEL' => [
// 'GAS'=>'汽油', 'HEV'=>'油电混动', 'PHEV'=>'插电混动', 'EV'=>'纯电', 'EREV'=>'增程'
// ],
// 'TRANS' => [
// 'MT'=>'手动', 'AT'=>'自动', 'CVT'=>'无级', 'DCT'=>'双离合', '1SPEED'=>'单速'
// ],
// 'RANGE_STD' => [
// 'CLTC'=>'CLTC', 'WLTP'=>'WLTP', 'EPA'=>'EPA', 'NEDC'=>'NEDC'
// ],
// 'EMISSION' => [
// 'CHN6'=>'国六', 'EURO6'=>'欧6', 'EPA'=>'美国EPA'
// ],
// ];
// // *_code取中文
// public function label(string $dict, ?string $code): ?string
// {
// if ($code === null || $code === '') return null;
// $dict = strtoupper($dict);
// $code = strtoupper($code);
// return self::MAPS[$dict][$code] ?? $code;
// }
// // 静态映射把 *_code → 中文
// private function backfill(array $d): array
// {
// $d['body_type'] = $this->label('BODY', $d['body_type_code']);
// $d['drive_type'] = $this->label('DRIVE', $d['drive_type_code']);
// $d['motor_layout'] = $this->label('MOTOR_LAYOUT', $d['motor_layout_code']);
// $d['fuel_type'] = $this->label('FUEL', $d['fuel_type_code']);
// $d['transmission'] = $this->label('TRANS', $d['transmission_code']);
// $d['range_std'] = $this->label('RANGE_STD', $d['range_std_code']);
// $d['emission_std'] = $this->label('EMISSION', $d['emission_std_code']);
// return $d;
// }
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['where']['is_active'] = 1;
$config['order']['id'] = 'desc';
// 关键字筛选
if (isset($p['keyword']) && $p['keyword']) {
$k = '%' . $p['keyword'] . '%';
foreach (['model_name','series_name','brand_name',] as $col) {
$config['orWhere'][] = [$col, 'like', $k];
}
}
break;
case 'index':
$config['where']['is_active'] = 1;
$config['order']['id'] = 'desc';
// 关键字筛选
if (isset($p['keyword']) && $p['keyword']) {
$k = '%' . $p['keyword'] . '%';
foreach (['model_name','series_name','brand_name',] as $col) {
$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'];
$config['where']['is_active'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
public function beforeServiceStore($data, Request $request)
{
// $data = $this->backfill($data);
if (empty($data['model_code'])) {
$data['model_code'] = ModelNaming::buildModelCode($data);
}
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;
$data['updated_by'] = $request->user->uid;
//指纹数组
$fingerprintArr = [
'brand_name' => $data['brand_name'],
'series_name' => $data['series_name'],
'model_name' => $data['model_name'],
'model_year' => $data['model_year'],
'msrp' => $data['msrp'],
];
// 生成指纹
$data['fingerprint'] = Fingerprint::makeFromArray($fingerprintArr);
return $data;
}
protected function afterServiceStore($res)
{
return $res;
}
public function beforeServiceUpdate($data, Request $request)
{
unset($data['created_at']);
unset($data['updated_at']);
// $data = $this->backfill($data);
if (empty($data['model_code'])) {
$data['model_code'] = ModelNaming::buildModelCode($data);
}
if (empty($data['model_name'])) {
$data['model_name'] = ModelNaming::buildModelName($data);
}
// 填充系统字段
$data['updated_by'] = $request->user->uid;
//指纹数组
$fingerprintArr = [
'brand_name' => $data['brand_name'],
'series_name' => $data['series_name'],
'model_name' => $data['model_name'],
'model_year' => $data['model_year'],
'msrp' => $data['msrp'],
];
// 生成指纹
$data['fingerprint'] = Fingerprint::makeFromArray($fingerprintArr);
return $data;
}
protected function afterServiceUpdate($res)
{
return $res;
}
/**
* 获取车型list
*/
public function getList($data)
{
return $this->logic->getList($data);
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\VehVehicleUnitValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\VehVehicleUnit;
class VehVehicleUnitService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = VehVehicleUnit::class;
/**
* 验证器
*/
protected $validator = VehVehicleUnitValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}