Skip to content

Commit

Permalink
Merge pull request #182 from ecosoft-odoo/15.0-fix-usability_webhooks…
Browse files Browse the repository at this point in the history
…-work_error

[15.0][FIX] usability_webhooks: improved word error with key
  • Loading branch information
Saran440 authored Apr 11, 2024
2 parents e0e0306 + 2c81fdc commit 8b9bed7
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 146 deletions.
10 changes: 6 additions & 4 deletions frappe_etax_service/models/account_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,12 @@ def create_replacement_etax(self):
if not (self.state == "posted" and self.etax_status == "success"):
raise ValidationError(_("Only posted etax invoice can have a substitution"))
res = self.with_context(include_business_fields=True).copy_data()
res = self.with_context({
"include_business_fields": True,
"force_copy_stock_moves": True,
}).copy_data()
res = self.with_context(
**{
"include_business_fields": True,
"force_copy_stock_moves": True,
}
).copy_data()
old_number = self.name
suffix = "-R"
if suffix in old_number:
Expand Down
111 changes: 17 additions & 94 deletions usability_webhooks/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,20 @@


class WebhookController(http.Controller):
@http.route("/api/create_data", type="json", auth="user")
def create_data(self, model, vals):
def _create_api_logs(self, model, vals, function):
# Add logs
data_dict = {
"data": json.dumps(vals),
"model": model,
"route": "/api/create_data",
"function_name": "create_data",
"route": "/api/%(function)s" % {"function": function},
"function_name": function,
}
# Create Data & Update logs

ICP = request.env["ir.config_parameter"]
rollback_state_failed = ICP.sudo().get_param("webhook.rollback_state_failed")
rollback_except = ICP.sudo().get_param("webhook.rollback_except")
try:
res = request.env["webhook.utils"].create_data(model, vals)
res = getattr(request.env["webhook.utils"], function)(model, vals)
state = "done" if res["is_success"] else "failed"
data_dict.update({"result": res, "state": state})
# Not success, rollback all data (if config in system parameter)
Expand All @@ -41,98 +40,22 @@ def create_data(self, model, vals):
request.env["api.log"].create(data_dict)
return res

@http.route("/api/create_data", type="json", auth="user")
def create_data(self, model, vals):
res = self._create_api_logs(model, vals, "create_data")
return res

@http.route("/api/update_data", type="json", auth="user")
def update_data(self, model, vals):
res = self._create_api_logs(model, vals, "update_data")
return res

@http.route("/api/create_update_data", type="json", auth="user")
def create_update_data(self, model, vals):
# Add logs
data_dict = {
"data": json.dumps(vals),
"model": model,
"route": "/api/create_update_data",
"function_name": "create_update_data",
}
# Create/Update Data & Update logs
ICP = request.env["ir.config_parameter"]
rollback_state_failed = ICP.sudo().get_param("webhook.rollback_state_failed")
rollback_except = ICP.sudo().get_param("webhook.rollback_except")
try:
res = request.env["webhook.utils"].create_update_data(model, vals)
state = "done" if res["is_success"] else "failed"
data_dict.update({"result": res, "state": state})
# Not success, rollback all data (if config in system parameter)
if not res["is_success"] and rollback_state_failed:
request.env.cr.rollback()
except Exception:
res = {
"is_success": False,
"messages": traceback.format_exc(),
}
data_dict.update({"result": res, "state": "failed"})
# Error from odoo exception, rollback all data (if config in system parameter)
if rollback_except:
request.env.cr.rollback()
request.env["api.log"].create(data_dict)
res = self._create_api_logs(model, vals, "create_update_data")
return res

@http.route("/api/search_data", type="json", auth="user")
def search_data(self, model, vals):
# Add logs
data_dict = {
"data": json.dumps(vals),
"model": model,
"route": "/api/search_data",
"function_name": "search_data",
}
# Search Data & Update logs
ICP = request.env["ir.config_parameter"]
rollback_state_failed = ICP.sudo().get_param("webhook.rollback_state_failed")
rollback_except = ICP.sudo().get_param("webhook.rollback_except")
try:
res = request.env["webhook.utils"].search_data(model, vals)
state = "done" if res["is_success"] else "failed"
data_dict.update({"result": res, "state": state})
# Not success, rollback all data (if config in system parameter)
if not res["is_success"] and rollback_state_failed:
request.env.cr.rollback()
except Exception:
res = {
"is_success": False,
"messages": traceback.format_exc(),
}
data_dict.update({"result": res, "state": "failed"})
# Error from odoo exception, rollback all data (if config in system parameter)
if rollback_except:
request.env.cr.rollback()
request.env["api.log"].create(data_dict)
return res

