From bf78651b319f58c1ab48839b26afa104aaa12d4d Mon Sep 17 00:00:00 2001 From: clementthomas Date: Thu, 20 Feb 2025 19:30:36 +0100 Subject: [PATCH] [NEW] survey_record_generation --- survey_record_generation/__init__.py | 1 + survey_record_generation/__manifest__.py | 26 +++ survey_record_generation/models/__init__.py | 6 + .../models/survey_question.py | 25 +++ .../models/survey_question_answer.py | 42 ++++ .../models/survey_record_creation.py | 42 ++++ .../survey_record_creation_field_values.py | 189 ++++++++++++++++++ .../models/survey_survey.py | 13 ++ .../models/survey_user_input.py | 53 +++++ .../security/ir.model.access.csv | 4 + .../views/survey_question_views.xml | 28 +++ .../views/survey_survey_views.xml | 113 +++++++++++ 12 files changed, 542 insertions(+) create mode 100644 survey_record_generation/__init__.py create mode 100644 survey_record_generation/__manifest__.py create mode 100644 survey_record_generation/models/__init__.py create mode 100644 survey_record_generation/models/survey_question.py create mode 100644 survey_record_generation/models/survey_question_answer.py create mode 100644 survey_record_generation/models/survey_record_creation.py create mode 100644 survey_record_generation/models/survey_record_creation_field_values.py create mode 100644 survey_record_generation/models/survey_survey.py create mode 100644 survey_record_generation/models/survey_user_input.py create mode 100644 survey_record_generation/security/ir.model.access.csv create mode 100644 survey_record_generation/views/survey_question_views.xml create mode 100644 survey_record_generation/views/survey_survey_views.xml diff --git a/survey_record_generation/__init__.py b/survey_record_generation/__init__.py new file mode 100644 index 0000000..9a7e03e --- /dev/null +++ b/survey_record_generation/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/survey_record_generation/__manifest__.py b/survey_record_generation/__manifest__.py new file mode 100644 index 0000000..e5ecaaf --- /dev/null +++ b/survey_record_generation/__manifest__.py @@ -0,0 +1,26 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Survey record generation", + 'summary': 'Allow to create record of any model when sending the form', + 'description': """ +Allow to create record of any model when sending the form : +---------------------------------------------------- +* Choose list of models created on survey submission +* Set default values for record created +* Associate question with fields +* For x2m fields : Associate values to questions +""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "Elabore", + "website": "https://www.elabore.coop", + "category": "", + "depends": ["survey"], + "data": [ + "security/ir.model.access.csv", + "views/survey_survey_views.xml", + "views/survey_question_views.xml", + ], + "installable": True, +} diff --git a/survey_record_generation/models/__init__.py b/survey_record_generation/models/__init__.py new file mode 100644 index 0000000..5f81d0c --- /dev/null +++ b/survey_record_generation/models/__init__.py @@ -0,0 +1,6 @@ +from . import survey_question_answer +from . import survey_question +from . import survey_record_creation_field_values +from . import survey_record_creation +from . import survey_survey +from . import survey_user_input \ No newline at end of file diff --git a/survey_record_generation/models/survey_question.py b/survey_record_generation/models/survey_question.py new file mode 100644 index 0000000..59ad7b1 --- /dev/null +++ b/survey_record_generation/models/survey_question.py @@ -0,0 +1,25 @@ + +import logging + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class SurveyQuestion(models.Model): + _inherit = 'survey.question' + + model_id = fields.Many2one('ir.model', string="Model") + answer_values_type = fields.Selection([('no', 'No values'),('value','Value'),('record','Record')]) + + @api.onchange('model_id') + def onchange_model_id(self): + if self.model_id: + rec = self.env[self.model_id.model].search([], limit=1) + if not rec: + raise UserError(_('No record found in %s',self.model_id.name)) + else: + for answer in self.suggested_answer_ids: + answer.record_id = f"{self.model_id.model},{rec.id}" + diff --git a/survey_record_generation/models/survey_question_answer.py b/survey_record_generation/models/survey_question_answer.py new file mode 100644 index 0000000..87b1144 --- /dev/null +++ b/survey_record_generation/models/survey_question_answer.py @@ -0,0 +1,42 @@ + +import logging + +from odoo import api, fields, models, _ + +_logger = logging.getLogger(__name__) + + +class SurveyQuestionAnswer(models.Model): + _inherit = 'survey.question.answer' + + record_id = fields.Reference(string="Referenced record", selection='_selection_target_model') + model_id = fields.Many2one('ir.model', related="question_id.model_id") + answer_values_type = fields.Selection(related="question_id.answer_values_type") + value_char = fields.Char('Value') + + @api.model + def _selection_target_model(self): + return [(model.model, model.name) for model in self.env['ir.model'].sudo().search([])] + + @api.onchange('record_id') + def onchange_record_id(self): + if self.record_id: + self.value = self.record_id.display_name + + @api.model + def default_get(self, fields): + result = super().default_get(fields) + if ( + not result.get("model_id") + or "record_id" not in fields + ): + return result + + model = self.env['ir.model'].browse(result.get("model_id")).model + res = self.env[model].search([], limit=1) + if res: + result["record_id"] = "%s,%s" % ( + model, + res.id, + ) + return result \ No newline at end of file diff --git a/survey_record_generation/models/survey_record_creation.py b/survey_record_generation/models/survey_record_creation.py new file mode 100644 index 0000000..04a2ff8 --- /dev/null +++ b/survey_record_generation/models/survey_record_creation.py @@ -0,0 +1,42 @@ + +import logging + +from odoo import api, fields, models, _ + +_logger = logging.getLogger(__name__) + + +class SurveyRecordCreation(models.Model): + """Configure list of models for wich record will be created on survey submission + """ + _name = 'survey.record.creation' + + name = fields.Char('Name') + survey_id = fields.Many2one('survey.survey', string="Survey") + model_id = fields.Many2one('ir.model', "Model", help="Model of generated record") + field_values_ids = fields.One2many('survey.record.creation.field.values', 'survey_record_creation_id', string="Field values") + warning_message = fields.Html('Warning message', compute="_compute_warning_message") + + @api.onchange('model_id') + def clear_field_values_ids(self): + self.field_values_ids = None + + @api.depends('model_id','field_values_ids') + def _compute_warning_message(self): + for record_creation in self: + # check if all mandatory fields set + if record_creation.model_id: + required_field_ids = self.model_id.field_id.filtered(lambda f:f.required and "property_" not in f.name) + set_field_ids = self.field_values_ids.field_id + missing_fields = required_field_ids - set_field_ids + + if missing_fields: + record_creation.warning_message = _("Some required fields are not set : %s",', '.join([f"{f.field_description} ({f.name})" for f in missing_fields])) + else: + record_creation.warning_message = None + else: + record_creation.warning_message = None + + + + \ No newline at end of file diff --git a/survey_record_generation/models/survey_record_creation_field_values.py b/survey_record_generation/models/survey_record_creation_field_values.py new file mode 100644 index 0000000..c6c8980 --- /dev/null +++ b/survey_record_generation/models/survey_record_creation_field_values.py @@ -0,0 +1,189 @@ + +import logging + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError +from odoo.tools.misc import format_date + +_logger = logging.getLogger(__name__) + + +type_mapping = { + "char": ["char_box", "numerical_box", "date", "datetime", "simple_choice", "multiple_choice"], + "text": ["char_box", "date"], + "html": ["text_box", "numerical_box", "datetime", "simple_choice", "multiple_choice"], + "integer": ["numerical_box"], + "float": ["numerical_box"], + "date": ["date"], + "datetime": ["datetime"], + "many2one": ["simple_choice"], + "many2many": ["multiple_choice"], + "selection": ["char_box"] +} + + +class SurveyRecordCreationFieldValues(models.Model): + """Configure default values of records created on survey submission + """ + _name = 'survey.record.creation.field.values' + + survey_record_creation_id = fields.Many2one('survey.record.creation') + survey_id = fields.Many2one('survey.survey', related="survey_record_creation_id.survey_id") + model_id = fields.Many2one('ir.model', related="survey_record_creation_id.model_id") + + field_id = fields.Many2one( + 'ir.model.fields', + domain="[('model_id','=',model_id),('readonly','=',False),('ttype','in',['char','selection','text','html','integer','float','date','datetime','many2one','many2many'])]") + field_relation = fields.Char(related='field_id.relation') + field_type = fields.Selection(related="field_id.ttype") + field_help = fields.Html('Help', compute="_compute_field_help") + + value_origin = fields.Selection( + [('fixed','Fixed'),('question','Question'),('other_record','From other created record')], + string="Value origin", + required=True, + default='fixed', + help="""* Fixed: you can set the value in value field + * Question: Response of the question will set the value. If you do not see your question, maybe the type of question do not match the type of field + * From other created record: You can set other record creation to link several created records. Can only be used with many2one fields.""") + + fixed_value_many2one = fields.Reference(string='Record', selection='_selection_target_model') + fixed_value_many2many = fields.One2many('survey.record.creation.field.values.x2m', "survey_record_creation_field_values_id") + fixed_value_char = fields.Char("Value") + fixed_value_selection = fields.Char("Value") + fixed_value_text = fields.Text("Value") + fixed_value_html = fields.Html("Value") + fixed_value_integer = fields.Integer("Value") + fixed_value_float = fields.Float("Value") + fixed_value_date = fields.Date("Value") + fixed_value_datetime = fields.Datetime("Value") + + displayed_value = fields.Char("Value", compute="_compute_displayed_value") + other_created_record_id = fields.Many2one("survey.record.creation", string="Other record", domain="[('survey_id','=',survey_id),('model_id.model','=',field_relation)]") + + allowed_question_ids = fields.Many2many('survey.question', compute='_compute_allowed_question_ids') + question_id = fields.Many2one('survey.question', string="Question", domain="[('id','in',allowed_question_ids)]") + + @api.depends("field_id") + def _compute_field_help(self): + for record in self: + field_help = _("Field type is : %s",record.field_type) + if record.field_type == "selection": + field_help += "
"+_("possible values are %s",', '.join([f"{s.value} ({s.name})" for s in record.field_id.selection_ids])) + record.field_help = field_help + + + @api.depends('field_id') + def _compute_allowed_question_ids(self): + for record_creation_field_values in self: + if not record_creation_field_values.survey_id or not record_creation_field_values.field_id: + record_creation_field_values.allowed_question_ids = None + return + question_domain = [('survey_id','=',record_creation_field_values.survey_id.id),'|','&',('answer_values_type','=','record'),('model_id','=',record_creation_field_values.field_id.relation),('answer_values_type','=','value')] + if record_creation_field_values.field_id.ttype in type_mapping: + question_domain.append(('question_type','in',type_mapping[record_creation_field_values.field_id.ttype])) + + record_creation_field_values.allowed_question_ids = self.env['survey.question'].search(question_domain) + + + @api.model + def _selection_target_model(self): + return [(model.model, model.name) for model in self.env['ir.model'].sudo().search([])] + + @api.onchange('field_id','origin') + def clean_values(self): + # clean values + self.fixed_value_many2many = None + self.fixed_value_many2one = None + self.fixed_value_char = None + self.fixed_value_selection = None + self.fixed_value_text = None + self.fixed_value_html = None + self.fixed_value_integer = None + self.fixed_value_float = None + self.fixed_value_date = None + self.fixed_value_datetime = None + self.other_created_record_id = None + self.question_id = None + + @api.onchange('field_id') + def _onchange_field_id(self): + # Set reference field model and select first record + if self.field_id and self.field_id.ttype == 'many2one' and self.field_id.relation: + rec = self.env[self.field_id.relation].search([], limit=1) + if rec: + self.fixed_value_many2one = f"{self.field_id.relation},{rec.id}" + else: + model_name = self.env['ir.model'].search([('model','=',self.field_id.relation)]).name + self.fixed_value_many2one = None + raise UserError(_('You should append at least one record in %s',model_name)) + else: + self.fixed_value_many2one = None + + + def get_fixed_value_for_record_creation(self): + """return val used in create() method + + """ + if self.value_origin == 'fixed': + if self.field_type == 'many2one': + if self.fixed_value_many2one: + return self.fixed_value_many2one.id + elif self.field_type == 'many2many': + return [m2m.value_reference.id for m2m in self.fixed_value_many2many if m2m.value_reference] + else: + return self["fixed_value_"+self.field_type] + + + @api.onchange("fixed_value_char","fixed_value_selection","fixed_value_text","fixed_value_html","fixed_value_integer","fixed_value_float","fixed_value_date","fixed_value_datetime",'fixed_value_many2one', "fixed_value_many2many","other_created_record_id","question_id") + def _compute_displayed_value(self): + for record in self: + if record.field_id: + if record.value_origin == 'other_record' and record.other_created_record_id: + record.displayed_value = _("Other created record: ")+record.other_created_record_id.name + elif record.value_origin == 'fixed': + if record.field_id.ttype == "many2one": + if record.fixed_value_many2one: + record.displayed_value = record.fixed_value_many2one.display_name + else: + record.displayed_value = None + elif record.field_id.ttype == "many2many": + if record.fixed_value_many2many: + record.displayed_value = ", ".join([r.value_reference.display_name for r in record.fixed_value_many2many if r.value_reference]) + else: + record.displayed_value = None + elif record.field_id.ttype == "date": + record.displayed_value = format_date(self.env, record.fixed_value_date) + elif record.field_id.ttype == "datetime": + record.displayed_value = format_date(self.env, record.fixed_value_datetime) + else: + record.displayed_value = str(record['fixed_value_'+record.field_id.ttype]) + else: #value_origin = question + record.displayed_value = _('Answer to question: %s',record.question_id.title) + else: + record.displayed_value = "" + +class SurveyRecordCreationFieldValuesX2m(models.Model): + """O2m an M2m default values + """ + _name = 'survey.record.creation.field.values.x2m' + + survey_record_creation_field_values_id = fields.Many2one('survey.record.creation.field.values') + value_reference = fields.Reference(string='Record', selection='_selection_target_model') + + @api.model + def _selection_target_model(self): + return [(model.model, model.name) for model in self.env['ir.model'].sudo().search([])] + + + @api.onchange('survey_record_creation_field_values_id') + def _onchange_model_name(self): + # Set reference field model and select first record + field = self.survey_record_creation_field_values_id.field_id + if field and "2many" in field.ttype and field.relation: + rec = self.env[field.relation].search([], limit=1) + if rec: + self.value_reference = f"{field.relation},{rec.id}" + else: + model_name = self.env['ir.model'].search([('model','=',field.relation)]).name + raise ValueError(_('You should append at least one record in %s',(model_name,))) \ No newline at end of file diff --git a/survey_record_generation/models/survey_survey.py b/survey_record_generation/models/survey_survey.py new file mode 100644 index 0000000..e60a8c4 --- /dev/null +++ b/survey_record_generation/models/survey_survey.py @@ -0,0 +1,13 @@ + +import logging + +from odoo import api, fields, models, _ + +_logger = logging.getLogger(__name__) + + +class SurveySurvey(models.Model): + _inherit = 'survey.survey' + + survey_record_creation_ids = fields.One2many('survey.record.creation', 'survey_id', 'Records creation', help="List of records created when survey submitted") + \ No newline at end of file diff --git a/survey_record_generation/models/survey_user_input.py b/survey_record_generation/models/survey_user_input.py new file mode 100644 index 0000000..d498c0f --- /dev/null +++ b/survey_record_generation/models/survey_user_input.py @@ -0,0 +1,53 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import models + + +class SurveyUserInput(models.Model): + _inherit = "survey.user_input" + + def _mark_done(self): + # generate records + for user_input in self: + created_records = {} + fields_to_update = [] + + for record_creation in user_input.survey_id.survey_record_creation_ids: + model = record_creation.model_id.model + vals = {} + + for field_value in record_creation.field_values_ids: + if field_value.value_origin == 'fixed': + vals[field_value.field_id.name] = field_value.get_fixed_value_for_record_creation() + elif field_value.value_origin == 'question': + # find user_input_lines of the question + user_input_lines = [user_input_line for user_input_line in user_input.user_input_line_ids if user_input_line.question_id == field_value.question_id] + + if field_value.question_id.question_type in ['simple_choice', 'multiple_choice','matrix']: + if field_value.question_id.answer_value_type == 'record': + record_ids = [] + for user_input_line in user_input_lines: + if user_input_line.suggested_answer_id and user_input_line.suggested_answer_id.record_id: + record_ids.append(user_input_line.suggested_answer_id.record_id.id) + if field_value.question_id.question_type == 'simple_choice': + vals[field_value.field_id.name] = record_ids[0] + else: + vals[field_value.field_id.name] = record_ids + if field_value.question_id.answer_value_type == 'value': + vals[field_value.field_id.name] = user_input_line.suggested_answer_id.value_char + else: + user_input_line = user_input_lines[0] + vals[field_value.field_id.name] = user_input_line[0][f"value_{user_input_line.answer_type}"] + elif field_value.value_origin == 'other_record': + fields_to_update.append(field_value) + + # Create record + record = self.env[model].create(vals) + created_records[record_creation.id] = record + + # update linked records + for field_to_update in fields_to_update: + record_to_update = created_records[field_to_update.survey_record_creation_id.id] + linked_record = created_records[field_to_update.other_created_record_id.id] + record_to_update.write({field_to_update.field_id.name:linked_record.id}) + + return super()._mark_done() diff --git a/survey_record_generation/security/ir.model.access.csv b/survey_record_generation/security/ir.model.access.csv new file mode 100644 index 0000000..09d5d90 --- /dev/null +++ b/survey_record_generation/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_survey_record_creation,survey.record.creation,survey_record_generation.model_survey_record_creation,survey.group_survey_user,1,1,1,1 +access_survey_record_creation_field_values,survey.record.creation.field.values,survey_record_generation.model_survey_record_creation_field_values,survey.group_survey_user,1,1,1,1 +access_survey_record_creation_field_values_x2m,survey.record.creation.field.values.x2m,survey_record_generation.model_survey_record_creation_field_values_x2m,survey.group_survey_user,1,1,1,1 \ No newline at end of file diff --git a/survey_record_generation/views/survey_question_views.xml b/survey_record_generation/views/survey_question_views.xml new file mode 100644 index 0000000..5366994 --- /dev/null +++ b/survey_record_generation/views/survey_question_views.xml @@ -0,0 +1,28 @@ + + + + survey.question.view.form.inherit.record.generation + survey.question + + + + + + + + {'default_question_id': active_id, 'default_model_id': model_id} + + + + + + + + + + diff --git a/survey_record_generation/views/survey_survey_views.xml b/survey_record_generation/views/survey_survey_views.xml new file mode 100644 index 0000000..7982fbd --- /dev/null +++ b/survey_record_generation/views/survey_survey_views.xml @@ -0,0 +1,113 @@ + + + + + survey.survey.view.form.inherit.record.generation + survey.survey + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + +
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+ + + +
+ +
+
+ +
+
+ +
+
+
+
+
+ +