139 Commits

Author SHA1 Message Date
mzl92 f73bad8082 Merge branch 'develop' into feature/sku_crawl 2025-11-04 13:11:27 +08:00
mzl92 1eccabb806 Finish mzl_dev 2025-11-04 10:33:40 +08:00
mzl92 013fc83362 Merge branch 'develop' into feature/mzl_dev 2025-11-04 10:32:22 +08:00
mzl92 42bf32a9a5 修正编辑后的备货单绕过文件上传 2025-11-04 10:31:45 +08:00
mzl92 2f8f7dccf9 调整备货单功能 2025-11-03 18:07:56 +08:00
18338781771 49e7709ae3 Merge branch 'feature/fin' into develop 2025-11-01 14:59:46 +08:00
18338781771 5b77b8ed83 待入账 2025-11-01 14:59:41 +08:00
18338781771 a98769fb4c Merge branch 'feature/fin' into develop 2025-11-01 14:44:58 +08:00
18338781771 56ad551707 收汇凭证倒计时 2025-11-01 14:44:47 +08:00
18338781771 2711ed8f6a Merge branch 'feature/fin' into develop 2025-11-01 14:14:01 +08:00
18338781771 f1d5fae122 入账记录汇率 2025-11-01 14:13:55 +08:00
18338781771 4f965f8b5f Merge branch 'feature/fin' into develop 2025-11-01 14:02:21 +08:00
18338781771 4dbd72acce 待入账与入账实际汇率 2025-11-01 14:02:15 +08:00
18338781771 0c789b7a1a Merge branch 'feature/fin' into develop
# Conflicts:
#	app/common/service/FinReceiptService.php
2025-11-01 13:37:39 +08:00
18338781771 8674063e6e 付款记录作废 2025-11-01 13:36:18 +08:00
18338781771 1094bc406f Revert "作废"
This reverts commit 139d3509f4.
2025-11-01 13:22:45 +08:00
18338781771 596313ed5a Merge branch 'feature/fin' into develop 2025-11-01 13:20:38 +08:00
18338781771 74ac8941ea 统计 2025-11-01 13:20:33 +08:00
18338781771 6493bdebb1 待入账 2025-11-01 11:34:49 +08:00
18338781771 07a3d66079 Merge branch 'feature/fin' into develop 2025-11-01 11:32:16 +08:00
18338781771 18eb3e7c5d 统计 2025-11-01 11:32:10 +08:00
18338781771 262add20f8 Merge branch 'feature/fin' into develop
# Conflicts:
#	app/common/service/FinReceiptService.php
2025-11-01 09:47:56 +08:00
18338781771 139d3509f4 作废 2025-11-01 09:45:41 +08:00
18338781771 f7faf79809 待入账列表 2025-11-01 08:56:48 +08:00
18338781771 29ba35f9b6 收付款统计 2025-10-31 16:22:28 +08:00
18338781771 0e38f0f9bb Merge branch 'feature/fin' into develop 2025-10-31 10:17:52 +08:00
18338781771 43fe263df1 备货单编号 2025-10-31 10:17:41 +08:00
mzl92 58a38d1463 Finish mzl_dev 2025-10-31 09:47:58 +08:00
mzl92 821f455e47 Merge branch 'develop' into feature/mzl_dev 2025-10-31 09:47:48 +08:00
mzl92 24805257c7 调整内勤仪表盘曲线查询 2025-10-31 09:46:57 +08:00
18338781771 b4ce4fe672 Merge branch 'feature/fin' into develop 2025-10-30 18:49:33 +08:00
18338781771 206eff87e2 附件 2025-10-30 18:49:28 +08:00
18338781771 2e85b46a3d Merge branch 'feature/fin' into develop 2025-10-30 18:37:46 +08:00
18338781771 72fa638cf5 排序优化 2025-10-30 18:37:40 +08:00
18338781771 5f25ac9895 Merge branch 'feature/fin' into develop 2025-10-30 18:12:34 +08:00
18338781771 ad67a88d0f 水单多文件 2025-10-30 18:11:50 +08:00
mzl92 4c3f8c9cf7 Finish mzl_dev 2025-10-30 17:55:32 +08:00
mzl92 3f8dbf97e4 Merge branch 'develop' into feature/mzl_dev 2025-10-30 17:55:27 +08:00
mzl92 e9636513ec 临时处理仪表盘显示 2025-10-30 17:55:08 +08:00
18338781771 9f167ad526 Merge branch 'feature/fin' into develop 2025-10-30 17:54:43 +08:00
18338781771 d737281283 水单多文件 2025-10-30 17:54:38 +08:00
18338781771 695c398962 Merge branch 'feature/fin' into develop 2025-10-30 17:38:46 +08:00
18338781771 0110e393ce 付款单关联备货单优化 2025-10-30 17:38:40 +08:00
18338781771 ea0d5b14bc Merge branch 'feature/fin' into develop 2025-10-30 17:21:20 +08:00
18338781771 a3819bcba7 入账资料附件格式处理 2025-10-30 17:21:10 +08:00
mzl92 3d6aa5f365 Finish mzl_dev 2025-10-30 17:10:06 +08:00
mzl92 79d0ae2cbd Merge branch 'develop' into feature/mzl_dev 2025-10-30 17:10:00 +08:00
mzl92 659f6d4866 ts 2025-10-30 17:09:41 +08:00
mzl92 1b4914f7b7 Finish mzl_dev 2025-10-30 16:52:26 +08:00
mzl92 c8cdc5ba00 Merge branch 'develop' into feature/mzl_dev 2025-10-30 16:52:18 +08:00
mzl92 94e9d6eb7e xg 2025-10-30 16:51:58 +08:00
mzl92 3d1a4ceeb5 Finish mzl_dev 2025-10-30 16:43:10 +08:00
mzl92 7db7e652de Merge branch 'develop' into feature/mzl_dev 2025-10-30 16:43:01 +08:00
mzl92 9fbe77a3a9 修改认领 2025-10-30 16:42:42 +08:00
18338781771 4c63c1b669 Merge branch 'feature/fin' into develop 2025-10-30 16:29:59 +08:00
18338781771 f64324fba5 超时未上传入账资料筛选 2025-10-30 16:29:54 +08:00
18338781771 499b0894b7 已认领筛选 2025-10-30 15:24:05 +08:00
18338781771 88ebef4fc3 Merge branch 'feature/fin' into develop 2025-10-30 14:58:40 +08:00
18338781771 15df6ad9a3 超时未认领筛选 2025-10-30 14:58:29 +08:00
mzl92 02fe911a44 Finish mzl_dev 2025-10-30 14:40:43 +08:00
mzl92 5facb1abba Merge branch 'develop' into feature/mzl_dev 2025-10-30 14:40:35 +08:00
mzl92 077edc5fd6 Merge branch 'feature/mzl_dev' into develop
# Conflicts:
#	config/route.php
2025-10-30 14:40:24 +08:00
mzl92 ef0c236130 Merge branch 'develop' into feature/mzl_dev
# Conflicts:
#	config/route.php
2025-10-30 14:39:35 +08:00
mzl92 8c3a445a39 Merge branch 'develop' into feature/mzl_dev
# Conflicts:
#	config/route.php
2025-10-30 14:39:10 +08:00
mzl92 a7d623656d 内勤仪表盘 2025-10-30 14:37:21 +08:00
18338781771 f004d99802 Merge branch 'feature/fin' into develop 2025-10-30 14:27:11 +08:00
18338781771 ea3d511c4d 未入账金额 2025-10-30 14:26:59 +08:00
18338781771 25f8017b3c 关键词抓取车型后创建 2025-10-30 14:15:18 +08:00
18338781771 59f474a037 Merge branch 'feature/fin' into develop 2025-10-30 13:35:59 +08:00
18338781771 672258e594 收款统计 2025-10-30 13:35:53 +08:00
18338781771 9f944ae92c 匹配排序优化 2025-10-30 09:52:20 +08:00
18338781771 c2d291bed4 Merge branch 'feature/fin' into develop 2025-10-29 18:38:42 +08:00
18338781771 f3b1c9bae4 付款记录排序 2025-10-29 18:38:37 +08:00
18338781771 346d67c084 超过24小时邮件通知提交入账资料 2025-10-29 18:05:23 +08:00
18338781771 eacdb93be2 Merge branch 'feature/fin' into develop 2025-10-29 15:58:29 +08:00
18338781771 cf45acbf0f 入账资料时间 2025-10-29 15:58:23 +08:00
18338781771 b235747a9a Merge branch 'feature/fin' into develop 2025-10-29 15:48:09 +08:00
18338781771 b440c03241 超时未认领筛选 2025-10-29 15:47:51 +08:00
18338781771 2b2e821779 Merge branch 'feature/fin' into develop 2025-10-29 14:23:53 +08:00
18338781771 07c0b2a2f3 收付款 2025-10-29 14:14:40 +08:00
18338781771 6baee549c0 Merge branch 'hotfix/series' into develop 2025-10-29 11:48:18 +08:00
18338781771 8a1b2f2369 系统设置 2025-10-28 17:05:23 +08:00
18338781771 2127473290 系统设置 2025-10-28 16:59:51 +08:00
18338781771 77dad7daed 模糊抓取车型 2025-10-28 16:19:37 +08:00
18338781771 38effed458 Merge branch 'feature/fin' into develop 2025-10-27 14:52:10 +08:00
18338781771 1bd2eee8db 入账时间 2025-10-27 14:52:04 +08:00
18338781771 36c7d19319 Merge branch 'feature/fin' into develop 2025-10-27 11:57:06 +08:00
18338781771 19cb169abd 历史记录 2025-10-27 11:57:00 +08:00
18338781771 a85fa8f072 Merge branch 'feature/fin' into develop 2025-10-27 10:57:54 +08:00
18338781771 83d76b16b4 确认入账 2025-10-27 10:57:47 +08:00
18338781771 2f9f483661 Merge branch 'feature/fin' into develop
# Conflicts:
#	app/controller/IndexController.php
#	config/route.php
2025-10-27 10:23:52 +08:00
18338781771 7fd28461fe Merge branch 'feature/audit' into develop 2025-10-24 16:32:34 +08:00
mzl92 5cc67b78f1 Finish mzl_dev 2025-10-24 15:47:18 +08:00
mzl92 770664e70f Merge branch 'develop' into feature/mzl_dev 2025-10-24 15:47:12 +08:00
mzl92 6bd4ed9ec3 ts备货单 2025-10-24 15:46:20 +08:00
18338781771 211fe575b8 Merge branch 'feature/audit' into develop 2025-10-24 15:25:44 +08:00
18338781771 18786591b1 Merge branch 'feature/audit' into develop 2025-10-24 15:02:01 +08:00
mzl92 1c3585b079 Finish mzl_dev 2025-10-24 11:04:35 +08:00
mzl92 8db33a40a4 Merge branch 'develop' into feature/mzl_dev 2025-10-24 11:04:27 +08:00
mzl92 c1ab40031d 调整采购单 2025-10-24 11:02:54 +08:00
mzl92 2c8efc18f9 Finish mzl_dev 2025-10-24 10:44:12 +08:00
mzl92 03ab55bd63 Merge branch 'develop' into feature/mzl_dev 2025-10-24 10:44:00 +08:00
mzl92 4326bcd68b 调整备货单 2025-10-24 10:43:36 +08:00
18338781771 7036e3b849 排序规则调整 2025-10-23 17:09:40 +08:00
18338781771 0f505d5c56 收款记录联查付款单 2025-10-23 16:25:02 +08:00
18338781771 33efdc8534 Merge branch 'hotfix/port' into develop 2025-10-23 14:19:43 +08:00
18338781771 6644cc1c58 收款记录 2025-10-23 11:27:24 +08:00
mzl92 797256e707 Finish mzl_dev 2025-10-22 16:16:01 +08:00
mzl92 448d151106 Merge branch 'develop' into feature/mzl_dev 2025-10-22 16:15:55 +08:00
mzl92 7486803468 调整pdf模板文件 2025-10-22 16:15:08 +08:00
18338781771 4c1ac68a2b Merge branch 'hotfix/sku' into develop 2025-10-22 10:45:24 +08:00
18338781771 1cb5a1d5b2 Merge branch 'hotfix/sku' into develop 2025-10-22 10:35:50 +08:00
mzl92 55850aef81 Finish mzl_dev 2025-10-21 19:54:42 +08:00
mzl92 13ab4b7868 根据10.17沟通调整 2025-10-21 19:53:21 +08:00
mzl92 67ef59f9fd Merge branch 'master' into develop 2025-10-21 15:11:45 +08:00
mzl92 07f9a4306f Merge branch 'master' into develop 2025-10-21 14:46:50 +08:00
mzl92 f17646238e Merge branch 'master' into develop 2025-10-21 14:06:24 +08:00
mzl92 03d010ec88 Finish mzl_dev 2025-10-21 13:41:40 +08:00
mzl92 13d24d041d Merge branch 'develop' into feature/mzl_dev 2025-10-21 13:41:27 +08:00
mzl92 4c94392ef6 ts 2025-10-21 13:35:17 +08:00
mzl92 5c6978c803 Merge branch 'master' into develop 2025-10-16 09:15:53 +08:00
18338781771 73ed4a413c 付款账号名 2025-10-09 10:24:13 +08:00
18338781771 d05106f9df 收款记录 2025-10-09 10:16:21 +08:00
18338781771 90cce108b0 收款记录创建与修改人 2025-09-28 10:10:15 +08:00
18338781771 a3a6cfefc0 收款附件优化 2025-09-28 09:56:01 +08:00
18338781771 40dfd99f4e 关联查询优化 2025-09-28 09:47:09 +08:00
18338781771 ae6cd4ec78 变更历史 2025-09-26 16:43:09 +08:00
18338781771 ec2502cd58 收款 2025-09-25 17:48:31 +08:00
18338781771 e0b11718a7 收款记录关联备货单 2025-09-24 17:55:16 +08:00
18338781771 82e4435a2c 收汇附件名称 2025-09-24 17:49:14 +08:00
18338781771 435d3ce677 付款记录 2025-09-24 15:23:47 +08:00
18338781771 abc1142e88 绑定付款记录 2025-09-24 10:16:34 +08:00
18338781771 54f83679ea 收款记录编辑 2025-09-23 18:38:02 +08:00
18338781771 db10ece6b7 付款详情-备货单 2025-09-23 18:30:13 +08:00
18338781771 870bca5e57 付款详情-备货单 2025-09-23 18:26:17 +08:00
18338781771 1d6caad7b0 付款申请参数结构修改 2025-09-23 17:33:09 +08:00
18338781771 e6d3cc3f04 付款申请查询相关字段名 2025-09-23 16:45:06 +08:00
18338781771 4717bb01ca 付款与收款 2025-09-23 15:14:08 +08:00
18338781771 36d1d333b1 付款与收款 2025-09-23 15:13:56 +08:00
20 changed files with 145 additions and 1106 deletions
+1 -4
View File
@@ -7,7 +7,4 @@
/tests/tmp
/tests/.phpunit.result.cache
/public/uploads
/webman
/start.php
/windows.php
/bootstrap.php
/webman
@@ -1,33 +0,0 @@
<?php
namespace app\admin\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\FinanceReconDubaiFilesService;
class FinanceReconDubaiFilesController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new FinanceReconDubaiFilesService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
// protected function indexFormat($data)
// {
// if (isset($data['created_at']))
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
// if (isset($data['updated_at']))
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
// return $data;
// }
}
+23 -50
View File
@@ -254,13 +254,10 @@ class BrandSeriesService
// 验证系列是否存在
$series = BrandSeries::where('id', $series_id)->first();
if (!$series) {
throw new \Exception("车辆系列未收录,联系管理员处理");
throw new \Exception("系列ID {$series_id} 不存在");
}
// 获取品牌信息
$brand = Brand::where('id', $series->brand_id)->first();
if (!$brand) {
throw new \Exception("车辆品牌未收录,联系管理员处理");
}
// 保存车型数据
$createdCount = 0;
@@ -330,60 +327,36 @@ class BrandSeriesService
//懂车帝模糊搜索返回车系地址
$href = $crawlService->modelSearch($keyword);
$seriesId = $this->extractSeriesId($href); //懂车帝车系id
$seriesId = $this->extractSeriesId($href);
if (empty($seriesId)) { //没有抓取到懂车帝系列id时,按关键词为品牌名抓取所有的系列名
$crawlSeries = $crawlService->brandSeries($keyword);
}
if (empty($seriesId) && empty($crawlSeries)) {
if (empty($seriesId)) {
throw new \Exception('抓取失败,未找到该系列车型');
}
$brandSerieses = [];
if(empty($seriesId) && !empty($crawlSeries)){ //关键词为品牌名时抓取所有车系的懂车帝id
$href = $crawlService->modelSearch($crawlSeries[0]);
$seriesId = $this->extractSeriesId($href); //懂车帝车系id
//抓取品牌与系列
$brandSeries = $crawlService->crawlBrandSeries($seriesId);
foreach ($crawlSeries as $item) {
// $brandSerieses[] = $crawlService->brandSeries($keyword);
$brandSerieses[] = ['brand' => $brandSeries['brand'], 'series' => $item];
//抓取品牌与系列
$brandSeries = $crawlService->crawlBrandSeries($seriesId);
// 验证品牌是否存在
$brand = Brand::where('brand_name', $brandSeries['brand'])->first();
// 获取品牌信息
$series = BrandSeries::where('series_name', $brandSeries['series'])->first();
$result['brand'] = ['brand_name' => $brandSeries['brand'], 'exists' => $brand?1:0];
if($brand){
$result['brand']['id'] = $brand['id'];
}
$result['series'] = ['series_name' => $brandSeries['series'], 'exists' => $series?1:0];
if($series){
$result['series']['id'] = $series['id'];
if(empty($result['brand']['id'])){
$result['brand']['id'] = $series['brand_id'];
}
}elseif (!empty($seriesId) && empty($crawlSeries)){
//抓取品牌与系列
$brandSerieses[] = $crawlService->crawlBrandSeries($seriesId);
}
$result = [];
try {
foreach ($brandSerieses as $brandSeries) {
$item = [];
// 验证品牌是否存在
$brand = Brand::where('brand_name', $brandSeries['brand'])->first();
// 获取品牌信息
$series = BrandSeries::where('series_name', $brandSeries['series'])->first();
$item['brand'] = ['brand_name' => $brandSeries['brand'], 'exists' => $brand ? 1 : 0];
if ($brand) {
$item['brand']['id'] = $brand['id'];
}
$item['series'] = ['series_name' => $brandSeries['series'], 'exists' => $series ? 1 : 0];
if ($series) {
$item['series']['id'] = $series['id'];
if (empty($item['brand']['id'])) {
$item['brand']['id'] = $series['brand_id'];
}
}
//获取车型数据
$item['model'] = [];
if (count($brandSerieses)<2) {
$item['model'] = $this->crawlSeriesModel($seriesId);
}
$result[] = $item;
}
return ['is_series' => (count($brandSerieses)<2)?1:0, 'data' => $result];
//获取车型数据
$result['model'] = $this->crawlSeriesModel($seriesId);
return $result;
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
-32
View File
@@ -1,32 +0,0 @@
<?php
namespace app\command;
use app\common\model\User;
use app\common\model\UserSn;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use support\Db;
use app\common\utils\email\Emails;
/**
* 批量处理用户编号(也用于调试)
*/
class FinanceRecoCommand extends Command
{
protected static $defaultName = 'finance-reco';
protected static $defaultDescription = '';
protected function execute(InputInterface $input, OutputInterface $output): int
{
// $tmp = (new \app\common\service\OssService)->generatePrivateUrl('fdesk_test/20251104/20251104_财务.xlsx');;
$service = new \app\common\service\FinanceReconDubaiFilesService();
$result = $service->dImport('fdesk_test/20251104/20251104_迪拜.xlsx');
return self::SUCCESS;
}
}
+2 -2
View File
@@ -91,7 +91,7 @@ class UploadController
$mimes = $ossservice->getMimeTypes(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'zip', 'rar']);
$result = Storage::adapter('oss')
->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd'))
->path('x_server/' . $sceneUrl . date('Ymd'))
->size(1024*1024*5)
->extYes($mimes)
->upload($file);
@@ -132,7 +132,7 @@ class UploadController
}
$result = Storage::adapter('oss')
->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd'))
->path('x_server/' . $sceneUrl . date('Ymd'))
->size(1024*1024*5)
->extYes(['image/jpeg','image/png'])
->extNo(['image/gif'])
@@ -47,7 +47,6 @@ class VehVehicleSkuController extends BaseController
public function store(Request $request)
{
return $this->error('请优先抓取车辆信息,如抓取不到相应车型请联系管理员,暂停手动录入车辆信息');
try {
$data = $this->validate($this->service->getValidator(), $request->post(), 'store');
$res = $this->service->serviceStore($request, $data);
@@ -1,17 +0,0 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\FinanceReconDubaiFiles;
class FinanceReconDubaiFilesLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = FinanceReconDubaiFiles::class;
}
@@ -1,49 +0,0 @@
<?php
namespace app\common\model;
use support\Model;
class FinanceReconDubaiFiles extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'finance_recon_dubai_files';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'caiwu_file_name', 'caiwu_file_url', 'dubai_file_name', 'dubai_file_url', 'created_by', 'created_at', 'updated_at'];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
@@ -1,290 +0,0 @@
<?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);
}
}
+20 -34
View File
@@ -655,7 +655,7 @@ class StockOrdersService
*/
private function generateVehicleUniqueId(array $veh): string
{
$keysToRemove = ['remark', 'modify_list', 'order_id'];
$keysToRemove = ['vin', 'remark', 'modify_list', 'order_id'];
$array = array_diff_key($veh, array_flip($keysToRemove));
$uniqueIdStr = json_encode($array);
return md5($uniqueIdStr);
@@ -884,7 +884,6 @@ class StockOrdersService
'num' => 1,
'mileage' => $v->mileage,
'remark' => $v->remark,
'vin' => $v->vin,
] + $moneyValues;
}
return ['list' => $vehList];
@@ -926,26 +925,25 @@ class StockOrdersService
{
$vehicleData['num'] += 1;
// ** 11.11 remove 价格*数量
// $fieldList = [
// 'fob_transport_fee',
// 'car_price',
// 'ocean_freight',
// 'commission',
// 'retention_price',
// 'port_misc_fee',
// 'total_price',
// 'clearance_price',
// 'profit'
// ];
$fieldList = [
'fob_transport_fee',
'car_price',
'ocean_freight',
'commission',
'retention_price',
'port_misc_fee',
'total_price',
'clearance_price',
'profit'
];
// foreach ($vehicleData as $field => $value) {
// if (in_array($field, $fieldList)) {
// $amount = $vehicleData[$field]['amount'] * $vehicleData['num'];
// $currencySymbol = SysCurrency::where('currency_code', $vehicleData[$field]['currency'])->value('currency_symbol');
// $vehicleData[$field]['currency_info'] = $currencySymbol . number_format($amount, 2, '.', ',');
// }
// }
foreach ($vehicleData as $field => $value) {
if (in_array($field, $fieldList)) {
$amount = $vehicleData[$field]['amount'] * $vehicleData['num'];
$currencySymbol = SysCurrency::where('currency_code', $vehicleData[$field]['currency'])->value('currency_symbol');
$vehicleData[$field]['currency_info'] = $currencySymbol . number_format($amount, 2, '.', ',');
}
}
}
/**
@@ -1116,17 +1114,5 @@ class StockOrdersService
}
public function serviceDestroy(Request $request, string $scene = 'destroy')
{
$config = $this->buildQueryConfig($request, $scene);
$item = $this->logicShow($config);
if (!$item) {
throw new BusinessException('数据不存在');
}
if ($item->approval_status == 1 || $item->approval_status == 2) {
throw new BusinessException('审核中或者已审核的订单不能删除');
}
$deleted = $this->logicDeleteBy($config);
return $this->postProcess(ucfirst($scene), $deleted);
}
}
+1 -1
View File
@@ -50,7 +50,7 @@ class UploadService
$size = filesize($filePath) ?: 0;
$sceneUrl = $scene ? ($scene . '/') : '';
$dir = config('oss.oss_main_dir') . $sceneUrl . date('Ymd') . '/';
$dir = 'x_server/' . $sceneUrl . date('Ymd') . '/';
// 使用传入的 original_name 命名(缺少扩展名时补上推断的扩展名),并做简单安全处理
$safeBaseName = preg_replace('/[\\\\\/]+/', '_', $originalName);
if ($extension && strtolower(pathinfo($safeBaseName, PATHINFO_EXTENSION)) !== strtolower($extension)) {
+11 -12
View File
@@ -159,10 +159,14 @@ class UserService
/** 分页列表 */
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);
if (method_exists($this, 'complexQuery')) {
$result = $this->complexQuery($request->get());
} else {
$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();
@@ -215,14 +219,9 @@ class UserService
$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';
$config['dataLimit']['type'] = 'relation';
$config['dataLimit']['userId'] = $request->user->uid;
$config['dataLimit']['userField'] = 'report_to';
break;
}
-133
View File
@@ -1,133 +0,0 @@
<?php
namespace app\common\utils\excel;
use support\Db;
/**
* ExcelImportTrait
*
* 把“读取 Sheet → 映射字段 → 规范化数据 → 打时间戳 → 批量入库”
* 这条通用导入链路抽成 Trait,便于在多个 Service 里复用。
*
* 依赖:
* - ExcelReader:提供 readSheet / readSheetFromUrl / remap / toDecimal / toBool / toDate
* - 你的 OssService:通过 generatePrivateUrl($objectKey) 生成 OSS 私有签名 URL
*/
trait ExcelImportTrait
{
/**
* 读取某个 Sheet 的“行数组”
* - $file 可为本地路径或 URL(由 $isUrl 控制)
* - headerRow:表头所在行(Excel 行号,从 1 开始)
* - startRow:数据起始行(不传则为 headerRow+1)
*/
protected function readSheetRows(string $file, string $sheet, bool $isUrl, int $headerRow = 1, ?int $startRow = null): array
{
$ret = $isUrl
? ExcelReader::readSheetFromUrl($file, $sheet, $headerRow, $startRow)
: ExcelReader::readSheet($file, $sheet, $headerRow, $startRow);
return $ret['rows'] ?? [];
}
/**
* 读取某个 Sheet(传入 OSS 对象键),内部会用你的 OssService 生成签名 URL 再读取
*/
protected function readSheetRowsFromOssKey(string $objectKey, string $sheet, int $headerRow = 1, ?int $startRow = null): array
{
$signedUrl = $this->ossSignedUrl($objectKey);
$ret = ExcelReader::readSheetFromUrl($signedUrl, $sheet, $headerRow, $startRow);
return $ret['rows'] ?? [];
}
/**
* 使用你的 OssService 生成私有签名 URL
* - 兼容你的现有写法:(new \app\common\service\OssService)->generatePrivateUrl($objectKey)
*/
protected function ossSignedUrl(string $objectKey): string
{
return (new \app\common\service\OssService())->generatePrivateUrl($objectKey);
}
/**
* 将一行做字段映射 + 基础类型规范化(decimal/bool/date)
* - $headerMap:表头映射(源列名 => 目标字段名)
* - $decimalFields/$boolFields/$dateFields:需要做类型规范化的目标字段名列表
*/
protected function mapAndNormalizeRow(
array $row,
array $headerMap,
array $decimalFields = [],
array $boolFields = [],
array $dateFields = []
): array {
$rec = ExcelReader::remap($row, $headerMap);
foreach ($decimalFields as $f) {
if (array_key_exists($f, $rec)) {
$rec[$f] = ExcelReader::toDecimal($rec[$f]); // 建议已升级为“去货币符号/千分位/括号负数”的版本
}
}
foreach ($boolFields as $f) {
if (array_key_exists($f, $rec)) {
$rec[$f] = ExcelReader::toBool($rec[$f]);
}
}
foreach ($dateFields as $f) {
if (array_key_exists($f, $rec)) {
$rec[$f] = ExcelReader::toDate($rec[$f]);
}
}
return $rec;
}
/**
* 基于 VIN 的跳过规则:VIN 为空、或是“合计/总计”行
* - $vinAliases:VIN 列的候选表头(如 ['车架号','VIN NO'])
*/
protected function shouldSkipByVinAliases(array $row, array $vinAliases = ['车架号', 'VIN NO']): bool
{
$vin = $this->pickFirstNonEmpty($row, $vinAliases);
if ($vin === null) return true;
return (bool)preg_match('/^(合计|总计)/u', $vin);
}
/**
* 在多个候选键里取第一个非空字符串
*/
protected function pickFirstNonEmpty(array $row, array $keys): ?string
{
foreach ($keys as $k) {
if (isset($row[$k])) {
$v = trim((string)$row[$k]);
if ($v !== '') return $v;
}
}
return null;
}
/**
* 统一补齐 created_at / updated_at 时间戳(int(11))
*/
protected function stamp(array &$rec): void
{
$now = time();
$rec['created_at'] = $now;
$rec['updated_at'] = $now;
}
/**
* 批量入库
* - 返回 ['inserted'=>N, 'error'=>null|string]
* - 发生异常时不抛出,返回 error 字符串,便于上层统一处理
*/
protected function batchInsert(string $table, array $rows): array
{
if (!$rows) return ['inserted' => 0, 'error' => null];
try {
Db::table($table)->insert($rows);
return ['inserted' => count($rows), 'error' => null];
} catch (\Throwable $e) {
return ['inserted' => 0, 'error' => $e->getMessage()];
}
}
}
-292
View File
@@ -1,292 +0,0 @@
<?php
namespace app\common\utils\excel;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;
class ExcelReader
{
/**
* (可选)环境自检:避免因缺少扩展导致读取失败
* - simplexml_load_string 存在性(ext-simplexml)
* - ext-zip
*/
public static function assertRequirements(): void
{
if (!function_exists('simplexml_load_string')) {
throw new \RuntimeException('缺少 PHP 扩展:ext-simplexml。请安装/启用后再试。');
}
if (!extension_loaded('zip')) {
throw new \RuntimeException('缺少 PHP 扩展:ext-zip。请安装/启用后再试。');
}
// 可按需继续检查 ext-mbstring/ext-dom 等
}
/**
* 读取指定 sheet,按某一行作为表头
* 返回:['headers'=>[], 'rows'=>[]]
* - $headerRow:从 1 开始计数(Excel 行号)
* - $startRow:数据起始行(默认 headerRow+1)
* - 注意:$filePath 必须是“本地文件路径”;若为 URL,请改用 readSheetFromUrl()
*/
public static function readSheet(string $filePath, string $sheetName, int $headerRow = 1, ?int $startRow = null): array
{
self::assertRequirements();
if (!is_file($filePath)) {
throw new \RuntimeException("File not found: {$filePath}");
}
$reader = IOFactory::createReaderForFile($filePath);
$reader->setReadDataOnly(true);
$spreadsheet = $reader->load($filePath);
$sheet = $spreadsheet->getSheetByName($sheetName);
if (!$sheet) {
$names = implode(' / ', $spreadsheet->getSheetNames());
$spreadsheet->disconnectWorksheets();
throw new \RuntimeException("Sheet '{$sheetName}' not found. Available: {$names}");
}
$highestRow = $sheet->getHighestDataRow();
$highestCol = $sheet->getHighestDataColumn();
$highestColIndex = Coordinate::columnIndexFromString($highestCol);
// 读取表头(去除可能的 UTF-8 BOM)
$headers = [];
for ($col = 1; $col <= $highestColIndex; $col++) {
$coord = Coordinate::stringFromColumnIndex($col) . $headerRow;
$cell = $sheet->getCell($coord);
$raw = $cell ? $cell->getCalculatedValue() : null;
$text = is_null($raw) ? '' : trim((string)$raw);
// 去掉 BOM
if ($col === 1) {
$text = preg_replace('/^\xEF\xBB\xBF/u', '', $text);
}
$headers[$col] = $text === '' ? "COL_{$col}" : $text;
}
// 读取数据
$rows = [];
$firstDataRow = $startRow ?? ($headerRow + 1);
for ($row = $firstDataRow; $row <= $highestRow; $row++) {
$record = [];
$isEmpty = true;
for ($col = 1; $col <= $highestColIndex; $col++) {
$coord = Coordinate::stringFromColumnIndex($col) . $row;
$cell = $sheet->getCell($coord);
$value = $cell ? $cell->getCalculatedValue() : null;
// 日期单元格转换(使用 PhpSpreadsheet 的日期检测)
if ($cell && \PhpOffice\PhpSpreadsheet\Shared\Date::isDateTime($cell) && is_numeric($value)) {
$dt = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value);
$value = $dt ? $dt->format('Y-m-d') : $value;
}
if (is_string($value)) {
$value = trim($value);
}
if ($value !== null && $value !== '') {
$isEmpty = false;
}
$record[$headers[$col]] = $value;
}
if (!$isEmpty) {
$rows[] = $record;
}
}
$spreadsheet->disconnectWorksheets();
return ['headers' => array_values($headers), 'rows' => $rows];
}
/**
* 从 URL 读取指定 sheet(内部先下载到临时文件)
*/
public static function readSheetFromUrl(string $url, string $sheetName, int $headerRow = 1, ?int $startRow = null): array
{
$tmp = self::downloadToTemp($url);
try {
return self::readSheet($tmp, $sheetName, $headerRow, $startRow);
} finally {
@unlink($tmp);
}
}
/**
* 按映射表将“源表头”=>“目标字段名”(snake_case)
* $map 形如:['车架号' => 'vin', '实际采购价' => 'actual_purchase_price', ...]
*/
public static function remap(array $row, array $map): array
{
$out = [];
foreach ($map as $from => $to) {
$out[$to] = $row[$from] ?? null;
}
return $out;
}
/** 将一批行做 remap */
public static function remapRows(array $rows, array $map): array
{
$out = [];
foreach ($rows as $row) {
$out[] = self::remap($row, $map);
}
return $out;
}
/**
* 将“金额字符串”解析成 float(适配多币种/多地区写法)
* - 支持:¥ ¥ $ € £ …、AED/Dirham:dh / dhs / aed、阿语 د.إ
* - 支持:中文/全角标点、阿拉伯小数/千分位、细空格、撇号千分位
* - 支持:括号负数 (xxx) 与显式负号
* - 返回 null 表示无法解析
*/
public static function toDecimal($v): ?float
{
if ($v === null || $v === '') return null;
if (is_numeric($v)) return (float)$v;
$s = trim((string)$v);
// 1) 统一常见 unicode 标点(全角/阿拉伯)与空格
$s = strtr($s, [
',' => ',', '.' => '.',
'٫' => '.', '٬' => ',',
"\xC2\xA0" => ' ', // NBSP
"\xE2\x80\xAF" => ' ', // NNBSP(窄不换行空格)
]);
// 2) 括号负数,如 (dh1,234.56)
$negative = false;
if (preg_match('/^\s*\((.*)\)\s*$/u', $s, $m)) {
$negative = true;
$s = $m[1];
}
// 3) 去币种/符号:AED/Dirham(dh/dhs/aed/د.إ)& 其它常见符号/字母
$s = preg_replace('/\b(dhs?|aed)\b/i', '', $s); // dh / dhs / aed
$s = str_replace(['د.إ', 'د', 'إ'], '', $s); // 常见阿语形式简化处理
$s = preg_replace('/[A-Za-z¥¥$€£₽₺₩₹﷼]+/u', '', $s); // 其它字母/货币符号
// 4) 只保留 数字/点/逗号/减号/空格/撇号,然后去掉空格与撇号(千分位)
$s = preg_replace('/[^\d\.,\-\s\']+/u', '', $s);
$s = str_replace([' ', "'"], '', $s);
// 5) 统一小数分隔符(判断最后出现的分隔符)
$lastComma = strrpos($s, ',');
$lastDot = strrpos($s, '.');
if ($lastComma !== false && $lastDot !== false) {
// 同时存在,最后出现者视为小数点
if ($lastComma > $lastDot) {
// 欧式 1.234,56
$s = str_replace('.', '', $s); // 去千分点
$s = substr_replace($s, '.', $lastComma, 1); // 最后一个逗号改为小数点
$s = str_replace(',', '', $s); // 其余逗号清空
} else {
// 美式 1,234.56
$s = str_replace(',', '', $s);
}
} elseif ($lastComma !== false) {
// 只有逗号
$digitsAfter = strlen($s) - $lastComma - 1;
if (substr_count($s, ',') > 1) {
// 多个逗号基本都是千分位
$s = str_replace(',', '', $s);
} else {
// 一个逗号:若逗号后 1~2 位,视为小数点,否则千分位
$s = ($digitsAfter >= 1 && $digitsAfter <= 2)
? str_replace(',', '.', $s)
: str_replace(',', '', $s);
}
} elseif ($lastDot !== false && substr_count($s, '.') > 1) {
// 多个点:保留最后一个为小数点,其他去掉(当作千分位)
$digitsAfter = strlen($s) - $lastDot - 1;
if ($digitsAfter >= 1 && $digitsAfter <= 2) {
$s = preg_replace('/\.(?=.*\.)/', '', $s); // 移除除最后一个外的点
} else {
$s = str_replace('.', '', $s);
}
}
// 6) 处理显式负号
if (strpos($s, '-') !== false) {
$negative = true;
$s = str_replace('-', '', $s);
}
// 7) 最终校验并转 float
if ($s === '' || !preg_match('/^\d+(\.\d+)?$/', $s)) {
return null; // 无法解析
}
$num = (float)$s;
return $negative ? -$num : $num;
}
/** 规范化布尔:'是/否'、'Y/N'、'1/0'、'true/false' */
public static function toBool($v): ?int
{
if ($v === null || $v === '') return null;
$s = mb_strtolower(trim((string)$v));
if (in_array($s, ['是','y','yes','true','1','√','✅'], true)) return 1;
if (in_array($s, ['否','n','no','false','0','×','❌'], true)) return 0;
return null;
}
/** 规范化日期(readSheet 已对 Excel 日期做过处理,这里再兜底) */
public static function toDate($v): ?string
{
if ($v === null || $v === '') return null;
if (is_numeric($v)) {
// Excel 序列号兜底
$ts = ExcelDate::excelToTimestamp($v);
return $ts ? date('Y-m-d', $ts) : null;
}
// 简单清洗
$s = trim((string)$v);
// 常见格式直接返回(如 2025-11-04 / 2025/11/04)
if (preg_match('/^\d{4}[-\/]\d{1,2}[-\/]\d{1,2}$/', $s)) {
return str_replace('/', '-', $s);
}
return $s; // 留给上层决定是否再严格校验
}
/**
* 把远程 URL 流式下载到临时文件;返回临时路径
*/
protected static function downloadToTemp(string $url, ?string $ext = null): string
{
$ext = $ext ? ('.' . ltrim($ext, '.')) : '';
$tmp = sys_get_temp_dir() . '/excel_' . uniqid() . $ext;
$fp = fopen($tmp, 'w');
if (!$fp) {
throw new \RuntimeException("Cannot open temp file: {$tmp}");
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 300,
CURLOPT_CONNECTTIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'Webman-ExcelReader/1.0',
]);
$ok = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
fclose($fp);
if ($ok === false || $http >= 400) {
@unlink($tmp);
throw new \RuntimeException("Download failed: HTTP {$http} {$err}");
}
return $tmp;
}
}
+7 -15
View File
@@ -89,7 +89,7 @@
<div class="container">
<!-- 头部 -->
<div class="header">
<h1 class="title">合塑实业-汽车事业部-备货单<?php if ($data['is_final'] == 0) { echo '(客户下定)'; } else { echo '(终版)'; } ?></h1>
<h1 class="title">合塑实业-汽车事业部-备货单(客户下定)</h1>
<div>
<span class="sub-title">备货单号: <?php echo htmlspecialchars($data['order_no'] ?? 'N/A'); ?></span>
<span class="sub-title">业务员: <?php echo htmlspecialchars($data['salesperson_snapshot'] ?? ' - '); ?></span>
@@ -126,7 +126,7 @@
<th>车型</th>
<th>数量</th>
<th>颜色</th>
<th colspan="8">费用明细</th>
<th colspan="7">费用明细</th>
</tr>
</thead>
@@ -142,12 +142,9 @@
<!-- 车型 跨4行(不含备注行) -->
<td rowspan="4">
<?php echo htmlspecialchars($vehicle['model_snapshot'] ?? ' - '); ?><br/><br/>
<?php echo htmlspecialchars($vehicle['model_snapshot'] ?? ' - '); ?>
<?php if (!empty($vehicle['mileage']) && $vehicle['mileage'] > 0): ?>
<span class="nowrap">里程:<?php echo htmlspecialchars($vehicle['mileage']); ?>km</span>
<?php endif; ?><br/>
<?php if ($vehicle['vin']): ?>
<span class="nowrap">VIN:<?php echo htmlspecialchars($vehicle['vin']); ?></span>
<span class="nowrap">(<?php echo htmlspecialchars($vehicle['mileage']); ?>km)</span>
<?php endif; ?>
</td>
@@ -163,7 +160,6 @@
</td>
<!-- 费用明细(第1组)标题 -->
<th>手续费</th>
<th>改装费</th>
<th>配件费</th>
<th>收款价</th>
@@ -175,7 +171,6 @@
<!-- 行2:费用明细(第1组)数据 -->
<tr>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['handling_fee']['currency_info'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['retrofit_fee']['currency_info'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['accessory_price']['currency_info'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['unit_price']['currency_info'] ?? ' - '); ?></td>
@@ -196,7 +191,6 @@
<!-- 行3:费用明细(第2组)标题 -->
<tr>
<th>加价</th>
<th>报关价</th>
<th>FOB运费</th>
<th>车价</th>
@@ -208,7 +202,6 @@
<!-- 行4:费用明细(第2组)数据 -->
<tr>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['increase_price']['currency_info'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['clearance_price']['currency_info'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['fob_transport_fee']['currency_info'] ?? ' - '); ?></td>
<td class="nowrap"><?php echo htmlspecialchars($vehicle['car_price']['currency_info'] ?? ' - '); ?></td>
@@ -221,7 +214,7 @@
<!-- 行5:备注(因为“编号”已跨到本行,所以这里合并剩余10列) -->
<tr>
<td colspan="1"><strong>备注</strong></td>
<td colspan="10" style="text-align:left;">
<td colspan="9" style="text-align:left;">
<span class="info-value">
<?php echo nl2br($vehicle['remark'] ?? ''); ?>
</span>
@@ -238,7 +231,7 @@
<span class="info-label">合同汇率:</span>
<span class="info-value"><?php echo htmlspecialchars($data['contract_exchange_rate'] ?? ' - '); ?></span>
</td>
<td colspan="8" style="text-align:left;">
<td colspan="7" style="text-align:left;">
<span class="info-label">合计到岸价:</span>
<span class="info-value"><?php echo htmlspecialchars($data['cif_total']['currency_info'] ?? ' - '); ?></span>
</td>
@@ -268,8 +261,7 @@
<td><span class="info-label">出运方式:</span> <span class="info-value"><?php echo htmlspecialchars($data['shipping_method_snapshot'] ?? ' - '); ?></span></td>
<td><span class="info-label">国内收货港:</span> <span class="info-value"><?php echo htmlspecialchars($data['domestic_port_snapshot'] ?? ' - '); ?></span></td>
<td><span class="info-label">贸易条款:</span> <span class="info-value"><?php echo htmlspecialchars($data['trade_term_snapshot'] ?? ' - '); ?></span></td>
<td><span class="info-label">清关目的港:</span> <span class="info-value"><?php echo htmlspecialchars($data['clearance_port_snapshot'] ?? ' - '); ?></span></td>
<td><span class="info-label">贸易条款/清关目的港:</span> <span class="info-value"><?php echo htmlspecialchars($data['trade_term_snapshot'] ?? ' - '); ?>/<?php echo htmlspecialchars($data['clearance_port_snapshot'] ?? ' - '); ?></span></td>
<?php if ($data['is_final'] == 1): ?>
<td><span class="info-label">提车日期:</span> <span class="info-value nowrap"><?php echo htmlspecialchars($data['pick_up_date'] ?? ' - '); ?></span></td>
@@ -1,29 +0,0 @@
<?php
namespace app\common\validate;
use think\Validate;
class FinanceReconDubaiFilesValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+9 -21
View File
@@ -15,9 +15,9 @@ use GuzzleHttp\Client;
class IndexController
{
protected $noNeedLogin = ['index', 'view', 'perm', 'createResource', 'pdf', 'test'];
protected $noNeedLogin = ['index', 'view', 'perm', 'createResource', 'test'];
protected $noNeedHeaderData = ['index', 'view', 'perm', 'createResource', 'pdf', 'test'];
protected $noNeedHeaderData = ['index', 'view', 'perm', 'createResource', 'test'];
public function index(Request $request)
{
@@ -74,25 +74,25 @@ class IndexController
$options = [
'controller' => [
'module' => 'common',
'name' => 'finance_recon_dubai_files'
'name' => 'stock_orders_files'
],
'model' => [
'module' => 'common',
'name' => 'finance_recon_dubai_files'
'name' => 'stock_orders_files'
],
'service' => [
'module' => 'common',
'name' => 'finance_recon_dubai_files'
'name' => 'stock_orders_files'
],
'logic' => [
'module' => 'common',
'name' => 'finance_recon_dubai_files'
'name' => 'stock_orders_files'
],
'validate' => [
'module' => 'common',
'name' => 'finance_recon_dubai_files'
'name' => 'stock_orders_files'
],
'table' => 'finance_recon_dubai_files'
'table' => 'stock_orders_files'
];
$result = (new Generate($options))->doGenerate();
@@ -114,7 +114,7 @@ class IndexController
}
public function pdf(Request $request)
public function test(Request $request)
{
$id = $request->get('id');
$service = new \app\common\service\StockOrdersService();
@@ -154,16 +154,4 @@ class IndexController
$data = array('header' => $header,'body' => $response);
return $data;
}
public function test(Request $request)
{
$tmp = (new \app\common\service\OssService)->generatePrivateUrl('fdesk_test/20251104/20251104_财务.xlsx');;
$service = new \app\common\service\FinanceRecoDubaiFilesService();
$result = $service->import($tmp, true);
return json(['code' => 0, 'msg' => 'ok', 'data' => $result]);
}
}
-10
View File
@@ -1,10 +0,0 @@
<?php
return [
'oss_accessid' => env('OSS_ACCESSID'),
'oss_accesssecret' => env('OSS_ACCESSSECRET'),
'oss_bucket' => env('OSS_BUCKET'),
'oss_endpoint' => env('OSS_ENDPOINT'),
'oss_static_url' => env('OSS_STATIC_URL'),
'oss_main_dir' => env('OSS_MAIN_DIR')
];
-10
View File
@@ -472,16 +472,6 @@ Route::group('/admin', function () {
Route::any('/claim', [app\admin\controller\dashboard\OfficeClerkController::class, 'claim']);
});
});
Route::group('/finance', function () {
Route::group('/dubai', function () {
Route::any('/list', [app\admin\controller\FinanceReconDubaiFilesController::class, 'index']);
Route::any('/show', [app\admin\controller\FinanceReconDubaiFilesController::class, 'show']);
Route::any('/store', [app\admin\controller\FinanceReconDubaiFilesController::class, 'store']);
Route::any('/update', [app\admin\controller\FinanceReconDubaiFilesController::class, 'update']);
Route::any('/destroy', [app\admin\controller\FinanceReconDubaiFilesController::class, 'destroy']);
});
});
});
Executable → Regular
+71 -71
View File
@@ -1,71 +1,71 @@
#!/usr/bin/env php
<?php
use Webman\Config;
use Webman\Console\Command;
use Webman\Console\Util;
use support\Container;
use Dotenv\Dotenv;
if (!Phar::running()) {
chdir(__DIR__);
}
require_once __DIR__ . '/vendor/autoload.php';
if (!$appConfigFile = config_path('app.php')) {
throw new RuntimeException('Config file not found: app.php');
}
if (class_exists(Dotenv::class) && file_exists(run_path('.env'))) {
if (method_exists(Dotenv::class, 'createUnsafeImmutable')) {
Dotenv::createUnsafeImmutable(run_path())->load();
} else {
Dotenv::createMutable(run_path())->load();
}
}
$appConfig = require $appConfigFile;
if ($timezone = $appConfig['default_timezone'] ?? '') {
date_default_timezone_set($timezone);
}
if ($errorReporting = $appConfig['error_reporting'] ?? '') {
error_reporting($errorReporting);
}
if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) {
require_once __DIR__ . '/support/bootstrap.php';
} else {
if (class_exists('Support\App')) {
Support\App::loadAllConfig(['route']);
} else {
Config::reload(config_path(), ['route', 'container']);
}
}
$cli = new Command();
$cli->setName('webman cli');
$cli->installInternalCommands();
if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) {
$cli->installCommands($command_path);
}
foreach (config('plugin', []) as $firm => $projects) {
if (isset($projects['app'])) {
foreach (['', '/app'] as $app) {
if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) {
$command_path = base_path() . "/plugin/$firm{$app}/$command_str";
$cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str");
}
}
}
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
$project['command'] ??= [];
array_walk($project['command'], [$cli, 'createCommandInstance']);
}
}
$cli->run();
#!/usr/bin/env php
<?php
use Webman\Config;
use Webman\Console\Command;
use Webman\Console\Util;
use support\Container;
use Dotenv\Dotenv;
if (!Phar::running()) {
chdir(__DIR__);
}
require_once __DIR__ . '/vendor/autoload.php';
if (!$appConfigFile = config_path('app.php')) {
throw new RuntimeException('Config file not found: app.php');
}
if (class_exists(Dotenv::class) && file_exists(run_path('.env'))) {
if (method_exists(Dotenv::class, 'createUnsafeImmutable')) {
Dotenv::createUnsafeImmutable(run_path())->load();
} else {
Dotenv::createMutable(run_path())->load();
}
}
$appConfig = require $appConfigFile;
if ($timezone = $appConfig['default_timezone'] ?? '') {
date_default_timezone_set($timezone);
}
if ($errorReporting = $appConfig['error_reporting'] ?? '') {
error_reporting($errorReporting);
}
if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) {
require_once __DIR__ . '/support/bootstrap.php';
} else {
if (class_exists('Support\App')) {
Support\App::loadAllConfig(['route']);
} else {
Config::reload(config_path(), ['route', 'container']);
}
}
$cli = new Command();
$cli->setName('webman cli');
$cli->installInternalCommands();
if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) {
$cli->installCommands($command_path);
}
foreach (config('plugin', []) as $firm => $projects) {
if (isset($projects['app'])) {
foreach (['', '/app'] as $app) {
if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) {
$command_path = base_path() . "/plugin/$firm{$app}/$command_str";
$cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str");
}
}
}
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
$project['command'] ??= [];
array_walk($project['command'], [$cli, 'createCommandInstance']);
}
}
$cli->run();