对账
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
<?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;
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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']);
|
$mimes = $ossservice->getMimeTypes(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'zip', 'rar']);
|
||||||
|
|
||||||
$result = Storage::adapter('oss')
|
$result = Storage::adapter('oss')
|
||||||
->path('x_server/' . $sceneUrl . date('Ymd'))
|
->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd'))
|
||||||
->size(1024*1024*5)
|
->size(1024*1024*5)
|
||||||
->extYes($mimes)
|
->extYes($mimes)
|
||||||
->upload($file);
|
->upload($file);
|
||||||
@@ -132,7 +132,7 @@ class UploadController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$result = Storage::adapter('oss')
|
$result = Storage::adapter('oss')
|
||||||
->path('x_server/' . $sceneUrl . date('Ymd'))
|
->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd'))
|
||||||
->size(1024*1024*5)
|
->size(1024*1024*5)
|
||||||
->extYes(['image/jpeg','image/png'])
|
->extYes(['image/jpeg','image/png'])
|
||||||
->extNo(['image/gif'])
|
->extNo(['image/gif'])
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?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 = [];
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,7 +50,7 @@ class UploadService
|
|||||||
$size = filesize($filePath) ?: 0;
|
$size = filesize($filePath) ?: 0;
|
||||||
|
|
||||||
$sceneUrl = $scene ? ($scene . '/') : '';
|
$sceneUrl = $scene ? ($scene . '/') : '';
|
||||||
$dir = 'x_server/' . $sceneUrl . date('Ymd') . '/';
|
$dir = config('oss.oss_main_dir') . $sceneUrl . date('Ymd') . '/';
|
||||||
// 使用传入的 original_name 命名(缺少扩展名时补上推断的扩展名),并做简单安全处理
|
// 使用传入的 original_name 命名(缺少扩展名时补上推断的扩展名),并做简单安全处理
|
||||||
$safeBaseName = preg_replace('/[\\\\\/]+/', '_', $originalName);
|
$safeBaseName = preg_replace('/[\\\\\/]+/', '_', $originalName);
|
||||||
if ($extension && strtolower(pathinfo($safeBaseName, PATHINFO_EXTENSION)) !== strtolower($extension)) {
|
if ($extension && strtolower(pathinfo($safeBaseName, PATHINFO_EXTENSION)) !== strtolower($extension)) {
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<?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()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\validate;
|
||||||
|
|
||||||
|
use think\Validate;
|
||||||
|
|
||||||
|
class FinanceReconDubaiFilesValidate extends Validate
|
||||||
|
{
|
||||||
|
// 规则
|
||||||
|
protected $rule = [
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
// 提示
|
||||||
|
protected $message = [
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
// 场景
|
||||||
|
protected $scene = [
|
||||||
|
'store' => [
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
'update' => [
|
||||||
|
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -74,25 +74,25 @@ class IndexController
|
|||||||
$options = [
|
$options = [
|
||||||
'controller' => [
|
'controller' => [
|
||||||
'module' => 'common',
|
'module' => 'common',
|
||||||
'name' => 'stock_orders_files'
|
'name' => 'finance_recon_dubai_files'
|
||||||
],
|
],
|
||||||
'model' => [
|
'model' => [
|
||||||
'module' => 'common',
|
'module' => 'common',
|
||||||
'name' => 'stock_orders_files'
|
'name' => 'finance_recon_dubai_files'
|
||||||
],
|
],
|
||||||
'service' => [
|
'service' => [
|
||||||
'module' => 'common',
|
'module' => 'common',
|
||||||
'name' => 'stock_orders_files'
|
'name' => 'finance_recon_dubai_files'
|
||||||
],
|
],
|
||||||
'logic' => [
|
'logic' => [
|
||||||
'module' => 'common',
|
'module' => 'common',
|
||||||
'name' => 'stock_orders_files'
|
'name' => 'finance_recon_dubai_files'
|
||||||
],
|
],
|
||||||
'validate' => [
|
'validate' => [
|
||||||
'module' => 'common',
|
'module' => 'common',
|
||||||
'name' => 'stock_orders_files'
|
'name' => 'finance_recon_dubai_files'
|
||||||
],
|
],
|
||||||
'table' => 'stock_orders_files'
|
'table' => 'finance_recon_dubai_files'
|
||||||
];
|
];
|
||||||
|
|
||||||
$result = (new Generate($options))->doGenerate();
|
$result = (new Generate($options))->doGenerate();
|
||||||
@@ -114,7 +114,7 @@ class IndexController
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function test(Request $request)
|
public function pdf(Request $request)
|
||||||
{
|
{
|
||||||
$id = $request->get('id');
|
$id = $request->get('id');
|
||||||
$service = new \app\common\service\StockOrdersService();
|
$service = new \app\common\service\StockOrdersService();
|
||||||
@@ -154,4 +154,16 @@ class IndexController
|
|||||||
$data = array('header' => $header,'body' => $response);
|
$data = array('header' => $header,'body' => $response);
|
||||||
return $data;
|
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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?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')
|
||||||
|
];
|
||||||
@@ -472,6 +472,16 @@ Route::group('/admin', function () {
|
|||||||
Route::any('/claim', [app\admin\controller\dashboard\OfficeClerkController::class, 'claim']);
|
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']);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user