@http.route("/api/update_data", type="json", auth="user")
def update_data(self, model, vals):
# Add logs
data_dict = {
"data": json.dumps(vals),
"model": model,
"route": "/api/update_data",
"function_name": "update_data",
}
# Update Data & logs
ICP = request.env["ir.config_parameter"]
rollback_state_failed = ICP.sudo().get_param("webhook.rollback_state_failed")
rollback_except = ICP.sudo().get_param("webhook.rollback_except")
try:
res = request.env["webhook.utils"].update_data(model, vals)
state = "done" if res["is_success"] else "failed"
data_dict.update({"result": res, "state": state})
# Not success, rollback all data (if config in system parameter)
if not res["is_success"] and rollback_state_failed:
request.env.cr.rollback()
except Exception:
res = {
"is_success": False,
"messages": traceback.format_exc(),
}
data_dict.update({"result": res, "state": "failed"})
# Error from odoo exception, rollback all data (if config in system parameter)
if rollback_except:
request.env.cr.rollback()
request.env["api.log"].create(data_dict)
res = self._create_api_logs(model, vals, "search_data")
return res
120 changes: 72 additions & 48 deletions usability_webhooks/controllers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import logging

from odoo import _, api, models
from odoo import _, api, models, tools
from odoo.exceptions import ValidationError

_logger = logging.getLogger(__name__)
Expand All @@ -13,6 +13,7 @@ class WebhookUtils(models.AbstractModel):
_name = "webhook.utils"
_description = "Utils Class"

