Commit Graph

34 Commits

Author SHA1 Message Date
dxc
e224a07a47 feat: 升级预警批量设置交互,引入批量操作模式切换,提升界面整洁度与体验 2026-03-11 13:28:11 +08:00
dxc
d2d9abe201 全局审计日fix: 使用鸭子类型强制安全解包 SQLAlchemy Row 对象,彻底解决 to_dict 报错志 2026-03-11 13:11:16 +08:00
dxc
e39c3fd030 筛选高级权限修改,基础信息启用禁用修改 2026-03-03 17:29:21 +08:00
dxc
ec8bdb2476 feat: sync advanced filter fields with column permissions
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 17:53:52 +08:00
dxc
cf821b78aa fix: make advanced filters work
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 17:36:25 +08:00
dxc
2ac64076dd feat: add advanced filter to material list
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 15:23:47 +08:00
dxc
9b794d7f64 inventory-backend/app/api/v1/material/base.py
```python
<<<<<<< SEARCH
    @auth_required()
    def get(self):
        """获取基础物料列表"""
        page = request.args.get('page', 1, type=int)
        size = request.args.get('size', 100, type=int)
        keyword = request.args.get('keyword', '').strip()
        category = request.args.get('category', '').strip()
        type_ = request.args.get('type', '').strip()
        company = request.args.get('company', '').strip()
        is_enabled = request.args.get('isEnabled', type=int)

        data = material_base_service.get_material_base_list(
            page=page,
            size=size,
            keyword=keyword,
            category=category,
            type_=type_,
            company=company,
            is_enabled=is_enabled
        )
        return jsonify({
            'code': 200,
            'msg': 'success',
            'data': data
        })
=======
    @auth_required()
    def get(self):
        """获取基础物料列表(支持排序和高级筛选)"""
        page = request.args.get('page', 1, type=int)
        size = request.args.get('size', 100, type=int)
        keyword = request.args.get('keyword', '').strip()
        category = request.args.get('category', '').strip()
        type_ = request.args.get('type', '').strip()
        company = request.args.get('company', '').strip()
        is_enabled = request.args.get('isEnabled', type=int)
        order_by = request.args.get('orderByColumn', '').strip()
        is_asc = request.args.get('isAsc', '').strip()
        advanced_filters = request.args.get('advancedFilters', '[]')

        try:
            filters = json.loads(advanced_filters) if advanced_filters else []
        except json.JSONDecodeError:
            filters = []

        data = material_base_service.get_material_base_list(
            page=page,
            size=size,
            keyword=keyword,
            category=category,
            type_=type_,
            company=company,
            is_enabled=is_enabled,
            order_by=order_by,
            is_asc=is_asc,
            advanced_filters=filters
        )
        return jsonify({
            'code': 200,
            'msg': 'success',
            'data': data
        })
>>>>>>> REPLACE
```

