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' => '
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('/]*>(.*?)<\/style>/is', $html, $m)) { + foreach ($m[1] as $block) { + // 去掉 HTML 注释,以防注释把 CSS 包起来 + $block = preg_replace('//', '', $block); + $cssParts[] = trim($block); + } + // 从 HTML 删除所有 - - - -
-

商品出货单

-

出货编号:

-
- -
-

发货方: Your Company Ltd.

-

地址: 123 Main Street, City

-

电话: (123) 456-7890

-
- -
- - - - - - - - - - - - - -
客户名称出货日期
订单编号运输方式
-
- -
-

收货地址

-

-
- -
-

出货明细

- - - - - - - - - - - - - - - $item): ?> - - - - - - - - - - - - - - - - -
序号产品编码产品名称规格单位数量备注
无出货明细
-
- - - - - \ No newline at end of file diff --git a/app/common/utils/pdf/template/reimbursement.php b/app/common/utils/pdf/template/reimbursement.php deleted file mode 100644 index 4b440ed..0000000 --- a/app/common/utils/pdf/template/reimbursement.php +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - 报销单 - <?php echo htmlspecialchars($data['reimbursement_id'] ?? 'N/A'); ?> - - - - -
-

费用报销申请单

-

报销编号:

-
- -
- -

公司名称: Your Company Ltd.

-

地址: 123 Main Street, City

-

电话: (123) 456-7890

-
- -
- - - - - - - - - - - - - - - - - -
申请人部门
申请日期报销总金额 元
事由说明
-
- -
-

费用明细

- - - - - - - - - - - - - - $item): ?> - - - - - - - - - - - - - - - - - - - - - - -
序号日期费用类别说明金额 (元)备注
无费用明细
合计:
-
- - - - - \ No newline at end of file diff --git a/app/common/utils/pdf/template/stockOrder.stub b/app/common/utils/pdf/template/stockOrder.stub new file mode 100644 index 0000000..a6aa700 --- /dev/null +++ b/app/common/utils/pdf/template/stockOrder.stub @@ -0,0 +1,237 @@ + + + + + 合塑实业-汽车事业部-备货单(客户下定) + + + +
+ +
+

合塑实业-汽车事业部-备货单(客户下定)

