2026-06-10 11:30:07 +08:00
|
|
|
|
from app.extensions import db
|
|
|
|
|
|
from app.models.bom_draft import BomDraftTable
|
|
|
|
|
|
from app.models.base import MaterialBase
|
|
|
|
|
|
from app.services.bom_service import BomService
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BomDraftService:
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2026-09-21 14:13:14 +08:00
|
|
|
|
def save_draft(bom_no, version, parent_id, children, bom_remark=''):
|
2026-06-10 11:30:07 +08:00
|
|
|
|
try:
|
|
|
|
|
|
# 1. 删除旧草稿
|
|
|
|
|
|
old = BomDraftTable.query.filter_by(bom_no=bom_no, version=version).all()
|
|
|
|
|
|
for rec in old:
|
|
|
|
|
|
db.session.delete(rec)
|
|
|
|
|
|
db.session.flush()
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 如果没有任何子件,必须插入一条只包含 parent_id 的占位头数据
|
|
|
|
|
|
if not children:
|
|
|
|
|
|
dummy_draft = BomDraftTable(
|
|
|
|
|
|
bom_no=bom_no, version=version, parent_id=parent_id,
|
2026-09-21 14:13:14 +08:00
|
|
|
|
child_id=None, dosage=0, loss_rate=0, remark='',
|
|
|
|
|
|
bom_remark=bom_remark
|
2026-06-10 11:30:07 +08:00
|
|
|
|
)
|
|
|
|
|
|
db.session.add(dummy_draft)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 正常批量插入新草稿行
|
|
|
|
|
|
for child in children:
|
|
|
|
|
|
draft = BomDraftTable(
|
|
|
|
|
|
bom_no=bom_no, version=version, parent_id=parent_id,
|
|
|
|
|
|
child_id=child.get('child_id'),
|
|
|
|
|
|
dosage=child.get('dosage', 0),
|
|
|
|
|
|
loss_rate=child.get('loss_rate', 0),
|
2026-09-08 10:14:44 +08:00
|
|
|
|
remark=child.get('remark', ''),
|
2026-09-21 14:13:14 +08:00
|
|
|
|
bom_remark=bom_remark,
|
2026-09-08 10:14:44 +08:00
|
|
|
|
child_bom_no=child.get('child_bom_no') or None,
|
|
|
|
|
|
child_bom_version=child.get('child_bom_version') or None
|
2026-06-10 11:30:07 +08:00
|
|
|
|
)
|
|
|
|
|
|
db.session.add(draft)
|
|
|
|
|
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
db.session.rollback()
|
|
|
|
|
|
logger.error(f"[BomDraft] save_draft 失败 bom_no={bom_no}: {e}")
|
|
|
|
|
|
raise
|
|
|
|
|
|
return bom_no
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def get_draft_detail(bom_no, version):
|
|
|
|
|
|
rows = db.session.query(
|
|
|
|
|
|
BomDraftTable,
|
|
|
|
|
|
MaterialBase.name.label('child_name'),
|
|
|
|
|
|
MaterialBase.spec_model.label('child_spec')
|
|
|
|
|
|
).outerjoin(
|
|
|
|
|
|
MaterialBase, BomDraftTable.child_id == MaterialBase.id
|
|
|
|
|
|
).filter(
|
|
|
|
|
|
BomDraftTable.bom_no == bom_no,
|
|
|
|
|
|
BomDraftTable.version == version
|
|
|
|
|
|
).all()
|
|
|
|
|
|
|
|
|
|
|
|
if not rows:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
first = rows[0].BomDraftTable
|
|
|
|
|
|
parent_id = first.parent_id
|
|
|
|
|
|
parent_material = MaterialBase.query.get(parent_id) if parent_id else None
|
|
|
|
|
|
|
|
|
|
|
|
children = []
|
|
|
|
|
|
for draft, child_name, child_spec in rows:
|
|
|
|
|
|
# 过滤掉保存 BOM 头时插入的占位空行
|
|
|
|
|
|
if draft.child_id is not None:
|
|
|
|
|
|
children.append({
|
|
|
|
|
|
'child_id': draft.child_id,
|
|
|
|
|
|
'child_name': child_name or '',
|
|
|
|
|
|
'child_spec': child_spec or '',
|
|
|
|
|
|
'dosage': float(draft.dosage) if draft.dosage else 0.0,
|
|
|
|
|
|
'loss_rate': float(draft.loss_rate) if draft.loss_rate else 0.0,
|
|
|
|
|
|
'remark': draft.remark or '',
|
2026-09-08 10:14:44 +08:00
|
|
|
|
'child_bom_no': draft.child_bom_no or '',
|
|
|
|
|
|
'child_bom_version': draft.child_bom_version or '',
|
2026-06-10 11:30:07 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'bom_no': bom_no,
|
|
|
|
|
|
'version': first.version,
|
|
|
|
|
|
'parent_id': parent_id,
|
|
|
|
|
|
'parent_name': parent_material.name if parent_material else '',
|
|
|
|
|
|
'parent_spec': parent_material.spec_model if parent_material else '',
|
2026-09-21 14:13:14 +08:00
|
|
|
|
'remark': first.bom_remark or '', # ★ BOM 级备注
|
2026-06-10 11:30:07 +08:00
|
|
|
|
'children': children,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def publish_draft(bom_no, version):
|
|
|
|
|
|
"""
|
|
|
|
|
|
发布草稿为正式 BOM:
|
|
|
|
|
|
1. 获取草稿数据
|
|
|
|
|
|
2. 强校验(父件不为空、子件列表非空、所有子件 ID>0、用量>0)
|
|
|
|
|
|
3. 调用 BomService.save_bom 写入正式 bom_table
|
|
|
|
|
|
4. 清空草稿数据
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 步骤 1
|
|
|
|
|
|
draft = BomDraftService.get_draft_detail(bom_no, version)
|
|
|
|
|
|
if not draft:
|
|
|
|
|
|
raise ValueError('草稿不存在')
|
|
|
|
|
|
|
|
|
|
|
|
# 步骤 2:强校验
|
|
|
|
|
|
if not draft.get('parent_id'):
|
|
|
|
|
|
raise ValueError('发布失败:父件不能为空')
|
|
|
|
|
|
|
|
|
|
|
|
children = draft.get('children', [])
|
|
|
|
|
|
if not children:
|
|
|
|
|
|
raise ValueError('发布失败:子件列表不能为空')
|
|
|
|
|
|
|
|
|
|
|
|
for child in children:
|
|
|
|
|
|
if not child.get('child_id') or child['child_id'] <= 0:
|
|
|
|
|
|
raise ValueError('发布失败:子件ID必须大于0')
|
|
|
|
|
|
dosage = child.get('dosage')
|
|
|
|
|
|
if not dosage or dosage <= 0:
|
|
|
|
|
|
raise ValueError('发布失败:子件用量必须大于0')
|
|
|
|
|
|
|
|
|
|
|
|
# 步骤 3:复用正式 BOM 的写入逻辑(跨版本查重 + 缓存清理均在 save_bom 内完成)
|
|
|
|
|
|
publish_data = {
|
|
|
|
|
|
'bom_no': bom_no,
|
|
|
|
|
|
'version': version,
|
|
|
|
|
|
'parent_id': draft['parent_id'],
|
2026-09-21 14:13:14 +08:00
|
|
|
|
# ★ 草稿的 BOM 级备注要带到正式表,否则发布即丢失
|
|
|
|
|
|
'remark': draft.get('remark', '') or '',
|
2026-06-10 11:30:07 +08:00
|
|
|
|
'children': [
|
|
|
|
|
|
{
|
|
|
|
|
|
'child_id': child['child_id'],
|
|
|
|
|
|
'dosage': child['dosage'],
|
|
|
|
|
|
'remark': child.get('remark', ''),
|
2026-09-08 10:14:44 +08:00
|
|
|
|
'child_bom_no': child.get('child_bom_no') or '',
|
|
|
|
|
|
'child_bom_version': child.get('child_bom_version') or '',
|
2026-06-10 11:30:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
for child in children
|
|
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
BomService.save_bom(publish_data)
|
|
|
|
|
|
|
|
|
|
|
|
# 步骤 4:清空草稿数据
|
|
|
|
|
|
old_rows = BomDraftTable.query.filter_by(bom_no=bom_no, version=version).all()
|
|
|
|
|
|
for rec in old_rows:
|
|
|
|
|
|
db.session.delete(rec)
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
logger.info(f"[BomDraft] publish_draft bom_no={bom_no} version={version} -> 已发布并清空草稿")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
db.session.rollback()
|
|
|
|
|
|
logger.error(f"[BomDraft] publish_draft 失败 bom_no={bom_no}: {e}")
|
|
|
|
|
|
raise
|
|
|
|
|
|
return bom_no
|