inventory-backend/app/services/material_base_service.py
```python
<<<<<<< SEARCH
def get_material_base_list(page=1, size=100, keyword='', category='', type_='', company='', is_enabled=None):
    """查询基础物料列表"""
    query = MaterialBase.query.filter_by(is_deleted=0)

    if keyword:
        query = query.filter(
            or_(
                MaterialBase.name.like(f'%{keyword}%'),
                MaterialBase.common_name.like(f'%{keyword}%'),
                MaterialBase.spec.like(f'%{keyword}%')
            )
        )
    if category:
        query = query.filter(MaterialBase.category == category)
    if type_:
        query = query.filter(MaterialBase.type == type_)
    if company:
        query = query.filter(MaterialBase.company_name == company)
    if is_enabled is not None:
        query = query.filter(MaterialBase.is_enabled == is_enabled)

    total = query.count()
    items = query.offset((page - 1) * size).limit(size).all()

    return {
        'items': [item.to_dict() for item in items],
        'total': total,
        'page': page,
        'size': size
    }
=======
def get_material_base_list(page=1, size=100, keyword='', category='', type_='', company='', is_enabled=None,
                           order_by='', is_asc='', advanced_filters=None):
    """查询基础物料列表(支持排序和高级筛选)"""
    from app.models.base import MaterialBase
    from sqlalchemy import or_, and_, text

    query = MaterialBase.query.filter_by(is_deleted=0)

    # 基础搜索条件
    if keyword:
        query = query.filter(
            or_(
                MaterialBase.name.like(f'%{keyword}%'),
                MaterialBase.common_name.like(f'%{keyword}%'),
                MaterialBase.spec.like(f'%{keyword}%')
            )
        )
    if category:
        query = query.filter(MaterialBase.category == category)
    if type_:
        query = query.filter(MaterialBase.type == type_)
    if company:
        query = query.filter(MaterialBase.company_name == company)
    if is_enabled is not None:
        query = query.filter(MaterialBase.is_enabled == is_enabled)

    # 高级动态筛选
    if advanced_filters:
        filter_conditions = []
        allowed_fields = {
            'companyName': 'company_name',
            'name': 'name',
            'commonName': 'common_name',
            'category': 'category',
            'type': 'type',
            'spec': 'spec',
            'unit': 'unit',
            'inventoryCount': 'inventory_count',
            'availableCount': 'available_count'
        }
        for condition in advanced_filters:
            field = condition.get('field')
            operator = condition.get('operator')
            value = condition.get('value')
            if not field or not operator or value is None:
                continue
            # 字段白名单校验
            db_field = allowed_fields.get(field)
            if not db_field:
                continue
            # 防止 SQL 注入:只允许预定义的字段名
            column = getattr(MaterialBase, db_field, None)
            if column is None:
                continue
            # 根据操作符构建条件
            if operator == 'eq':
                filter_conditions.append(column == value)
            elif operator == 'ne':
                filter_conditions.append(column != value)
            elif operator == 'contains':
                filter_conditions.append(column.like(f'%{value}%'))
            elif operator == 'ge':
                try:
                    num_val = float(value)
                    filter_conditions.append(column >= num_val)
                except ValueError:
                    continue
            elif operator == 'le':
                try:
                    num_val = float(value)
                    filter_conditions.append(column <= num_val)
                except ValueError:
                    continue
        if filter_conditions:
            query = query.filter(and_(*filter_conditions))

    # 排序处理
    if order_by:
        allowed_sort_fields = {
            'companyName': 'company_name',
            'name': 'name',
            'commonName': 'common_name',
            'category': 'category',
            'type': 'type',
            'spec': 'spec',
            'unit': 'unit',
            'inventoryCount': 'inventory_count',
            'availableCount': 'available_count'
        }
        db_field = allowed_sort_fields.get(order_by)
        if db_field:
            column = getattr(MaterialBase, db_field)
            if is_asc == 'asc':
                query = query.order_by(column.asc())
            elif is_asc == 'desc':
                query = query.order_by(column.desc())

    total = query.count()
    items = query.offset((page - 1) * size).limit(size).all()

    return {
        'items': [item.to_dict() for item in items],
        'total': total,
        'page': page,
        'size': size
    }
>>>>>>> REPLACE
```

