Commit Graph

295 Commits

Author SHA1 Message Date
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
dxc
cf75b80e13 半成品成品价格于BOM表关联 2026-03-02 13:39:52 +08:00
dxc
16350842f8 fix: correct cost calculation for semi and product exports
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 12:22:45 +08:00
dxc
d7dff943fc feat: use highest historical unit price for material bases in export 2026-03-02 12:22:30 +08:00
dxc
2f140e112f fix: remove total_price from product inbound service
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 12:12:57 +08:00
dxc
8264867b1c fix: add total_price field to product inbound creation and update calculation
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 12:10:41 +08:00
dxc
d993e6796e refactor: remove total_price from product inbound service 2026-03-02 12:09:24 +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
7f19867139 fix: adjust product service to use manual_cost instead of unit_total_cost
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 11:57:14 +08:00
dxc
bcd39729f8 fix: adjust BOM cost calculation SQL and refactor for consistency
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 11:52:24 +08:00
dxc
9cfbdc7d13 feat: refactor cost handling and add BOM cost calculation 2026-03-02 11:51:24 +08:00
dxc
d3510b0261 fix: correct BOM cost calculation by using raw SQL and manual_cost
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 11:47:44 +08:00
dxc
7b0082c6e0 feat: add BOM cost calculation for product inbound service 2026-03-02 11:44:50 +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
b688480892 refactor: use highest unit price per material base in export
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-03-02 09:55:49 +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
29fd397e4f fix: use path converter for BOM routes
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 15:44:38 +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
fb56359f41 fix: use ilike and trim for category, company and type filters
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 14:05:17 +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
cc33108e88 feat: add TransBorrow.get_borrowed_quantity method
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 11:43:10 +08:00
dxc
d78ef22251 fix: prevent price data leak in inventory export
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 11:32:21 +08:00
dxc
c3e2494b3e fix: correct default sorting and export desensitization logic
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-28 11:23:00 +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