@tools.ormcache("model")
def _search_key(self, model):
"""Return the unique search key for each model, else use 'name'"""
keys = {
Expand All @@ -22,6 +23,15 @@ def _search_key(self, model):
key = keys.get(model, "name")
return key

@tools.ormcache("model", "val")
def _call_name_search_cache(self, model, val, args=None):
args = args or []
return model.name_search(val, args=args, operator="=")

@tools.ormcache("model", "val")
def _call_search_cache(self, model, val):
return model.search([("id", "=", val)])

def _get_ctx_lines(self):
"""For hooks add context to do unlink lines"""
return {}
Expand Down Expand Up @@ -77,47 +87,9 @@ def _create_file_attachment(self, obj, data_dict, line_all_fields):
if file_attach:
Attachment.create(file_attach)

@api.model
def friendly_create_data(self, model, vals):
"""Accept friendly data_dict in following format to create data,
and auto_create data if found no match.
-------------------------------------------------------------
vals:
{
'payload': {
'field1': value1,
'field2_id': value2, # can be ID or name search string
'attachment_ids': [ # for attach file
{
'name': value3,
'datas': value4,
}
]
'line_ids': [
{
'field3': value5,
'field4_id': value6, # can be ID or name search string
},
'attachment_ids': [ # for attach file in line
{
'name': value7,
'datas': value8,
}
],
{..new record..}, {..new record..}, ...
],
},
'auto_create': {
'field2_id': {'name': 'some name', ...},
'field4_id': {'name': 'some name', ...},
# If more than 1 value, you can use list instead
# 'field4_id': [{'name': 'some name', ...}, {...}, {...}]
}
}
"""
def _convert_data_to_id(self, model, vals):
data_dict = vals.get("payload", {})
auto_create = vals.get("auto_create", {})
res = {}
rec = self.env[model].new() # Dummy record
rec_fields = []
line_all_fields = []
Expand All @@ -144,7 +116,6 @@ def friendly_create_data(self, model, vals):
for line_sub_field in line_fields:
final_sub_line_dict = []
final_sub_line_append = final_sub_line_dict.append

# Loop all o2m sub lines, and recreate it
for line_sub_data_dict in line_data_dict[line_sub_field]:
sub_line_dict, line_sub_fields = self._get_o2m_line(
Expand All @@ -155,6 +126,7 @@ def friendly_create_data(self, model, vals):
)
final_sub_line_append((0, 0, sub_line_dict))
if line_sub_fields:
rec.clear_caches()
raise ValidationError(
_(
"friendly_create_data() support "
Expand All @@ -164,6 +136,48 @@ def friendly_create_data(self, model, vals):
line_dict.update({line_sub_field: final_sub_line_dict})
final_line_append((0, 0, line_dict))
rec_dict[line_field] = final_line_dict
return rec_dict, rec, line_all_fields

@api.model
def friendly_create_data(self, model, vals):
"""Accept friendly data_dict in following format to create data,
and auto_create data if found no match.
-------------------------------------------------------------
vals:
{
'payload': {
'field1': value1,
'field2_id': value2, # can be ID or name search string
'attachment_ids': [ # for attach file
{
'name': value3,
'datas': value4,
}
]
'line_ids': [
{
'field3': value5,
'field4_id': value6, # can be ID or name search string
},
'attachment_ids': [ # for attach file in line
{
'name': value7,
'datas': value8,
}
],
{..new record..}, {..new record..}, ...
],
},
'auto_create': {
'field2_id': {'name': 'some name', ...},
'field4_id': {'name': 'some name', ...},
# If more than 1 value, you can use list instead
# 'field4_id': [{'name': 'some name', ...}, {...}, {...}]
}
}
"""
data_dict = vals.get("payload", {})
rec_dict, rec, line_all_fields = self._convert_data_to_id(model, vals)
# Send context to function create()
obj = rec.with_context(api_payload=data_dict).create(rec_dict)
# Create Attachment (if any)
Expand All @@ -173,6 +187,8 @@ def friendly_create_data(self, model, vals):
"result": {"id": obj.id},
"messages": _("Record created successfully"),
}
# Clear cache
rec.clear_caches()
return res

@api.model
Expand Down Expand Up @@ -212,11 +228,13 @@ def friendly_update_data(self, model, vals, key_field):
rec = self.env[model].search([(key_field, "=", data_dict[key_field])])
if not rec:
raise ValidationError(
_('Search key "%s" not found!') % data_dict[key_field]
_("Search key '%(key_field)s' not found!")
% {"key_field": data_dict[key_field]}
)
elif len(rec) > 1:
raise ValidationError(
_('Search key "%s" found mutiple matches!') % data_dict[key_field]
_("Search key '%(key_field)s' found mutiple matches!")
% {"key_field": data_dict[key_field]}
)
rec_fields = []
line_all_fields = []
Expand Down Expand Up @@ -256,6 +274,7 @@ def friendly_update_data(self, model, vals, key_field):
)
final_sub_line_append((0, 0, sub_line_dict))
if line_sub_fields:
rec.clear_caches()
raise ValidationError(
_(
"friendly_update_data() support "
Expand Down Expand Up @@ -323,15 +342,16 @@ def _finalize_data_to_write(self, rec, rec_dict, auto_create=False):
args = []
if key == "account_id" and rec_dict.get("company_id"):
args = [("company_id", "=", rec_dict.get("company_id"))]
values = Model.name_search(val, args=args, operator="=")
values = self._call_name_search_cache(Model, val, args)
# If failed, try again by ID
if len(values) != 1 and val and isinstance(val, int):
rec = Model.search([("id", "=", val)])
rec = self._call_search_cache(Model, val)
values = len(rec) == 1 and [(rec.id,)] or values
# Found > 1, can't continue
if len(values) > 1:
Model.clear_caches()
raise ValidationError(
_('"%s" matched more than 1 record') % val
_("'%(val)s' matched more than 1 record") % {"val": val}
)
# If not found, but auto_create it
if len(values) != 1 and auto_create.get(key):
Expand All @@ -342,9 +362,13 @@ def _finalize_data_to_write(self, rec, rec_dict, auto_create=False):
new_recs.append(auto_create[key])
for new_rec in new_recs:
self.friendly_create_data(model, {"payload": new_rec})
values = Model.name_search(val, operator="=")
values = self._call_name_search_cache(Model, val, args)
elif not values:
raise ValidationError(_('"%s" found no match.') % val)
Model.clear_caches()
raise ValidationError(
_("'%(key)s': '%(val)s' found no match.")
% {"key": key, "val": val}
)
if ftype == "many2one":
value = values[0][0]
elif ftype == "many2many":
Expand Down

0 comments on commit 8b9bed7

Please sign in to comment.