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/service/StockOrdersService.php b/app/common/service/StockOrdersService.php index 3d82a2d..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; @@ -64,6 +65,7 @@ class StockOrdersService $config['order']['id'] = 'desc'; break; case 'show': + case 'print': $config['where']['id'] = $p['id']; $config['with'][] = 'items'; @@ -174,4 +176,143 @@ class StockOrdersService } return $res; } + + + public function servicePrint(Request $request) + { + $config = $this->buildQueryConfig($request, 'print'); + $item = $this->logicShow($config); + $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/utils/approval/model/ApprovalCcs.php b/app/common/utils/approval/model/ApprovalCcs.php index 89be176..f44dfff 100644 --- a/app/common/utils/approval/model/ApprovalCcs.php +++ b/app/common/utils/approval/model/ApprovalCcs.php @@ -1,6 +1,6 @@ generatePdfById($id); - - $filename = 'reimbursement_' . $id . '_' . date('Ymd_His') . '.pdf'; - return response($pdfData, 200, [ - 'Content-Type' => 'application/pdf', - 'Content-Disposition' => 'attachment; filename="' . $filename . '"', - ]); - - } catch (Exception $e) { - // 记录日志 - // \support\Log::error("Failed to generate reimbursement PDF: " . $e->getMessage()); - return json(['code' => 1, 'msg' => 'Failed to generate PDF: ' . $e->getMessage()]); - } - } - - /** - * 在浏览器中查看报销单 PDF - * 访问路由: GET /document/reimbursement/view/{id} - * 示例: /document/reimbursement/view/123 - */ - public function viewReimbursementPdf(Request $request, int $id) - { - try { - $service = new ReimbursementDocumentService(); - $pdfData = $service->generatePdfById($id); - - return response($pdfData, 200, [ - 'Content-Type' => 'application/pdf', - 'Content-Disposition' => 'inline; filename="reimbursement_view_' . $id . '.pdf"', // 'inline' 关键 - ]); - - } catch (Exception $e) { - return json(['code' => 1, 'msg' => 'Failed to view PDF: ' . $e->getMessage()]); - } - } - - /** - * 保存报销单 PDF 到服务器 - * 访问路由: POST /document/reimbursement/save - * 参数: id (POST) - */ - public function saveReimbursementPdf(Request $request) - { - $id = $request->post('id'); - if (!$id || !is_numeric($id)) { - return json(['code' => 1, 'msg' => 'Invalid ID']); - } - - try { - // 指定保存路径 - $savePath = runtime_path() . '/generated_pdfs/reimbursements/'; - $service = new ReimbursementDocumentService(); - $service->savePdfToFile((int)$id, $savePath); - - return json(['code' => 0, 'msg' => 'PDF saved successfully', 'path' => $savePath]); - - } catch (Exception $e) { - return json(['code' => 1, 'msg' => 'Failed to save PDF: ' . $e->getMessage()]); - } - } - - /** - * 通用 HTML 到 PDF 生成接口 (示例) - * 访问路由: POST /document/generate-from-html - * 参数: html_content (POST) - */ - public function generateFromHtml(Request $request) - { - $htmlContent = $request->post('html_content'); - if (!$htmlContent) { - return json(['code' => 1, 'msg' => 'HTML content is required']); - } - - try { - $generator = new PdfGeneratorService(); - // 这里直接传入 HTML,不需要模板。可以创建一个简单的 'html_wrapper' 模板来应用样式。 - // 或者直接处理 HTML 字符串。 - // 为简化,我们假设传入的 HTML 已经包含所需样式。 - // 注意:TCPDF 对复杂 CSS 支持有限,内联样式通常更可靠。 - - // 创建一个临时数据结构 - $tempData = ['content' => $htmlContent]; - // 创建一个简单的模板 app/view/pdf/html_wrapper.php - /* 内容示例: */ - // 但更简单的是直接使用 TCPDF 的 writeHTMLCell 或类似方法处理原始 HTML 片段 - // 这里我们直接调用 TCPDF (绕过服务的模板机制) - - $pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); - $pdf->SetCreator('Your System'); - $pdf->SetTitle('Generated from HTML'); - $pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED); - $pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT); - $pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM); - $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); - $pdf->setFontSubsetting(true); - $pdf->SetFont('dejavusans', '', 10, '', true); - $pdf->AddPage(); - // 直接写入 HTML - $pdf->writeHTML($htmlContent, true, false, true, false, ''); - $pdf->endPage(); - $pdfData = $pdf->Output('', 'S'); - - $filename = 'custom_html_' . date('Ymd_His') . '.pdf'; - return response($pdfData, 200, [ - 'Content-Type' => 'application/pdf', - 'Content-Disposition' => 'attachment; filename="' . $filename . '"', - ]); - - } catch (Exception $e) { - return json(['code' => 1, 'msg' => 'Failed to generate PDF from HTML: ' . $e->getMessage()]); - } - } -} \ No newline at end of file diff --git a/app/common/utils/pdf/controller/PdfController.php b/app/common/utils/pdf/controller/PdfController.php new file mode 100644 index 0000000..b647628 --- /dev/null +++ b/app/common/utils/pdf/controller/PdfController.php @@ -0,0 +1,76 @@ + '备货单', + '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' => '
客户:永生花
'; + $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('/ - - - -出货编号:
-发货方: Your Company Ltd.
-地址: 123 Main Street, City
-电话: (123) 456-7890
-| 客户名称 | -- | 出货日期 | -- |
|---|---|---|---|
| 订单编号 | -- | 运输方式 | -- |
| 序号 | -产品编码 | -产品名称 | -规格 | -单位 | -数量 | -备注 | -
|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - |
| 无出货明细 | -||||||