From 000f4198a863c0a5ebbc0c955f9f44afa930c002 Mon Sep 17 00:00:00 2001 From: reddaniel110 Date: Thu, 17 Sep 2026 14:27:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=A4=A7=E8=AE=A2=E5=8D=95PDF?= =?UTF-8?q?=E5=85=AC=E5=8F=B8=E6=8A=AC=E5=A4=B4=E5=B9=B6=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?Win7=E6=89=93=E5=8C=85=E7=8E=AF=E5=A2=83=E5=A4=8D=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- output/pdf/委外订单_签章调整样张.pdf | Bin 140626 -> 140616 bytes output/pdf/采购单_签章调整样张.pdf | Bin 137820 -> 137816 bytes packaging/win7/00_check_environment.cmd | 8 ++ packaging/win7/01_install_env.cmd | 35 +++--- packaging/win7/02_build_exe.cmd | 7 +- packaging/win7/README_WIN7.md | 13 ++- packaging/win7/create_bundle.py | 5 +- packaging/win7/install_env.ps1 | 143 ++++++++++++++++++++++++ po_app/pdf_export.py | 4 +- tests/test_workflow.py | 2 +- 10 files changed, 184 insertions(+), 33 deletions(-) create mode 100644 packaging/win7/00_check_environment.cmd create mode 100644 packaging/win7/install_env.ps1 diff --git a/output/pdf/委外订单_签章调整样张.pdf b/output/pdf/委外订单_签章调整样张.pdf index 944874b20102a6cbeda52f33e336da714402c12b..758b2ac7585f1ca0228c848aab457c73d05ff132 100644 GIT binary patch delta 4012 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 D%h!&D delta 4022 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 Iq>O+6UomKyUH||9 diff --git a/output/pdf/采购单_签章调整样张.pdf b/output/pdf/采购单_签章调整样张.pdf index 2731855e8640a8a42e36dde8973096e21b11d20a..b80167e1a3c800012b8dacb073c3783a1ab6d9ab 100644 GIT binary patch delta 4190 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 delta 4194 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 diff --git a/packaging/win7/00_check_environment.cmd b/packaging/win7/00_check_environment.cmd new file mode 100644 index 0000000..10bd955 --- /dev/null +++ b/packaging/win7/00_check_environment.cmd @@ -0,0 +1,8 @@ +@echo off +setlocal EnableExtensions DisableDelayedExpansion +set "PO_KIT_ROOT=%~dp0" +cd /d "%PO_KIT_ROOT%" +"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%PO_KIT_ROOT%win7\install_env.ps1" -ProbeOnly +set "PO_RESULT=%ERRORLEVEL%" +pause +exit /b %PO_RESULT% diff --git a/packaging/win7/01_install_env.cmd b/packaging/win7/01_install_env.cmd index 82b81e5..f5edc42 100644 --- a/packaging/win7/01_install_env.cmd +++ b/packaging/win7/01_install_env.cmd @@ -1,30 +1,23 @@ @echo off setlocal EnableExtensions DisableDelayedExpansion -cd /d "%~dp0" +set "PO_KIT_ROOT=%~dp0" +cd /d "%PO_KIT_ROOT%" set "PYTHONUTF8=1" set "PYTHONIOENCODING=utf-8" set "PYTHONHOME=" set "PYTHONPATH=" -set "PO_PYTHON=%~dp0.python38\python.exe" -set "PO_INSTALLER=%~dp0installers\python-3.8.10.exe" -if /i "%PROCESSOR_ARCHITECTURE%"=="AMD64" set "PO_INSTALLER=%~dp0installers\python-3.8.10-amd64.exe" -if /i "%PROCESSOR_ARCHITEW6432%"=="AMD64" set "PO_INSTALLER=%~dp0installers\python-3.8.10-amd64.exe" -if exist "%PO_PYTHON%" goto ready -echo Installing the bundled Python 3.8.10. Windows 7 SP1 is required. -echo If Windows requests a restart, restart and run this script again. -start /wait "" "%PO_INSTALLER%" /passive InstallAllUsers=0 TargetDir="%~dp0.python38" Include_launcher=0 InstallLauncherAllUsers=0 PrependPath=0 Include_doc=0 Include_test=0 Include_tcltk=0 Shortcuts=0 AssociateFiles=0 -if errorlevel 1 goto failed -:ready -if not exist "%PO_PYTHON%" goto failed -"%PO_PYTHON%" "%~dp0win7\build.py" setup -if errorlevel 1 goto failed -echo. +"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%PO_KIT_ROOT%win7\install_env.ps1" +set "PO_RESULT=%ERRORLEVEL%" +if "%PO_RESULT%"=="3010" goto restart +if "%PO_RESULT%"=="1641" goto restart +if not "%PO_RESULT%"=="0" goto failed echo Setup passed. Run 02_build_exe.cmd next. -pause -exit /b 0 +goto finish +:restart +echo Windows requested a restart. Restart, then run 01_install_env.cmd again. +goto finish :failed -echo. -echo SETUP FAILED. See README_WIN7.txt and the message above. -echo Do not install the modern Python 3.11 build on Windows 7. +echo SETUP FAILED. Exit code: %PO_RESULT%. Keep the logs folder and the message above. +:finish pause -exit /b 1 +exit /b %PO_RESULT% diff --git a/packaging/win7/02_build_exe.cmd b/packaging/win7/02_build_exe.cmd index 5484b27..94c0ede 100644 --- a/packaging/win7/02_build_exe.cmd +++ b/packaging/win7/02_build_exe.cmd @@ -1,12 +1,13 @@ @echo off setlocal EnableExtensions DisableDelayedExpansion -cd /d "%~dp0" +set "PO_KIT_ROOT=%~dp0" +cd /d "%PO_KIT_ROOT%" set "PYTHONUTF8=1" set "PYTHONIOENCODING=utf-8" set "PYTHONHOME=" set "PYTHONPATH=" -if not exist "%~dp0.buildenv\Scripts\python.exe" goto missing -"%~dp0.buildenv\Scripts\python.exe" "%~dp0win7\build.py" build +if not exist "%PO_KIT_ROOT%.buildenv\Scripts\python.exe" goto missing +"%PO_KIT_ROOT%.buildenv\Scripts\python.exe" "%PO_KIT_ROOT%win7\build.py" build if errorlevel 1 goto failed echo. echo EXE is in the new dated subfolder under dist. See BUILD_INFO.json. diff --git a/packaging/win7/README_WIN7.md b/packaging/win7/README_WIN7.md index f70b383..bc8a6e1 100644 --- a/packaging/win7/README_WIN7.md +++ b/packaging/win7/README_WIN7.md @@ -2,9 +2,11 @@ 本包用于在你的 Windows 7 服务电脑上,从 v4 源码生成 EXE。包括 Python 3.8.10 官方完整安装程序、32 位与 64 位离线依赖、构建脚本、环境与业务自检,以及现有 16 页使用升级手册。 +安装脚本修复:新版解压到另一文件夹时,先检查本包及系统登记的 Python 3.8.10,确认位数和运行正常后复用;不再仅因新目录没有 `.python38` 就重复安装。只有找不到可用环境时才运行官方安装器。安装器错误码、依赖安装及自检输出会保存在 `logs/`。 + 2026-09-17 文件名更新:在“供应商管理”的新增或编辑窗口填写“公司简称”,PDF 文件名即为“采购单_订单号_简称.pdf”(委外单使用对应类型名称);未填写简称时使用供应商全名。已审批订单重新下载也使用新文件名,归档 PDF 内容不变,无需重新审批。Excel 模板新增末列“公司简称”,旧表缺少该列时保留原简称。本次启动会先备份数据库,再升级到数据库结构版本 5,新增简称字段。 -2026-09-17 排版更新:采购专用章直径增加 30%,固定在右侧需方签章空位;供方和需方签章区之间留出 20 毫米间隔。采购单与委外订单均已调整,示例见 `samples/`。本次 PDF 模板版本为 `po-v4`。 +2026-09-17 排版更新:公司抬头从 12.5 磅加大到 20 磅,保持黑体居中。采购专用章直径增加 30%,固定在右侧需方签章空位;供方和需方签章区之间留出 20 毫米间隔。采购单与委外订单均已调整,示例见 `samples/`。本次 PDF 模板版本为 `po-v5`。 升级后新审批生成的正式 PDF 使用新排版。已审批归档的 PDF 保留原版;需要采用新版的订单,请通过系统的“变更重审”流程重新审批。使用本包重新解压、安装环境并生成 EXE 后,按下文备份和替换服务程序。 @@ -18,10 +20,11 @@ ## 2. 两步生成 EXE 1. 双击 `01_install_env.cmd`。 - - 使用包内 Python 3.8.10 安装程序;安装到包内 `.python38`,不把它加入系统 PATH,也不替换其他 Python 版本。 + - 优先复用已安装且位数匹配的 Python 3.8.10;没有可用环境时才用包内安装器安装到 `.python38`。不改系统 PATH。 + - 如果复用的是旧包文件夹内的 Python,请保留该文件夹,以便后续打包;生成的 EXE 运行不依赖该 Python 环境。 - 从本包 `wheels/x64` 或 `wheels/x86` 离线安装到 `.buildenv`,不连接 pip 源、不临时升级依赖。 - 自动检查密码哈希、中文 PDF、制单审批、部分/全部付款交货、Excel 台账和 HTTP 服务。自检仅使用临时数据库,通知不开启。 - - 看到 `SETUP COMPLETE` 后关闭窗口。失败时保留报错内容。 + - 看到 `SETUP COMPLETE` 后关闭窗口。失败时查看并保留 `logs/setup-时间.log`;运行过安装器时还会有 `logs/python-installer-时间.log` 及其子日志。返回码 3010 或 1641 表示需要先重启电脑。 2. 双击 `02_build_exe.cmd`。 - 再次核对文件校验值、Python 与依赖版本,然后构建单文件程序。 - 打包期间可能数分钟没有新输出;详细日志在 `build/时间-位数/build.log`。 @@ -42,6 +45,8 @@ | 现象 | 处理 | | --- | --- | +| 旧包可以安装,新包刚开始就提示 SETUP FAILED | 使用这个修复包,保留原来可用的 Python 所在文件夹。先运行 `00_check_environment.cmd` 可查看识别到的 Python 路径;这个检查不安装依赖 | +| Python 已装好但自动没有找到 | 在包根目录新建 `python_path.txt`,仅填一行完整路径,例如 `D:\旧打包包\.python38\python.exe`,再运行第一个脚本。会检查是否为匹配位数的 Python 3.8.10,不需要复制旧的 `.buildenv` | | Python 安装提示需要 SP1、更新或重启 | 先检查 Windows 7 SP1、DLL 加载更新及 UCRT。完成更新并重启,再运行第一个脚本 | | 缺少 api-ms-win-crt、VCRUNTIME140、找不到程序入口点 | 检查系统运行库和更新;若自检提示具体 DLL,保留完整名称及错误信息。不要从陌生网站下载单个 DLL 覆盖系统目录 | | 文件校验失败 | 重新完整解压本包;确认杀毒软件没有隔离文件、源码和 wheels 未被修改 | @@ -57,7 +62,7 @@ Waitress 使用本项目的 **3.0.2+win7.1** 兼容 wheel。HTTP 服务及安全修复代码与官方 3.0.2 保持逐字节一致,仅为命令行入口的名称解析增加 Python 3.8 回退,并修改版本元数据、重算 wheel 校验记录;原许可证保留。这样保留 3.0.1/3.0.2 的修复,没有降到旧的 3.0.0。上游官方支持范围仍为 Python 3.9+,这是本项目单独测试维护的兼容组合,不代表上游承诺 Win7 支持。原始 wheel 和重打包脚本随包提供。 -`PACKAGE_INFO.json` 记录源代码提交与验证范围;`SHA256SUMS.json` 记录本包文件校验值。构建输出的 `BUILD_INFO.json` 记录实际操作系统、Python、位数和 EXE SHA-256。最终以你的服务器上的自检和现场流程验收为准。 +`PACKAGE_INFO.json` 记录源代码提交与验证范围;`SHA256SUMS.json` 记录本包文件校验值。构建输出的 `BUILD_INFO.json` 记录实际操作系统、Python、位数和 EXE SHA-256。最终以你的服务器上的自检和现场流程验收为准。`00_check_environment.cmd` 只检查系统、可复用的 Python 并保存诊断日志,不安装软件或改系统设置。 ## 官方参考 diff --git a/packaging/win7/create_bundle.py b/packaging/win7/create_bundle.py index 6bb7956..7cdfeff 100644 --- a/packaging/win7/create_bundle.py +++ b/packaging/win7/create_bundle.py @@ -35,9 +35,9 @@ def assemble(cache, output): continue if name.endswith('.py') or name.startswith(('po_app/templates/', 'po_app/static/')): copy(REPO/name, name) - for name in ['build.py', 'selftest.py', 'probe_exe.py', 'requirements-win7.txt', 'make_waitress_wheel.py']: + for name in ['build.py', 'selftest.py', 'probe_exe.py', 'requirements-win7.txt', 'make_waitress_wheel.py', 'install_env.ps1']: copy(HERE/name, 'win7/' + name) - for name in ['01_install_env.cmd', '02_build_exe.cmd']: + for name in ['00_check_environment.cmd', '01_install_env.cmd', '02_build_exe.cmd']: (output/name).write_bytes((HERE/name).read_text(encoding='utf-8').replace('\r\n', '\n').replace('\n', '\r\n').encode('ascii')) (output/'README_WIN7.txt').write_text((HERE/'README_WIN7.md').read_text(encoding='utf-8'), encoding='utf-8-sig') copy(REPO/'output/pdf/博特维采购订单系统_v4_使用与升级手册.pdf', 'manual/博特维采购订单系统_v4_使用与升级手册.pdf') @@ -64,6 +64,7 @@ def assemble(cache, output): raise ValueError('Incomplete wheel set: %s, missing %s' % (arch, set(pins)-set(selected))) copy(cache/'waitress-3.0.2-py3-none-any.whl', 'third_party/waitress-3.0.2-py3-none-any.whl') info = {'source_commit': commit, 'created_at': datetime.now().isoformat(timespec='seconds'), + 'setup_revision': '20260917-reuse-installed-python', 'purpose': 'Build v4 on the target Windows 7 SP1 server', 'architectures': ['x86', 'x64'], 'python': '3.8.10', 'windows7_field_verified': False, diff --git a/packaging/win7/install_env.ps1 b/packaging/win7/install_env.ps1 new file mode 100644 index 0000000..dc029f5 --- /dev/null +++ b/packaging/win7/install_env.ps1 @@ -0,0 +1,143 @@ +# Compatible with Windows PowerShell 2.0. Reuse a verified full Python install. +param([switch]$ProbeOnly) + +$ErrorActionPreference = 'Stop' +$kitRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) +$logDirectory = Join-Path $kitRoot 'logs' +[void][IO.Directory]::CreateDirectory($logDirectory) +$runId = Get-Date -Format 'yyyyMMdd-HHmmss-fff' +$setupLog = Join-Path $logDirectory ('setup-' + $runId + '.log') +$utf8 = New-Object System.Text.UTF8Encoding($true) +[IO.File]::WriteAllText($setupLog, '', $utf8) +$env:PYTHONUTF8 = '1' +$env:PYTHONIOENCODING = 'utf-8' +$env:PYTHONHOME = $null +$env:PYTHONPATH = $null +[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false) +$targetBits = 32 +if ($env:PROCESSOR_ARCHITECTURE -eq 'AMD64' -or $env:PROCESSOR_ARCHITEW6432 -eq 'AMD64') { + $targetBits = 64 +} + +function Write-Log([string]$message) { + Write-Host $message + [IO.File]::AppendAllText($setupLog, $message + [Environment]::NewLine, $utf8) +} + +function Invoke-Logged([string]$executable, [string[]]$arguments) { + # PowerShell 2 treats native stderr as ErrorRecord; preserve it in the log + # without mistaking harmless warnings for a failed process. + $savedPreference = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + try { + $global:LASTEXITCODE = $null + & $executable @arguments 2>&1 | ForEach-Object { Write-Log ([string]$_) } + $code = $global:LASTEXITCODE + } finally { + $ErrorActionPreference = $savedPreference + } + if ($null -eq $code) { return 1 } + return $code +} + +function Test-Python([string]$candidate) { + if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { return $false } + Write-Log ('Checking Python: ' + $candidate) + $probe = 'import sys, struct, venv, ensurepip, ssl, sqlite3; print(sys.version); print(sys.executable); sys.exit(0 if sys.version_info[:3] == (3,8,10) and struct.calcsize(''P'')*8 == ' + $targetBits + ' else 3)' + $code = Invoke-Logged $candidate @('-I', '-X', 'utf8', '-c', $probe) + if ($code -ne 0) { Write-Log ('Python rejected; exit code: ' + $code) } + return ($code -eq 0) +} + +function Find-Python { + $manualFile = Join-Path $kitRoot 'python_path.txt' + if (Test-Path -LiteralPath $manualFile) { + $manualPath = [IO.File]::ReadAllText($manualFile).Trim().Trim('"') + if (Test-Python $manualPath) { return $manualPath } + throw 'python_path.txt must contain the full path to a working Python 3.8.10 of the required architecture.' + } + $candidates = New-Object System.Collections.ArrayList + [void]$candidates.Add((Join-Path $kitRoot '.python38\python.exe')) + # Python's official installer registers the full interpreter, even when + # its files live in an older source kit directory. + foreach ($registryRoot in @('HKCU:\Software\Python\PythonCore', + 'HKCU:\Software\WOW6432Node\Python\PythonCore', + 'HKLM:\Software\Python\PythonCore', + 'HKLM:\Software\WOW6432Node\Python\PythonCore')) { + foreach ($tag in @('3.8', '3.8-32')) { + $keyPath = Join-Path $registryRoot ($tag + '\InstallPath') + try { + if (Test-Path -LiteralPath $keyPath) { + $key = Get-Item -LiteralPath $keyPath + $executable = [string]$key.GetValue('ExecutablePath') + $directory = [string]$key.GetValue('') + if ($executable) { [void]$candidates.Add($executable) } + if ($directory) { [void]$candidates.Add((Join-Path $directory 'python.exe')) } + } + } catch { + Write-Log ('Registry lookup skipped: ' + $keyPath + ' : ' + $_.Exception.Message) + } + } + } + foreach ($candidate in ($candidates | Select-Object -Unique)) { + if (Test-Python $candidate) { return $candidate } + } + return $null +} + +try { + Write-Log ('Setup log: ' + $setupLog) + Write-Log ('Package: ' + $kitRoot) + Write-Log ('Windows: ' + [Environment]::OSVersion.VersionString) + Write-Log ('PowerShell: ' + $PSVersionTable.PSVersion.ToString()) + Write-Log ('Required Python: 3.8.10, ' + $targetBits + '-bit') + $python = Find-Python + if ($ProbeOnly) { + if ($python) { Write-Log ('FOUND reusable Python: ' + $python) } + else { Write-Log 'No working matching Python was found. No installation was attempted.' } + Write-Log 'DIAGNOSTIC COMPLETE. No dependencies or system settings were changed.' + exit 0 + } + if (-not $python) { + $installerName = 'python-3.8.10.exe' + if ($targetBits -eq 64) { $installerName = 'python-3.8.10-amd64.exe' } + $installer = Join-Path $kitRoot ('installers\' + $installerName) + if (-not (Test-Path -LiteralPath $installer)) { throw ('Installer missing: ' + $installer) } + $installerLog = Join-Path $logDirectory ('python-installer-' + $runId + '.log') + $targetDirectory = Join-Path $kitRoot '.python38' + Write-Log ('No reusable Python found. Starting installer: ' + $installer) + Write-Log ('Installer log: ' + $installerLog) + $installerArgs = @('/passive', '/log', ('"' + $installerLog + '"'), + 'InstallAllUsers=0', ('TargetDir="' + $targetDirectory + '"'), + 'Include_launcher=0', 'InstallLauncherAllUsers=0', 'PrependPath=0', + 'Include_doc=0', 'Include_test=0', 'Include_tcltk=0', 'Shortcuts=0', 'AssociateFiles=0') + $process = Start-Process -FilePath $installer -ArgumentList $installerArgs -Wait -PassThru -WindowStyle Hidden + $installerCode = $process.ExitCode + Write-Log ('Python installer exit code: ' + $installerCode) + if ($installerCode -eq 3010 -or $installerCode -eq 1641) { + Write-Log 'Windows requested a restart. Restart the computer and run 01_install_env.cmd again.' + exit $installerCode + } + if ($installerCode -ne 0) { + throw ('Python installer failed with exit code ' + $installerCode + '. See ' + $installerLog) + } + # Maintenance mode may leave Python in its original registered folder. + $python = Find-Python + if (-not $python) { + throw 'Installer returned success but no matching Python was found. Keep logs; do not uninstall the old working environment.' + } + } + Write-Log ('Using existing Python: ' + $python) + Write-Log 'Keep this Python directory while using the build environment.' + $buildScript = Join-Path $kitRoot 'win7\build.py' + $code = Invoke-Logged $python @('-I', '-X', 'utf8', $buildScript, 'setup') + Write-Log ('Environment setup exit code: ' + $code) + if ($code -ne 0) { exit $code } + Write-Log 'SETUP COMPLETE. Run 02_build_exe.cmd next.' + exit 0 +} catch { + Write-Log ('FAILED: ' + $_.Exception.Message) + Write-Log ('Details: ' + $_.ScriptStackTrace) + Write-Log ('Keep the logs folder. Setup log: ' + $setupLog) + exit 1 +} diff --git a/po_app/pdf_export.py b/po_app/pdf_export.py index 24d9645..4d53f62 100644 --- a/po_app/pdf_export.py +++ b/po_app/pdf_export.py @@ -60,7 +60,7 @@ STATUS_WATERMARK = { SEAL_COLOR = colors.HexColor("#C8102E") SEAL_NUMBER = "3205070994140" -PDF_TEMPLATE_VERSION = "po-v4" +PDF_TEMPLATE_VERSION = "po-v5" PDF_SEAL_SIZE = 32 * 1.3 * mm @@ -278,7 +278,7 @@ def build_pdf(order, items, out_path, watermark=None): styles = { "title": ParagraphStyle("title", fontName=FONT_BOLD, fontSize=17, leading=22, alignment=TA_CENTER), - "company": ParagraphStyle("company", fontName=FONT_BOLD, fontSize=12.5, leading=17, + "company": ParagraphStyle("company", fontName=FONT_BOLD, fontSize=20, leading=26, alignment=TA_CENTER), "cell": ParagraphStyle("cell", fontName=FONT_BODY, fontSize=8.6, leading=11.5, alignment=TA_LEFT), diff --git a/tests/test_workflow.py b/tests/test_workflow.py index ad8014e..cfc79c4 100644 --- a/tests/test_workflow.py +++ b/tests/test_workflow.py @@ -86,7 +86,7 @@ def test_full_flow_and_frozen_pdf(app, clients, kind, n): v = db.query(OrderVersion).filter_by(order_id=oid, revision=1).one() assert v.pdf_sha256 == hashlib.sha256(response.data).hexdigest() assert len(json.loads(v.content_json)['items']) == n - assert v.template_version == 'po-v4' + assert v.template_version == 'po-v5' assert save(app, clients['maker'], oid).status_code == 403 assert clients['maker'].get('/orders/%s/edit' % oid).status_code == 302 assert clients['maker'].get('/orders/%s/versions/1' % oid).status_code == 200