From 42171ed6123ae52d49ca6114e0bfa27339456440 Mon Sep 17 00:00:00 2001 From: dxc Date: Tue, 24 Feb 2026 15:43:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E9=87=87=E8=B4=AD=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E7=A8=8E=E7=8E=87=E6=B7=BB=E5=8A=A0=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E6=89=80=E5=B1=9E=E5=85=AC=E5=8F=B8=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inventory-backend/app/models/inbound/buy.py | 31 ++- .../app/services/inbound/buy_service.py | 74 ++--- inventory-web/src/views/material/list.vue | 2 +- inventory-web/src/views/stock/inbound/buy.vue | 255 ++++++++++++++---- 4 files changed, 264 insertions(+), 98 deletions(-) diff --git a/inventory-backend/app/models/inbound/buy.py b/inventory-backend/app/models/inbound/buy.py index fa58ecd..4e9cc2f 100644 --- a/inventory-backend/app/models/inbound/buy.py +++ b/inventory-backend/app/models/inbound/buy.py @@ -4,6 +4,7 @@ import json # 显式导入 MaterialBase 以防 relationship 找不到引用 from app.models.base import MaterialBase + class StockBuy(db.Model): """ 采购入库库存表 @@ -32,35 +33,36 @@ class StockBuy(db.Model): available_quantity = db.Column(db.Numeric(19, 4), default=0) # 财务与商务 - unit_price = db.Column(db.Numeric(19, 4), default=0) - total_price = db.Column(db.Numeric(19, 4), default=0) + unit_price = db.Column(db.Numeric(19, 4), default=0) # 现意为:不含税单价 + total_price = db.Column(db.Numeric(19, 4), default=0) # 总价 + # [新增] 税率 + tax_rate = db.Column(db.Numeric(5, 2), default=0) + currency = db.Column(db.String(20), default='CNY') exchange_rate = db.Column(db.Numeric(15, 6), default=1.0) supplier_name = db.Column(db.String(255)) - buyer_name = db.Column(db.String(100)) # 对应 SQL: buyer_name - buyer_email = db.Column(db.String(100)) # 对应 SQL: buyer_email - original_link = db.Column(db.Text) # 对应 SQL: original_link + buyer_name = db.Column(db.String(100)) + buyer_email = db.Column(db.String(100)) + original_link = db.Column(db.Text) detail_link = db.Column(db.Text) # 图片字段 (存储 JSON 字符串) arrival_photo = db.Column(db.Text) - # [新增] 检测报告图片路径 (存储 JSON 字符串) inspection_report = db.Column(db.Text) - # [新增] 全局打印流水号 (用于跨表连续编号,对应 Sequence: global_print_seq) + # 全局打印流水号 global_print_id = db.Column(db.Integer) - # 关系定义 [已修改] + # 关系定义 base = db.relationship('MaterialBase', back_populates='stock_buys') def to_dict(self): - # 辅助解析函数:将数据库存储的 JSON 字符串转为 List + # 辅助解析函数 def parse_img_list(json_str): if not json_str: return [] try: - # 兼容旧数据:如果不是 JSON 格式(比如是单个 URL),则包装成 list if not json_str.startswith('['): return [json_str] return json.loads(json_str) @@ -70,7 +72,9 @@ class StockBuy(db.Model): return { 'id': self.id, 'base_id': self.base_id, - # [已修改] 使用 self.base + + # [修改] 增加公司名称 + 'company_name': self.base.company_name if self.base else '', 'material_name': self.base.name if self.base else '', 'spec_model': self.base.spec_model if self.base else '', 'category': self.base.category if self.base else '', @@ -95,6 +99,9 @@ class StockBuy(db.Model): 'unit_price': float(self.unit_price or 0), 'total_price': float(self.total_price or 0), + # [新增] 税率 + 'tax_rate': float(self.tax_rate or 0), + 'currency': self.currency, 'exchange_rate': float(self.exchange_rate or 1.0), @@ -104,11 +111,9 @@ class StockBuy(db.Model): 'source_link': self.original_link, 'detail_link': self.detail_link, - # [修改] 解析为数组返回给前端 'arrival_photo': parse_img_list(self.arrival_photo), 'inspection_report': parse_img_list(self.inspection_report), - # [新增] 返回全局打印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/buy_service.py b/inventory-backend/app/services/inbound/buy_service.py index c975ea5..87532b3 100644 --- a/inventory-backend/app/services/inbound/buy_service.py +++ b/inventory-backend/app/services/inbound/buy_service.py @@ -1,3 +1,4 @@ +# inventory-backend/app/services/inbound/buy_service.py from app.extensions import db from app.models.inbound.buy import StockBuy from app.models.base import MaterialBase @@ -47,7 +48,8 @@ class BuyInboundService: query = query.filter(and_( or_( MaterialBase.name.ilike(k_str), - MaterialBase.spec_model.ilike(k_str) + MaterialBase.spec_model.ilike(k_str), + MaterialBase.company_name.ilike(k_str) # 支持搜公司 ) )) @@ -58,6 +60,7 @@ class BuyInboundService: for item in pagination.items: items.append({ 'id': item.id, + 'company_name': item.company_name, # [新增] 'name': item.name, 'spec': item.spec_model, 'category': item.category, @@ -114,6 +117,7 @@ class BuyInboundService: in_qty = float(data.get('in_quantity') or 0) u_price = float(data.get('unit_price') or 0) + tax_rate = float(data.get('tax_rate') or 0) # [新增] try: seq_sql = text("SELECT nextval('global_print_seq')") @@ -131,8 +135,14 @@ class BuyInboundService: status=data.get('status', '在库'), in_quantity=in_qty, stock_quantity=in_qty, available_quantity=in_qty, inspection_status=data.get('inspection_status', '未检'), warehouse_location=data.get('warehouse_location'), - unit_price=u_price, total_price=in_qty * u_price, currency=data.get('currency', 'CNY'), + + # 价格信息 + unit_price=u_price, + tax_rate=tax_rate, # [新增] + total_price=in_qty * u_price, + currency=data.get('currency', 'CNY'), exchange_rate=data.get('exchange_rate', 1.0), + supplier_name=data.get('supplier_name'), buyer_name=data.get('purchaser'), buyer_email=data.get('purchaser_email'), original_link=data.get('source_link'), detail_link=data.get('detail_link'), @@ -172,13 +182,18 @@ class BuyInboundService: if 'arrival_photo' in data: stock.arrival_photo = json.dumps(data['arrival_photo']) if 'inspection_report' in data: stock.inspection_report = json.dumps(data['inspection_report']) + # [新增] 更新税率 + if 'tax_rate' in data: stock.tax_rate = float(data['tax_rate']) + if 'in_quantity' in data: diff = float(data['in_quantity']) - float(stock.in_quantity) if diff != 0: stock.in_quantity = float(data['in_quantity']) stock.stock_quantity = float(stock.stock_quantity) + diff stock.available_quantity = float(stock.available_quantity) + diff + if 'unit_price' in data: stock.unit_price = float(data['unit_price']) + stock.total_price = float(stock.in_quantity) * float(stock.unit_price) db.session.commit() return stock @@ -205,7 +220,7 @@ class BuyInboundService: # 5. 获取列表 # ============================================================ @staticmethod - def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None): + def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None, company=None): try: query = db.session.query(StockBuy).outerjoin(MaterialBase, StockBuy.base_id == MaterialBase.id) @@ -219,18 +234,23 @@ class BuyInboundService: StockBuy.serial_number.ilike(k_str), StockBuy.supplier_name.ilike(k_str), StockBuy.buyer_name.ilike(k_str), - MaterialBase.name.ilike(k_str), # 名称 - MaterialBase.spec_model.ilike(k_str), # 规格 + MaterialBase.name.ilike(k_str), + MaterialBase.spec_model.ilike(k_str), + MaterialBase.company_name.ilike(k_str), # 关键词也支持搜公司 ] query = query.filter(or_(*conditions)) # 2. 类别独立搜索 if category and category.strip(): - query = query.filter(MaterialBase.category == category.strip()) # 下拉框通常是精确匹配 + query = query.filter(MaterialBase.category == category.strip()) # 3. 类型独立搜索 if material_type and material_type.strip(): - query = query.filter(MaterialBase.material_type == material_type.strip()) # 精确匹配 + query = query.filter(MaterialBase.material_type == material_type.strip()) + + # 3.1 公司独立搜索 [新增] + if company and company.strip(): + query = query.filter(MaterialBase.company_name == company.strip()) # 4. 状态筛选 if not statuses: statuses = ['在库', '借库'] @@ -242,52 +262,44 @@ class BuyInboundService: pagination = query.order_by(StockBuy.in_date.desc()).paginate(page=page, per_page=limit, error_out=False) items = [] for item in pagination.items: - items.append({ - 'id': item.id, 'base_id': item.base_id, 'material_name': item.base.name if item.base else '', - 'spec_model': item.base.spec_model if item.base else '', - 'category': item.base.category if item.base else '', 'unit': item.base.unit if item.base else '', - 'material_type': item.base.material_type if item.base else '', 'sku': item.sku, - 'inbound_date': str(item.in_date)[:10] if item.in_date else '', 'barcode': item.barcode, - 'serial_number': item.serial_number, 'batch_number': item.batch_number, 'status': item.status, - 'inspection_status': item.inspection_status, 'qty_inbound': float(item.in_quantity or 0), - 'qty_stock': float(item.stock_quantity or 0), 'qty_available': float(item.available_quantity or 0), - 'warehouse_loc': item.warehouse_location, 'unit_price': float(item.unit_price or 0), - 'total_price': float(item.total_price or 0), 'currency': item.currency, - 'exchange_rate': float(item.exchange_rate or 1), 'supplier_name': item.supplier_name, - 'purchaser': item.buyer_name, 'purchaser_email': item.buyer_email, - 'source_link': item.original_link, 'detail_link': item.detail_link, - 'arrival_photo': json.loads(item.arrival_photo) if item.arrival_photo else [], - 'inspection_report': json.loads(item.inspection_report) if item.inspection_report else [], - 'global_print_id': item.global_print_id - }) + items.append(item.to_dict()) # 直接使用 model 的 to_dict return {"total": pagination.total, "items": items} except Exception: traceback.print_exc() return {"total": 0, "items": []} # ============================================================ - # 6. [新增] 获取筛选选项(类别、类型) + # 6. 获取筛选选项(类别、类型、公司)并排序 # ============================================================ @staticmethod def get_filter_options(): try: - # 获取所有非空的类别 + # 类别 categories = db.session.query(MaterialBase.category) \ .filter(MaterialBase.category != None, MaterialBase.category != '') \ .distinct().all() + sorted_categories = sorted([r[0] for r in categories]) - # 获取所有非空的类型 + # 类型 types = db.session.query(MaterialBase.material_type) \ .filter(MaterialBase.material_type != None, MaterialBase.material_type != '') \ .distinct().all() + sorted_types = sorted([r[0] for r in types]) + + # [新增] 公司 + companies = db.session.query(MaterialBase.company_name) \ + .filter(MaterialBase.company_name != None, MaterialBase.company_name != '') \ + .distinct().all() + sorted_companies = sorted([r[0] for r in companies]) return { - "categories": [r[0] for r in categories], - "types": [r[0] for r in types] + "categories": sorted_categories, + "types": sorted_types, + "companies": sorted_companies } except Exception: traceback.print_exc() - return {"categories": [], "types": []} + return {"categories": [], "types": [], "companies": []} # 7-10 建议类接口保持不变 @staticmethod diff --git a/inventory-web/src/views/material/list.vue b/inventory-web/src/views/material/list.vue index 025fddb..ff5a1b0 100644 --- a/inventory-web/src/views/material/list.vue +++ b/inventory-web/src/views/material/list.vue @@ -271,7 +271,7 @@ />
- * 必须构成4层结构 + * 必须构成4层结构
diff --git a/inventory-web/src/views/stock/inbound/buy.vue b/inventory-web/src/views/stock/inbound/buy.vue index ae5ceed..3bd3822 100644 --- a/inventory-web/src/views/stock/inbound/buy.vue +++ b/inventory-web/src/views/stock/inbound/buy.vue @@ -1,16 +1,31 @@ -inventory-web/src/views/stock/inbound/buy.vue + + + +