inventory-backend/app/models/base.py
```python
<<<<<<< SEARCH
class MaterialBase(db.Model):
    __tablename__ = 'material_base'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    company_name = db.Column(db.String(100), nullable=False, comment='所属公司')
    name = db.Column(db.String(200), nullable=False, comment='名称')
    common_name = db.Column(db.String(200), comment='俗名')
    category = db.Column(db.String(50), comment='类别')
    type = db.Column(db.String(50), comment='类型')
    spec = db.Column(db.String(300), comment='规格型号')
    unit = db.Column(db.String(20), comment='单位')
    inventory_count = db.Column(db.Float, default=0, comment='库存数')
    available_count = db.Column(db.Float, default=0, comment='可用数')
    is_enabled = db.Column(db.Integer, default=1, comment='启用状态 1启用 0停用')
    is_deleted = db.Column(db.Integer, default=0, comment='删除标志 0未删除 1已删除')
    create_time = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
    update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
=======
class MaterialBase(db.Model):
    __tablename__ = 'material_base'

    # 允许排序和筛选的字段映射(前端字段名 -> 数据库字段名)
    SORT_FIELD_MAP = {
        'companyName': 'company_name',
        'name': 'name',
        'commonName': 'common_name',
        'category': 'category',
        'type': 'type',
        'spec': 'spec',
        'unit': 'unit',
        'inventoryCount': 'inventory_count',
        'availableCount': 'available_count'
    }
    FILTER_FIELD_MAP = {
        'companyName': 'company_name',
        'name': 'name',
        'commonName': 'common_name',
        'category': 'category',
        'type': 'type',
        'spec': 'spec',
        'unit': 'unit',
        'inventoryCount': 'inventory_count',
        'availableCount': 'available_count'
    }

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    company_name = db.Column(db.String(100), nullable=False, comment='所属公司')
    name = db.Column(db.String(200), nullable=False, comment='名称')
    common_name = db.Column(db.String(200), comment='俗名')
    category = db.Column(db.String(50), comment='类别')
    type = db.Column(db.String(50), comment='类型')
    spec = db.Column(db.String(300), comment='规格型号')
    unit = db.Column(db.String(20), comment='单位')
    inventory_count = db.Column(db.Float, default=0, comment='库存数')
    available_count = db.Column(db.Float, default=0, comment='可用数')
    is_enabled = db.Column(db.Integer, default=1, comment='启用状态 1启用 0停用')
    is_deleted = db.Column(db.Integer, default=0, comment='删除标志 0未删除 1已删除')
    create_time = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
    update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
>>>>>>> REPLACE
```

Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 15:18:38 +08:00
dxc
fed85e51c5 feat: add sorting and export desensitization to material list
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 11:09:02 +08:00
dxc
079987e7f3 feat: enforce field-level permissions for material creation and update
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 14:41:27 +08:00
dxc
a547d6b164 fix: restore strict column permission control
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 10:57:30 +08:00
dxc
661ce4e5a0 fix: disable column hiding by permissions in material list view
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 10:54:18 +08:00
dxc
d6d9621bf3 fix: resolve global permission code collision with material_list prefix
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 10:43:32 +08:00
dxc
73ee163352 feat: add MaterialBase permission control with field-level filtering
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 10:16:43 +08:00
dxc
57c2c532ca fix: skip column permission checks for super admin and IRIS
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-26 16:14:01 +08:00
dxc
dad7ffdc66 fix: tie column display to user permissions in material list
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-26 16:07:54 +08:00
dxc
b798c42abf feat: add permission control for material list page
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-26 15:58:23 +08:00
dxc
63a3cf269d 库存资产excel文件导出 2026-02-25 09:55:25 +08:00
dxc
447b1890ab 对于页面展示内容进行按照规格型号进行排序,同时修改类别下拉框大小 2026-02-24 17:15:43 +08:00
dxc
8a7e367d00 基础信息分页功能补回 2026-02-24 16:20:10 +08:00
dxc
42171ed612 对于采购件的税率添加以及所属公司添加 2026-02-24 15:43:14 +08:00
dxc
7e2fa8db8e 基础信息修改,新增所属公司,同时修正类别排序以及新增时候类别选择的功能 2026-02-24 15:13:37 +08:00
dxc
5532c87684 基础信息展示以及搜索逻辑进行修复 2026-02-11 13:12:05 +08:00
dxc
706476d421 修改是基础信息内容展示库存数和可用数 2026-02-11 10:12:10 +08:00
dxc
a0ed92319c 修改拍照上传逻辑,避免平板不可以调用照相机 2026-02-10 09:27:52 +08:00
dxc
d4b23790a1 fix: only close camera dialog on successful upload
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-09 17:15:50 +08:00
dxc
107c311391 feat: add WebRTC camera component for in-app photo capture
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-09 16:57:47 +08:00
dxc
50361dba9a 针对于上传图片以及借库还库和出库选单进行更改 2026-02-09 16:08:47 +08:00
dxc
c1e08062f2 修正名称与俗名关系 2026-02-04 14:37:18 +08:00
dxc
fd5600b65b 修改登录退出逻辑 2026-02-04 14:29:59 +08:00
dxc
6f4917f57e 针对于采购页面进行优化逻辑 2026-01-28 11:49:59 +08:00
dxc
7a4ea8acfb 基础信息读取错误,未修改完成 2026-01-28 08:54:11 +08:00
dxc
7a78975ce7 采购件管理修改页面文字大小以及调整文字栏间距 2026-01-27 16:43:44 +08:00
dxc
3afea217b7 物料-采购件入库页面功能实现 2026-01-27 15:50:23 +08:00
dxc
2f8a5c55b1 python-flask和Vue两种模式初模板 2026-01-26 17:00:12 +08:00