ERPNext Server Script API返回200但未更新数据库且无响应结果
ERPNext Server Script API返回200但未更新数据库且无响应结果
我之前在做ERPNext子表更新的Server Script时,也碰到过类似“返回200但数据没变化”的坑,结合你的代码和场景来看,大概率是这几个地方出了问题,咱们一步步来解决:
一、先排查最容易踩的参数坑
你返回200但没更新,首先要确认Postman传递的参数是否真的匹配到了子表行:
- 你代码里是用子表行的
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';
- 怎么查正确的
- 检查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请求的正确姿势
确保你的请求完全符合以下要求:
- URL:
https://<你的ERP域名>/api/method/custom_update_so_items - Headers:
Content-Type: application/jsonAuthorization: token <你的API Token>(在ERPNext用户设置→API生成Token)
- 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()的方式搞定的😎




