From 976aabde1d14345b35dba943a0d92db1fb08a674 Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Tue, 8 Jan 2019 18:04:26 -0600 Subject: [PATCH] [IMP] Add dynamic field editor --- agreement/models/agreement.py | 203 ++++++++++++------------- agreement/models/agreement_appendix.py | 40 ++++- agreement/models/agreement_clause.py | 40 ++++- agreement/models/agreement_recital.py | 37 +++++ agreement/models/agreement_section.py | 67 +++++--- agreement/report/agreement.xml | 2 +- agreement/views/agreement.xml | 29 +++- agreement/views/agreement_appendix.xml | 29 +++- agreement/views/agreement_clause.xml | 29 +++- agreement/views/agreement_recital.xml | 29 +++- agreement/views/agreement_section.xml | 29 +++- agreement/views/agreement_type.xml | 6 +- 12 files changed, 387 insertions(+), 153 deletions(-) diff --git a/agreement/models/agreement.py b/agreement/models/agreement.py index 8588552b..c2ce8bb2 100644 --- a/agreement/models/agreement.py +++ b/agreement/models/agreement.py @@ -17,85 +17,75 @@ class Agreement(models.Model): string="Is a Template?", default=False, copy=False, - help="Make this agreement a template." - ) + help="Make this agreement a template.") version = fields.Integer( string="Version", default=1, copy=False, help="The versions are used to keep track of document history and " - "previous versions can be referenced." - ) + "previous versions can be referenced.") revision = fields.Integer( string="Revision", default=0, copy=False, - help="The revision will increase with every save event." - ) + help="The revision will increase with every save event.") description = fields.Text( string="Description", track_visibility='onchange', - help="Description of the agreement" - ) + help="Description of the agreement") dynamic_description = fields.Text( compute="_compute_dynamic_description", string="Dynamic Description", - help='compute dynamic description') + help='Compute dynamic description') start_date = fields.Date( string="Start Date", track_visibility='onchange', - help="When the agreement starts." - ) + help="When the agreement starts.") end_date = fields.Date( string="End Date", track_visibility='onchange', - help="When the agreement ends." - ) + help="When the agreement ends.") color = fields.Integer() active = fields.Boolean( string="Active", default=True, help="If unchecked, it will allow you to hide the agreement without " - "removing it." - ) + "removing it.") company_signed_date = fields.Date( string="Signed on", track_visibility='onchange', - help="Date the contract was signed by Company." - ) + help="Date the contract was signed by Company.") partner_signed_date = fields.Date( string="Signed on", track_visibility='onchange', - help="Date the contract was signed by the Partner." - ) + help="Date the contract was signed by the Partner.") term = fields.Integer( string="Term (Months)", track_visibility='onchange', help="Number of months this agreement/contract is in effect with the " - "partner." - ) + "partner.") expiration_notice = fields.Integer( string="Exp. Notice (Days)", track_visibility='onchange', - help="Number of Days before expiration to be notified." - ) + help="Number of Days before expiration to be notified.") change_notice = fields.Integer( string="Change Notice (Days)", track_visibility='onchange', - help="Number of Days to be notified before changes." - ) + help="Number of Days to be notified before changes.") special_terms = fields.Text( string="Special Terms", track_visibility='onchange', help="Any terms that you have agreed to and want to track on the " - "agreement/contract." - ) + "agreement/contract.") + dynamic_special_terms = fields.Text( + compute="_compute_dynamic_special_terms", + string="Dynamic Special Terms", + help='Compute dynamic special terms') contract_value = fields.Monetary( compute='_compute_contract_value', string="Contract Value", help="Total value of the contract over ther entire term.", - store=True - ) + store=True) reference = fields.Char( string="Reference", copy=False, @@ -106,114 +96,92 @@ class Agreement(models.Model): total_company_mrc = fields.Monetary( 'Company MRC', currency_field='currency_id', - help="Total company monthly recurring costs." - ) + help="Total company monthly recurring costs.") total_customer_mrc = fields.Monetary( 'Customer MRC', currency_field='currency_id', - help="Total custemer monthly recurring costs." - ) + help="Total custemer monthly recurring costs.") total_company_nrc = fields.Monetary( 'Company NRC', currency_field='currency_id', - help="Total company non-recurring costs." - ) + help="Total company non-recurring costs.") total_customer_nrc = fields.Monetary( 'Customer NRC', currency_field='currency_id', - help="Total custemer non-monthly recurring costs." - ) + help="Total custemer non-monthly recurring costs.") increase_type_id = fields.Many2one( 'agreement.increasetype', string="Increase Type", track_visibility='onchange', - help="The amount that certain rates may increase." - ) + help="The amount that certain rates may increase.") termination_requested = fields.Date( string="Termination Requested Date", track_visibility='onchange', - help="Date that a request for termination was received." - ) + help="Date that a request for termination was received.") termination_date = fields.Date( string="Termination Date", track_visibility='onchange', - help="Date that the contract was terminated." - ) + help="Date that the contract was terminated.") reviewed_date = fields.Date( string="Reviewed Date", - track_visibility='onchange' - ) + track_visibility='onchange') reviewed_user_id = fields.Many2one( 'res.users', string="Reviewed By", - track_visibility='onchange' - ) + track_visibility='onchange') approved_date = fields.Date( string="Approved Date", - track_visibility='onchange' - ) + track_visibility='onchange') approved_user_id = fields.Many2one( 'res.users', string="Approved By", - track_visibility='onchange' - ) + track_visibility='onchange') currency_id = fields.Many2one( 'res.currency', - string='Currency' - ) + string='Currency') partner_id = fields.Many2one( 'res.partner', - string="Partmer", + string="Partner", copy=True, - help="The customer or vendor this agreement is related to." - ) + help="The customer or vendor this agreement is related to.") company_partner_id = fields.Many2one( 'res.partner', string="Company", copy=True, - default=lambda self: self.env.user.company_id.partner_id - ) + default=lambda self: self.env.user.company_id.partner_id) partner_contact_id = fields.Many2one( 'res.partner', string="Partner Contact", copy=True, - help="The primary partner contact (If Applicable)." - ) + help="The primary partner contact (If Applicable).") partner_contact_phone = fields.Char( related='partner_contact_id.phone', - string="Phone" - ) + string="Phone") partner_contact_email = fields.Char( related='partner_contact_id.email', - string="Email" - ) + string="Email") company_contact_id = fields.Many2one( 'res.partner', string="Company Contact", copy=True, - help="The primary contact in the company." - ) + help="The primary contact in the company.") company_contact_phone = fields.Char( related='company_contact_id.phone', - string="Phone" - ) + string="Phone") company_contact_email = fields.Char( related='company_contact_id.email', - string="Email" - ) + string="Email") agreement_type_id = fields.Many2one( 'agreement.type', string="Agreement Type", track_visibility='onchange', - help="Select the type of agreement." - ) + help="Select the type of agreement.") agreement_subtype_id = fields.Many2one( 'agreement.subtype', string="Agreement Sub-type", track_visibility='onchange', help="Select the sub-type of this agreement. Sub-Types are related to " - "agreement types." - ) + "agreement types.") product_ids = fields.Many2many( 'product.template', string="Products & Services") @@ -222,51 +190,43 @@ class Agreement(models.Model): string="Sales Order", track_visibility='onchange', copy=False, - help="Select the Sales Order that this agreement is related to." - ) + help="Select the Sales Order that this agreement is related to.") payment_term_id = fields.Many2one( 'account.payment.term', string="Payment Term", track_visibility='onchange', - help="Terms of payments." - ) + help="Terms of payments.") assigned_user_id = fields.Many2one( 'res.users', string="Assigned To", track_visibility='onchange', - help="Select the user who manages this agreement." - ) + help="Select the user who manages this agreement.") company_signed_user_id = fields.Many2one( 'res.users', string="Signed By", track_visibility='onchange', help="The user at our company who authorized/signed the agreement or " - "contract." - ) + "contract.") partner_signed_user_id = fields.Many2one( 'res.partner', string="Signed By", track_visibility='onchange', - help="Contact on the account that signed the agreement/contract." - ) + help="Contact on the account that signed the agreement/contract.") parent_agreement_id = fields.Many2one( 'agreement', string="Parent Agreement", help="Link this agreement to a parent agreement. For example if this " "agreement is an amendment to another agreement. This list will " - "only show other agreements related to the same account." - ) + "only show other agreements related to the same account.") renewal_type_id = fields.Many2one( 'agreement.renewaltype', string="Renewal Type", track_visibility='onchange', - help="Describes what happens after the contract expires." - ) + help="Describes what happens after the contract expires.") order_lines_services_ids = fields.One2many( related='sale_order_id.order_line', string="Service Order Lines", - copy=False - ) + copy=False) recital_ids = fields.One2many('agreement.recital', 'agreement_id', string="Recitals", copy=True) sections_ids = fields.One2many('agreement.section', 'agreement_id', @@ -289,15 +249,13 @@ class Agreement(models.Model): 'parent_agreement_id', string="Child Agreements", copy=False, - domain=[('active', '=', False)] - ) + domain=[('active', '=', False)]) child_agreements_ids = fields.One2many( 'agreement', 'parent_agreement_id', string="Child Agreements", copy=False, - domain=[('active', '=', True)] - ) + domain=[('active', '=', True)]) line_ids = fields.One2many('agreement.line', 'agreement_id', string="Products/Services", copy=False) state = fields.Selection([ @@ -305,21 +263,38 @@ class Agreement(models.Model): ('active', 'Active'), ('inactive', 'Inactive')], default='draft', - track_visibility='always' - ) + track_visibility='always') notification_address_id = fields.Many2one( 'res.partner', string="Notification Address", help="The address to send notificaitons to, if different from " - "customer address.(Address Type = Other)" - ) + "customer address.(Address Type = Other)") signed_contract_filename = fields.Char( - string="Filename" - ) + string="Filename") signed_contract = fields.Binary( string="Signed Document", - track_visibility='always' - ) + track_visibility='always') + field_id = fields.Many2one( + 'ir.model.fields', string="Field", + help="""Select target field from the related document model. If it is a + relationship field you will be able to select a target field at the + destination of the relationship.""") + sub_object_id = fields.Many2one( + 'ir.model', string="Sub-model", + help="""When a relationship field is selected as first field, this + field shows the document model the relationship goes to.""") + sub_model_object_field_id = fields.Many2one( + 'ir.model.fields', string="Sub-field", + help="""When a relationship field is selected as first field, this + field lets you select the target field within the destination document + model (sub-model).""") + default_value = fields.Char( + string="Default Value", + help="Optional value to use if the target field is empty.") + copyvalue = fields.Char( + string="Placeholder Expression", + help="""Final placeholder expression, to be copy-pasted in the desired + template field.""") # compute the dynamic content for mako expression @api.multi @@ -332,6 +307,16 @@ class Agreement(models.Model): agreement.description, 'agreement', agreement.id) agreement.dynamic_description = description + @api.multi + def _compute_dynamic_special_terms(self): + MailTemplates = self.env['mail.template'] + for agreement in self: + lang = agreement.partner_id.lang or 'en_US' + special_terms = MailTemplates.with_context( + lang=lang).render_template( + agreement.special_terms, 'agreement', agreement.id) + agreement.dynamic_special_terms = special_terms + # compute contract_value field @api.depends('total_customer_mrc', 'total_customer_nrc', 'term') def _compute_contract_value(self): @@ -349,6 +334,20 @@ class Agreement(models.Model): for record in self: record.total_company_mrc = amount_total + @api.onchange('field_id') + def onchange_sub_object_id(self): + if self.field_id: + self.sub_object_id = self.env['ir.model'].search( + [('model', '=', self.field_id.relation)])[0] + + @api.onchange('sub_model_object_field_id', 'default_value') + def onchange_copyvalue(self): + if self.sub_model_object_field_id or self.default_value: + self.copyvalue = "${object.%s.%s or %s}" % \ + (self.field_id.name, + self.sub_model_object_field_id.name, + self.default_value or '\'\'') + # Used for Kanban grouped_by view @api.model def _read_group_stage_ids(self, stages, domain, order): diff --git a/agreement/models/agreement_appendix.py b/agreement/models/agreement_appendix.py index 514b6d41..8699d579 100644 --- a/agreement/models/agreement_appendix.py +++ b/agreement/models/agreement_appendix.py @@ -22,11 +22,47 @@ class AgreementAppendix(models.Model): agreement_id = fields.Many2one('agreement', string="Agreement", ondelete="cascade") active = fields.Boolean( - string="Active", - default=True, + string="Active", default=True, help="If unchecked, it will allow you to hide this appendix without " "removing it.") + # Dynamic field editor + field_id = fields.Many2one( + 'ir.model.fields', string="Field", + help="""Select target field from the related document model. If it is a + relationship field you will be able to select a target field at the + destination of the relationship.""") + sub_object_id = fields.Many2one( + 'ir.model', string="Sub-model", + help="""When a relationship field is selected as first field, this + field shows the document model the relationship goes to.""") + sub_model_object_field_id = fields.Many2one( + 'ir.model.fields', string="Sub-field", + help="""When a relationship field is selected as first field, this + field lets you select the target field within the destination document + model (sub-model).""") + default_value = fields.Char( + string="Default Value", + help="Optional value to use if the target field is empty.") + copyvalue = fields.Char( + string="Placeholder Expression", + help="""Final placeholder expression, to be copy-pasted in the desired + template field.""") + + @api.onchange('field_id') + def onchange_sub_object_id(self): + if self.field_id: + self.sub_object_id = self.env['ir.model'].search( + [('model', '=', self.field_id.relation)])[0] + + @api.onchange('sub_model_object_field_id', 'default_value') + def onchange_copyvalue(self): + if self.sub_model_object_field_id or self.default_value: + self.copyvalue = "${object.%s.%s or %s}" % \ + (self.field_id.name, + self.sub_model_object_field_id.name, + self.default_value or '\'\'') + # compute the dynamic content for mako expression @api.multi def _compute_dynamic_content(self): diff --git a/agreement/models/agreement_clause.py b/agreement/models/agreement_clause.py index a0551d3e..00651f98 100644 --- a/agreement/models/agreement_clause.py +++ b/agreement/models/agreement_clause.py @@ -33,8 +33,44 @@ class AgreementClause(models.Model): string="Active", default=True, help="If unchecked, it will allow you to hide the agreement without " - "removing it." - ) + "removing it.") + + # Dynamic field editor + field_id = fields.Many2one( + 'ir.model.fields', string="Field", + help="""Select target field from the related document model. If it is a + relationship field you will be able to select a target field at the + destination of the relationship.""") + sub_object_id = fields.Many2one( + 'ir.model', string="Sub-model", + help="""When a relationship field is selected as first field, this + field shows the document model the relationship goes to.""") + sub_model_object_field_id = fields.Many2one( + 'ir.model.fields', string="Sub-field", + help="""When a relationship field is selected as first field, this + field lets you select the target field within the destination document + model (sub-model).""") + default_value = fields.Char( + string="Default Value", + help="Optional value to use if the target field is empty.") + copyvalue = fields.Char( + string="Placeholder Expression", + help="""Final placeholder expression, to be copy-pasted in the desired + template field.""") + + @api.onchange('field_id') + def onchange_sub_object_id(self): + if self.field_id: + self.sub_object_id = self.env['ir.model'].search( + [('model', '=', self.field_id.relation)])[0] + + @api.onchange('sub_model_object_field_id', 'default_value') + def onchange_copyvalue(self): + if self.sub_model_object_field_id or self.default_value: + self.copyvalue = "${object.%s.%s or %s}" % \ + (self.field_id.name, + self.sub_model_object_field_id.name, + self.default_value or '\'\'') # compute the dynamic content for mako expression @api.multi diff --git a/agreement/models/agreement_recital.py b/agreement/models/agreement_recital.py index 482ab190..c466f45d 100644 --- a/agreement/models/agreement_recital.py +++ b/agreement/models/agreement_recital.py @@ -27,6 +27,43 @@ class AgreementRecital(models.Model): help="If unchecked, it will allow you to hide this recital without " "removing it.") + # Dynamic field editor + field_id = fields.Many2one( + 'ir.model.fields', string="Field", + help="""Select target field from the related document model. If it is a + relationship field you will be able to select a target field at the + destination of the relationship.""") + sub_object_id = fields.Many2one( + 'ir.model', string="Sub-model", + help="""When a relationship field is selected as first field, this + field shows the document model the relationship goes to.""") + sub_model_object_field_id = fields.Many2one( + 'ir.model.fields', string="Sub-field", + help="""When a relationship field is selected as first field, this + field lets you select the target field within the destination document + model (sub-model).""") + default_value = fields.Char( + string="Default Value", + help="Optional value to use if the target field is empty.") + copyvalue = fields.Char( + string="Placeholder Expression", + help="""Final placeholder expression, to be copy-pasted in the desired + template field.""") + + @api.onchange('field_id') + def onchange_sub_object_id(self): + if self.field_id: + self.sub_object_id = self.env['ir.model'].search( + [('model', '=', self.field_id.relation)])[0] + + @api.onchange('sub_model_object_field_id', 'default_value') + def onchange_copyvalue(self): + if self.sub_model_object_field_id or self.default_value: + self.copyvalue = "${object.%s.%s or %s}" % \ + (self.field_id.name, + self.sub_model_object_field_id.name, + self.default_value or '\'\'') + # compute the dynamic content for mako expression @api.multi def _compute_dynamic_content(self): diff --git a/agreement/models/agreement_section.py b/agreement/models/agreement_section.py index 7fbee027..2ee345c6 100644 --- a/agreement/models/agreement_section.py +++ b/agreement/models/agreement_section.py @@ -14,27 +14,54 @@ class AgreementSection(models.Model): help="The title is displayed on the PDF." "The name is not.") sequence = fields.Integer(string="Sequence") - agreement_id = fields.Many2one( - 'agreement', - string="Agreement", - ondelete="cascade" - ) - clauses_ids = fields.One2many( - 'agreement.clause', - 'section_id', - string="Clauses" - ) + agreement_id = fields.Many2one('agreement', string="Agreement", + ondelete="cascade") + clauses_ids = fields.One2many('agreement.clause', 'section_id', + string="Clauses") content = fields.Html(string="Section Content") - dynamic_content = fields.Html( - compute="_compute_dynamic_content", - string="Dynamic Content", - help='compute dynamic Content') - active = fields.Boolean( - string="Active", - default=True, - help="If unchecked, it will allow you to hide the agreement without " - "removing it." - ) + dynamic_content = fields.Html(compute="_compute_dynamic_content", + string="Dynamic Content", + help='compute dynamic Content') + active = fields.Boolean(string="Active", default=True, + help="""If unchecked, it will allow you to hide the + agreement without removing it.""") + + # Dynamic field editor + field_id = fields.Many2one( + 'ir.model.fields', string="Field", + help="""Select target field from the related document model. If it is a + relationship field you will be able to select a target field at the + destination of the relationship.""") + sub_object_id = fields.Many2one( + 'ir.model', string="Sub-model", + help="""When a relationship field is selected as first field, this + field shows the document model the relationship goes to.""") + sub_model_object_field_id = fields.Many2one( + 'ir.model.fields', string="Sub-field", + help="""When a relationship field is selected as first field, this + field lets you select the target field within the destination document + model (sub-model).""") + default_value = fields.Char( + string="Default Value", + help="Optional value to use if the target field is empty.") + copyvalue = fields.Char( + string="Placeholder Expression", + help="""Final placeholder expression, to be copy-pasted in the desired + template field.""") + + @api.onchange('field_id') + def onchange_sub_object_id(self): + if self.field_id: + self.sub_object_id = self.env['ir.model'].search( + [('model', '=', self.field_id.relation)])[0] + + @api.onchange('sub_model_object_field_id', 'default_value') + def onchange_copyvalue(self): + if self.sub_model_object_field_id or self.default_value: + self.copyvalue = "${object.%s.%s or %s}" % \ + (self.field_id.name, + self.sub_model_object_field_id.name, + self.default_value or '\'\'') # compute the dynamic content for mako expression @api.multi diff --git a/agreement/report/agreement.xml b/agreement/report/agreement.xml index 6a8ae52c..f4555823 100644 --- a/agreement/report/agreement.xml +++ b/agreement/report/agreement.xml @@ -89,7 +89,7 @@

