Files
order_system/po_app/excel_io.py
T

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)