完善采购审批流程、企业微信通知和采购专用章
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import os
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from flask import current_app
|
||||
|
||||
|
||||
def save_upload(file):
|
||||
if not file or not file.filename:
|
||||
raise ValueError('请选择 Excel 文件')
|
||||
extension = Path(file.filename).suffix.lower()
|
||||
if extension not in ('.xls', '.xlsx'):
|
||||
raise ValueError('仅支持 .xls 和 .xlsx 文件')
|
||||
path = os.path.join(current_app.config['PO_UPLOADS'], uuid.uuid4().hex + extension)
|
||||
file.save(path)
|
||||
return path
|
||||
|
||||
|
||||
def read_rows(path):
|
||||
"""读取 xlsx/xls 明细,返回 [代码,规格,单位,数量,单价,交期,备注]。"""
|
||||
ext = os.path.splitext(path)[1].lower()
|
||||
grid = []
|
||||
if ext in (".xlsx", ".xlsm"):
|
||||
import openpyxl
|
||||
wb = openpyxl.load_workbook(path, data_only=True, read_only=True)
|
||||
for row in wb[wb.sheetnames[0]].iter_rows(values_only=True):
|
||||
grid.append(["" if c is None else c for c in row])
|
||||
wb.close()
|
||||
else:
|
||||
import xlrd
|
||||
bk = xlrd.open_workbook(path)
|
||||
sh = bk.sheet_by_index(0)
|
||||
for r in range(sh.nrows):
|
||||
grid.append([xlrd.xldate_as_datetime(sh.cell_value(r, c), bk.datemode)
|
||||
if sh.cell_type(r, c) == xlrd.XL_CELL_DATE else sh.cell_value(r, c)
|
||||
for c in range(sh.ncols)])
|
||||
keys = {"code": [u"代码", u"料号", u"物料编码", u"品号"],
|
||||
"spec": [u"规格", u"名称", u"品名", u"描述"],
|
||||
"unit": [u"单位"],
|
||||
"qty": [u"数量", u"数量(QTY)"],
|
||||
"price": [u"单价", u"含税单价", u"价格"],
|
||||
"date": [u"交期", u"交货日期", u"日期"],
|
||||
"remark": [u"备注"]}
|
||||
order = ["code", "spec", "unit", "qty", "price", "date", "remark"]
|
||||
header_idx = None
|
||||
for i, row in enumerate(grid[:8]):
|
||||
text = " ".join(str(c) for c in row)
|
||||
if any(k in text for k in keys["code"]) and any(k in text for k in keys["qty"]):
|
||||
header_idx = i
|
||||
break
|
||||
if header_idx is None:
|
||||
return [[c for c in row[:7]] for row in grid]
|
||||
heads = [str(c or "").strip() for c in grid[header_idx]]
|
||||
mapping = {}
|
||||
for idx, h in enumerate(heads):
|
||||
for k, ks in keys.items():
|
||||
if any(x in h for x in ks):
|
||||
mapping[k] = idx
|
||||
break
|
||||
out = []
|
||||
for row in grid[header_idx + 1:]:
|
||||
vals = []
|
||||
for k in order:
|
||||
i = mapping.get(k)
|
||||
vals.append(row[i] if (i is not None and i < len(row)) else "")
|
||||
out.append(vals)
|
||||
return out
|
||||
|
||||
|
||||
def read_table(path, keys, order):
|
||||
"""通用表格读取:按关键词映射表头,返回按 order 排列的二维列表。
|
||||
|
||||
与 read_rows() 的区别是表头判定更宽松(命中任意关键词即可),
|
||||
因为物料表 / 供应商表都没有"数量"列,套用 read_rows 会退化成按位置读。
|
||||
"""
|
||||
ext = os.path.splitext(path)[1].lower()
|
||||
grid = []
|
||||
if ext in (".xlsx", ".xlsm"):
|
||||
import openpyxl
|
||||
wb = openpyxl.load_workbook(path, data_only=True, read_only=True)
|
||||
for row in wb[wb.sheetnames[0]].iter_rows(values_only=True):
|
||||
grid.append(["" if c is None else c for c in row])
|
||||
wb.close()
|
||||
else:
|
||||
import xlrd
|
||||
bk = xlrd.open_workbook(path)
|
||||
sh = bk.sheet_by_index(0)
|
||||
for r in range(sh.nrows):
|
||||
grid.append([xlrd.xldate_as_datetime(sh.cell_value(r, c), bk.datemode)
|
||||
if sh.cell_type(r, c) == xlrd.XL_CELL_DATE else sh.cell_value(r, c)
|
||||
for c in range(sh.ncols)])
|
||||
header_idx = None
|
||||
for i, row in enumerate(grid[:8]):
|
||||
text = " ".join(str(c) for c in row)
|
||||
if any(any(k in text for k in ks) for ks in keys.values()):
|
||||
header_idx = i
|
||||
break
|
||||
if header_idx is None:
|
||||
return [list(row[:len(order)]) for row in grid]
|
||||
heads = [str(c or "").strip() for c in grid[header_idx]]
|
||||
mapping = {}
|
||||
for idx, h in enumerate(heads):
|
||||
for k, ks in keys.items():
|
||||
if k in mapping:
|
||||
continue
|
||||
if any(x in h for x in ks):
|
||||
mapping[k] = idx
|
||||
break
|
||||
out = []
|
||||
for row in grid[header_idx + 1:]:
|
||||
vals = []
|
||||
for k in order:
|
||||
i = mapping.get(k)
|
||||
vals.append(row[i] if (i is not None and i < len(row)) else "")
|
||||
out.append(vals)
|
||||
return out
|
||||
|
||||
|
||||
MATERIAL_KEYS = {
|
||||
"code": [u"物料代码", u"料号", u"物料编码", u"品号", u"代码"],
|
||||
"spec": [u"规格", u"名称", u"品名", u"描述"],
|
||||
"unit": [u"单位"],
|
||||
"price": [u"单价", u"价格"],
|
||||
}
|
||||
MATERIAL_ORDER = ["code", "spec", "unit", "price"]
|
||||
|
||||
SUPPLIER_KEYS = {
|
||||
"code": [u"编号", u"供应商编号", u"代码"],
|
||||
"name": [u"名称", u"供应商名称", u"公司"],
|
||||
"contact": [u"联系人"],
|
||||
"phone": [u"电话", u"手机", u"联系方式"],
|
||||
"payment_terms": [u"付款方式", u"结算方式", u"账期"],
|
||||
"tax_no": [u"税号", u"纳税人识别号"],
|
||||
"bank_name": [u"开户银行", u"银行名称"],
|
||||
"bank_account": [u"银行帐号", u"银行账号", u"帐号", u"账号"],
|
||||
}
|
||||
SUPPLIER_ORDER = ["code", "name", "contact", "phone", "payment_terms",
|
||||
"tax_no", "bank_name", "bank_account"]
|
||||
|
||||
|
||||
def read_material_rows(path):
|
||||
"""读取物料表,返回 [代码, 规格, 单位, 单价]。"""
|
||||
return read_table(path, MATERIAL_KEYS, MATERIAL_ORDER)
|
||||
|
||||
|
||||
def read_supplier_rows(path):
|
||||
"""读取供应商表,返回 [编号,名称,联系人,电话,付款方式,税号,开户银行,银行帐号]。"""
|
||||
return read_table(path, SUPPLIER_KEYS, SUPPLIER_ORDER)
|
||||
Reference in New Issue
Block a user