import base64 from sqlalchemy.ext.asyncio import AsyncSession from exceptions.exception import ServiceException from module_admin.entity.vo.user_vo import CurrentUserModel from module_business.dao.master_dao import MasterDao from module_business.dao.operation_dao import OperationDao from module_business.dao.shipment_label_dao import ShipmentLabelDao from module_business.domain.enums import DocumentStatus from module_business.domain.test_batch_scope import TestBatchScope, assert_record_in_scope from module_business.entity.vo.shipment_label_vo import ShipmentLabelModel from module_business.service.pallet_service import PalletService from module_business.service.shipment_service import ShipmentService class ShipmentLabelService: """复用发货双数据范围读取原始物料资料,不改变托盘、批号或库存。""" @staticmethod async def get_labels_services( query_db: AsyncSession, shipment_id: int, current_user: CurrentUserModel, test_scope: TestBatchScope, ) -> list[ShipmentLabelModel]: warehouse_ids, site_ids = await ShipmentService._scopes(query_db, current_user) shipment = await OperationDao.get_shipment_by_id( query_db, shipment_id, test_scope, warehouse_ids=warehouse_ids, site_ids=site_ids, ) if shipment is None: ShipmentService._raise_shipment_not_accessible(current_user) assert_record_in_scope(shipment, test_scope, '发货单') if shipment.status not in (DocumentStatus.DRAFT.value, DocumentStatus.CONFIRMED.value): raise ServiceException(message='当前发货单状态不支持打印标签') if not shipment.items: raise ServiceException(message='发货单没有可打印的托盘') for item in shipment.items: assert_record_in_scope(item, test_scope, '发货明细') # 先授权单据,再读取其目标现场与所属客户,避免通过标签枚举跨范围资料。 site = await MasterDao.get_site(query_db, shipment.target_site_id, test_scope) assert_record_in_scope(site, test_scope, '发货现场') customer = await MasterDao.get_customer(query_db, site.customer_id, test_scope) assert_record_in_scope(customer, test_scope, '发货客户') rows = await ShipmentLabelDao.get_label_sources(query_db, shipment, test_scope) if ( len(rows) != len(shipment.items) or {row[0].shipment_item_id for row in rows} != {item.shipment_item_id for item in shipment.items} ): raise ServiceException(message='发货明细已变化,请刷新后重新打印') draft = shipment.status == DocumentStatus.DRAFT.value # 整单全部验证后才生成任何二维码,缺少关联资料时不得输出部分标签。 for item, pallet, filling, quantity in rows: for record, name in ((item, '发货明细'), (pallet, '托盘'), (filling, '装填记录')): assert_record_in_scope(record, test_scope, name) if ( item.shipment_id != shipment.shipment_id or item.pallet_id != pallet.pallet_id or filling.pallet_id != pallet.pallet_id or type(quantity) is not int or quantity <= 0 or not pallet.qr_token ): raise ServiceException(message='发货标签资料不完整,请核对托盘与装填记录') if draft: if pallet.asset_status != '0' or filling.active_slot != 'ACTIVE' or quantity > pallet.capacity_packages: raise ServiceException(message='草稿托盘已不满足发货条件,请刷新后重新打印') elif item.active_filling_id != filling.filling_id or quantity != item.package_qty: raise ServiceException(message='发货快照不完整,无法打印标签') return [ ShipmentLabelModel( shipment_no=shipment.shipment_no, shipment_status=shipment.status, pallet_code=pallet.pallet_code, customer_name=customer.customer_name, site_name=site.site_name, strain_name=filling.strain_name_snapshot, batch_no=filling.batch_no, production_date=filling.production_date, expiry_date=filling.expiry_date, package_qty=quantity, qr_code_data_url='data:image/png;base64,' + base64.b64encode( PalletService.render_qr_png(pallet.qr_token), ).decode('ascii'), ) for item, pallet, filling, quantity in rows ]