Finish mzl_dev
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\admin\service\ApprovalFlowsService;
|
||||
|
||||
class ApprovalFlowsController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new ApprovalFlowsService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
|
||||
public function desc()
|
||||
{
|
||||
|
||||
|
||||
// type sequential(顺序会签), parallel(并行会签), any(或签)
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\admin\model\ApprovalFlows;
|
||||
|
||||
class ApprovalFlowsLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = ApprovalFlows::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class ApprovalFlows extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'approval_flows';
|
||||
|
||||
/**
|
||||
* 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', 'name', 'type', 'steps', 'is_active', 'timeout_hours', 'timeout_action', 'created_at', 'updated_at'];
|
||||
|
||||
/**
|
||||
* 不允许自动填充模型字段
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
|
||||
protected $casts = [
|
||||
'steps' => 'array',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\service;
|
||||
|
||||
use app\admin\validate\ApprovalFlowsValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\admin\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,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class ApprovalFlowsValidate extends Validate
|
||||
{
|
||||
// 规则
|
||||
protected $rule = [
|
||||
|
||||
];
|
||||
|
||||
// 提示
|
||||
protected $message = [
|
||||
|
||||
];
|
||||
|
||||
// 场景
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
|
||||
],
|
||||
|
||||
'update' => [
|
||||
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -43,21 +43,8 @@ class StockOrderItems extends Model
|
||||
*/
|
||||
protected $fillable = [
|
||||
'id', 'order_id', 'product_code', 'sku_id', 'sku_snapshot',
|
||||
'model_snapshot', 'quantity', 'color_interior_coide', 'color_interior_snapshot',
|
||||
'color_exterior_coide', 'color_exterior_snapshot', 'remark',
|
||||
// 'unit_price',
|
||||
// 'handling_fee',
|
||||
// 'fob_transport_fee',
|
||||
// 'port_misc_fee',
|
||||
// 'retrofit_fee',
|
||||
// 'total_price',
|
||||
'created_at', 'updated_at',
|
||||
// 'unit_price_value_id',
|
||||
// 'handling_fee_value_id',
|
||||
// 'fob_transport_fee_value_id',
|
||||
// 'port_misc_fee_value_id',
|
||||
// 'retrofit_fee_value_id',
|
||||
// 'total_price_value_id'
|
||||
'model_snapshot', 'quantity', 'color_interior_code', 'color_interior_snapshot',
|
||||
'color_exterior_code', 'color_exterior_snapshot', 'remark', 'vin', 'created_at', 'updated_at'
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
use app\common\traits\HasMoneyJournal;
|
||||
|
||||
class StockOrders extends Model
|
||||
{
|
||||
@@ -58,6 +59,13 @@ class StockOrders extends Model
|
||||
protected $guarded = [];
|
||||
|
||||
|
||||
use HasMoneyJournal;
|
||||
// 实现抽象方法
|
||||
protected static function moneyObjectScene(): string
|
||||
{
|
||||
return 'stock_orders';
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联订单项
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ use app\common\model\StockOrderItems;
|
||||
use app\common\validate\StockOrdersValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\StockOrders;
|
||||
use app\common\model\SysCurrency;
|
||||
use support\Request;
|
||||
use app\common\model\User;
|
||||
use support\exception\BusinessException;
|
||||
@@ -45,9 +46,26 @@ class StockOrdersService
|
||||
$config['model'] = $this->modelClass;
|
||||
switch ($scene) {
|
||||
case 'index':
|
||||
|
||||
// ====== ====== 筛选 ====== ======
|
||||
// 业务id
|
||||
if (isset($p['salesperson_id']) && $p['salesperson_id']) {
|
||||
$config['where']['salesperson_id'] = $p['salesperson_id'];
|
||||
}
|
||||
// 初、终版
|
||||
$config['where']['is_final'] = $p['is_final'] ?? 0;
|
||||
// 关键字
|
||||
if (isset($p['keyword']) && $p['keyword']) {
|
||||
$k = '%' . $p['keyword'] . '%';
|
||||
foreach (['pi_no', 'order_no', 'customer_snapshot'] as $col) {
|
||||
$config['orWhere'][] = [$col, 'like', $k];
|
||||
}
|
||||
}
|
||||
|
||||
$config['order']['id'] = 'desc';
|
||||
break;
|
||||
case 'show':
|
||||
case 'print':
|
||||
$config['where']['id'] = $p['id'];
|
||||
|
||||
$config['with'][] = 'items';
|
||||
@@ -79,6 +97,10 @@ class StockOrdersService
|
||||
$created = $this->logicCreate($data, $dl); // 表1
|
||||
|
||||
// 车辆
|
||||
$cifTotalAmount = 0;
|
||||
$cifTotalAmountRate = 0;
|
||||
$cifTotalAmountCurrency = 'CNY';
|
||||
// $profit = 0;
|
||||
foreach ($data['veh_list'] as $veh) {
|
||||
$veh['order_id'] = $created->id;
|
||||
$item = StockOrderItems::create($veh); // 表2
|
||||
@@ -93,6 +115,15 @@ class StockOrdersService
|
||||
$item->addMoney('port_misc_fee', $veh['port_misc_fee']['currency'], $veh['port_misc_fee']['amount'], $veh['port_misc_fee']['rate'], '港杂');
|
||||
$item->addMoney('retrofit_fee', $veh['retrofit_fee']['currency'], $veh['retrofit_fee']['amount'], $veh['retrofit_fee']['rate'], '改装费');
|
||||
|
||||
// 利润
|
||||
$profit = $veh['unit_price']['amount'] - ($veh['official_price']['amount'] / $created->contract_exchange_rate) - $veh['discount_price']['amount'] - $veh['handling_fee']['amount'] - $veh['fob_transport_fee']['amount'] - $veh['port_misc_fee']['amount'] - $veh['retrofit_fee']['amount'];
|
||||
$item->addMoney('profit', $veh['unit_price']['currency'], $profit, $veh['unit_price']['rate'], '利润');
|
||||
|
||||
// cif_total_amount
|
||||
$cifTotalAmount += $veh['total_price']['amount'];
|
||||
$cifTotalAmountRate = $veh['retrofit_fee']['rate'];
|
||||
$cifTotalAmountCurrency = $veh['retrofit_fee']['currency'];
|
||||
|
||||
// 改装
|
||||
if (count($veh['modify_list']) > 0) {
|
||||
foreach ($veh['modify_list'] as $modify) {
|
||||
@@ -102,6 +133,9 @@ class StockOrdersService
|
||||
}
|
||||
}
|
||||
|
||||
// 计算&&插入 表1
|
||||
$created->addMoney('cif_total_amount', $cifTotalAmountCurrency, $cifTotalAmount, $cifTotalAmountRate, '合计到岸价');
|
||||
|
||||
// 账户
|
||||
if (count($data['account_list']) > 0) {
|
||||
foreach ($data['account_list'] as $account) {
|
||||
@@ -129,17 +163,156 @@ class StockOrdersService
|
||||
return $data;
|
||||
}
|
||||
|
||||
// protected function afterServiceStore($res)
|
||||
// {
|
||||
|
||||
// return $res;
|
||||
// }
|
||||
|
||||
|
||||
public function serviceShow(Request $request)
|
||||
protected function afterServiceStore($res)
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, 'show');
|
||||
// 处理原单
|
||||
if ($res->is_final == 1 && $res->object_id > 0) {
|
||||
$order = StockOrders::find($res->object_id);
|
||||
$order->is_use = 1;
|
||||
$order->object_id = $res->id;
|
||||
if (!$order->save()) {
|
||||
flg('备货单【'.$order->order_no.'】创建终版成功,但更新原单失败');
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
public function servicePrint(Request $request)
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, 'print');
|
||||
$item = $this->logicShow($config);
|
||||
return $this->postProcess('Show', $item);
|
||||
$pdfData = $this->pdfData($item);
|
||||
|
||||
// 渲染模板为最终HTML
|
||||
$tplPath = app_path('common/utils/pdf/template/stockOrder.stub');
|
||||
$html = \app\common\utils\pdf\service\PdfHelper::render($tplPath, ['data' => $pdfData]);
|
||||
|
||||
// 生成PDF
|
||||
$driver = new \app\common\utils\pdf\service\MpdfDriver();
|
||||
$pdfBin = $driver->htmlToPdf($html);
|
||||
|
||||
// 存储
|
||||
$dir = public_path('pdf');
|
||||
$fileName = '/'.($pdfData['order_no'] ?? date('YmdHis')) . '.pdf';
|
||||
|
||||
// 本地保存
|
||||
$filePath = \app\common\utils\pdf\service\PdfStorage::saveBinary(
|
||||
$pdfBin,
|
||||
$dir,
|
||||
$fileName
|
||||
);
|
||||
|
||||
// // 在线预览
|
||||
// return new \support\Response(200, [
|
||||
// 'Content-Type' => 'application/pdf',
|
||||
// 'Content-Disposition' => 'inline; filename="stock-order.pdf"',
|
||||
// ], $pdfBin);
|
||||
|
||||
return ['file_url' => $dir.$fileName];
|
||||
}
|
||||
|
||||
|
||||
public function pdfData($item)
|
||||
{
|
||||
$vehList = [];
|
||||
$index = 0;
|
||||
foreach ($item->items as $k => $v) {
|
||||
// 存在则数量加1
|
||||
if (count($vehList) > 0 && isset($vehList[$v->sku_id])) {
|
||||
$vehList[$v->sku_id]['num'] += 1;
|
||||
$vehList[$v->sku_id]['total_price'] += $vehList[$v->sku_id]['unit_price'];
|
||||
$vehList[$v->sku_id]['total_price_text'] = $vehList[$v->sku_id]['total_price_symbol'].number_format($vehList[$v->sku_id]['total_price'], 2, '.', ',');
|
||||
continue;
|
||||
}
|
||||
$index += 1;
|
||||
// 处理金额
|
||||
$mv = [];
|
||||
foreach ($v->moneyValues as $money) {
|
||||
$mv[$money->field] = [
|
||||
'currency' => $money->currency,
|
||||
'amount' => $money->amount,
|
||||
'rate' => $money->rate,
|
||||
'currency_symbol' => SysCurrency::where('currency_code', $money->currency)->value('currency_symbol'),
|
||||
];
|
||||
}
|
||||
// 处理改装
|
||||
$mfy = '';
|
||||
foreach ($v->options as $my) {
|
||||
$mfy .= $my->option_name_snapshot.' ';
|
||||
}
|
||||
|
||||
// 处理结果集
|
||||
$vehList[$v->sku_id] = [
|
||||
'index' => $index,
|
||||
'model_snapshot' => $v->model_snapshot,
|
||||
'color_interior_snapshot' => $v->color_interior_snapshot,
|
||||
'color_exterior_snapshot' => $v->color_exterior_snapshot,
|
||||
'num' => 1,
|
||||
'modify' => $mfy,
|
||||
|
||||
'unit_price' => $mv['unit_price']['amount'],
|
||||
'total_price' => $mv['total_price']['amount'],
|
||||
'official_price' => $mv['official_price']['amount'],
|
||||
'discount_price' => $mv['discount_price']['amount'],
|
||||
|
||||
'unit_price_symbol' => $mv['unit_price']['currency_symbol'],
|
||||
'total_price_symbol' => $mv['total_price']['currency_symbol'],
|
||||
'official_price_symbol' => $mv['official_price']['currency_symbol'],
|
||||
'discount_price_symbol' => $mv['discount_price']['currency_symbol'],
|
||||
|
||||
'unit_price_text' => $mv['unit_price']['currency_symbol'].number_format($mv['unit_price']['amount'], 2, '.', ','),
|
||||
'total_price_text' => $mv['total_price']['currency_symbol'].number_format($mv['total_price']['amount'], 2, '.', ','),
|
||||
'official_price_text' => $mv['official_price']['currency_symbol'].number_format($mv['official_price']['amount'], 2, '.', ','),
|
||||
'discount_price_text' => $mv['discount_price']['currency_symbol'].number_format($mv['discount_price']['amount'], 2, '.', ','),
|
||||
];
|
||||
}
|
||||
|
||||
$accList = [];
|
||||
$accIndex = 1;
|
||||
foreach ($item->accounts as $k => $v) {
|
||||
$accList[] = [
|
||||
'index' => '收款'.$k + 1,
|
||||
'remit_country_name_snapshot' => $v->remit_country_name_snapshot,
|
||||
'remit_date' => date('Y-m-d', $v->remit_date),
|
||||
'currency_name_snapshot' => $v->currency_name_snapshot,
|
||||
'account_name_snapshot' => $v->account_name_snapshot,
|
||||
'remitter_name' => $v->remitter_name,
|
||||
'amount' => $v->amount,
|
||||
];
|
||||
$accIndex += 1;
|
||||
}
|
||||
|
||||
$veh = [
|
||||
'order_no' => $item->order_no,
|
||||
'salesperson_snapshot' => $item->salesperson_snapshot,
|
||||
|
||||
'pi_no' => $item->pi_no,
|
||||
'biz_date' => date('Y-m-d', $item->biz_date),
|
||||
'clearance_country_snapshot' => $item->clearance_country_snapshot,
|
||||
'customer_source_snapshot' => $item->customer_source_snapshot,
|
||||
'customer_snapshot' => $item->customer_snapshot,
|
||||
'customer_country_snapshot' => $item->customer_country_snapshot,
|
||||
|
||||
// 订单项
|
||||
'veh_list' => $vehList,
|
||||
|
||||
'contract_exchange_rate' => $item->contract_exchange_rate,
|
||||
'cif_total_amount' => $item->cif_total_amount,
|
||||
|
||||
'declarant_name' => $item->declarant_name,
|
||||
'eta_ship_date' => date('Y-m-d', $item->eta_ship_date),
|
||||
'shipping_method_snapshot' => $item->shipping_method_snapshot,
|
||||
'domestic_port_snapshot' => $item->domestic_port_snapshot,
|
||||
'trade_term_snapshot' => $item->trade_term_snapshot,
|
||||
'clearance_port_snapshot' => $item->clearance_port_snapshot,
|
||||
'latest_arrival_date' => date('Y-m-d', $item->latest_arrival_date),
|
||||
|
||||
// 账户
|
||||
'account_list' => $accList,
|
||||
'acc_index' => $accIndex
|
||||
];
|
||||
|
||||
return $veh;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\approval\model;
|
||||
namespace app\common\utils\approval\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\approval\model;
|
||||
namespace app\common\utils\approval\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\approval\model;
|
||||
namespace app\common\utils\approval\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\approval\model;
|
||||
namespace app\common\utils\approval\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
+1
-7
@@ -1,15 +1,9 @@
|
||||
<?php
|
||||
namespace app\common\approval\service;
|
||||
namespace app\common\utils\approval\service;
|
||||
|
||||
use DateTime;
|
||||
use support\exception\BusinessException;
|
||||
use support\Db;
|
||||
|
||||
use app\common\approval\model\ApprovalCcs;
|
||||
use app\common\approval\model\ApprovalFlows;
|
||||
use app\common\approval\model\ApprovalInstances;
|
||||
use app\common\approval\model\ApprovalRecords;
|
||||
|
||||
class ApprovalFlowService
|
||||
{
|
||||
// --- 公共方法 ---
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace app\common\approval\service;
|
||||
namespace app\common\utils\approval\service;
|
||||
|
||||
use DateTime;
|
||||
use support\exception\BusinessException;
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// app/controller/PdfController.php
|
||||
namespace app\controller;
|
||||
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use support\View;
|
||||
use app\common\utils\pdf\service\MpdfDriver;
|
||||
use app\common\utils\pdf\service\PdfHelper;
|
||||
|
||||
class PdfController
|
||||
{
|
||||
// ① 从“现有 HTML 文件 + 外链CSS”生成并在线预览
|
||||
public function previewFromFile(Request $request): Response
|
||||
{
|
||||
// 你的模板路径(示例)
|
||||
$tpl = base_path('resource/templates/stock-order.html');
|
||||
// 可选:把 {{变量}} 替换
|
||||
$html = PdfHelper::loadTemplate($tpl, [
|
||||
'title' => '备货单',
|
||||
'order_no' => 'SO' . date('YmdHis'),
|
||||
'customer' => 'Magic Bridge Auto FZE',
|
||||
]);
|
||||
|
||||
$driver = new MpdfDriver();
|
||||
$pdf = $driver->htmlToPdf($html, [
|
||||
// 若 HTML 里用到相对资源(img/css),给个基准目录
|
||||
'asset_base' => public_path('assets/pdf'),
|
||||
// 也可以单独传 CSS 文件(推荐把 CSS 放在 public/assets/pdf)
|
||||
'css_files' => [
|
||||
public_path('assets/pdf/stock-order.css'),
|
||||
],
|
||||
'header_html' => '<div style="text-align:right;color:#888;font-size:10px">Company • Stock Order</div>',
|
||||
'footer_html' => '<div style="text-align:center;color:#888;font-size:10px">{PAGENO} / {nbpg}</div>',
|
||||
'watermark_text' => '', // 'DRAFT'
|
||||
]);
|
||||
|
||||
return new Response(200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'inline; filename="stock-order.pdf"',
|
||||
], $pdf);
|
||||
}
|
||||
|
||||
// ② 直接下载
|
||||
public function download(Request $request): Response
|
||||
{
|
||||
$html = '<h1>发票</h1><p>客户:永生花</p>';
|
||||
$pdf = (new MpdfDriver())->htmlToPdf($html);
|
||||
return new Response(200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="invoice.pdf"',
|
||||
], $pdf);
|
||||
}
|
||||
|
||||
// ③ 保存到本地(或上传 OSS 前先落盘)
|
||||
public function save(Request $request): Response
|
||||
{
|
||||
$tpl = base_path('resource/templates/invoice.html');
|
||||
$html = PdfHelper::loadTemplate($tpl, [
|
||||
'invoice_no' => 'INV' . date('YmdHis'),
|
||||
'amount' => '12345.67',
|
||||
]);
|
||||
|
||||
$pdf = (new MpdfDriver())->htmlToPdf($html, [
|
||||
'css_files' => [ public_path('assets/pdf/invoice.css') ],
|
||||
'asset_base'=> public_path('assets/pdf'),
|
||||
]);
|
||||
|
||||
$dir = runtime_path('pdf');
|
||||
if (!is_dir($dir)) mkdir($dir, 0777, true);
|
||||
$path = $dir . '/invoice-' . date('YmdHis') . '.pdf';
|
||||
file_put_contents($path, $pdf);
|
||||
|
||||
return json(['path' => $path]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
// app/service/pdf/MpdfDriver.php
|
||||
namespace app\common\utils\pdf\service;
|
||||
|
||||
use Mpdf\Mpdf;
|
||||
use Mpdf\Config\ConfigVariables;
|
||||
use Mpdf\Config\FontVariables;
|
||||
use Mpdf\HTMLParserMode;
|
||||
|
||||
class MpdfDriver
|
||||
{
|
||||
|
||||
public function htmlToPdf(string $html, array $options = []): string
|
||||
{
|
||||
$opts = array_merge([
|
||||
'format' => 'A4',
|
||||
'orientation' => 'L',
|
||||
'margin_top' => 12,
|
||||
'margin_bottom' => 12,
|
||||
'margin_left' => 10,
|
||||
'margin_right' => 10,
|
||||
'tempDir' => runtime_path('mpdf'),
|
||||
'header_html' => '',
|
||||
'footer_html' => '',
|
||||
'watermark_text'=> '',
|
||||
'css_files' => [],
|
||||
'default_font' => 'notosanssc',
|
||||
'font_dirs' => [public_path('fonts')],
|
||||
'font_data' => [
|
||||
'notosanssc' => ['R'=>'NotoSansSC-Regular.ttf','B'=>'NotoSansSC-Bold.ttf'],
|
||||
],
|
||||
'asset_base' => '',
|
||||
'enable_html5_parser' => true,
|
||||
], $options);
|
||||
|
||||
$defaultConfig = (new ConfigVariables())->getDefaults();
|
||||
$defaultFontConfig = (new FontVariables())->getDefaults();
|
||||
|
||||
$fontDirs = array_merge($defaultConfig['fontDir'], $opts['font_dirs']);
|
||||
$fontData = array_merge($defaultFontConfig['fontdata'], $opts['font_data']);
|
||||
|
||||
$mpdf = new Mpdf([
|
||||
'format' => $opts['format'],
|
||||
'orientation' => $opts['orientation'],
|
||||
'margin_left' => $opts['margin_left'],
|
||||
'margin_right' => $opts['margin_right'],
|
||||
'margin_top' => $opts['margin_top'],
|
||||
'margin_bottom' => $opts['margin_bottom'],
|
||||
'tempDir' => $opts['tempDir'],
|
||||
'fontDir' => $fontDirs,
|
||||
'fontdata' => $fontData,
|
||||
'default_font' => $opts['default_font'],
|
||||
'enable_html5_parser' => $opts['enable_html5_parser'],
|
||||
]);
|
||||
|
||||
if ($opts['header_html']) $mpdf->SetHTMLHeader($opts['header_html']);
|
||||
if ($opts['footer_html']) $mpdf->SetHTMLFooter($opts['footer_html']);
|
||||
|
||||
if (!empty($opts['watermark_text'])) {
|
||||
$mpdf->SetWatermarkText($opts['watermark_text']);
|
||||
$mpdf->showWatermarkText = true;
|
||||
$mpdf->watermarkTextAlpha = 0.08;
|
||||
}
|
||||
|
||||
// 资源绝对化(如果你有相对路径的图片等)
|
||||
$html = PdfHelper::absolutizeAssetUrls($html, $opts['asset_base']);
|
||||
|
||||
// === 关键改动:把 <style>…</style> 抽出来,作为 HEADER_CSS 写入 ===
|
||||
[$inlineCss, $cleanHtml] = $this->extractCssAndCleanHtml($html);
|
||||
|
||||
// 先写外部 css_files
|
||||
if (!empty($opts['css_files'])) {
|
||||
foreach ($opts['css_files'] as $cssFile) {
|
||||
if (is_file($cssFile)) {
|
||||
$mpdf->WriteHTML(file_get_contents($cssFile), HTMLParserMode::HEADER_CSS);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 再写内联 <style> 的 CSS
|
||||
if ($inlineCss !== '') {
|
||||
$mpdf->WriteHTML($inlineCss, HTMLParserMode::HEADER_CSS);
|
||||
}
|
||||
|
||||
// 最后写 HTML 正文(已剔除 <style>,避免任何情况下把 CSS 当文字打印)
|
||||
$mpdf->WriteHTML($cleanHtml, HTMLParserMode::HTML_BODY);
|
||||
|
||||
return $mpdf->Output('', \Mpdf\Output\Destination::STRING_RETURN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把所有 <style>...</style> 的内容拼成一段 CSS,并把这些 <style> 标签从 HTML 中移除
|
||||
* @return array{0:string,1:string} [css, htmlWithoutStyle]
|
||||
*/
|
||||
private function extractCssAndCleanHtml(string $html): array
|
||||
{
|
||||
$cssParts = [];
|
||||
// 抓取所有 <style ...>...</style>,DOTALL 支持跨行
|
||||
if (preg_match_all('/<style\b[^>]*>(.*?)<\/style>/is', $html, $m)) {
|
||||
foreach ($m[1] as $block) {
|
||||
// 去掉 HTML 注释,以防注释把 CSS 包起来
|
||||
$block = preg_replace('/<!--|-->/', '', $block);
|
||||
$cssParts[] = trim($block);
|
||||
}
|
||||
// 从 HTML 删除所有 <style> 块
|
||||
$html = preg_replace('/<style\b[^>]*>.*?<\/style>/is', '', $html);
|
||||
}
|
||||
$css = implode("\n\n", $cssParts);
|
||||
|
||||
// 额外保险:把 <head> 也清理掉,防止某些解析器把 head 里残余文本当正文
|
||||
$html = preg_replace('/<head\b[^>]*>.*?<\/head>/is', '', $html);
|
||||
|
||||
// 确保剩余 HTML 有 <body>,没有就包一层
|
||||
if (!preg_match('/<body\b/i', $html)) {
|
||||
$html = '<body>' . $html . '</body>';
|
||||
}
|
||||
return [$css, $html];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
// app/service/pdf/PdfHelper.php
|
||||
namespace app\common\utils\pdf\service;
|
||||
|
||||
class PdfHelper
|
||||
{
|
||||
/** 从文件加载 HTML(支持占位符 {{key}} 简单替换) */
|
||||
public static function loadTemplate(string $file, array $vars = []): string
|
||||
{
|
||||
if (!is_file($file)) {
|
||||
throw new \RuntimeException("Template not found: {$file}");
|
||||
}
|
||||
$html = file_get_contents($file);
|
||||
if ($vars) {
|
||||
$search = [];
|
||||
$replace = [];
|
||||
foreach ($vars as $k => $v) {
|
||||
$search[] = '{{' . $k . '}}';
|
||||
$replace[] = htmlspecialchars((string)$v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
$html = str_replace($search, $replace, $html);
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 HTML 里的相对资源路径(img/link/script href/src)改成绝对路径
|
||||
* 仅处理以 ./ ../ 或非 http(s) 的相对量;base 传 public/assets/pdf 等目录
|
||||
*/
|
||||
public static function absolutizeAssetUrls(string $html, string $baseDir): string
|
||||
{
|
||||
$baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR);
|
||||
// img src
|
||||
$html = preg_replace_callback('#(<img[^>]+src=["\'])([^"\']+)(["\'])#i', function($m) use ($baseDir){
|
||||
$u = $m[2];
|
||||
if (preg_match('#^https?://#i', $u) || strpos($u, 'data:') === 0) return $m[0];
|
||||
$abs = realpath($baseDir . DIRECTORY_SEPARATOR . ltrim($u, '/')) ?: ($baseDir . '/' . ltrim($u, '/'));
|
||||
return $m[1] . $abs . $m[3];
|
||||
}, $html);
|
||||
|
||||
// link href(如果你在 HTML 里写了 <link>,通常建议走 css_files 方式,这里也兼容)
|
||||
$html = preg_replace_callback('#(<link[^>]+href=["\'])([^"\']+)(["\'])#i', function($m) use ($baseDir){
|
||||
$u = $m[2];
|
||||
if (preg_match('#^https?://#i', $u)) return $m[0];
|
||||
$abs = realpath($baseDir . DIRECTORY_SEPARATOR . ltrim($u, '/')) ?: ($baseDir . '/' . ltrim($u, '/'));
|
||||
return $m[1] . $abs . $m[3];
|
||||
}, $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 渲染一个原生 PHP 模板为字符串
|
||||
* @param string $file 模板路径(如 resource/templates/stock-order.php)
|
||||
* @param array $vars 将在模板中以变量名存在(如 $data)
|
||||
* @return string 完整 HTML
|
||||
*/
|
||||
public static function render(string $file, array $vars = []): string
|
||||
{
|
||||
if (!is_file($file)) {
|
||||
throw new \RuntimeException("Template not found: {$file}");
|
||||
}
|
||||
// 将键名导出为同名变量,例如 'data' => [...] 变为 $data
|
||||
extract($vars, EXTR_OVERWRITE);
|
||||
|
||||
// 开启缓冲,将 include 的输出抓取为字符串
|
||||
ob_start();
|
||||
ob_implicit_flush(false);
|
||||
try {
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
include $file;
|
||||
} catch (\Throwable $e) {
|
||||
// 清理缓冲并抛出更清晰的异常
|
||||
ob_end_clean();
|
||||
throw $e;
|
||||
}
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace app\common\utils\pdf\service;
|
||||
|
||||
final class PdfStorage
|
||||
{
|
||||
/**
|
||||
* @param string $pdfBin PDF 二进制
|
||||
* @param string|null $dir 保存目录,默认 runtime/pdf
|
||||
* @param string|null $name 文件名(不含目录),默认:stock-order-YYYYmmdd-His-uniqid.pdf
|
||||
* @return string 绝对路径
|
||||
*/
|
||||
public static function saveBinary(string $pdfBin, ?string $dir = null, ?string $name = null): string
|
||||
{
|
||||
$dir = $dir ?: runtime_path('pdf');
|
||||
if (!is_dir($dir)) {
|
||||
if (!@mkdir($dir, 0775, true) && !is_dir($dir)) {
|
||||
throw new \RuntimeException("Failed to create dir: {$dir}");
|
||||
}
|
||||
}
|
||||
|
||||
// 生成安全文件名
|
||||
$name = $name ?: ('stock-order-' . date('Ymd-His') . '-' . substr(uniqid('', true), -6) . '.pdf');
|
||||
$name = preg_replace('/[^a-zA-Z0-9_\-.]/', '_', $name); // 简易净化
|
||||
$path = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name;
|
||||
|
||||
// 先写入临时文件,再原子重命名,避免并发/写入中被读取
|
||||
$tmp = $path . '.part';
|
||||
if (file_put_contents($tmp, $pdfBin, LOCK_EX) === false) {
|
||||
throw new \RuntimeException("Failed to write temp file: {$tmp}");
|
||||
}
|
||||
if (!@rename($tmp, $path)) {
|
||||
@unlink($tmp);
|
||||
throw new \RuntimeException("Failed to move temp to: {$path}");
|
||||
}
|
||||
@chmod($path, 0664);
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>合塑实业-汽车事业部-备货单(客户下定)</title>
|
||||
<style>
|
||||
/* 横版:A4 297mm,左右各 12mm => 可用宽度 273mm */
|
||||
@page { margin: 12mm; }
|
||||
|
||||
body {
|
||||
font-family: 'notosanssc', 'DejaVu Sans', Arial, sans-serif;
|
||||
font-size: 10pt;
|
||||
line-height: 1.3;
|
||||
color: #000;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 273mm; /* 横向版心 */
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 10pt;
|
||||
padding-bottom: 6pt;
|
||||
}
|
||||
.title {
|
||||
font-size: 14pt;
|
||||
font-weight: bold;
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
.sub-title {
|
||||
font-size: 9pt;
|
||||
color: #000;
|
||||
margin: 0 6pt;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10pt;
|
||||
font-size: 9pt;
|
||||
table-layout: fixed; /* 横向固定布局,配合 colgroup 更稳 */
|
||||
}
|
||||
th, td {
|
||||
border: 0.5pt solid #000;
|
||||
padding: 6pt 6pt;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
word-break: break-all;
|
||||
}
|
||||
th {
|
||||
background-color: #f0f0f0;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* 跨页友好 */
|
||||
thead { display: table-header-group; }
|
||||
tfoot { display: table-row-group; }
|
||||
tr, img { page-break-inside: avoid; }
|
||||
|
||||
/* 信息类表格左对齐 */
|
||||
.info-table td,
|
||||
.logistics-table td { text-align: left; }
|
||||
|
||||
.info-label { font-weight: bold; color: #000; }
|
||||
.info-value { color: #000; }
|
||||
|
||||
.summary-row {
|
||||
background-color: #f0f0f0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.payment-title {
|
||||
background-color: #f0f0f0;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nowrap { white-space: nowrap; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 头部 -->
|
||||
<div class="header">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<table class="info-table">
|
||||
<colgroup>
|
||||
<col style="width:18%">
|
||||
<col style="width:15%">
|
||||
<col style="width:14%">
|
||||
<col style="width:14%">
|
||||
<col style="width:19%">
|
||||
<col style="width:20%">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="info-label">阿里订单号/PI号:</span> <span class="info-value"><?php echo htmlspecialchars($data['pi_no'] ?? ' - '); ?></span></td>
|
||||
<td><span class="info-label">日期:</span> <span class="info-value nowrap"><?php echo htmlspecialchars($data['biz_date'] ?? ' - '); ?></span></td>
|
||||
<td><span class="info-label">关目的国:</span> <span class="info-value"><?php echo htmlspecialchars($data['clearance_country_snapshot'] ?? ' - '); ?></span></td>
|
||||
<td><span class="info-label">客户来源:</span> <span class="info-value"><?php echo htmlspecialchars($data['customer_source_snapshot'] ?? ' - '); ?></span></td>
|
||||
<td><span class="info-label">客户名称:</span> <span class="info-value"><?php echo htmlspecialchars($data['customer_snapshot'] ?? ' - '); ?></span></td>
|
||||
<td><span class="info-label">客户所在国:</span> <span class="info-value"><?php echo htmlspecialchars($data['customer_country_snapshot'] ?? ' - '); ?></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 产品列表 -->
|
||||
<table class="product-table">
|
||||
<colgroup>
|
||||
<col style="width:6%">
|
||||
<col style="width:34%">
|
||||
<col style="width:6%">
|
||||
<col style="width:6%">
|
||||
<col style="width:6%">
|
||||
<col style="width:14%">
|
||||
<col style="width:8%">
|
||||
<col style="width:8%">
|
||||
<col style="width:6%">
|
||||
<col style="width:6%">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>编号</th>
|
||||
<th>车型</th>
|
||||
<th>数量/台</th>
|
||||
<th>颜色/外</th>
|
||||
<th>内饰</th>
|
||||
<th>改装</th>
|
||||
<th class="nowrap">单价(美金)</th>
|
||||
<th class="nowrap">总价(美金)</th>
|
||||
<th class="nowrap">官方指导价</th>
|
||||
<th class="nowrap">优惠</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($data['veh_list'] ?? []) as $vehicle): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($vehicle['index'] ?? ' - '); ?></td>
|
||||
<td><?php echo htmlspecialchars($vehicle['model_snapshot'] ?? ' - '); ?></td>
|
||||
<td><?php echo htmlspecialchars($vehicle['num'] ?? ' - '); ?></td>
|
||||
<td><?php echo htmlspecialchars($vehicle['color_exterior_snapshot'] ?? ' - '); ?></td>
|
||||
<td><?php echo htmlspecialchars($vehicle['color_interior_snapshot'] ?? ' - '); ?></td>
|
||||
<td><?php echo htmlspecialchars($vehicle['modify'] ?? ' - '); ?></td>
|
||||
<td class="nowrap"><?php echo htmlspecialchars($vehicle['unit_price_text'] ?? ' - '); ?></td>
|
||||
<td class="nowrap"><?php echo htmlspecialchars($vehicle['total_price_text'] ?? ' - '); ?></td>
|
||||
<td class="nowrap"><?php echo htmlspecialchars($vehicle['official_price_text'] ?? ' - '); ?></td>
|
||||
<td class="nowrap"><?php echo htmlspecialchars($vehicle['discount_price_text'] ?? ' - '); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<tr class="summary-row">
|
||||
<td colspan="6" style="text-align:left;">
|
||||
<span class="info-label">合同汇率:</span> <span class="info-value"><?php echo htmlspecialchars($data['contract_exchange_rate'] ?? ' - '); ?></span>
|
||||
</td>
|
||||
<td colspan="4" style="text-align:left;">
|
||||
<span class="info-label">合计到岸价:</span> <span class="info-value"><?php echo htmlspecialchars($data['cif_total_amount'] ?? ' - '); ?></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 物流信息 -->
|
||||
<table class="logistics-table">
|
||||
<colgroup>
|
||||
<col style="width:16%">
|
||||
<col style="width:18%">
|
||||
<col style="width:12%">
|
||||
<col style="width:18%">
|
||||
<col style="width:22%">
|
||||
<col style="width:14%">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="info-label">清关人:</span> <span class="info-value"><?php echo htmlspecialchars($data['declarant_name'] ?? ' - '); ?></span></td>
|
||||
<td><span class="info-label">预计发运日期:</span> <span class="info-value nowrap"><?php echo htmlspecialchars($data['eta_ship_date'] ?? ' - '); ?></span></td>
|
||||
<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'] ?? ' - '); ?>/<?php echo htmlspecialchars($data['clearance_port_snapshot'] ?? ' - '); ?></span></td>
|
||||
<td><span class="info-label">最迟到港时间:</span> <span class="info-value nowrap"><?php echo htmlspecialchars($data['latest_arrival_date'] ?? ' - '); ?></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 计划收款信息 -->
|
||||
<table class="payment-table">
|
||||
<colgroup>
|
||||
<col style="width:12%">
|
||||
<col style="width:10%">
|
||||
<col style="width:14%">
|
||||
<col style="width:12%">
|
||||
<col style="width:12%">
|
||||
<col style="width:20%">
|
||||
<col style="width:10%">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:24mm" rowspan="<?php echo htmlspecialchars($data['acc_index']); ?>">计划收款信息</th>
|
||||
<th>序号</th>
|
||||
<th>汇款国</th>
|
||||
<th>日期</th>
|
||||
<th>币种</th>
|
||||
<th>账户</th>
|
||||
<th>汇款人</th>
|
||||
<th>金额</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($data['account_list'] ?? []) as $account): ?>
|
||||
<tr>
|
||||
<td class="nowrap"><?php echo htmlspecialchars($account['index'] ?? ' - '); ?></td>
|
||||
<td><?php echo htmlspecialchars($account['remit_country_name_snapshot'] ?? ' - '); ?></td>
|
||||
<td class="nowrap"><?php echo htmlspecialchars($account['remit_date'] ?? ' - '); ?></td>
|
||||
<td class="nowrap"><?php echo htmlspecialchars($account['currency_name_snapshot'] ?? ' - '); ?></td>
|
||||
<td class="nowrap"><?php echo htmlspecialchars($account['account_name_snapshot'] ?? ' - '); ?></td>
|
||||
<td style="text-align:left;"><?php echo htmlspecialchars($account['remitter_name'] ?? ' - '); ?></td>
|
||||
<td class="nowrap"><?php echo htmlspecialchars($account['amount'] ?? ' - '); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -77,27 +77,27 @@ class IndexController
|
||||
public function createResource()
|
||||
{
|
||||
$options = [
|
||||
// 'controller' => [
|
||||
// 'module' => 'common',
|
||||
// 'name' => 'stock_orders'
|
||||
// ],
|
||||
'model' => [
|
||||
'module' => 'approval',
|
||||
'name' => 'approval_records'
|
||||
'controller' => [
|
||||
'module' => 'admin',
|
||||
'name' => 'approval_flows'
|
||||
],
|
||||
// 'service' => [
|
||||
// 'module' => 'common',
|
||||
// 'name' => 'stock_orders'
|
||||
// ],
|
||||
// 'logic' => [
|
||||
// 'module' => 'common',
|
||||
// 'name' => 'stock_orders'
|
||||
// ],
|
||||
// 'validate' => [
|
||||
// 'module' => 'common',
|
||||
// 'name' => 'stock_orders'
|
||||
// ],
|
||||
'table' => 'approval_records'
|
||||
'model' => [
|
||||
'module' => 'admin',
|
||||
'name' => 'approval_flows'
|
||||
],
|
||||
'service' => [
|
||||
'module' => 'admin',
|
||||
'name' => 'approval_flows'
|
||||
],
|
||||
'logic' => [
|
||||
'module' => 'admin',
|
||||
'name' => 'approval_flows'
|
||||
],
|
||||
'validate' => [
|
||||
'module' => 'admin',
|
||||
'name' => 'approval_flows'
|
||||
],
|
||||
'table' => 'approval_flows'
|
||||
];
|
||||
|
||||
$result = (new Generate($options))->doGenerate();
|
||||
@@ -107,46 +107,13 @@ class IndexController
|
||||
|
||||
public function test()
|
||||
{
|
||||
$data = [
|
||||
'id' => 1,
|
||||
'user_id' => 1,
|
||||
'leave_type_id' => 1,
|
||||
'start_date' => '2019-01-01',
|
||||
'end_date' => '2019-01-02',
|
||||
'reason' => 'test',
|
||||
'status' => 1
|
||||
];
|
||||
$code = $this->submitLeaveApplication(1, $data);
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
// 假设这是你的业务逻辑
|
||||
public function submitLeaveApplication(int $bizId, $leaveData) {
|
||||
// 1. 保存业务数据
|
||||
// $bizId = DB::table('leave_applications')->insertGetId($leaveData);
|
||||
|
||||
// 2. 启动审批流程
|
||||
$service = new \app\common\approval\service\ApprovalFlowService();
|
||||
try {
|
||||
$result = $service->start(
|
||||
'leave', // 流程类型,对应 approval_flows.type
|
||||
'leave_application', // 业务类型标识
|
||||
$bizId, // 业务ID
|
||||
$leaveData, // 表单数据快照
|
||||
$_SESSION['user_id'] ?? 1 // 发起人ID
|
||||
);
|
||||
|
||||
// 3. 将审批实例ID关联回业务表
|
||||
DB::table('leave_applications')
|
||||
->where('id', $bizId)
|
||||
->update(['approval_instance_id' => $result['instance_id']]);
|
||||
|
||||
return ['status' => 'success', 'message' => '申请已提交,等待审批'];
|
||||
} catch (\Exception $e) {
|
||||
// 处理错误,可能需要回滚业务数据插入
|
||||
return ['status' => 'error', 'message' => $e->getMessage()];
|
||||
}
|
||||
return json([
|
||||
'code' => 0,
|
||||
'msg' => 'success',
|
||||
'data' => [
|
||||
'time' => time(),
|
||||
'date' => date('Y-m-d H:i:s')
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@ use support\Request;
|
||||
|
||||
class StockOrdersController extends BaseController
|
||||
{
|
||||
protected $noNeedLogin = ['print'];
|
||||
|
||||
protected $noNeedHeaderData = ['print'];
|
||||
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
@@ -23,20 +27,104 @@ class StockOrdersController extends BaseController
|
||||
* @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;
|
||||
// }
|
||||
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']);
|
||||
if (isset($data['eta_ship_date'])) $data['eta_ship_date'] = date('Y-m-d', $data['eta_ship_date']);
|
||||
if (isset($data['biz_date'])) $data['biz_date'] = date('Y-m-d', $data['biz_date']);
|
||||
if (isset($data['latest_arrival_date'])) $data['latest_arrival_date'] = date('Y-m-d', $data['latest_arrival_date']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
protected function showFormat($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']);
|
||||
if (isset($data['eta_ship_date'])) $data['eta_ship_date'] = date('Y-m-d', $data['eta_ship_date']);
|
||||
if (isset($data['biz_date'])) $data['biz_date'] = date('Y-m-d', $data['biz_date']);
|
||||
if (isset($data['latest_arrival_date'])) $data['latest_arrival_date'] = date('Y-m-d', $data['latest_arrival_date']);
|
||||
|
||||
if (!empty($data['contract'])) {
|
||||
$data['contract_url'] = (new \app\common\service\OssService)->generatePrivateUrl($data['contract']);
|
||||
}
|
||||
|
||||
if (count($data['moneyValues']) > 0) {
|
||||
foreach ($data['moneyValues'] as $mvs) {
|
||||
$data['cif_total_amount'] = [
|
||||
'exchange_rate' => $mvs['exchange_rate'],
|
||||
'amount' => $mvs['amount'],
|
||||
'currency' => $mvs['currency'],
|
||||
'id' => $mvs['id']
|
||||
];
|
||||
}
|
||||
}
|
||||
unset($data['moneyValues']);
|
||||
|
||||
foreach ($data['items'] as &$item) {
|
||||
foreach ($item['moneyValues'] as $mv) {
|
||||
$item[$mv['field']] = [
|
||||
'exchange_rate' => $mv['exchange_rate'],
|
||||
'amount' => $mv['amount'],
|
||||
'currency' => $mv['currency'],
|
||||
'id' => $mv['id']
|
||||
];
|
||||
}
|
||||
unset($item['moneyValues']);
|
||||
|
||||
foreach ($item['options'] as $opt) {
|
||||
$item['modify_list'][] = [
|
||||
"id" => $opt['id'],
|
||||
"order_item_id" => $opt['order_item_id'],
|
||||
"option_id" => $opt['option_id'],
|
||||
"option_name_snapshot" => $opt['option_name_snapshot'],
|
||||
"option_price" => $opt['option_price']
|
||||
];
|
||||
}
|
||||
unset($item['options']);
|
||||
}
|
||||
unset($item);
|
||||
|
||||
|
||||
foreach ($data['accounts'] as $acc) {
|
||||
$data['account_list'][] = [
|
||||
"id" => $acc['id'],
|
||||
"order_id" => $acc['order_id'],
|
||||
"seq_no" => $acc['seq_no'],
|
||||
"remit_country_id" => $acc['remit_country_id'],
|
||||
"remit_country_name_snapshot" => $acc['remit_country_name_snapshot'],
|
||||
"remit_date" => date('Y-m-d', $acc['remit_date']),
|
||||
"currency_code" => $acc['currency_code'],
|
||||
"currency_name_snapshot" => $acc['currency_name_snapshot'],
|
||||
"account_id" => $acc['account_id'],
|
||||
"account_name_snapshot" => $acc['account_name_snapshot'],
|
||||
"remitter_name" => $acc['remitter_name'],
|
||||
"amount" => $acc['amount']
|
||||
];
|
||||
}
|
||||
unset($data['accounts']);
|
||||
|
||||
$data['veh_list'] = $data['items'];
|
||||
unset($data['items']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function print(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->servicePrint($request);
|
||||
return $this->success($raw);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function index(Request $request)
|
||||
public function desc(Request $request)
|
||||
{
|
||||
|
||||
$data = [
|
||||
|
||||
+2
-1
@@ -46,7 +46,8 @@
|
||||
"workerman/crontab": "^1.0",
|
||||
"phpoffice/phpspreadsheet": "^4.5",
|
||||
"phpmailer/phpmailer": "^6.10",
|
||||
"webman/gateway-worker": "^1.0"
|
||||
"webman/gateway-worker": "^1.0",
|
||||
"mpdf/mpdf": "^8.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "For better performance. "
|
||||
|
||||
Generated
+306
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "7c70d8c8bda8a5098b8baed463db8839",
|
||||
"content-hash": "68e74d2aef3a2f7f8163bfaf65431cb2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aliyuncs/oss-sdk-php",
|
||||
@@ -2890,6 +2890,239 @@
|
||||
],
|
||||
"time": "2024-11-12T12:43:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mpdf/mpdf",
|
||||
"version": "v8.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mpdf/mpdf.git",
|
||||
"reference": "dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mpdf/mpdf/zipball/dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44",
|
||||
"reference": "dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-gd": "*",
|
||||
"ext-mbstring": "*",
|
||||
"mpdf/psr-http-message-shim": "^1.0 || ^2.0",
|
||||
"mpdf/psr-log-aware-trait": "^2.0 || ^3.0",
|
||||
"myclabs/deep-copy": "^1.7",
|
||||
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
|
||||
"php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"psr/log": "^1.0 || ^2.0 || ^3.0",
|
||||
"setasign/fpdi": "^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.3.0",
|
||||
"mpdf/qrcode": "^1.1.0",
|
||||
"squizlabs/php_codesniffer": "^3.5.0",
|
||||
"tracy/tracy": "~2.5",
|
||||
"yoast/phpunit-polyfills": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Needed for generation of some types of barcodes",
|
||||
"ext-xml": "Needed mainly for SVG manipulation",
|
||||
"ext-zlib": "Needed for compression of embedded resources, such as fonts"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Mpdf\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matěj Humpál",
|
||||
"role": "Developer, maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Ian Back",
|
||||
"role": "Developer (retired)"
|
||||
}
|
||||
],
|
||||
"description": "PHP library generating PDF files from UTF-8 encoded HTML",
|
||||
"homepage": "https://mpdf.github.io",
|
||||
"keywords": [
|
||||
"pdf",
|
||||
"php",
|
||||
"utf-8"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://mpdf.github.io",
|
||||
"issues": "https://github.com/mpdf/mpdf/issues",
|
||||
"source": "https://github.com/mpdf/mpdf"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.paypal.me/mpdf",
|
||||
"type": "custom"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-18T08:51:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mpdf/psr-http-message-shim",
|
||||
"version": "v2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mpdf/psr-http-message-shim.git",
|
||||
"reference": "f25a0153d645e234f9db42e5433b16d9b113920f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mpdf/psr-http-message-shim/zipball/f25a0153d645e234f9db42e5433b16d9b113920f",
|
||||
"reference": "f25a0153d645e234f9db42e5433b16d9b113920f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"psr/http-message": "^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Mpdf\\PsrHttpMessageShim\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mark Dorison",
|
||||
"email": "mark@chromatichq.com"
|
||||
},
|
||||
{
|
||||
"name": "Kristofer Widholm",
|
||||
"email": "kristofer@chromatichq.com"
|
||||
},
|
||||
{
|
||||
"name": "Nigel Cunningham",
|
||||
"email": "nigel.cunningham@technocrat.com.au"
|
||||
}
|
||||
],
|
||||
"description": "Shim to allow support of different psr/message versions.",
|
||||
"support": {
|
||||
"issues": "https://github.com/mpdf/psr-http-message-shim/issues",
|
||||
"source": "https://github.com/mpdf/psr-http-message-shim/tree/v2.0.1"
|
||||
},
|
||||
"time": "2023-10-02T14:34:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mpdf/psr-log-aware-trait",
|
||||
"version": "v3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mpdf/psr-log-aware-trait.git",
|
||||
"reference": "a633da6065e946cc491e1c962850344bb0bf3e78"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mpdf/psr-log-aware-trait/zipball/a633da6065e946cc491e1c962850344bb0bf3e78",
|
||||
"reference": "a633da6065e946cc491e1c962850344bb0bf3e78",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"psr/log": "^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Mpdf\\PsrLogAwareTrait\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mark Dorison",
|
||||
"email": "mark@chromatichq.com"
|
||||
},
|
||||
{
|
||||
"name": "Kristofer Widholm",
|
||||
"email": "kristofer@chromatichq.com"
|
||||
}
|
||||
],
|
||||
"description": "Trait to allow support of different psr/log versions.",
|
||||
"support": {
|
||||
"issues": "https://github.com/mpdf/psr-log-aware-trait/issues",
|
||||
"source": "https://github.com/mpdf/psr-log-aware-trait/tree/v3.0.0"
|
||||
},
|
||||
"time": "2023-05-03T06:19:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
|
||||
"reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/collections": "<1.6.8",
|
||||
"doctrine/common": "<2.13.3 || >=3 <3.2.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/collections": "^1.6.8",
|
||||
"doctrine/common": "^2.13.3 || ^3.2.2",
|
||||
"phpspec/prophecy": "^1.10",
|
||||
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/DeepCopy/deep_copy.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"DeepCopy\\": "src/DeepCopy/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Create deep copies (clones) of your objects",
|
||||
"keywords": [
|
||||
"clone",
|
||||
"copy",
|
||||
"duplicate",
|
||||
"object",
|
||||
"object graph"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/myclabs/DeepCopy/issues",
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-01T08:46:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "3.10.0",
|
||||
@@ -3975,6 +4208,78 @@
|
||||
},
|
||||
"time": "2019-03-08T08:55:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "setasign/fpdi",
|
||||
"version": "v2.6.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Setasign/FPDI.git",
|
||||
"reference": "4b53852fde2734ec6a07e458a085db627c60eada"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Setasign/FPDI/zipball/4b53852fde2734ec6a07e458a085db627c60eada",
|
||||
"reference": "4b53852fde2734ec6a07e458a085db627c60eada",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-zlib": "*",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"setasign/tfpdf": "<1.31"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7",
|
||||
"setasign/fpdf": "~1.8.6",
|
||||
"setasign/tfpdf": "~1.33",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"tecnickcom/tcpdf": "^6.8"
|
||||
},
|
||||
"suggest": {
|
||||
"setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"setasign\\Fpdi\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jan Slabon",
|
||||
"email": "jan.slabon@setasign.com",
|
||||
"homepage": "https://www.setasign.com"
|
||||
},
|
||||
{
|
||||
"name": "Maximilian Kresse",
|
||||
"email": "maximilian.kresse@setasign.com",
|
||||
"homepage": "https://www.setasign.com"
|
||||
}
|
||||
],
|
||||
"description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.",
|
||||
"homepage": "https://www.setasign.com/fpdi",
|
||||
"keywords": [
|
||||
"fpdf",
|
||||
"fpdi",
|
||||
"pdf"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Setasign/FPDI/issues",
|
||||
"source": "https://github.com/Setasign/FPDI/tree/v2.6.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/setasign/fpdi",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-05T09:57:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "shopwwi/filesystem-oss",
|
||||
"version": "v1.0.3",
|
||||
|
||||
@@ -289,6 +289,14 @@ Route::group('/admin', function () {
|
||||
Route::any('/destroy', [app\admin\controller\SysPortController::class, 'destroy']);
|
||||
});
|
||||
|
||||
// 审批
|
||||
Route::group('/approval', function () {
|
||||
Route::any('/list', [app\admin\controller\ApprovalFlowsController::class, 'index']);
|
||||
Route::any('/show', [app\admin\controller\ApprovalFlowsController::class, 'show']);
|
||||
Route::any('/store', [app\admin\controller\ApprovalFlowsController::class, 'store']);
|
||||
Route::any('/update', [app\admin\controller\ApprovalFlowsController::class, 'update']);
|
||||
Route::any('/destroy', [app\admin\controller\ApprovalFlowsController::class, 'destroy']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -358,7 +366,13 @@ Route::group('/crm', function () {
|
||||
Route::any('/list', [app\crm\controller\StockOrdersController::class, 'index']);
|
||||
Route::any('/show', [app\crm\controller\StockOrdersController::class, 'show']);
|
||||
Route::any('/store', [app\crm\controller\StockOrdersController::class, 'store']);
|
||||
Route::any('/print', [app\crm\controller\StockOrdersController::class, 'print']);
|
||||
// Route::any('/update', [app\crm\controller\StockOrdersController::class, 'update']);
|
||||
// Route::any('/destroy', [app\crm\controller\StockOrdersController::class, 'destroy']);
|
||||
});
|
||||
|
||||
// Route::group('/pdf', function () {
|
||||
// Route::any('/bx/{id}', [app\common\utils\pdf\controller\DocumentController::class, 'downloadReimbursementPdf']);
|
||||
// Route::any('/ckbx/{id}', [app\common\utils\pdf\controller\DocumentController::class, 'viewReimbursementPdf']);
|
||||
// });
|
||||
});
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user