Commit Graph

428 Commits

Author SHA1 Message Date
DXC
be6575344a feat: 新增企业级操作审计日志闭环模块(包含底层模型、记录装饰器与前端看板) 2026-03-10 12:15:26 +08:00
DXC
525acae423 feat: 新增按钮级权限指令 v-permission 与 Axios 全局防重复提交机制 2026-03-10 11:59:41 +08:00
dxc
3ab47ab2fb 修改登录tocken逻辑,不存在1天需要刷新逻辑 2026-03-10 10:01:13 +08:00
DXC
7536bfd75e chore: 优化 Vite 热更新配置,支持局域网动态 IP 访问并修复 wss 跨域报错 2026-03-10 09:59:49 +08:00
DXC
1708a8ba79 fix: 修复 axios 类型导出导致的 vite 构建报错及白屏问题 2026-03-10 09:55:26 +08:00
DXC
e4632086a1 feat: 重构鉴权系统为双Token无感刷新,并增加前端Token过期安全预判机制 2026-03-10 09:45:41 +08:00
DXC
6fc6851e57 perf: 开启列表图片懒加载并优化分页大小 2026-03-10 09:29:49 +08:00
dxc
529b2a218a 3.9上传服务器版本 2026-03-10 08:59:48 +08:00
DXC
43d16cd196 feat: 列表页搜索框增加即时搜索与防抖机制 2026-03-09 17:36:54 +08:00
DXC
5d813c24bc feat: 采购/半成品/成品列表拆分SKU独立搜索框 2026-03-09 17:29:24 +08:00
DXC
646baa08fe feat: 采购/半成品/成品列表支持基于SKU的模糊搜索 2026-03-09 17:21:00 +08:00
dxc
fbc7c9d7b6 测试环境替换生产环境脚本代码 2026-03-09 17:05:55 +08:00
DXC
bab7f34c17 refactor: 安全提取纯净BOM编号,将原后缀输入框改为备注功能 2026-03-06 17:32:35 +08:00
DXC
59eebb5736 feat: 优化库位选择器,支持打开时自动回显至已选层级 2026-03-06 17:12:52 +08:00
DXC
1adaa38893 style: 优化库位树折叠状态及下钻选择器点击热区 2026-03-06 17:07:38 +08:00
DXC
f9eb3e9646 feat: 封装下钻式库位选择器,并修复层级颜色识别问题 2026-03-06 15:11:30 +08:00
DXC
8aaf45468e style: 优化库位树层级颜色,并升级入库页面的库位选择为树形选择器 2026-03-06 15:00:04 +08:00
DXC
7201b658fb feat: 优化库位树UI层级颜色,并将入库页面库位输入升级为级联选择器 2026-03-06 14:52:47 +08:00
dxc
359b8a8345 "feat: 1-实现动态层级树形库位管理功能
2 - 首页新增库位设置按钮和树形管理弹窗
     3 - 后端添加 SysWarehouseLocation 模型和 CRUD API
     4 - 树形结构支持无限层级,自动计算 full_path
     5 - 修复 product.vue 中 defaultColumns 未定义 bug
2026-03-06 14:33:13 +08:00
dxc
cc26f91b50 fix: 修复报错提示弹两次的问题并增加保存按钮loading状态 2026-03-06 10:46:10 +08:00
dxc
4048447123 feat: 增加 BOM 另存为跨版本内容查重校验 2026-03-06 10:38:58 +08:00
dxc
902c4f248f fix: 完善另存为版本推算逻辑,自动避让已存在的版本号 2026-03-06 10:33:48 +08:00
dxc
9c32733234 fix: 修复获取BOM详情时未传递版本号导致数据串用的问题 2026-03-06 10:30:04 +08:00
dxc
14bfe44e46 feat: 增加子件查重与模糊搜索功能 2026-03-06 10:26:19 +08:00
dxc
3cc5e77729 feat: 优化BOM表单交互 - 另存为版本号选择与用量输入体验" 2026-03-06 10:12:12 +08:00
dxc
8091c5f326 fix: 修复 BOM 删除时错误删除所有版本的问题 2026-03-06 09:55:59 +08:00
dxc
86fa5ef458 修改启用查询问题 2026-03-06 09:36:34 +08:00
dxc
a5ffc200f5 入库记录只显示批次不显示序列号问题修复 2026-03-04 09:00:22 +08:00
dxc
e39c3fd030 筛选高级权限修改,基础信息启用禁用修改 2026-03-03 17:29:21 +08:00
dxc
35f602d308 Merge remote-tracking branch 'origin/2.0权限管理' into 2.0权限管理 2026-03-03 11:12:28 +08:00
dxc
348409b69d 统一采购件入库页面高级管理权限 2026-03-03 11:12:19 +08:00
dxc
051f0a05e5 半成品成品价格于BOM表关联 2026-03-03 11:11:28 +08:00
d095a370ad feat: 统一半成品和成品入库页面的高级筛选权限管理
- 将semi.vue和product.vue的fieldOptions改为computed属性,根据用户权限动态过滤
- 确保筛选字段与后端支持的字段完全一致
- 完善permissionMap,添加缺失的字段权限映射
- 遵循buy.vue和material/list.vue的权限管理模式
2026-03-03 03:08:29 +00: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
e3a143f730 fix: correct field labels in product advanced filter
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 17:18:23 +08:00
dxc
f4b8acb916 feat: add total_price field and update advanced filter options
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 17:09:25 +08:00
dxc
09db84b0ce fix: sync advanced filter field options with actual form fields
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 17:00:30 +08:00
dxc
06ec540c41 fix: correct advanced filter field options in semi.vue
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 16:54:47 +08:00
dxc
71e763bcb6 feat: calculate semi-inbound cost based on BOM code
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 16:47:49 +08:00
dxc
a5fcbd70f8 feat: add table sorting and advanced filtering for products
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 16:36:24 +08:00
dxc
b377c93e1f feat: support filtering for all table headers in semi-inbound
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 16:28:35 +08:00
dxc
465daca758 feat: Add sorting and advanced filters to inbound semi view
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 16:21:16 +08:00
dxc
37f4b1a94f feat: add full-column sorting and advanced filtering to semi module
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 16:18:51 +08:00
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
c5872aed3c feat: add advanced filtering and full-field sorting to material list
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 15:22:04 +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