diff --git a/inventory-backend/app/models/inbound/product.py b/inventory-backend/app/models/inbound/product.py index cc380e7..30bf40a 100644 --- a/inventory-backend/app/models/inbound/product.py +++ b/inventory-backend/app/models/inbound/product.py @@ -1,5 +1,6 @@ # app/models/inbound/product.py from app.extensions import db +import json class StockProduct(db.Model): @@ -17,7 +18,6 @@ class StockProduct(db.Model): production_date = db.Column(db.Date) barcode = db.Column(db.String(100)) serial_number = db.Column(db.String(100)) - # Note: 成品通常按SN管理,SQL定义无 batch_number # 数量 in_quantity = db.Column(db.Numeric(19, 4), default=0) @@ -29,27 +29,32 @@ class StockProduct(db.Model): warehouse_location = db.Column(db.String(100)) # 生产与成本 - bom_code = db.Column('bom_id', db.String(100)) # 映射 SQL: bom_id + bom_code = db.Column('bom_id', db.String(100)) bom_version = db.Column(db.String(50)) - work_order_code = db.Column('work_order_id', db.String(100)) # 映射 SQL: work_order_id + work_order_code = db.Column('work_order_id', db.String(100)) raw_material_cost = db.Column(db.Numeric(19, 4), default=0) manual_cost = db.Column(db.Numeric(19, 4), default=0) - production_manager = db.Column('producer_name', db.String(100)) # 映射 SQL: producer_name + production_manager = db.Column('producer_name', db.String(100)) production_time_range = db.Column(db.String(255)) - # 质量与链接 + # 质量与检测 (均为 JSON 存储) quality_status = db.Column(db.String(50)) - quality_report_link = db.Column(db.Text) - detail_link = db.Column(db.Text) + quality_report_link = db.Column(db.Text) # 质量报告 + inspection_report_link = db.Column(db.Text) # 检测报告(旧字段升级为JSON) - # 成品特有字段 + # [新增] 成品实拍图 (JSON 存储) + product_photo = db.Column(db.Text) + + detail_link = db.Column(db.Text) + remark = db.Column(db.Text) + + # 销售相关 sale_price = db.Column(db.Numeric(19, 4), default=0) - inspection_report_link = db.Column(db.Text) order_id = db.Column(db.String(100)) - # [新增] 全局打印流水号 (用于跨表连续编号,对应 Sequence: global_print_seq) + # 全局打印流水号 global_print_id = db.Column(db.Integer) # 关系定义 @@ -60,6 +65,17 @@ class StockProduct(db.Model): man_val = float(self.manual_cost or 0) unit_total = raw_val + man_val + # 辅助解析函数 + def parse_img_list(json_str): + if not json_str: + return [] + try: + if not json_str.startswith('['): + return [json_str] # 兼容旧数据单链接 + return json.loads(json_str) + except: + return [] + return { 'id': self.id, 'base_id': self.base_id, @@ -97,14 +113,18 @@ class StockProduct(db.Model): 1] if self.production_time_range and ' ~ ' in self.production_time_range else '', 'quality_status': self.quality_status, - 'quality_report_link': self.quality_report_link, + + # [核心修改] 三个图片/链接字段全部解析为数组 + 'product_photo': parse_img_list(self.product_photo), + 'quality_report_link': parse_img_list(self.quality_report_link), + 'inspection_report_link': parse_img_list(self.inspection_report_link), + 'detail_link': self.detail_link, + 'remark': self.remark, 'sale_price': float(self.sale_price or 0), - 'inspection_report_link': self.inspection_report_link, 'order_id': self.order_id, - # [新增] 返回全局打印ID及其格式化字符串 'global_print_id': self.global_print_id, 'global_print_id_str': f"{self.global_print_id:010d}" if self.global_print_id else "" } \ No newline at end of file diff --git a/inventory-backend/app/services/inbound/product_service.py b/inventory-backend/app/services/inbound/product_service.py index bb0790b..80a3f65 100644 --- a/inventory-backend/app/services/inbound/product_service.py +++ b/inventory-backend/app/services/inbound/product_service.py @@ -1,10 +1,10 @@ # app/services/inbound/product_service.py from app.extensions import db from app.models.base import MaterialBase -from app.models.inbound.product import StockProduct from datetime import datetime from sqlalchemy import or_, func, text import traceback +import json class ProductInboundService: @@ -12,7 +12,6 @@ class ProductInboundService: def search_base_material(keyword): try: if not keyword: - # 如果没有关键词,返回最新的20条 query = MaterialBase.query.filter(MaterialBase.is_enabled == True).order_by( MaterialBase.id.desc()).limit(20) else: @@ -34,6 +33,8 @@ class ProductInboundService: @staticmethod def handle_inbound(data): + from app.models.inbound.product import StockProduct + try: base_id = data.get('base_id') if not base_id: raise ValueError("必须选择基础物料") @@ -43,45 +44,39 @@ class ProductInboundService: in_date_val = datetime.utcnow().date() if data.get('in_date'): try: - # 兼容字符串格式日期处理 date_str = str(data['in_date']) - if len(date_str) > 10: - date_str = date_str[:10] + if len(date_str) > 10: date_str = date_str[:10] in_date_val = datetime.strptime(date_str, '%Y-%m-%d').date() except: pass in_qty = float(data.get('in_quantity') or 0) - # 处理生产时间范围 p_start = data.get('production_start_time', '') p_end = data.get('production_end_time', '') time_range = f"{p_start} ~ {p_end}" if p_start or p_end else None - # ------------------------------------------------------------------ - # 1. 获取全局打印流水号 (跨表唯一,用于打印逻辑) - # ------------------------------------------------------------------ + # 全局流水号 seq_sql = text("SELECT nextval('global_print_seq')") result = db.session.execute(seq_sql) next_global_id = result.scalar() - # ------------------------------------------------------------------ - # 2. 自动生成 SKU (格式: 10位数字,补零) - # ------------------------------------------------------------------ generated_sku = str(next_global_id).zfill(10) + final_barcode = data.get('barcode') or generated_sku - # ------------------------------------------------------------------ - # 3. 条码逻辑处理 - # 如果前端没传条码,则默认使用 SKU 作为条码 - # ------------------------------------------------------------------ - final_barcode = data.get('barcode') - if not final_barcode: - final_barcode = generated_sku + # [核心修改] 处理三个图片/链接列表 + photo_list = data.get('product_photo', []) + quality_list = data.get('quality_report_link', []) + inspection_list = data.get('inspection_report_link', []) + + if not isinstance(photo_list, list): photo_list = [] + if not isinstance(quality_list, list): quality_list = [] + if not isinstance(inspection_list, list): inspection_list = [] new_stock = StockProduct( base_id=material.id, - global_print_id=next_global_id, # 新增全局打印ID - sku=generated_sku, # 使用自动生成的SKU + global_print_id=next_global_id, + sku=generated_sku, production_date=in_date_val, barcode=final_barcode, serial_number=data.get('serial_number'), @@ -103,18 +98,21 @@ class ProductInboundService: manual_cost=float(data.get('manual_cost') or 0), quality_status=data.get('quality_status', '合格'), - quality_report_link=data.get('quality_report_link'), + + # 存为 JSON + product_photo=json.dumps(photo_list), + quality_report_link=json.dumps(quality_list), + inspection_report_link=json.dumps(inspection_list), + detail_link=data.get('detail_link'), + remark=data.get('remark'), sale_price=float(data.get('sale_price') or 0), - inspection_report_link=data.get('inspection_report_link'), order_id=data.get('order_id') ) db.session.add(new_stock) db.session.commit() - - # 返回对象实例以便上层调用 to_dict() return new_stock except Exception as e: db.session.rollback() @@ -122,26 +120,38 @@ class ProductInboundService: @staticmethod def update_inbound(stock_id, data): + from app.models.inbound.product import StockProduct + try: stock = StockProduct.query.get(stock_id) if not stock: raise ValueError("记录不存在") - # 允许更新的字段列表 fields = [ 'barcode', 'serial_number', 'warehouse_location', 'status', 'quality_status', 'bom_code', 'bom_version', - 'work_order_code', 'production_manager', 'quality_report_link', - 'detail_link', 'inspection_report_link', 'order_id' + 'work_order_code', 'production_manager', + 'detail_link', 'order_id', 'remark' ] for f in fields: if f in data: setattr(stock, f, data[f]) - # 数值类型处理 + # [核心修改] 更新 JSON 字段 + if 'product_photo' in data: + imgs = data['product_photo'] + if isinstance(imgs, list): stock.product_photo = json.dumps(imgs) + + if 'quality_report_link' in data: + imgs = data['quality_report_link'] + if isinstance(imgs, list): stock.quality_report_link = json.dumps(imgs) + + if 'inspection_report_link' in data: + imgs = data['inspection_report_link'] + if isinstance(imgs, list): stock.inspection_report_link = json.dumps(imgs) + if 'sale_price' in data: stock.sale_price = float(data['sale_price']) if 'raw_material_cost' in data: stock.raw_material_cost = float(data['raw_material_cost']) if 'manual_cost' in data: stock.manual_cost = float(data['manual_cost']) - # 数量更新逻辑 (同步更新库存和可用量) if 'in_quantity' in data: new_qty = float(data['in_quantity']) old_qty = float(stock.in_quantity) @@ -151,14 +161,11 @@ class ProductInboundService: stock.stock_quantity = float(stock.stock_quantity) + diff stock.available_quantity = float(stock.available_quantity) + diff - # 时间范围处理 if 'production_start_time' in data or 'production_end_time' in data: old_range = stock.production_time_range or " ~ " parts = old_range.split(' ~ ') - # 获取原值防止越界 old_start = parts[0] if len(parts) > 0 else '' old_end = parts[1] if len(parts) > 1 else '' - start = data.get('production_start_time', old_start) end = data.get('production_end_time', old_end) stock.production_time_range = f"{start} ~ {end}" @@ -171,6 +178,7 @@ class ProductInboundService: @staticmethod def delete_inbound(stock_id): + from app.models.inbound.product import StockProduct try: stock = StockProduct.query.get(stock_id) if stock: @@ -183,8 +191,8 @@ class ProductInboundService: @staticmethod def get_list(page, limit, keyword=None): + from app.models.inbound.product import StockProduct try: - # 联表查询 query = db.session.query(StockProduct).outerjoin(MaterialBase, StockProduct.base_id == MaterialBase.id) if keyword: @@ -199,7 +207,6 @@ class ProductInboundService: pagination = query.order_by(StockProduct.id.desc()).paginate(page=page, per_page=limit, error_out=False) - # 计算聚合库存 current_items = pagination.items base_ids = list(set([i.base_id for i in current_items])) stock_map = {} diff --git a/inventory-web/src/views/stock/inbound/product.vue b/inventory-web/src/views/stock/inbound/product.vue index fe3016c..341b860 100644 --- a/inventory-web/src/views/stock/inbound/product.vue +++ b/inventory-web/src/views/stock/inbound/product.vue @@ -13,18 +13,38 @@ 表头 - {{ c.label }} + {{ c.label }} - + - + - + + + {{ scope.row.material_name }} + + + + {{ scope.row[col.prop] }} - @@ -41,7 +61,28 @@ {{ scope.row.quality_status }} - + + + + + + + + +{{getImagesOnly(scope.row[col.prop]).length}} + + + 链接 + + - + + + 查看 @@ -54,10 +95,10 @@ - + - 打印 + 编辑 删除 @@ -68,134 +109,206 @@ - + + - - 1. 基础信息 - - - - - - - - {{ item.name }} - {{ item.spec }} - 历史 - 系统 + + 1. 基础信息 + + + + + + + + {{ item.name }} + {{ item.spec }} + 历史 + 系统 + + + + + + + + 未输入时展示最新物料;输入关键词进行精确搜索。 + + + + + + + + + + + + + + + 2. 入库详情 + + + + + + + + + + + + + SN + + + + + + + + + + + + + + + + + + + + + + + + + + 拍照 - - - - - - - 未输入时展示最新物料;输入关键词进行精确搜索。 - - - - - - - - - - - - - - - 2. 入库详情 - - - - - - - - - - - - - SN + + + + + - - + + + + + + + 拍照 + + + + + + + + + + + + + + + 拍照 + + + + - - - - - - - - - - - - - - - 3. 生产与销售信息 - - - - - - - - - - - - - - ¥ - + + 3. 生产与销售信息 + + + + + + + + + + + + + + ¥ + - - - - - - - - - - - - + + + + + + + + + + + + + - - + +