加大订单PDF公司抬头并修复Win7打包环境复用

This commit is contained in:
reddaniel110
2026-09-17 14:27:11 +08:00
parent a86f0b0d5b
commit 000f4198a8
10 changed files with 184 additions and 33 deletions
Binary file not shown.
Binary file not shown.
+8
View File
@@ -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%
+14 -21
View File
@@ -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%
+4 -3
View File
@@ -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.
+9 -4
View File
@@ -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 并保存诊断日志,不安装软件或改系统设置。
## 官方参考
+3 -2
View File
@@ -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,
+143
View File
@@ -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
}
+2 -2
View File
@@ -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),
+1 -1
View File
@@ -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