Special Terms

-

+

Signatures

diff --git a/agreement/views/agreement.xml b/agreement/views/agreement.xml index 76f391ee..68d68634 100644 --- a/agreement/views/agreement.xml +++ b/agreement/views/agreement.xml @@ -63,13 +63,32 @@ required="True" nolabel="1"/> -
+ + + + + + + +

- For dynamic content use mako expression '${expression}'. For ex: - 1. object's field name: ${object.field_name} or - 2. many2one field name: ${object.many2one_field_id.field_name} + This section (on the left) allows you to add dynamic fields inside the description and special terms. +

    +
  1. Select the agreement field
  2. +
  3. Select the sub-field
  4. +
  5. Enter the default value if the field is empty
  6. +
  7. Copy and paste the placeholder expression in the description or the special terms
  8. +

-
+ diff --git a/agreement/views/agreement_appendix.xml b/agreement/views/agreement_appendix.xml index ad4a772f..6ba7098f 100644 --- a/agreement/views/agreement_appendix.xml +++ b/agreement/views/agreement_appendix.xml @@ -42,13 +42,32 @@ -
+ + + + + + + +

- For dynamic content use mako expression '${expression}'. For ex: - 1. object's field name: ${object.field_name} or - 2. many2one field name: ${object.many2one_field_id.field_name} + This section (on the left) allows you to add dynamic fields inside the content. +

    +
  1. Select the appendix field
  2. +
  3. Select the sub-field
  4. +
  5. Enter the default value if the field is empty
  6. +
  7. Copy and paste the placeholder expression in the content
  8. +

