Commit Graph

270 Commits

Author SHA1 Message Date
dxc
893be24071 feat: add column sorting and advanced filtering for purchase inbound
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 15:39:32 +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
80ee0fe88e 2.4版本,录入用的2.3,这个是用于进行录入之后遇到的问题等进行的修改 2026-03-02 15:07:29 +08:00
dxc
f49f8dba04 修改半成品价格名称 2026-03-02 13:41:15 +08:00
dxc
cf75b80e13 半成品成品价格于BOM表关联 2026-03-02 13:39:52 +08:00
dxc
4e05734865 fix: split cost fields into multiple rows in product.vue
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 12:03:06 +08:00
dxc
b08196c479 refactor: replace manual_cost with unit_total_cost and total_price
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 11:39:49 +08:00
dxc
68ea351c99 refactor: replace manual_cost with unit_total_cost and total_price
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 11:35:55 +08:00
dxc
f001be9eef feat: replace manual cost with unit total cost in inbound forms
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 10:28:43 +08:00
dxc
545cd86632 refactor: simplify cost calculation to 3 fields, drop manual_cost
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 10:24:51 +08:00
dxc
646804bb98 修改半成品的分为单价和总价格 2026-03-02 09:22:41 +08:00
dxc
3daf7e4500 成品下拉框修改完成 2026-02-28 17:37:34 +08:00
dxc
e61c179d77 修改半成品和成品新增时候搜索下拉框显示问题,新增负责人和生产人历史记录功能 2026-02-28 17:27:57 +08:00
dxc
f7cfb5a346 修改半成品和成品新增时候搜索下拉框显示问题,新增负责人和生产人历史记录功能 2026-02-28 17:08:35 +08:00
dxc
54d83803c4 fix: URL-encode BOM numbers containing slashes
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 15:40:59 +08:00
dxc
05fbb4e3b3 fix: sanitize bomNo to avoid duplicate path in detail API
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 15:38:02 +08:00
dxc
00ebffb9fd 修改盘库时候数量增加减少的按钮大小 2026-02-28 12:05:21 +08:00
dxc
4b29912f6f feat: add borrowed quantity column and update stocktake export formulas
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 11:55:19 +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
d2082c712b 2.0录入测试版 2026-02-28 10:49:09 +08:00
dxc
b85f28fc72 修改采购件页面金额显示,修改权限管理页面非字段级内容可见与可编辑联动 2026-02-28 09:23:07 +08:00
dxc
8f6d0cd40b 修改采购件页面金额显示,修改权限管理页面非字段级内容可见与可编辑联动 2026-02-28 09:10:51 +08:00
dxc
dda54e829b feat: add category and type filters to product search
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 17:18:25 +08:00
dxc
c1e4acc1d8 fix: standardize role case handling in permission logic
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 17:07:45 +08:00
dxc
a0993767fe fix: make SUPER_ADMIN role checks case-insensitive across app
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 17:04:22 +08:00
dxc
ad8bb5a75d feat: adapt semi and product inbound views for tablet and hide barcode input
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 16:53:28 +08:00
dxc
c414efc7a4 权限管理完成,在进行采购件税前税后单价新增字段 2026-02-27 16:45:17 +08:00
dxc
fbff519ac9 fix: remove duplicate updatePrices function
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 16:06:18 +08:00
dxc
657c916703 feat: add post-tax unit price, company filter, and frontend price linkage
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 15:58:55 +08:00
dxc
3c1c822f88 feat: add pre/post-tax price linkage, hide barcode, and tablet adapt
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 15:44:10 +08:00
dxc
4324e5a688 feat: add field-level data protection for BOM and user management
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 15:16:11 +08:00
dxc
afcf90a859 feat: enforce field-level permissions for buy and service modules
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 15:03:44 +08:00
dxc
5bc3dab31c feat: add field-level permission control for inbound modules
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 14:52:12 +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
00c45c72fb inventory-web/src/views/system/UserCreate.vue
```vue
<<<<<<< SEARCH
          <el-button type="primary" @click="handleCreate">
            + 新增员工
          </el-button>
=======
          <el-button v-if="userStore.hasPermission('system_user:operation')" type="primary" @click="handleCreate">
            + 新增员工
          </el-button>
>>>>>>> REPLACE
```

Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 14:34:48 +08:00
dxc
6fa5233ea6 feat: implement RBAC and field masking for system_user module
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 14:28:48 +08:00
dxc
3f83e8742b fix: remove duplicate error messages in BOM manage page
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 14:20:51 +08:00
dxc
348e4dd024 feat: add RBAC read-write separation and field masking for bom_manage
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 14:13:02 +08:00
dxc
42b0cddd3e feat: add column permission checks to transaction records table
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 14:07:48 +08:00
dxc
a2b1a62132 feat: add RBAC and field masking for borrow/return/records pages
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 14:05:52 +08:00
dxc
5065410662 feat: add RBAC control for outbound list module
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 13:57:59 +08:00
dxc
3714dd180b feat: apply RBAC read/write separation to outbound_create module
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 13:54:06 +08:00
dxc
af41eb1803 feat: add RBAC controls for outbound selection module
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 13:45:49 +08:00
dxc
f79fb53b17 inventory-web/src/views/stock/stocktake/index.vue
```vue
<<<<<<< SEARCH
          <el-button type="primary" size="large" class="action-btn-full" @click="startNewSession" :loading="btnLoading">
            开始新盘点
          </el-button>
=======
          <el-button v-if="userStore.hasPermission('inventory_stocktake:operation')" type="primary" size="large" class="action-btn-full" @click="startNewSession" :loading="btnLoading">
            开始新盘点
          </el-button>
>>>>>>> REPLACE
```

inventory-web/src/views/stock/stocktake/index.vue
```vue
<<<<<<< SEARCH
          <el-button
              v-if="serverDraftCount > 0"
              type="warning"
              plain
              size="large"
              class="action-btn-full"
              @click="resumeSession"
              :loading="btnLoading"
          >
            继续上次盘点 <span class="sub-text">({{ serverDraftCount }}项)</span>
          </el-button>
=======
          <el-button
              v-if="serverDraftCount > 0 && userStore.hasPermission('inventory_stocktake:operation')"
              type="warning"
              plain
              size="large"
              class="action-btn-full"
              @click="resumeSession"
              :loading="btnLoading"
          >
            继续上次盘点 <span class="sub-text">({{ serverDraftCount }}项)</span>
          </el-button>
>>>>>>> REPLACE
```

Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 13:40:55 +08:00
dxc
38f0bbe41d feat: add RBAC for inventory stocktake module
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 13:36:10 +08:00
dxc
1ad477eda8 feat: add permission management to inbound service module
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 13:12:45 +08:00
dxc
1d2e8feced feat: apply RBAC permission control to product module
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 13:03:27 +08:00
dxc
6e914f1e96 feat: add RBAC permission control for semi inbound module
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 12:08:57 +08:00
dxc
b5b1efdc4e fix: remove duplicate allColumns declaration
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-27 11:56:15 +08:00