+
+ 备货单号: + 业务员: +
+
+ + + + + + + + + + + + + + + + + + + + + +
阿里订单号/PI号: 日期: 关目的国: 客户来源: 客户名称: 客户所在国:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
编号车型数量/台颜色/外内饰改装单价(美金)总价(美金)官方指导价优惠
+ 合同汇率: + + 合计到岸价: +
+ + + + + + + + + + + + + + + + + + + + + +
清关人: 预计发运日期: 出运方式: 国内收货港: 贸易条款/清关目的港: /最迟到港时间:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
计划收款信息序号汇款国日期币种账户汇款人金额
+
+ + \ No newline at end of file diff --git a/app/common/utils/pdf/template/style.css b/app/common/utils/pdf/template/style.css deleted file mode 100644 index 2c79224..0000000 --- a/app/common/utils/pdf/template/style.css +++ /dev/null @@ -1,81 +0,0 @@ -/* styles.css */ -body { - font-family: "DejaVu Sans", sans-serif; /* TCPDF 支持的字体 */ - font-size: 10pt; - color: #333; - margin: 20mm 15mm 20mm 15mm; /* 页边距 */ -} - -.header { - text-align: center; - border-bottom: 2px solid #000; - padding-bottom: 5mm; - margin-bottom: 8mm; -} - -.header h1 { - margin: 0; - font-size: 16pt; - color: #000; -} - -.header p { - margin: 2mm 0; -} - -.company-info { - font-size: 9pt; - text-align: right; - margin-bottom: 10mm; -} - -.document-details { - margin-bottom: 10mm; -} - -.document-details table { - width: 100%; - border-collapse: collapse; -} - -.document-details th, .document-details td { - border: 1px solid #999; - padding: 2mm; - text-align: left; - vertical-align: top; -} - -.document-details th { - background-color: #f0f0f0; - font-weight: bold; - text-align: center; -} - -.items-table { - width: 100%; - border-collapse: collapse; - margin-bottom: 10mm; -} - -.items-table th, .items-table td { - border: 1px solid #999; - padding: 2mm; - text-align: left; -} - -.items-table th { - background-color: #eaeaea; - font-weight: bold; -} - -.items-table tfoot td { - font-weight: bold; - background-color: #f9f9f9; -} - -.footer { - border-top: 1px solid #ccc; - padding-top: 5mm; - font-size: 9pt; - text-align: center; -} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 1c69073..8f6428f 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -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') + ] + ]); } } \ No newline at end of file diff --git a/app/crm/controller/StockOrdersController.php b/app/crm/controller/StockOrdersController.php index ef7c0cf..f32f03f 100644 --- a/app/crm/controller/StockOrdersController.php +++ b/app/crm/controller/StockOrdersController.php @@ -8,6 +8,10 @@ use support\Request; class StockOrdersController extends BaseController { + protected $noNeedLogin = ['print']; + + protected $noNeedHeaderData = ['print']; + use CrudControllerTrait; protected $service; @@ -41,6 +45,11 @@ class StockOrdersController extends BaseController 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'] = [ @@ -102,6 +111,16 @@ class StockOrdersController extends BaseController } + + public function print(Request $request) + { + try { + $raw = $this->service->servicePrint($request); + return $this->success($raw); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } diff --git a/composer.json b/composer.json index ea356fb..fcc2089 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "phpoffice/phpspreadsheet": "^4.5", "phpmailer/phpmailer": "^6.10", "webman/gateway-worker": "^1.0", - "tecnickcom/tcpdf": "^6.10" + "mpdf/mpdf": "^8.2" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index 4b82414..2272db8 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "475042c507f4dff165d21e0c64715771", + "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", @@ -6211,77 +6516,6 @@ ], "time": "2025-05-15T09:04:05+00:00" }, - { - "name": "tecnickcom/tcpdf", - "version": "6.10.0", - "source": { - "type": "git", - "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "ca5b6de294512145db96bcbc94e61696599c391d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/ca5b6de294512145db96bcbc94e61696599c391d", - "reference": "ca5b6de294512145db96bcbc94e61696599c391d", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "php": ">=7.1.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "config", - "include", - "tcpdf.php", - "tcpdf_barcodes_1d.php", - "tcpdf_barcodes_2d.php", - "include/tcpdf_colors.php", - "include/tcpdf_filters.php", - "include/tcpdf_font_data.php", - "include/tcpdf_fonts.php", - "include/tcpdf_images.php", - "include/tcpdf_static.php", - "include/barcodes/datamatrix.php", - "include/barcodes/pdf417.php", - "include/barcodes/qrcode.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-3.0-or-later" - ], - "authors": [ - { - "name": "Nicola Asuni", - "email": "info@tecnick.com", - "role": "lead" - } - ], - "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", - "homepage": "http://www.tcpdf.org/", - "keywords": [ - "PDFD32000-2008", - "TCPDF", - "barcodes", - "datamatrix", - "pdf", - "pdf417", - "qrcode" - ], - "support": { - "issues": "https://github.com/tecnickcom/TCPDF/issues", - "source": "https://github.com/tecnickcom/TCPDF/tree/6.10.0" - }, - "funding": [ - { - "url": "https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ", - "type": "custom" - } - ], - "time": "2025-05-27T18:02:28+00:00" - }, { "name": "topthink/think-container", "version": "v3.0.2", diff --git a/config/route.php b/config/route.php index 8eb39cd..cd2c03c 100644 --- a/config/route.php +++ b/config/route.php @@ -287,6 +287,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']); + }); }); @@ -356,12 +364,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']); - }); + // 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']); + // }); }); \ No newline at end of file diff --git a/public/fonts/NotoSansSC-Bold.ttf b/public/fonts/NotoSansSC-Bold.ttf new file mode 100644 index 0000000..b9010df Binary files /dev/null and b/public/fonts/NotoSansSC-Bold.ttf differ diff --git a/public/fonts/NotoSansSC-Regular.ttf b/public/fonts/NotoSansSC-Regular.ttf new file mode 100644 index 0000000..4d4cadb Binary files /dev/null and b/public/fonts/NotoSansSC-Regular.ttf differ