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 def save_draft(bom_no, version, parent_id, children): 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, child_id=None, dosage=0, loss_rate=0, remark='' ) 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), remark=child.get('remark', '') ) 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 '', }) 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 '', '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'], 'children': [ { 'child_id': child['child_id'], 'dosage': child['dosage'], 'remark': child.get('remark', ''), } 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