You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

ERPNext Server Script API返回200但未更新数据库且无响应结果

ERPNext Server Script API返回200但未更新数据库且无响应结果

我之前在做ERPNext子表更新的Server Script时,也碰到过类似“返回200但数据没变化”的坑,结合你的代码和场景来看,大概率是这几个地方出了问题,咱们一步步来解决:


一、先排查最容易踩的参数坑

你返回200但没更新,首先要确认Postman传递的参数是否真的匹配到了子表行

  1. 你代码里是用子表行的docname来匹配的,但这个docname是ERPNext系统自动生成的(比如Sales Order Item-00001),不是你填的item_code或者其他自定义字段。
    • 怎么查正确的docname?打开目标Sales Order,进入开发者模式(右上角头像→开发者设置→开启),然后在子表行右键→“查看”,就能看到该行的name(也就是代码里的docname);或者直接用SQL查:
      SELECT name FROM `tabSales Order Item` WHERE parent = 'Ashish-1990';
      
  2. 检查Postman的API路径是否和你的Server Script方法名对应:如果你的函数叫custom_update_so_items,URL应该是https://<WebURL>/api/method/custom_update_so_items,别写错方法名。

二、代码核心问题:子行修改未标记为“脏数据”

ERPNext的Doc对象默认只会同步**被标记为修改(dirty)**的子行,你直接给row.qty赋值的方式,不会触发这个标记,导致so.save()时根本没把修改同步到数据库。

修改后的可运行代码

我给你调整了代码逻辑,加了脏数据标记、日志排查、以及提交后SO的强更新逻辑:

def custom_update_so_items(sales_order, items=None):
    """ Update Sales Order items (qty, rate, custom fields) with validations. Works for draft and submitted Sales Orders. """
    try:
        # 解析JSON参数
        if isinstance(items, str):
            items = frappe.parse_json(items)
        
        if not items:
            frappe.log_error("参数'items'不能为空", "SO更新错误")
            return {"status": "error", "message": "Parameter 'items' is required."}
        
        # 加载销售订单
        so = frappe.get_doc("Sales Order", sales_order)
        # 提交后更新的必要权限/验证跳过配置
        so.flags.ignore_validate_update_after_submit = True
        so.flags.ignore_permissions = True
        so.flags.ignore_mandatory = True

        # 用子行docname做映射,方便快速匹配
        by_name = {d.name: d for d in so.items}
        updated_rows = []
        skipped_rows = []

        # 遍历更新子行
        for it in items:
            rowname = (it or {}).get("docname")
            if not rowname or rowname not in by_name:
                skipped_rows.append(rowname)
                continue
            
            row = by_name[rowname]
            updated_rows.append(rowname)

            # 用row.set()替代直接赋值,强制标记子行为脏数据
            # 更新标准字段
            if it.get("qty") is not None:
                row.set("qty", frappe.utils.flt(it["qty"]))
            if it.get("rate") is not None:
                row.set("rate", frappe.utils.flt(it["rate"]))
            
            # 更新自定义字段
            if it.get("custom_buying_price") is not None:
                row.set("custom_buying_price", frappe.utils.flt(it["custom_buying_price"]))
            if it.get("custom_margin_amount") is not None:
                row.set("custom_margin_amount", frappe.utils.flt(it["custom_margin_amount"]))
            if it.get("custom_margin_percentage") is not None:
                row.set("custom_margin_percentage", frappe.utils.flt(it["custom_margin_percentage"]))
            
            # 手动计算金额(或者让ERPNext自动计算,这里确保同步)
            row.set("amount", row.qty * row.rate)

        # 只有存在有效更新行时才执行保存
        if updated_rows:
            # 保存主文档
            so.save(ignore_permissions=True, ignore_validate_update_after_submit=True)
            frappe.db.commit()
            
            # 额外做一次直接数据库更新(针对提交后的SO,防止自定义字段同步延迟)
            for it in items:
                rowname = (it or {}).get("docname")
                if not rowname or rowname not in updated_rows:
                    continue
                updates = {}
                if it.get("custom_buying_price") is not None:
                    updates["custom_buying_price"] = frappe.utils.flt(it["custom_buying_price"])
                if it.get("custom_margin_amount") is not None:
                    updates["custom_margin_amount"] = frappe.utils.flt(it["custom_margin_amount"])
                if it.get("custom_margin_percentage") is not None:
                    updates["custom_margin_percentage"] = frappe.utils.flt(it["custom_margin_percentage"])
                if updates:
                    frappe.db.set_value("Sales Order Item", rowname, updates, update_modified=False)
            frappe.db.commit()
            
            frappe.log_error(f"成功更新SO行:{','.join(updated_rows)}", "SO更新成功")
        else:
            frappe.log_error(f"无有效行可更新,跳过的无效docname:{','.join(skipped_rows)}", "SO更新警告")

        # 返回更详细的结果,方便排查
        return {
            "status": "ok",
            "sales_order": so.name,
            "updated_rows": updated_rows,
            "skipped_rows": skipped_rows
        }

    except Exception as e:
        frappe.log_error(frappe.get_traceback(), "custom_update_so_items异常")
        return {"status": "error", "message": str(e), "traceback": frappe.get_traceback()}

三、Postman请求的正确姿势

确保你的请求完全符合以下要求:

  1. URLhttps://<你的ERP域名>/api/method/custom_update_so_items
  2. Headers
    • Content-Type: application/json
    • Authorization: token <你的API Token>(在ERPNext用户设置→API生成Token)
  3. Body(Raw JSON)
    {
        "sales_order": "Ashish-1990",
        "items": [
            {
                "docname": "Sales Order Item-00001",  // 替换成你从数据库查到的真实docname
                "qty": 5,
                "rate": 120,
                "custom_buying_price": 100,
                "custom_margin_amount": 20,
                "custom_margin_percentage": 16.7
            }
        ]
    }
    

四、快速排查技巧

如果还是没更新,直接去ERPNext的日志模块(搜索“SO更新”)看日志:

  • 如果日志显示无有效行可更新:说明docname传错了,重新查正确的子行名称
  • 如果日志显示成功更新SO行但数据库没变化:检查API Token对应的用户是否有编辑已提交销售订单的权限(在角色权限管理里给用户加这个权限)
  • 如果有异常日志:直接看traceback定位具体报错点

按这个流程来,基本能解决你遇到的“返回200但没更新”的问题,我之前就是靠加日志和改row.set()的方式搞定的😎

火山引擎 最新活动