-
+
diff --git a/agreement/views/agreement_clause.xml b/agreement/views/agreement_clause.xml index 96467c7d..ef60c56c 100644 --- a/agreement/views/agreement_clause.xml +++ b/agreement/views/agreement_clause.xml @@ -44,13 +44,32 @@ -
+ + + + + + + +

- For dynamic content use mako expression '${expression}'. For ex: - 1. object's field name: ${object.field_name} or - 2. many2one field name: ${object.many2one_field_id.field_name} + This section (on the left) allows you to add dynamic fields inside the content. +

    +
  1. Select the clause field
  2. +
  3. Select the sub-field
  4. +
  5. Enter the default value if the field is empty
  6. +
  7. Copy and paste the placeholder expression in the content
  8. +

-
+
diff --git a/agreement/views/agreement_recital.xml b/agreement/views/agreement_recital.xml index 904c5989..b0ed3772 100644 --- a/agreement/views/agreement_recital.xml +++ b/agreement/views/agreement_recital.xml @@ -42,13 +42,32 @@ -
+ + + + + + + +

- For dynamic content use mako expression '${expression}'. For ex: - 1. object's field name: ${object.field_name} or - 2. many2one field name: ${object.many2one_field_id.field_name} + This section (on the left) allows you to add dynamic fields inside the content. +

    +
  1. Select the recital field
  2. +
  3. Select the sub-field
  4. +
  5. Enter the default value if the field is empty
  6. +
  7. Copy and paste the placeholder expression in the content
  8. +

-
+
diff --git a/agreement/views/agreement_section.xml b/agreement/views/agreement_section.xml index dcca2d41..fc13100d 100644 --- a/agreement/views/agreement_section.xml +++ b/agreement/views/agreement_section.xml @@ -44,13 +44,32 @@ -
+ + + + + + + +

- For dynamic content use mako expression '${expression}'. For ex: - 1. object's field name: ${object.field_name} or - 2. many2one field name: ${object.many2one_field_id.field_name} + This section (on the left) allows you to add dynamic fields inside the content. +

    +
  1. Select the section field
  2. +
  3. Select the sub-field
  4. +
  5. Enter the default value if the field is empty
  6. +
  7. Copy and paste the placeholder expression in the content
  8. +

-
+
+ nolabel="1"> + + + +