diff --git a/backend/app.pid b/backend/app.pid index 729aa994..bb785457 100644 --- a/backend/app.pid +++ b/backend/app.pid @@ -1 +1 @@ -33473 +34476 diff --git a/backend/app/api/market.py b/backend/app/api/market.py index b3efc849..ae933c0a 100644 --- a/backend/app/api/market.py +++ b/backend/app/api/market.py @@ -34,6 +34,7 @@ async def get_my_material_usage_list( records, total = mat_svc.get_my_usages( db=db, current_user_id=user_id, + current_nick_name=(user.get("real_name") or "").strip(), tenant_no=tenant_no, page=page, page_size=page_size, diff --git a/backend/app/api/material.py b/backend/app/api/material.py index 111e47db..18bf1129 100644 --- a/backend/app/api/material.py +++ b/backend/app/api/material.py @@ -134,6 +134,7 @@ class MaterialBatchUpdateBody(BaseModel): remark: Optional[str] = None material_code: Optional[str] = Field(None, alias="materialCode") material_name: Optional[str] = Field(None, alias="materialName") + category: Optional[str] = None spec_model: Optional[str] = Field(None, alias="specModel") unit: Optional[str] = None warning_qty: Optional[float] = Field(None, alias="warningQty") @@ -181,6 +182,7 @@ async def list_material_batches( pageSize: int = Query(10, ge=1, le=100), keyword: Optional[str] = None, materialId: Optional[str] = None, + category: Optional[str] = None, status: Optional[str] = None, db: Session = Depends(get_db), user: dict = Depends(require_menu_code("biz:material:list")), @@ -193,6 +195,7 @@ async def list_material_batches( page_size=pageSize, keyword=keyword, material_id=materialId, + category=category, status=status, tenant_no=tenant_no, user_info=user, diff --git a/backend/app/crud/material.py b/backend/app/crud/material.py index 68a972da..9e495d80 100644 --- a/backend/app/crud/material.py +++ b/backend/app/crud/material.py @@ -297,6 +297,7 @@ def update_material_fields( user_id: Optional[str] = None, material_code: Optional[str] = None, material_name: Optional[str] = None, + category: Optional[str] = None, spec_model: Optional[str] = None, unit: Optional[str] = None, warning_qty: Optional[Decimal] = None, @@ -319,6 +320,8 @@ def update_material_fields( if not name: raise ValueError("物料名称不能为空") obj.material_name = name + if category is not None: + obj.category = (category or "").strip() or None if spec_model is not None: obj.spec_model = (spec_model or "").strip() or None if unit is not None: @@ -437,11 +440,27 @@ def _my_usage_user_nick_name_map(db: Session, user_ids: List[str]) -> Dict[str, return {u.id: (u.nick_name or "").strip() for u in rows} +def _mine_usage_receiver_filter(user_id: str, nick_name: str): + """当前登录人的手动出库领用记录:领用人字段存昵称或用户 ID(与生产/质检一致)。""" + uid = (user_id or "").strip() + nn = (nick_name or "").strip() + receiver_match: List[Any] = [] + if nn: + receiver_match.append(BizMaterialUsage.receiver == nn) + receiver_match.append(BizMaterialUsage.remark.like(f"%领用人:{nn}%")) + if uid: + receiver_match.append(BizMaterialUsage.receiver == uid) + if receiver_match: + return or_(*receiver_match) + return BizMaterialUsage.id == "" + + def get_my_usages( db: Session, current_user_id: str, tenant_no: str, *, + current_nick_name: str = "", page: int = 1, page_size: int = 10, keyword: Optional[str] = None, @@ -457,7 +476,7 @@ def get_my_usages( 筛选条件: - tenant_no = 当前用户租户 - source_type = 'manual_out'(手动出库) - - receiver = 当前用户ID + - receiver 匹配当前用户昵称或用户 ID - del_flag = 0 按 create_time DESC 排序 @@ -466,7 +485,7 @@ def get_my_usages( BizMaterialUsage.tenant_no == tenant_no, BizMaterialUsage.del_flag == 0, BizMaterialUsage.source_type == "manual_out", - BizMaterialUsage.receiver == current_user_id, + _mine_usage_receiver_filter(current_user_id, current_nick_name), ) if material_id: diff --git a/backend/app/crud/material_stock_batch.py b/backend/app/crud/material_stock_batch.py index de2a1032..213e7759 100644 --- a/backend/app/crud/material_stock_batch.py +++ b/backend/app/crud/material_stock_batch.py @@ -81,6 +81,7 @@ def _batch_row_to_dict(b: BizMaterialStockBatch, material: Optional[BizMaterial] "materialId": b.material_id, "materialCode": b.material_code or "", "materialName": b.material_name or "", + "category": (mat.category or "") if mat else "", "specModel": b.spec_model or "", "unit": b.unit or "", "stockInNo": b.stock_in_no or "", @@ -148,6 +149,7 @@ def list_batches( page_size: int = 10, keyword: Optional[str] = None, material_id: Optional[str] = None, + category: Optional[str] = None, status: Optional[str] = None, tenant_no: Optional[str] = None, user_info=None, @@ -180,6 +182,9 @@ def list_batches( ) if material_id: q0 = q0.filter(BizMaterialStockBatch.material_id == material_id) + cat = (category or "").strip() + if cat: + q0 = q0.filter(BizMaterial.category == cat) st = (status or "").strip() if st in ("0", "1"): q0 = q0.filter(BizMaterial.status == st) @@ -766,6 +771,7 @@ def update_batch( remark: Optional[str] = None, material_code: Optional[str] = None, material_name: Optional[str] = None, + category: Optional[str] = None, spec_model: Optional[str] = None, unit: Optional[str] = None, warning_qty: Optional[Any] = None, @@ -783,6 +789,8 @@ def update_batch( mat_data["material_code"] = material_code if material_name is not None: mat_data["material_name"] = material_name + if category is not None: + mat_data["category"] = (category or "").strip() or None if spec_model is not None: mat_data["spec_model"] = spec_model if unit is not None: diff --git a/backend/scripts/seed_market_materials.py b/backend/scripts/seed_market_materials.py new file mode 100644 index 00000000..8958c5b7 --- /dev/null +++ b/backend/scripts/seed_market_materials.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +"""市场物料库测试数据(biz_material + 入库批次 + 变动记录),已存在编码/入库编号则跳过。""" +from __future__ import annotations + +import sys +from decimal import Decimal +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from app.core.database import SessionLocal +from app.crud import material as material_svc +from app.crud import material_stock_batch as batch_svc +from app.models.material import BizMaterial + +# (material_code, material_name, category, spec_model, unit, warning_qty, order_num, remark, +# stock_in_no, stock_in_date, expire_date, catalog_no, batch_no, manufacturer, +# unit_price, qty) +MARKET_MATERIAL_SEEDS = [ + ( + "MKT-MAT-001", + "品牌宣传册", + "xccyw", + "A4 彩色 20页", + "册", + Decimal("50"), + 1, + "市场宣传用物料", + "MKT-RK-20260615-001", + "2026-06-10", + "2028-06-10", + "CAT-BRO-A4-20", + "BN20260601", + "安徽印务有限公司", + Decimal("12.50"), + Decimal("200"), + ), + ( + "MKT-MAT-002", + "细胞存储服务手册", + "xccyw", + "A5 精装", + "本", + Decimal("30"), + 2, + "客户咨询配套资料", + "MKT-RK-20260615-002", + "2026-06-10", + "2028-06-10", + "CAT-MAN-A5", + "BN20260602", + "安徽印务有限公司", + Decimal("18.00"), + Decimal("100"), + ), + ( + "MKT-MAT-003", + "采集专用管套装", + "ccyw", + "50ml×6支/套", + "套", + Decimal("20"), + 3, + "采集业务现场使用", + "MKT-RK-20260615-003", + "2026-06-11", + "2027-06-11", + "CAT-TUBE-50-6", + "BN20260603", + "康宁生物耗材", + Decimal("85.00"), + Decimal("50"), + ), + ( + "MKT-MAT-004", + "知情同意书范本", + "ccyw", + "A4 单页", + "份", + Decimal("100"), + 4, + "采集业务合规文件", + "MKT-RK-20260615-004", + "2026-06-11", + "2027-12-31", + "CAT-ICF-A4", + "BN20260604", + "法务文印中心", + Decimal("0.80"), + Decimal("500"), + ), + ( + "MKT-MAT-005", + "会议展架易拉宝", + "xccyw", + "80×200cm 含支架", + "个", + Decimal("5"), + 5, + "展会及会议布置", + "MKT-RK-20260615-005", + "2026-06-12", + "2029-06-12", + "CAT-ROLL-80200", + "BN20260605", + "合肥广告制作", + Decimal("120.00"), + Decimal("10"), + ), +] + + +def _get_material_by_code(db, code: str, tenant_no: str) -> BizMaterial | None: + return ( + db.query(BizMaterial) + .filter( + BizMaterial.material_code == code, + BizMaterial.tenant_no == tenant_no, + BizMaterial.del_flag == 0, + ) + .first() + ) + + +def seed(tenant_no: str = "zkxw", user_id: str = "d3000000000000000000000000000001") -> None: + db = SessionLocal() + created_material = 0 + skipped_material = 0 + created_batch = 0 + skipped_batch = 0 + try: + for row in MARKET_MATERIAL_SEEDS: + ( + code, + name, + category, + spec, + unit, + warning_qty, + order_num, + remark, + stock_in_no, + stock_in_date, + expire_date, + catalog_no, + batch_no, + manufacturer, + unit_price, + qty, + ) = row + amount = (unit_price * qty).quantize(Decimal("0.01")) + + material = _get_material_by_code(db, code, tenant_no) + if material: + skipped_material += 1 + else: + material_svc.create_material( + db, + { + "materialCode": code, + "materialName": name, + "category": category, + "specModel": spec, + "unit": unit, + "warningQty": warning_qty, + "orderNum": order_num, + "remark": remark, + "status": "1", + }, + tenant_no=tenant_no, + user_id=user_id, + ) + created_material += 1 + material = _get_material_by_code(db, code, tenant_no) + + if not material: + print(f"跳过批次 {stock_in_no}:物料 {code} 不存在") + continue + + if batch_svc.stock_in_no_exists(db, stock_in_no, tenant_no): + skipped_batch += 1 + continue + + batch_svc.stock_in_one( + db, + material.id, + stock_in_no=stock_in_no, + stock_in_date=stock_in_date, + expire_date=expire_date, + catalog_no=catalog_no, + batch_no=batch_no, + manufacturer=manufacturer, + unit_price=unit_price, + amount=amount, + qty=qty, + remark="测试数据初始化", + tenant_no=tenant_no, + user_id=user_id, + ) + created_batch += 1 + + print( + f"市场物料库测试数据:物料新增 {created_material} 条、跳过 {skipped_material} 条;" + f"入库批次新增 {created_batch} 条、跳过 {skipped_batch} 条。" + ) + finally: + db.close() + + +if __name__ == "__main__": + seed() diff --git a/web-admin/src/views/cell-storage/material/index.vue b/web-admin/src/views/cell-storage/material/index.vue index d7bb015c..1b40ddc6 100644 --- a/web-admin/src/views/cell-storage/material/index.vue +++ b/web-admin/src/views/cell-storage/material/index.vue @@ -67,10 +67,6 @@ 入库 - - - 批量入库 - 批量出库