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 @@
入库
-
-
- 批量入库
-
批量出库