149 lines
5.6 KiB
Python
149 lines
5.6 KiB
Python
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, missing=""):
|
|
"""通用表格读取:按关键词映射表头,返回按 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 missing)
|
|
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 = {
|
|
"short_name": [u"简称", u"别名"],
|
|
"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", "short_name"]
|
|
|
|
|
|
def read_material_rows(path):
|
|
"""读取物料表,返回 [代码, 规格, 单位, 单价]。"""
|
|
return read_table(path, MATERIAL_KEYS, MATERIAL_ORDER)
|
|
|
|
|
|
def read_supplier_rows(path):
|
|
"""简称追加在末列;旧表缺少该列时以 None 表示,更新时保留已有简称。"""
|
|
return read_table(path, SUPPLIER_KEYS, SUPPLIER_ORDER, missing=None)
|