diff --git a/app/admin/controller/ApprovalFlowsController.php b/app/admin/controller/ApprovalFlowsController.php
new file mode 100644
index 0000000..8f8ae24
--- /dev/null
+++ b/app/admin/controller/ApprovalFlowsController.php
@@ -0,0 +1,44 @@
+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(或签)
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/app/admin/logic/ApprovalFlowsLogic.php b/app/admin/logic/ApprovalFlowsLogic.php
new file mode 100644
index 0000000..1608782
--- /dev/null
+++ b/app/admin/logic/ApprovalFlowsLogic.php
@@ -0,0 +1,17 @@
+ 'array',
+ ];
+}
\ No newline at end of file
diff --git a/app/admin/service/ApprovalFlowsService.php b/app/admin/service/ApprovalFlowsService.php
new file mode 100644
index 0000000..3f7a4e9
--- /dev/null
+++ b/app/admin/service/ApprovalFlowsService.php
@@ -0,0 +1,62 @@
+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;
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/app/admin/validate/ApprovalFlowsValidate.php b/app/admin/validate/ApprovalFlowsValidate.php
new file mode 100644
index 0000000..9b64000
--- /dev/null
+++ b/app/admin/validate/ApprovalFlowsValidate.php
@@ -0,0 +1,29 @@
+ [
+
+ ],
+
+ 'update' => [
+
+ ]
+ ];
+}
\ No newline at end of file
diff --git a/app/common/model/StockOrderItems.php b/app/common/model/StockOrderItems.php
index 610f694..da57006 100644
--- a/app/common/model/StockOrderItems.php
+++ b/app/common/model/StockOrderItems.php
@@ -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'
];
/**
diff --git a/app/common/model/StockOrders.php b/app/common/model/StockOrders.php
index 123eb66..2c4c8d3 100644
--- a/app/common/model/StockOrders.php
+++ b/app/common/model/StockOrders.php
@@ -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';
+ }
+
/**
* 关联订单项
*/
diff --git a/app/common/service/StockOrdersService.php b/app/common/service/StockOrdersService.php
index c694177..17e9fbd 100644
--- a/app/common/service/StockOrdersService.php
+++ b/app/common/service/StockOrdersService.php
@@ -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;
}
}
\ No newline at end of file
diff --git a/app/common/approval/model/ApprovalCcs.php b/app/common/utils/approval/model/ApprovalCcs.php
similarity index 95%
rename from app/common/approval/model/ApprovalCcs.php
rename to app/common/utils/approval/model/ApprovalCcs.php
index 89be176..f44dfff 100644
--- a/app/common/approval/model/ApprovalCcs.php
+++ b/app/common/utils/approval/model/ApprovalCcs.php
@@ -1,6 +1,6 @@
'备货单',
+ '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' => '
Company • Stock Order
',
+ 'footer_html' => '{PAGENO} / {nbpg}
',
+ '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 = '发票
客户:永生花
';
+ $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]);
+ }
+}
diff --git a/app/common/utils/pdf/service/MpdfDriver.php b/app/common/utils/pdf/service/MpdfDriver.php
new file mode 100644
index 0000000..d59427a
--- /dev/null
+++ b/app/common/utils/pdf/service/MpdfDriver.php
@@ -0,0 +1,118 @@
+ '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']);
+
+ // === 关键改动:把 抽出来,作为 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);
+ }
+ }
+ }
+ // 再写内联 的内容拼成一段 CSS,并把这些 ,DOTALL 支持跨行
+ if (preg_match_all('/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 阿里订单号/PI号: |
+ 日期: |
+ 关目的国: |
+ 客户来源: |
+ 客户名称: |
+ 客户所在国: |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 编号 |
+ 车型 |
+ 数量/台 |
+ 颜色/外 |
+ 内饰 |
+ 改装 |
+ 单价(美金) |
+ 总价(美金) |
+ 官方指导价 |
+ 优惠 |
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+
+
+
+ |
+ 合同汇率:
+ |
+
+ 合计到岸价:
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 清关人: |
+ 预计发运日期: |
+ 出运方式: |
+ 国内收货港: |
+ 贸易条款/清关目的港: / |
+ 最迟到港时间: |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 计划收款信息 |
+ 序号 |
+ 汇款国 |
+ 日期 |
+ 币种 |
+ 账户 |
+ 汇款人 |
+ 金额 |
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+
+
+
+
+
+