init: 博特维采购订单系统 v1.0 源码
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Dump .xls (BIFF) content: sheets, merged cells, all non-empty cell values."""
|
||||
import sys
|
||||
import xlrd
|
||||
|
||||
def dump(path):
|
||||
print("=" * 100)
|
||||
print("FILE:", path)
|
||||
try:
|
||||
bk = xlrd.open_workbook(path, formatting_info=True)
|
||||
fi = True
|
||||
except Exception as e:
|
||||
print(" [formatting_info unavailable: %s]" % e)
|
||||
bk = xlrd.open_workbook(path)
|
||||
fi = False
|
||||
print("sheets:", bk.sheet_names(), " nsheets:", bk.nsheets)
|
||||
for sh in bk.sheets():
|
||||
print("-" * 100)
|
||||
print("SHEET: %s (%d rows x %d cols)" % (sh.name, sh.nrows, sh.ncols))
|
||||
if fi:
|
||||
try:
|
||||
merged = sh.merged_cells
|
||||
if merged:
|
||||
print("MERGED:", merged)
|
||||
except Exception:
|
||||
pass
|
||||
for r in range(sh.nrows):
|
||||
row_vals = []
|
||||
for c in range(sh.ncols):
|
||||
v = sh.cell_value(r, c)
|
||||
if isinstance(v, float) and v == int(v):
|
||||
v = int(v)
|
||||
s = str(v).strip()
|
||||
if s:
|
||||
row_vals.append("%s%d=%s" % (chr(65 + c) if c < 26 else "c" + str(c), r + 1, s))
|
||||
if row_vals:
|
||||
print(" | ".join(row_vals))
|
||||
|
||||
for p in sys.argv[1:]:
|
||||
dump(p)
|
||||
@@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""端到端冒烟:制单 → 提交 → 主管审核 → 经理批准 → 导出 PDF。"""
|
||||
import http.cookiejar
|
||||
import os
|
||||
import re
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
BASE = "http://127.0.0.1:8080"
|
||||
OUT = r"D:\2026-09-09-task-18\tools\out"
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
|
||||
|
||||
def new_session():
|
||||
cj = http.cookiejar.CookieJar()
|
||||
return urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
|
||||
|
||||
|
||||
def post(op, path, data):
|
||||
body = urllib.parse.urlencode(data).encode()
|
||||
req = urllib.request.Request(BASE + path, data=body)
|
||||
r = op.open(req, timeout=30)
|
||||
return r.geturl(), r.read().decode("utf-8", "replace")
|
||||
|
||||
|
||||
def get(op, path):
|
||||
r = op.open(BASE + path, timeout=30)
|
||||
return r.geturl(), r.read()
|
||||
|
||||
|
||||
def login(user, pwd):
|
||||
op = new_session()
|
||||
url, html = post(op, "/login", {"username": user, "password": pwd})
|
||||
assert "/login" not in url, "%s 登录失败" % user
|
||||
print(" login %s ok" % user)
|
||||
return op
|
||||
|
||||
|
||||
def make_order(op, doc_type, supplier_id, n_items):
|
||||
url, _ = post(op, "/orders/new", {"doc_type": doc_type, "supplier_id": supplier_id})
|
||||
m = re.search(r"/orders/(\d+)", url)
|
||||
oid = int(m.group(1))
|
||||
form = [
|
||||
("order_date", "2026-09-09"), ("tax_rate", "13%"),
|
||||
("supplier_name", "苏州精锐五金制品有限公司"),
|
||||
("supplier_contact", "周经理"), ("supplier_phone", "13800000001"),
|
||||
("payment_terms", "月结30天"), ("supplier_tax_no", "91320500MA1XXXXXXX"),
|
||||
("supplier_bank_account", "4562 1088 0012 3456"),
|
||||
("supplier_bank_name", "中国银行苏州工业园区支行"),
|
||||
("our_company", "苏州博特维智能科技有限公司"),
|
||||
("our_contact", "胡乃琛"), ("our_phone", "13913172516"),
|
||||
("our_tax_no", "91320594MA1YYYYYYY"),
|
||||
("delivery_place", "苏州市工业园区星汉街5号腾飞新苏工业坊B幢5楼05-06单元 胡乃琛 13913172516"),
|
||||
("note", "本单为系统测试单,含 & <特殊> 字符校验"),
|
||||
]
|
||||
for i in range(n_items):
|
||||
form += [
|
||||
("item_code", "M-10%02d" % (i + 1)),
|
||||
("item_spec", "测试物料 规格 A&B <%d>" % (i + 1)),
|
||||
("item_unit", "件"), ("item_qty", "12.5"), ("item_price", "38.5"),
|
||||
("item_date", "2026-09-20"), ("item_remark", "行%d" % (i + 1)),
|
||||
]
|
||||
post(op, "/orders/%d" % oid, form)
|
||||
return oid
|
||||
|
||||
|
||||
def run(tag, doc_type, n_items):
|
||||
print("[%s] doc=%s items=%d" % (tag, doc_type, n_items))
|
||||
maker = login("maker", "123456")
|
||||
oid = make_order(maker, doc_type, 1, n_items)
|
||||
print(" 制单 ok, id=%d" % oid)
|
||||
post(maker, "/orders/%d/submit" % oid, {"comment": "请审核"})
|
||||
|
||||
rv = login("reviewer", "123456")
|
||||
post(rv, "/orders/%d/review" % oid, {"comment": "同意"})
|
||||
|
||||
ap = login("approver", "123456")
|
||||
post(ap, "/orders/%d/approve" % oid, {"comment": "批准"})
|
||||
print(" 审核流程 ok")
|
||||
|
||||
_, data = get(ap, "/orders/%d/pdf" % oid)
|
||||
path = os.path.join(OUT, "%s.pdf" % tag)
|
||||
with open(path, "wb") as f:
|
||||
f.write(data)
|
||||
print(" PDF -> %s (%d bytes)" % (path, len(data)))
|
||||
|
||||
# 草稿水印版
|
||||
oid2 = make_order(maker, doc_type, 1, 2)
|
||||
_, data2 = get(maker, "/orders/%d/pdf" % oid2)
|
||||
p2 = os.path.join(OUT, "%s_draft.pdf" % tag)
|
||||
with open(p2, "wb") as f:
|
||||
f.write(data2)
|
||||
print(" 草稿 PDF -> %s (%d bytes)" % (p2, len(data2)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run("purchase", "purchase", 3)
|
||||
run("outsourcing_30rows", "outsourcing", 30)
|
||||
print("ALL DONE")
|
||||
@@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Init local git repo + commit source (respecting .gitignore) using dulwich.
|
||||
No network/credentials needed. Push happens separately once remote URL+token are known.
|
||||
"""
|
||||
import os, sys
|
||||
from dulwich import porcelain
|
||||
from dulwich.repo import Repo
|
||||
|
||||
ROOT = r"D:\2026-09-09-task-18"
|
||||
GITDIR = os.path.join(ROOT, ".git")
|
||||
|
||||
# 1) init (skip if already a repo)
|
||||
if os.path.exists(GITDIR):
|
||||
print("repo already exists, reusing")
|
||||
repo = Repo(GITDIR)
|
||||
else:
|
||||
repo = porcelain.init(ROOT)
|
||||
print("repo initialized at", GITDIR)
|
||||
|
||||
# 2) stage everything; dulwich add honors .gitignore via ignore manager
|
||||
porcelain.add(repo, [ROOT])
|
||||
print("staged files (count):", len(repo.open_index()))
|
||||
|
||||
# 3) show what got staged (verify no db / dist / backups)
|
||||
staged = list(repo.open_index())
|
||||
bad = [p for p in staged if
|
||||
p.endswith(b".db") or b"\\dist\\" in p or p.startswith(b"dist/") or
|
||||
b"dist_backup" in p or b"\\instance\\" in p or b"/instance/" in p or
|
||||
p.startswith(b".workbuddy")]
|
||||
print("SUSPECT_STAGED:", [p.decode("utf-8","replace") for p in bad])
|
||||
|
||||
if bad:
|
||||
print("ABORT: ignored files would be committed; fix .gitignore")
|
||||
sys.exit(2)
|
||||
|
||||
# 4) commit (with identity)
|
||||
porcelain.commit(repo,
|
||||
message=b"init: \xe5\x8d\x9a\xe7\x89\xb9\xe7\xbb\xb4\xe9\x87\x87\xe8\xb4\xad\xe8\xae\xa2\xe5\x8d\x95\xe7\xb3\xbb\xe7\xbb\x9f v1.0 \xe6\xba\x90\xe7\xa0\x81",
|
||||
author=b"star <star@btw-ocr.com>",
|
||||
committer=b"star <star@btw-ocr.com>")
|
||||
print("COMMITTED. HEAD:", repo.head().decode())
|
||||
|
||||
# 5) report tracked file list
|
||||
tracked = []
|
||||
for e in repo.open_index().iteritems():
|
||||
tracked.append(e[0].decode("utf-8","replace"))
|
||||
print("TRACKED_FILES:")
|
||||
for f in sorted(tracked):
|
||||
print(" ", f)
|
||||
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""把 PDF 指定页渲染成 PNG,用于版式校验。"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pymupdf
|
||||
|
||||
OUT = r"D:\2026-09-09-task-18\tools\out"
|
||||
|
||||
|
||||
def render(pdf, pages=(0,), dpi=110, prefix=None):
|
||||
doc = pymupdf.open(pdf)
|
||||
prefix = prefix or os.path.splitext(os.path.basename(pdf))[0]
|
||||
for p in pages:
|
||||
if p >= len(doc):
|
||||
continue
|
||||
page = doc[p]
|
||||
pix = page.get_pixmap(dpi=dpi)
|
||||
path = os.path.join(OUT, "%s_p%d.png" % (prefix, p + 1))
|
||||
pix.save(path)
|
||||
print(path)
|
||||
print("pages:", len(doc))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
name = sys.argv[1]
|
||||
pages = [int(x) for x in sys.argv[2].split(",")] if len(sys.argv) > 2 else [0]
|
||||
render(os.path.join(OUT, name), pages)
|
||||
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
PY = r"C:\Users\wps18\.workbuddy\binaries\python\envs\default\Scripts\python.exe"
|
||||
CWD = r"D:\2026-09-09-task-18\po_app"
|
||||
|
||||
p = subprocess.run([PY, "-c", "import app; print('IMPORT OK')"], cwd=CWD,
|
||||
capture_output=True, text=True, encoding="utf-8", errors="replace",
|
||||
timeout=60)
|
||||
print("RC", p.returncode)
|
||||
print("STDOUT:", p.stdout[-4000:])
|
||||
print("STDERR:", p.stderr[-4000:])
|
||||
@@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""模拟:经理批准也需先加章;批准通过后 PDF 上审核章与批准章并排。
|
||||
|
||||
用法:python test_approve_stamp.py 8099
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
BASE = "http://127.0.0.1:%s" % (sys.argv[1] if len(sys.argv) > 1 else "8099")
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
OUT = os.path.join(HERE, "out")
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
DB = os.path.join(os.path.dirname(HERE), "po_app", "instance", "po.db")
|
||||
FAILS = []
|
||||
|
||||
|
||||
def check(label, cond, extra=""):
|
||||
print((" OK " if cond else " FAIL ") + label + (" " + str(extra) if extra else ""))
|
||||
if not cond:
|
||||
FAILS.append(label)
|
||||
|
||||
|
||||
def sess(u, p):
|
||||
s = requests.Session()
|
||||
s.post(BASE + "/login", data={"username": u, "password": p})
|
||||
return s
|
||||
|
||||
|
||||
def flash_of(h):
|
||||
return " | ".join(re.sub(r"<[^>]+>", "", m.group(1)).strip()
|
||||
for m in re.finditer(r'class="flash[^"]*"[^>]*>(.*?)</div>', h, re.S))
|
||||
|
||||
|
||||
def dbq(sql, a=()):
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute(sql, a)
|
||||
r = cur.fetchone()
|
||||
con.close()
|
||||
return r
|
||||
|
||||
|
||||
def main():
|
||||
admin = sess("admin", "admin123")
|
||||
r = admin.post(BASE + "/orders/new", data={"doc_type": "purchase"})
|
||||
oid = int(re.search(r"/orders/(\d+)/edit", r.url).group(1))
|
||||
admin.post(BASE + "/orders/%d" % oid, data={
|
||||
"supplier_name": u"测试供应商",
|
||||
"item_code": ["M-1001"], "item_spec": [u"测试物料"], "item_unit": [u"件"],
|
||||
"item_qty": ["10"], "item_price": ["12.5"], "item_date": [""], "item_remark": [""],
|
||||
})
|
||||
admin.post(BASE + "/orders/%d/submit" % oid, data={"comment": u"模拟"})
|
||||
|
||||
print("\n[1] 主管审核(加章)")
|
||||
rv = sess("reviewer", "123456")
|
||||
rv.post(BASE + "/orders/%d/review" % oid, data={"comment": u"ok", "stamped": "1"})
|
||||
check("审核通过", dbq("SELECT status FROM orders WHERE id=?", (oid,))[0] == "pending_approval")
|
||||
|
||||
print("\n[2] 经理批准:未加章应被拒绝")
|
||||
ap = sess("approver", "123456")
|
||||
h = ap.post(BASE + "/orders/%d/approve" % oid, data={"comment": u"直接批", "stamped": "0"}).text
|
||||
check("未加章 → 提示", u"请先加盖批准章" in flash_of(h), flash_of(h))
|
||||
check("状态未变", dbq("SELECT status FROM orders WHERE id=?", (oid,))[0] == "pending_approval")
|
||||
|
||||
print("\n[3] 加章后批准")
|
||||
ap.post(BASE + "/orders/%d/approve" % oid, data={"comment": u"同意", "stamped": "1"})
|
||||
row = dbq("SELECT status, approved_by FROM orders WHERE id=?", (oid,))
|
||||
check("已批准", row[0] == "approved", row)
|
||||
|
||||
print("\n[4] 导出 PDF(应含两枚章)")
|
||||
b = admin.get(BASE + "/orders/%d/pdf" % oid).content
|
||||
p = os.path.join(OUT, "seal_both.pdf")
|
||||
open(p, "wb").write(b)
|
||||
check("PDF 生成", len(b) > 10000, "%d bytes" % len(b))
|
||||
try:
|
||||
import pymupdf
|
||||
doc = pymupdf.open(p)
|
||||
doc[-1].get_pixmap(dpi=130).save(os.path.join(OUT, "seal_both.png"))
|
||||
print(" 预览:", os.path.join(OUT, "seal_both.png"), "%d 页" % doc.page_count)
|
||||
doc.close()
|
||||
except Exception as e:
|
||||
print(" 渲染失败:", e)
|
||||
|
||||
print("\n[5] 清理")
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute("DELETE FROM order_items WHERE order_id=?", (oid,))
|
||||
cur.execute("DELETE FROM orders WHERE id=?", (oid,))
|
||||
con.commit()
|
||||
con.close()
|
||||
print(" 已删除订单 %d" % oid)
|
||||
print("\n" + ("全部通过" if not FAILS else "失败: " + "; ".join(FAILS)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,208 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""批量导入 + 需方预置 + PO 号规则 E2E 测试。
|
||||
|
||||
用法:先启动服务(python app.py 8099),再运行本脚本。
|
||||
覆盖:物料/供应商导入的 新增 / 更新 / 跳过 / 失败 四条路径,
|
||||
模板下载,PO 号 4 位规则,以及旧格式单号的兼容解析。
|
||||
"""
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
import openpyxl
|
||||
import requests
|
||||
|
||||
PORT = sys.argv[1] if len(sys.argv) > 1 else "8099"
|
||||
BASE = "http://127.0.0.1:%s" % PORT
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
OUT = os.path.join(HERE, "out")
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
DB = os.path.join(os.path.dirname(HERE), "po_app", "instance", "po.db")
|
||||
|
||||
s = requests.Session()
|
||||
FAILS = []
|
||||
|
||||
|
||||
def check(label, cond, extra=""):
|
||||
print((" OK " if cond else " FAIL ") + label + (" " + str(extra) if extra else ""))
|
||||
if not cond:
|
||||
FAILS.append(label)
|
||||
|
||||
|
||||
def login():
|
||||
r = s.post(BASE + "/login",
|
||||
data={"username": "admin", "password": "admin123"},
|
||||
allow_redirects=False)
|
||||
return r.status_code in (200, 302)
|
||||
|
||||
|
||||
def make_material_xlsx(path):
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.append([u"物料代码", u"物料规格", u"单位", u"默认含税单价"])
|
||||
ws.append(["M-9001", u"测试新增物料A", u"件", 12.5]) # 新增
|
||||
ws.append(["M-9002", u"测试新增物料B", u"台", 88.0]) # 新增
|
||||
ws.append(["M-1001", u"铝合金支架(已存在-应被更新)", u"件", 99.9]) # 已存在
|
||||
ws.append(["", u"缺代码应失败", u"件", 1.0]) # 失败
|
||||
wb.save(path)
|
||||
|
||||
|
||||
def make_supplier_xlsx(path):
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.append([u"编号", u"名称", u"联系人", u"电话", u"付款方式", u"税号",
|
||||
u"开户银行", u"银行帐号"])
|
||||
ws.append(["S901", u"测试新增供应商A", u"王经理", "13700000001",
|
||||
u"月结30天", "91320500TEST001", u"工商银行苏州分行", "1111 2222 3333"])
|
||||
ws.append(["S001", u"苏州精锐五金制品有限公司", u"周经理(已更新)", "13800000001",
|
||||
u"月结60天", "91320500MA1XXXXXXX", u"中国银行苏州工业园区支行",
|
||||
"4562 1088 0012 3456"]) # 已存在
|
||||
ws.append(["S902", u"", u"缺名称", "13900000009", u"", u"", u"", u""]) # 失败
|
||||
wb.save(path)
|
||||
|
||||
|
||||
def post_import(url, path, mode):
|
||||
with open(path, "rb") as f:
|
||||
return s.post(url,
|
||||
files={"file": (os.path.basename(path), f,
|
||||
"application/vnd.openxmlformats-officedocument."
|
||||
"spreadsheetml.sheet")},
|
||||
data={"mode": mode})
|
||||
|
||||
|
||||
def flash_texts(html):
|
||||
out = []
|
||||
for m in re.finditer(r'class="flash[^"]*"[^>]*>(.*?)</div>', html, re.S):
|
||||
out.append(re.sub(r"<[^>]+>", "", m.group(1)).strip())
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
print("登录:", "OK" if login() else "FAIL")
|
||||
# 预清理:让脚本可重复运行(上次残留的测试数据不影响本次断言)
|
||||
try:
|
||||
today0 = datetime.datetime.now().strftime("%Y%m%d")
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute("UPDATE materials SET spec=?, unit=?, price=? WHERE code=?",
|
||||
(u"铝合金支架 6061-T6 200×80×10mm", u"件", 38.5, "M-1001"))
|
||||
cur.execute("UPDATE suppliers SET contact=?, payment_terms=? WHERE code=?",
|
||||
(u"周经理", u"月结30天", "S001"))
|
||||
cur.execute("DELETE FROM materials WHERE code IN ('M-9001','M-9002')")
|
||||
cur.execute("DELETE FROM suppliers WHERE code IN ('S901','S902')")
|
||||
for p in ("PO", "WO"):
|
||||
cur.execute("DELETE FROM order_items WHERE order_id IN "
|
||||
"(SELECT id FROM orders WHERE order_no LIKE ?)",
|
||||
(p + today0 + "%",))
|
||||
cur.execute("DELETE FROM orders WHERE order_no LIKE ?", (p + today0 + "%",))
|
||||
con.commit()
|
||||
con.close()
|
||||
except Exception as e:
|
||||
print("预清理失败:", e)
|
||||
|
||||
mp = os.path.join(OUT, "materials_test.xlsx")
|
||||
sp = os.path.join(OUT, "suppliers_test.xlsx")
|
||||
make_material_xlsx(mp)
|
||||
make_supplier_xlsx(sp)
|
||||
|
||||
print("\n[1] 物料导入 update 模式")
|
||||
r = post_import(BASE + "/materials/import_excel", mp, "update")
|
||||
t = " | ".join(flash_texts(r.text))
|
||||
print(" flash:", t)
|
||||
check("新增2 / 更新1 / 失败1", u"新增 2" in t and u"更新 1" in t and u"失败 1" in t)
|
||||
|
||||
print("\n[2] 供应商导入 update 模式")
|
||||
r = post_import(BASE + "/suppliers/import_excel", sp, "update")
|
||||
t = " | ".join(flash_texts(r.text))
|
||||
print(" flash:", t)
|
||||
check("新增1 / 更新1 / 失败1", u"新增 1" in t and u"更新 1" in t and u"失败 1" in t)
|
||||
|
||||
print("\n[3] 再导一次,skip 模式(应全部跳过)")
|
||||
r = post_import(BASE + "/materials/import_excel", mp, "skip")
|
||||
t = " | ".join(flash_texts(r.text))
|
||||
print(" 物料 flash:", t)
|
||||
check("物料跳过3(M-9001/M-9002/M-1001 此时都已存在)", u"跳过 3" in t)
|
||||
r = post_import(BASE + "/suppliers/import_excel", sp, "skip")
|
||||
t = " | ".join(flash_texts(r.text))
|
||||
print(" 供应商 flash:", t)
|
||||
check("供应商跳过2(S901/S001 此时都已存在)", u"跳过 2" in t)
|
||||
|
||||
print("\n[4] 列表核对")
|
||||
html = s.get(BASE + "/materials").text
|
||||
check("M-9001 已新增", "M-9001" in html)
|
||||
check("M-1001 单价已更新为 99.90", "99.90" in html)
|
||||
html = s.get(BASE + "/suppliers").text
|
||||
check("S901 已新增", "S901" in html)
|
||||
check("联系人已更新为 周经理(已更新)", u"周经理(已更新)" in html)
|
||||
|
||||
print("\n[5] 模板下载")
|
||||
for u in ("/materials/import_template.xlsx", "/suppliers/import_template.xlsx"):
|
||||
rr = s.get(BASE + u)
|
||||
check(u, rr.status_code == 200 and len(rr.content) > 3000,
|
||||
"%s bytes" % len(rr.content))
|
||||
|
||||
print("\n[6] 新建采购单:PO 号规则 + 需方预置")
|
||||
created = []
|
||||
r = s.post(BASE + "/orders/new", data={"doc_type": "purchase"})
|
||||
m = re.search(r"PO\d{12}", r.text)
|
||||
no = m.group(0) if m else ""
|
||||
created.append(no)
|
||||
print(" 单号:", no)
|
||||
today = datetime.datetime.now().strftime("%Y%m%d")
|
||||
check("格式为 PO+年月日+4位", bool(re.match(r"^PO\d{8}\d{4}$", no)))
|
||||
check("日期为今天 " + today, no.startswith("PO" + today))
|
||||
for k, v in (("our_contact", u"胡乃琛"), ("our_phone", u"13913172516"),
|
||||
("our_tax_no", u"91320507MA1WW8D15D"), ("tax_rate", u"13%")):
|
||||
check("预置 %s = %s" % (k, v), v in r.text)
|
||||
|
||||
print("\n[7] 旧格式单号兼容(插入带横杠的 PO%s-0007)" % today)
|
||||
legacy = "PO%s-0007" % today
|
||||
try:
|
||||
con = sqlite3.connect(DB)
|
||||
con.execute("INSERT INTO orders (order_no, doc_type, status, order_date) "
|
||||
"VALUES (?,?,?,?)",
|
||||
(legacy, "purchase", "draft",
|
||||
datetime.datetime.now().strftime("%Y-%m-%d")))
|
||||
con.commit()
|
||||
con.close()
|
||||
r = s.post(BASE + "/orders/new", data={"doc_type": "purchase"})
|
||||
m2 = re.search(r"PO\d{12}", r.text)
|
||||
got = m2.group(0) if m2 else ""
|
||||
print(" 新单号:", got, " 期望: PO%s0008" % today)
|
||||
check("旧格式未导致序号重置", got == "PO%s0008" % today)
|
||||
created.extend([got, legacy])
|
||||
except Exception as e:
|
||||
print(" 跳过,原因:", e)
|
||||
|
||||
print("\n[8] 清理测试数据(还原被更新的既有记录)")
|
||||
try:
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute("UPDATE materials SET spec=?, unit=?, price=? WHERE code=?",
|
||||
(u"铝合金支架 6061-T6 200×80×10mm", u"件", 38.5, "M-1001"))
|
||||
cur.execute("UPDATE suppliers SET contact=?, payment_terms=? WHERE code=?",
|
||||
(u"周经理", u"月结30天", "S001"))
|
||||
cur.execute("DELETE FROM materials WHERE code IN ('M-9001','M-9002')")
|
||||
cur.execute("DELETE FROM suppliers WHERE code IN ('S901','S902')")
|
||||
for no_ in [x for x in created if x]:
|
||||
cur.execute("DELETE FROM order_items WHERE order_id IN "
|
||||
"(SELECT id FROM orders WHERE order_no=?)", (no_,))
|
||||
cur.execute("DELETE FROM orders WHERE order_no=?", (no_,))
|
||||
con.commit()
|
||||
cur.execute("SELECT COUNT(*) FROM materials WHERE code LIKE 'M-900%'")
|
||||
n1 = cur.fetchone()[0]
|
||||
cur.execute("SELECT COUNT(*) FROM suppliers WHERE code IN ('S901','S902')")
|
||||
n2 = cur.fetchone()[0]
|
||||
con.close()
|
||||
check("测试物料已清除", n1 == 0)
|
||||
check("测试供应商已清除", n2 == 0)
|
||||
except Exception as e:
|
||||
print(" 清理失败:", e)
|
||||
|
||||
print("\n" + ("全部通过" if not FAILS else "失败项: " + "; ".join(FAILS)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,119 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""编辑功能 + 明细行模板 冒烟测试。
|
||||
|
||||
覆盖:
|
||||
1) 新建订单(0 条明细,正是"点添加一行没反应"的场景)页面必须带 rowTpl 模板行
|
||||
2) 物料列表编辑:POST /materials 带 id 能改库
|
||||
3) 供应商列表编辑:POST /suppliers 带 id 能改库
|
||||
|
||||
用法:先启动服务(python app.py 8099),再 python test_edit_row.py 8099
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
PORT = sys.argv[1] if len(sys.argv) > 1 else "8099"
|
||||
BASE = "http://127.0.0.1:%s" % PORT
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
DB = os.path.join(os.path.dirname(HERE), "po_app", "instance", "po.db")
|
||||
|
||||
s = requests.Session()
|
||||
FAILS = []
|
||||
|
||||
|
||||
def check(label, cond, extra=""):
|
||||
print((" OK " if cond else " FAIL ") + label + (" " + str(extra) if extra else ""))
|
||||
if not cond:
|
||||
FAILS.append(label)
|
||||
|
||||
|
||||
def main():
|
||||
r = s.post(BASE + "/login", data={"username": "admin", "password": "admin123"})
|
||||
check("登录", r.status_code == 200, r.status_code)
|
||||
|
||||
print("\n[1] 新建订单(0 明细)—— 明细行模板")
|
||||
r = s.post(BASE + "/orders/new", data={"doc_type": "purchase"})
|
||||
html = r.text
|
||||
no = ""
|
||||
m = re.search(r"PO\d{12}", html)
|
||||
if m:
|
||||
no = m.group(0)
|
||||
check("新建订单页含 rowTpl 模板", 'id="rowTpl"' in html)
|
||||
tpl = re.search(r'<template id="rowTpl">(.*?)</template>', html, re.S)
|
||||
check("模板内含 tr 行", bool(tpl) and "<tr>" in tpl.group(1))
|
||||
check("模板含 item_code 输入框", bool(tpl) and 'name="item_code"' in tpl.group(1))
|
||||
check("模板含 matpick 下拉", bool(tpl) and "matpick" in tpl.group(1))
|
||||
n_code = html.count('name="item_code"')
|
||||
check("当前确实 0 条明细(模板是唯一来源)", n_code == 1,
|
||||
"item_code 出现 %d 次" % n_code)
|
||||
|
||||
print("\n[2] 物料列表编辑")
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute("SELECT id FROM materials WHERE code='M-1002'")
|
||||
row = cur.fetchone()
|
||||
con.close()
|
||||
if not row:
|
||||
check("找到物料 M-1002", False)
|
||||
else:
|
||||
mid = row[0]
|
||||
r = s.post(BASE + "/materials",
|
||||
data={"id": mid, "code": "M-1002", "spec": u"步进电机(已编辑)",
|
||||
"unit": u"台", "price": "299.5"})
|
||||
check("物料编辑请求成功", r.status_code == 200, r.status_code)
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute("SELECT spec, unit, price FROM materials WHERE id=?", (mid,))
|
||||
got = cur.fetchone()
|
||||
check("物料已更新", got == (u"步进电机(已编辑)", u"台", 299.5), got)
|
||||
cur.execute("UPDATE materials SET spec=?, price=? WHERE id=?",
|
||||
(u"步进电机 57BYG250 2相", 265.0, mid))
|
||||
con.commit()
|
||||
con.close()
|
||||
print(" 已还原 M-1002")
|
||||
|
||||
print("\n[3] 供应商列表编辑")
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute("SELECT id FROM suppliers WHERE code='S002'")
|
||||
row = cur.fetchone()
|
||||
con.close()
|
||||
if not row:
|
||||
check("找到供应商 S002", False)
|
||||
else:
|
||||
sid = row[0]
|
||||
r = s.post(BASE + "/suppliers",
|
||||
data={"id": sid, "code": "S002", "name": u"昆山宏远电子科技有限公司",
|
||||
"contact": u"陈主管(已编辑)", "phone": "13900000002",
|
||||
"payment_terms": u"货到付款", "tax_no": "91320583MA2XXXXXXX",
|
||||
"bank_name": u"建设银行昆山分行",
|
||||
"bank_account": "3220 1988 2233 4455"})
|
||||
check("供应商编辑请求成功", r.status_code == 200, r.status_code)
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute("SELECT contact, payment_terms FROM suppliers WHERE id=?", (sid,))
|
||||
got = cur.fetchone()
|
||||
check("供应商已更新", got == (u"陈主管(已编辑)", u"货到付款"), got)
|
||||
cur.execute("UPDATE suppliers SET contact=? WHERE id=?", (u"陈主管", sid))
|
||||
con.commit()
|
||||
con.close()
|
||||
print(" 已还原 S002")
|
||||
|
||||
print("\n[4] 清理测试订单 " + no)
|
||||
if no:
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute("DELETE FROM order_items WHERE order_id IN "
|
||||
"(SELECT id FROM orders WHERE order_no=?)", (no,))
|
||||
cur.execute("DELETE FROM orders WHERE order_no=?", (no,))
|
||||
con.commit()
|
||||
con.close()
|
||||
|
||||
print("\n" + ("全部通过" if not FAILS else "失败: " + "; ".join(FAILS)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""测试 Excel 明细导入接口(multipart 上传)。"""
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
import openpyxl
|
||||
|
||||
BASE = "http://127.0.0.1:8080"
|
||||
OUT = r"D:\2026-09-09-task-18\tools\out"
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
|
||||
|
||||
def new_session():
|
||||
import http.cookiejar
|
||||
cj = http.cookiejar.CookieJar()
|
||||
return urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
|
||||
|
||||
|
||||
def post_form(op, path, data):
|
||||
body = urllib.parse.urlencode(data).encode()
|
||||
r = op.open(urllib.request.Request(BASE + path, data=body), timeout=30)
|
||||
return r.geturl(), r.read()
|
||||
|
||||
|
||||
def multipart_upload(op, path, field, filename, content, extra=None):
|
||||
boundary = "----PoTestBoundary1234"
|
||||
buf = io.BytesIO()
|
||||
for k, v in (extra or {}).items():
|
||||
buf.write(("--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n"
|
||||
% (boundary, k, v)).encode("utf-8"))
|
||||
buf.write(("--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n"
|
||||
"Content-Type: application/octet-stream\r\n\r\n" % (boundary, field, filename))
|
||||
.encode("utf-8"))
|
||||
buf.write(content)
|
||||
buf.write(("\r\n--%s--\r\n" % boundary).encode())
|
||||
req = urllib.request.Request(BASE + path, data=buf.getvalue(), method="POST")
|
||||
req.add_header("Content-Type", "multipart/form-data; boundary=%s" % boundary)
|
||||
r = op.open(req, timeout=30)
|
||||
return r.geturl(), r.read().decode("utf-8", "replace")
|
||||
|
||||
|
||||
def main():
|
||||
op = new_session()
|
||||
post_form(op, "/login", {"username": "maker", "password": "123456"})
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.append([u"物料代码", u"物料规格", u"单位", u"数量", u"含税单价", u"交期", u"备注"])
|
||||
ws.append([u"X-001", u"导入测试件 & <一>", u"件", 10, 99.9, "2026-10-01", u"首件"])
|
||||
ws.append([u"X-002", u"导入测试件 二", u"套", 3, 128.5, "2026-10-05", u""])
|
||||
buf = io.BytesIO()
|
||||
wb.save(buf)
|
||||
xlsx = buf.getvalue()
|
||||
|
||||
url, _ = post_form(op, "/orders/new", {"doc_type": "purchase", "supplier_id": ""})
|
||||
oid = int(re.search(r"/orders/(\d+)", url).group(1))
|
||||
print("order id", oid)
|
||||
|
||||
url, html = multipart_upload(op, "/orders/%d/import_excel" % oid, "file",
|
||||
"test.xlsx", xlsx, {"mode": "replace"})
|
||||
ok = "已导入 2 行明细" in html
|
||||
print("import rows ok:", ok)
|
||||
ok2 = "99.90" in html or "99.9" in html
|
||||
print("price present:", ok2)
|
||||
assert ok and ok2
|
||||
print("IMPORT TEST PASSED")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""快速检查统计页与台账导出。"""
|
||||
import http.cookiejar
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
BASE = "http://127.0.0.1:8080"
|
||||
|
||||
|
||||
def main():
|
||||
cj = http.cookiejar.CookieJar()
|
||||
op = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
|
||||
op.open(urllib.request.Request(
|
||||
BASE + "/login",
|
||||
data=urllib.parse.urlencode({"username": "admin", "password": "admin123"}).encode()),
|
||||
timeout=30)
|
||||
for path, expect in [("/stats", b"200"), ("/export/ledger", b"xlsx"),
|
||||
("/orders", b"200"), ("/suppliers", b"200"),
|
||||
("/materials", b"200"), ("/users", b"200"), ("/", b"200")]:
|
||||
r = op.open(BASE + path, timeout=30)
|
||||
data = r.read()
|
||||
print(path, r.status, len(data))
|
||||
assert r.status == 200, path
|
||||
print("PAGES OK")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,156 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""模拟完整流程:停用/启用/删除 + 主管审核加章,并导出带章 PDF 渲染成 PNG。
|
||||
|
||||
用法:先启动服务(python app.py 8099),再 python test_stamp_toggle.py 8099
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
PORT = sys.argv[1] if len(sys.argv) > 1 else "8099"
|
||||
BASE = "http://127.0.0.1:%s" % PORT
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
OUT = os.path.join(HERE, "out")
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
DB = os.path.join(os.path.dirname(HERE), "po_app", "instance", "po.db")
|
||||
|
||||
FAILS = []
|
||||
|
||||
|
||||
def check(label, cond, extra=""):
|
||||
print((" OK " if cond else " FAIL ") + label + (" " + str(extra) if extra else ""))
|
||||
if not cond:
|
||||
FAILS.append(label)
|
||||
|
||||
|
||||
def new_sess(u, p):
|
||||
s = requests.Session()
|
||||
r = s.post(BASE + "/login", data={"username": u, "password": p})
|
||||
assert r.status_code == 200, r.status_code
|
||||
return s
|
||||
|
||||
|
||||
def flash_of(html):
|
||||
return " | ".join(re.sub(r"<[^>]+>", "", m.group(1)).strip()
|
||||
for m in re.finditer(r'class="flash[^"]*"[^>]*>(.*?)</div>',
|
||||
html, re.S))
|
||||
|
||||
|
||||
def row_html(html, key):
|
||||
"""取出包含指定关键字的那个 <tr>...</tr>。"""
|
||||
for m in re.finditer(r"<tr[^>]*>(.*?)</tr>", html, re.S):
|
||||
if key in m.group(1):
|
||||
return m.group(0)
|
||||
return ""
|
||||
|
||||
|
||||
def dbq(sql, args=()):
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute(sql, args)
|
||||
r = cur.fetchone()
|
||||
con.close()
|
||||
return r
|
||||
|
||||
|
||||
def main():
|
||||
admin = new_sess("admin", "admin123")
|
||||
|
||||
print("\n[1] 供应商:停用 → 整行置灰 → 启用 → 彻底删除")
|
||||
admin.post(BASE + "/suppliers",
|
||||
data={"code": "S-T1", "name": u"测试供应商T1", "contact": u"测试联系人"})
|
||||
sid = dbq("SELECT id FROM suppliers WHERE code='S-T1'")[0]
|
||||
h = admin.get(BASE + "/suppliers").text
|
||||
check("S-T1 已创建", "S-T1" in h)
|
||||
check("初始为启用态(未置灰)", "row-off" not in row_html(h, "S-T1"))
|
||||
admin.post(BASE + "/suppliers/%d/toggle" % sid)
|
||||
h = admin.get(BASE + "/suppliers").text
|
||||
check("停用后整行置灰", "row-off" in row_html(h, "S-T1"))
|
||||
check("停用后按钮变为『启用』", u"启用" in row_html(h, "S-T1"))
|
||||
check("停用后仍可编辑(保留入口)", "openEdit" in row_html(h, "S-T1"))
|
||||
admin.post(BASE + "/suppliers/%d/toggle" % sid)
|
||||
h = admin.get(BASE + "/suppliers").text
|
||||
check("启用后恢复(不再置灰)", "row-off" not in row_html(h, "S-T1"))
|
||||
admin.post(BASE + "/suppliers/%d/delete" % sid)
|
||||
h = admin.get(BASE + "/suppliers").text
|
||||
check("删除后从列表消失", "S-T1" not in h)
|
||||
|
||||
print("\n[2] 物料:停用 → 整行置灰 → 启用 → 彻底删除")
|
||||
admin.post(BASE + "/materials",
|
||||
data={"code": "M-T1", "spec": u"测试物料T1", "unit": u"件", "price": "9.9"})
|
||||
mid = dbq("SELECT id FROM materials WHERE code='M-T1'")[0]
|
||||
h = admin.get(BASE + "/materials").text
|
||||
check("M-T1 已创建", "M-T1" in h)
|
||||
check("初始为启用态(未置灰)", "row-off" not in row_html(h, "M-T1"))
|
||||
admin.post(BASE + "/materials/%d/toggle" % mid)
|
||||
h = admin.get(BASE + "/materials").text
|
||||
check("停用后整行置灰", "row-off" in row_html(h, "M-T1"))
|
||||
admin.post(BASE + "/materials/%d/toggle" % mid)
|
||||
h = admin.get(BASE + "/materials").text
|
||||
check("启用后恢复", "row-off" not in row_html(h, "M-T1"))
|
||||
admin.post(BASE + "/materials/%d/delete" % mid)
|
||||
h = admin.get(BASE + "/materials").text
|
||||
check("删除后从列表消失", "M-T1" not in h)
|
||||
|
||||
print("\n[3] 主管审核:确认前必须先加章")
|
||||
r = admin.post(BASE + "/orders/new", data={"doc_type": "purchase"})
|
||||
m = re.search(r"/orders/(\d+)/edit", r.url)
|
||||
oid = int(m.group(1))
|
||||
admin.post(BASE + "/orders/%d" % oid, data={
|
||||
"supplier_name": u"测试供应商",
|
||||
"item_code": ["M-1001"], "item_spec": [u"测试物料"],
|
||||
"item_unit": [u"件"], "item_qty": ["10"], "item_price": ["12.5"],
|
||||
"item_date": [""], "item_remark": [""],
|
||||
})
|
||||
admin.post(BASE + "/orders/%d/submit" % oid, data={"comment": u"模拟提交"})
|
||||
st = dbq("SELECT status FROM orders WHERE id=?", (oid,))[0]
|
||||
check("订单已提交,状态待审核", st == "pending_review", st)
|
||||
|
||||
rv = new_sess("reviewer", "123456")
|
||||
r = rv.post(BASE + "/orders/%d/review" % oid,
|
||||
data={"comment": u"直接点确认试试", "stamped": "0"})
|
||||
check("未加章 → 被拒绝并提示", u"请先加盖审核章" in flash_of(r.text),
|
||||
flash_of(r.text))
|
||||
st = dbq("SELECT status FROM orders WHERE id=?", (oid,))[0]
|
||||
check("未加章时状态不变", st == "pending_review", st)
|
||||
|
||||
r = rv.post(BASE + "/orders/%d/review" % oid,
|
||||
data={"comment": u"已核对无误", "stamped": "1"})
|
||||
row = dbq("SELECT status, reviewed_by FROM orders WHERE id=?", (oid,))
|
||||
check("加章后审核通过", row[0] == "pending_approval", row)
|
||||
check("记录了审核人", bool(row[1]), row[1])
|
||||
|
||||
print("\n[4] 导出带章 PDF 并渲染预览")
|
||||
pdf_bytes = admin.get(BASE + "/orders/%d/pdf" % oid).content
|
||||
pdf_path = os.path.join(OUT, "seal_review.pdf")
|
||||
with open(pdf_path, "wb") as f:
|
||||
f.write(pdf_bytes)
|
||||
check("PDF 已生成", len(pdf_bytes) > 10000, "%d bytes" % len(pdf_bytes))
|
||||
try:
|
||||
import fitz
|
||||
doc = fitz.open(pdf_path)
|
||||
pix = doc[-1].get_pixmap(dpi=130)
|
||||
png_path = os.path.join(OUT, "seal_review.png")
|
||||
pix.save(png_path)
|
||||
print(" 渲染预览:", png_path, "共 %d 页" % doc.page_count)
|
||||
doc.close()
|
||||
except Exception as e:
|
||||
print(" 渲染预览失败:", e)
|
||||
|
||||
print("\n[5] 清理测试订单")
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute("DELETE FROM order_items WHERE order_id=?", (oid,))
|
||||
cur.execute("DELETE FROM orders WHERE id=?", (oid,))
|
||||
con.commit()
|
||||
con.close()
|
||||
print(" 已删除订单 %d" % oid)
|
||||
|
||||
print("\n" + ("模拟全部通过" if not FAILS else "失败: " + "; ".join(FAILS)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""打包后 EXE 冒烟测试。
|
||||
|
||||
确认这些确实进了包:批量导入入口与模板、列表编辑入口、弹窗样式,
|
||||
以及「新建订单明细为空时仍能添加行」所依赖的 rowTpl 模板。
|
||||
|
||||
用法:python verify_exe.py 8080 [数据库路径]
|
||||
不传数据库路径时默认 po_app/dist/instance/po.db
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
BASE = "http://127.0.0.1:%s" % (sys.argv[1] if len(sys.argv) > 1 else "8080")
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
DB = sys.argv[2] if len(sys.argv) > 2 else os.path.join(
|
||||
os.path.dirname(HERE), "po_app", "dist", "instance", "po.db")
|
||||
|
||||
s = requests.Session()
|
||||
FAILS = []
|
||||
|
||||
|
||||
def check(label, cond, extra=""):
|
||||
print((" OK " if cond else " FAIL ") + label + (" " + str(extra) if extra else ""))
|
||||
if not cond:
|
||||
FAILS.append(label)
|
||||
|
||||
|
||||
def main():
|
||||
r = s.post(BASE + "/login", data={"username": "admin", "password": "admin123"})
|
||||
check("登录", r.status_code == 200, r.status_code)
|
||||
|
||||
# 注意:url_for() 会被 Jinja 渲染成实际路径,页面里不会出现函数名
|
||||
for path, marks in (
|
||||
("/materials", [u"批量导入 Excel", "matImpModal", "/materials/import_excel",
|
||||
"/materials/import_template.xlsx", "openEdit", "editModal",
|
||||
"/toggle", "/delete", u"停用", u"删除"]),
|
||||
("/suppliers", [u"批量导入 Excel", "supImpModal", "/suppliers/import_excel",
|
||||
"/suppliers/import_template.xlsx", "openEdit", "editModal",
|
||||
"/toggle", "/delete", u"停用", u"删除"]),
|
||||
):
|
||||
h = s.get(BASE + path).text
|
||||
missing = [m for m in marks if m not in h]
|
||||
check("%s 含导入/编辑/停用删除/置灰" % path, not missing, missing)
|
||||
|
||||
css = s.get(BASE + "/static/style.css").text
|
||||
check("style.css 含弹窗样式", "modal-mask" in css and "modal-box" in css)
|
||||
|
||||
for u in ("/materials/import_template.xlsx", "/suppliers/import_template.xlsx"):
|
||||
r = s.get(BASE + u)
|
||||
check("模板下载 %s" % u, r.status_code == 200 and len(r.content) > 3000,
|
||||
"%s bytes" % len(r.content))
|
||||
|
||||
# 新建订单:明细为空时必须仍能添加行(依赖 rowTpl 模板存在)
|
||||
r = s.post(BASE + "/orders/new", data={"doc_type": "purchase"})
|
||||
h = r.text
|
||||
no = ""
|
||||
m = re.search(r"PO\d{12}", h)
|
||||
if m:
|
||||
no = m.group(0)
|
||||
check("新建订单页含 rowTpl 明细行模板", 'id="rowTpl"' in h)
|
||||
tpl = re.search(r'<template id="rowTpl">(.*?)</template>', h, re.S)
|
||||
check("rowTpl 内含完整空行", bool(tpl) and 'name="item_code"' in tpl.group(1))
|
||||
|
||||
if no and os.path.exists(DB):
|
||||
con = sqlite3.connect(DB)
|
||||
cur = con.cursor()
|
||||
cur.execute("DELETE FROM order_items WHERE order_id IN "
|
||||
"(SELECT id FROM orders WHERE order_no=?)", (no,))
|
||||
cur.execute("DELETE FROM orders WHERE order_no=?", (no,))
|
||||
con.commit()
|
||||
con.close()
|
||||
print(" 已清理测试订单 %s" % no)
|
||||
|
||||
print("\n" + ("打包版冒烟全部通过" if not FAILS else "失败: " + "; ".join(FAILS)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Dump .xls layout: row heights, col widths, print setup, print area, formulas."""
|
||||
import sys
|
||||
import xlrd
|
||||
|
||||
def dump(path):
|
||||
print("=" * 90)
|
||||
print("FILE:", path)
|
||||
bk = xlrd.open_workbook(path, formatting_info=True)
|
||||
try:
|
||||
names = {k: (v.name, getattr(v, 'result', None)) for k, v in list(bk.name_map.items())}
|
||||
print("NAMES:", names)
|
||||
except Exception as e:
|
||||
print("NAMES err", e)
|
||||
for sh in bk.sheets():
|
||||
print("-" * 90)
|
||||
print("SHEET", sh.name, "nrows", sh.nrows, "ncols", sh.ncols)
|
||||
print("defcolwidth", sh.defcolwidth, "standardwidth", getattr(sh, 'standardwidth', None))
|
||||
print("colwidths:", [(c, sh.colinfo_map[c].width if c in sh.colinfo_map else None) for c in range(sh.ncols)])
|
||||
print("rowheights:", [(r + 1, sh.rowinfo_map[r].height if r in sh.rowinfo_map else None) for r in range(sh.nrows)])
|
||||
try:
|
||||
pa = sh.phony_dim_rows
|
||||
print("phony_dim_rows", pa)
|
||||
except Exception:
|
||||
pass
|
||||
for attr in ("print_area", "print_titles"):
|
||||
print(attr, getattr(bk, attr, None))
|
||||
|
||||
for p in sys.argv[1:]:
|
||||
dump(p)
|
||||
Reference in New Issue
Block a user