[IMP] contract: add section and notes in contract line

This commit is contained in:
Ernesto Tejeda
2020-03-17 17:27:28 -04:00
committed by Pedro M. Baeza
parent f86bb0de30
commit 9291305c48
16 changed files with 274 additions and 67 deletions

View File

@@ -74,7 +74,8 @@ Known issues / Roadmap
====================== ======================
* Recover states and others functional fields in Contracts. * Recover states and others functional fields in Contracts.
* Depending on the contract lines, some sections of contract
line have no meaning that they are propagated to certain invoices
Bug Tracker Bug Tracker
=========== ===========

View File

@@ -35,6 +35,7 @@
'wizards/contract_manually_create_invoice.xml', 'wizards/contract_manually_create_invoice.xml',
'wizards/contract_contract_terminate.xml', 'wizards/contract_contract_terminate.xml',
'views/contract_tag.xml', 'views/contract_tag.xml',
'views/assets.xml',
'views/abstract_contract_line.xml', 'views/abstract_contract_line.xml',
'views/contract.xml', 'views/contract.xml',
'views/contract_line.xml', 'views/contract_line.xml',

View File

@@ -17,13 +17,13 @@ class ContractAbstractContractLine(models.AbstractModel):
_description = 'Abstract Recurring Contract Line' _description = 'Abstract Recurring Contract Line'
product_id = fields.Many2one( product_id = fields.Many2one(
'product.product', string='Product', required=True 'product.product', string='Product'
) )
name = fields.Text(string='Description', required=True) name = fields.Text(string='Description', required=True)
quantity = fields.Float(default=1.0, required=True) quantity = fields.Float(default=1.0, required=True)
uom_id = fields.Many2one( uom_id = fields.Many2one(
'uom.uom', string='Unit of Measure', required=True 'uom.uom', string='Unit of Measure'
) )
automatic_price = fields.Boolean( automatic_price = fields.Boolean(
string="Auto-price?", string="Auto-price?",
@@ -125,6 +125,9 @@ class ContractAbstractContractLine(models.AbstractModel):
required=True, required=True,
ondelete='cascade', ondelete='cascade',
) )
display_type = fields.Selection([
('line_section', "Section"),
('line_note', "Note")], default=False, help="Technical field for UX purpose.")
@api.model @api.model
def _get_default_recurring_invoicing_offset( def _get_default_recurring_invoicing_offset(

View File

@@ -146,6 +146,9 @@ class ContractLine(models.Model):
def _compute_state(self): def _compute_state(self):
today = fields.Date.context_today(self) today = fields.Date.context_today(self)
for rec in self: for rec in self:
if rec.display_type:
rec.state = False
continue
if rec.is_canceled: if rec.is_canceled:
rec.state = 'canceled' rec.state = 'canceled'
continue continue
@@ -648,15 +651,15 @@ class ContractLine(models.Model):
@api.depends('recurring_next_date', 'date_start', 'date_end') @api.depends('recurring_next_date', 'date_start', 'date_end')
def _compute_create_invoice_visibility(self): def _compute_create_invoice_visibility(self):
# TODO: depending on the lines, and their order, some sections
# have no meaning in certain invoices
today = fields.Date.context_today(self) today = fields.Date.context_today(self)
for rec in self: for rec in self:
if rec.date_start: if (not rec.display_type and
if today < rec.date_start: rec.date_start and today >= rec.date_start):
rec.create_invoice_visibility = False rec.create_invoice_visibility = bool(rec.recurring_next_date)
else: else:
rec.create_invoice_visibility = bool( rec.create_invoice_visibility = False
rec.recurring_next_date
)
@api.multi @api.multi
def _prepare_invoice_line(self, invoice_id=False, invoice_values=False): def _prepare_invoice_line(self, invoice_id=False, invoice_values=False):
@@ -665,6 +668,7 @@ class ContractLine(models.Model):
self.last_date_invoiced, self.recurring_next_date self.last_date_invoiced, self.recurring_next_date
) )
invoice_line_vals = { invoice_line_vals = {
'display_type': self.display_type,
'product_id': self.product_id.id, 'product_id': self.product_id.id,
'quantity': self._get_quantity_to_invoice(*dates), 'quantity': self._get_quantity_to_invoice(*dates),
'uom_id': self.uom_id.id, 'uom_id': self.uom_id.id,
@@ -1299,7 +1303,8 @@ class ContractLine(models.Model):
@api.multi @api.multi
def unlink(self): def unlink(self):
"""stop unlink uncnacled lines""" """stop unlink uncnacled lines"""
if not all(self.mapped('is_canceled')): for record in self:
if not (record.is_canceled or record.display_type):
raise ValidationError( raise ValidationError(
_("Contract line must be canceled before delete") _("Contract line must be canceled before delete")
) )

View File

@@ -1,2 +1,3 @@
* Recover states and others functional fields in Contracts. * Recover states and others functional fields in Contracts.
* Depending on the contract lines, some sections of contract
line have no meaning that they are propagated to certain invoices

View File

@@ -5,27 +5,27 @@
<t t-call="web.html_container"> <t t-call="web.html_container">
<t t-foreach="docs" t-as="o"> <t t-foreach="docs" t-as="o">
<t t-call="web.external_layout"> <t t-call="web.external_layout">
<t t-set="o" t-value="o.with_context(lang=o.partner_id.lang)" />
<t t-set="address">
<p id="partner_info"><strong>Partner:</strong></p>
<div t-field="o.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "mobile", "fax", "email"], "no_marker": true, "phone_icons": true}'/>
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</t>
<div class="page"> <div class="page">
<div class="oe_structure"/> <div class="oe_structure"/>
<div class="row" id="partner_info">
<div class="col-xs-5 col-xs-offset-7">
<p id="partner_info"><strong>Partner:</strong></p>
<div t-field="o.partner_id" t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "mobile", "fax", "email"], "no_marker": true, "phone_icons": true}'/>
<p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
</div>
</div>
<div class="row" id="header_info"> <div class="row" id="header_info">
<div class="col-xs-3"> <div class="col-3">
<strong>Responsible: </strong><p t-field="o.user_id"/> <strong>Responsible: </strong><p t-field="o.user_id"/>
<strong>Contract: </strong><p t-field="o.code"/> <strong>Contract: </strong><p t-field="o.code"/>
</div> </div>
</div> </div>
<div class="row" id="invoice_info"> <div class="row" id="invoice_info">
<t t-set="total" t-value="0"/> <t t-set="total" t-value="0"/>
<div class="col-xs-12"> <div class="col-12">
<t t-set="total" t-value="0"/> <t t-set="total" t-value="0"/>
<p id="services_info"><strong>Recurring Items</strong></p> <p id="services_info"><strong>Recurring Items</strong></p>
<table class="table table-condensed"> <table class="table table-sm">
<thead> <thead>
<tr> <tr>
<th><strong>Description</strong></th> <th><strong>Description</strong></th>
@@ -36,7 +36,11 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr t-foreach="o.contract_line_ids" t-as="l"> <t t-set="current_subtotal" t-value="0"/>
<t t-foreach="o.contract_line_ids" t-as="l">
<t t-set="current_subtotal" t-value="current_subtotal + l.price_subtotal"/>
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if l.display_type == 'line_section' else 'font-italic o_line_note' if l.display_type == 'line_note' else ''">
<t t-if="not l.display_type">
<td> <td>
<span t-field="l.name"/> <span t-field="l.name"/>
</td> </td>
@@ -49,17 +53,44 @@
<td class="text-right"> <td class="text-right">
<span t-field="l.price_subtotal" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/> <span t-field="l.price_subtotal" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
</td> </td>
<td> <td class="text-right">
<span t-field="l.date_start"/> <span t-field="l.date_start"/>
</td> </td>
<t t-set="total" t-value="total + l.price_subtotal"/> <t t-set="total" t-value="total + l.price_subtotal"/>
</t>
<t t-if="l.display_type == 'line_section'">
<td colspan="99">
<span t-field="l.name"/>
</td>
<t t-set="current_section" t-value="l"/>
<t t-set="current_subtotal" t-value="0"/>
</t>
<t t-if="l.display_type == 'line_note'">
<td colspan="99">
<span t-field="l.name"/>
</td>
</t>
</tr> </tr>
<t t-if="current_section and (l_last or o.contract_line_ids[l_index+1].display_type == 'line_section')">
<tr class="is-subtotal text-right">
<td colspan="99">
<strong class="mr16">Subtotal</strong>
<span
t-esc="current_subtotal"
t-options='{"widget": "monetary", "display_currency": o.currency_id}'
/>
</td>
</tr>
</t>
</t>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="col-xs-4 pull-right"> </div>
<table class="table table-condensed"> <div id="total" class="row" name="total">
<tr class="border-black"> <div class="col-4 ml-auto">
<table class="table table-sm">
<tr class="border-black o_subtotal" style="">
<td><strong>Total</strong></td> <td><strong>Total</strong></td>
<td class="text-right"> <td class="text-right">
<span t-esc="total" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/> <span t-esc="total" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
@@ -68,12 +99,15 @@
</table> </table>
</div> </div>
</div> </div>
<div class="row" id="note"> <div>
<strong>Notes: </strong> <div class="row mt-4" id="note">
<br></br> <div><strong>Notes: </strong></div>
</div>
<div class="row">
<p t-field="o.note"/> <p t-field="o.note"/>
</div> </div>
</div> </div>
</div>
</t> </t>
</t> </t>
</t> </t>

View File

@@ -425,6 +425,8 @@ To use it, just select the template on the contract and fields will be filled au
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1> <h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
<ul class="simple"> <ul class="simple">
<li>Recover states and others functional fields in Contracts.</li> <li>Recover states and others functional fields in Contracts.</li>
<li>Depending on the contract lines, some sections of contract
line have no meaning that they are propagated to certain invoices</li>
</ul> </ul>
</div> </div>
<div class="section" id="bug-tracker"> <div class="section" id="bug-tracker">

View File

@@ -0,0 +1,38 @@
/* Copyright 2020 Tecnativa - Ernesto Tejeda
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*/
/*
If in the sub-tree view where the sections and notes are to be used
there are fields that have defined in the XML attrs = {'invisible': ....}
and this condition is met, then an extra space appears in the rows
corresponding to the sections and lines.
This js was written to deal with that problem, but a solution based on
this can be applied directly to Odoo*/
odoo.define('contract.section_and_note_backend', function (require) {
"use strict";
var fieldRegistry = require('web.field_registry');
var section_and_note_one2many = fieldRegistry.get('section_and_note_one2many');
section_and_note_one2many.include({
_getRenderer: function () {
var result = this._super.apply(this, arguments);
if (this.view.arch.tag === 'tree') {
result.include({
_renderBodyCell: function (record, node, index, options) {
var $cell = this._super.apply(this, arguments);
var isSection = record.data.display_type === 'line_section';
var isNote = record.data.display_type === 'line_note';
if (isSection || isNote) {
$cell.removeClass('o_invisible_modifier');
}
return $cell;
}
})
}
return result
},
});
});

View File

@@ -7,24 +7,38 @@
<field name="model">contract.abstract.contract.line</field> <field name="model">contract.abstract.contract.line</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<header/> <field name="display_type" invisible="1"/>
<header attrs="{'invisible': [('display_type', '!=', False)]}"/>
<sheet> <sheet>
<group col="4">
<field colspan="4" name="product_id"/>
<field colspan="4" name="name"/>
<field colspan="2" name="quantity"/>
<field colspan="2" name="uom_id"/>
<field colspan="2" name="automatic_price"/>
<field name="specific_price" invisible="1"/> <field name="specific_price" invisible="1"/>
<group col="4" attrs="{'invisible': [('display_type', '!=', False)]}">
<field colspan="4"
name="product_id"
attrs="{'required': [('display_type', '=', False)]}"/>
<label for="quantity"/>
<div class="o_row">
<field name="quantity" class="oe_inline"/>
<field name="uom_id"
class="oe_inline"
groups="uom.group_uom"
attrs="{'required': [('display_type', '=', False)]}"/>
</div>
<field colspan="2" name="automatic_price"/>
<field colspan="2" name="price_unit" <field colspan="2" name="price_unit"
attrs="{'readonly': [('automatic_price', '=', True)]}"/> attrs="{'readonly': [('automatic_price', '=', True)]}"/>
<field colspan="2" name="discount" groups="base.group_no_one"/> <field colspan="2" name="discount" groups="base.group_no_one"/>
</group> </group>
<group col="4">
<label for="name" string="Description" attrs="{'invisible': [('display_type', '!=', False)]}"/>
<label for="name" string="Section" attrs="{'invisible': [('display_type', '!=', 'line_section')]}"/>
<label for="name" string="Note" attrs="{'invisible': [('display_type', '!=', 'line_note')]}"/>
<field name="name" nolabel="1"/>
<group col="4" attrs="{'invisible': [('display_type', '!=', False)]}">
<field colspan="2" name="is_auto_renew"/> <field colspan="2" name="is_auto_renew"/>
<field colspan="2" name="is_canceled" invisible="1"/> <field colspan="2" name="is_canceled" invisible="1"/>
</group> </group>
<group attrs="{'invisible':[('is_auto_renew', '=', False)]}"> <group attrs="{'invisible':['|', ('is_auto_renew', '=', False), ('display_type', '!=', False)]}">
<group> <group>
<label for="auto_renew_interval"/> <label for="auto_renew_interval"/>
<div> <div>
@@ -48,10 +62,10 @@
</div> </div>
</group> </group>
</group> </group>
<group name="recurrence_info"> <group name="recurrence_info" attrs="{'invisible': [('display_type', '!=', False)]}">
<group> <group>
<label for="recurring_interval"/> <label for="recurring_interval"/>
<div> <div class="o_row">
<field name="recurring_interval" <field name="recurring_interval"
class="oe_inline" nolabel="1"/> class="oe_inline" nolabel="1"/>
<field name="recurring_rule_type" <field name="recurring_rule_type"

10
contract/views/assets.xml Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<template id="assets_backend" name="contract assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/contract/static/src/js/section_and_note_fields_backend.js"></script>
</xpath>
</template>
</odoo>

View File

@@ -96,8 +96,75 @@
<notebook> <notebook>
<page name="recurring_invoice_line" <page name="recurring_invoice_line"
string="Recurring Invoices"> string="Recurring Invoices">
<field name="contract_line_ids" attrs="{'readonly': [('is_terminated','=',True)]}" <field name="contract_line_ids"
context="{'default_contract_type': contract_type}"/> attrs="{'readonly': [('is_terminated','=',True)]}"
widget="section_and_note_one2many"
context="{'default_contract_type': contract_type}">
<tree decoration-muted="is_canceled"
decoration-info="create_invoice_visibility and not is_canceled">
<control>
<create string="Add a line"/>
<create string="Add a section" context="{'default_display_type': 'line_section'}"/>
<create string="Add a note" context="{'default_display_type': 'line_note'}"/>
</control>
<field name="display_type" invisible="1"/>
<field name="sequence" widget="handle"/>
<field name="product_id"/>
<field name="name" widget="section_and_note_text"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="analytic_tag_ids" widget="many2many_tags" groups="analytic.group_analytic_tags"/>
<field name="quantity"/>
<field name="uom_id"/>
<field name="automatic_price" attrs="{'column_invisible': [('parent.contract_type', '=', 'purchase')]}"/>
<field name="price_unit"
attrs="{'readonly': [('automatic_price', '=', True)]}"/>
<field name="specific_price" invisible="1"/>
<field name="discount" groups="base.group_no_one"/>
<field name="price_subtotal"/>
<field name="recurring_interval" invisible="1"/>
<field name="recurring_rule_type" invisible="1"/>
<field name="recurring_invoicing_type" invisible="1"/>
<field name="date_start" required="1"/>
<field name="date_end"/>
<field name="recurring_next_date" required="1"/>
<field name="last_date_invoiced" groups="base.group_no_one"/>
<field name="create_invoice_visibility" invisible="1"/>
<field name="is_plan_successor_allowed" invisible="1"/>
<field name="is_stop_plan_successor_allowed" invisible="1"/>
<field name="is_stop_allowed" invisible="1"/>
<field name="is_cancel_allowed" invisible="1"/>
<field name="is_un_cancel_allowed" invisible="1"/>
<field name="is_auto_renew" invisible="1"/>
<field name="is_canceled" invisible="1"/>
<button name="action_plan_successor"
string="Plan Start" type="object"
icon="fa-calendar text-success"
attrs="{'invisible': [('is_plan_successor_allowed', '=', False)]}"/>
<button name="action_stop_plan_successor"
string="Stop Plan Successor"
type="object"
icon="fa-pause text-muted"
attrs="{'invisible': [('is_stop_plan_successor_allowed', '=', False)]}"/>
<button name="action_stop" string="Stop"
type="object"
icon="fa-stop text-danger"
attrs="{'invisible': [('is_stop_allowed', '=', False)]}"/>
<button name="cancel" string="Cancel"
type="object"
icon="fa-ban text-danger"
confirm="Are you sure you want to cancel this line"
attrs="{'invisible': [('is_cancel_allowed', '=', False)]}"/>
<button name="action_uncancel"
string="Un-cancel" type="object"
icon="fa-ban text-success"
attrs="{'invisible': [('is_un_cancel_allowed', '=', False)]}"/>
<button name="renew" string="Renew"
type="object"
icon="fa-fast-forward text-success"
groups="base.group_no_one"
attrs="{'invisible': [('is_auto_renew', '=', False)]}"/>
</tree>
</field>
<field name="note"/> <field name="note"/>
</page> </page>
<page name="info" string="Other Information"> <page name="info" string="Other Information">

View File

@@ -38,7 +38,9 @@
</group> </group>
</group> </group>
<group name="recurrence_info" position="after"> <group name="recurrence_info" position="after">
<group name="analytic" groups="analytic.group_analytic_accounting,analytic.group_analytic_tags"> <group name="analytic"
groups="analytic.group_analytic_accounting,analytic.group_analytic_tags"
attrs="{'invisible': [('display_type', '!=', False)]}">
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/> <field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="analytic_tag_ids" widget="many2many_tags" groups="analytic.group_analytic_tags"/> <field name="analytic_tag_ids" widget="many2many_tags" groups="analytic.group_analytic_tags"/>
</group> </group>
@@ -77,6 +79,7 @@
</field> </field>
</record> </record>
<!-- TODO: Delete this view in migration to v13 or further migrations -->
<!--TREE view--> <!--TREE view-->
<record id="contract_line_tree_view" model="ir.ui.view"> <record id="contract_line_tree_view" model="ir.ui.view">
<field name="name">contract.line tree view (in contract)</field> <field name="name">contract.line tree view (in contract)</field>

View File

@@ -19,11 +19,19 @@
</group> </group>
</group> </group>
<group name="group_invoice_lines" string="Contract Template Lines"> <group name="group_invoice_lines" string="Contract Template Lines">
<field name="contract_line_ids" nolabel="1"> <field name="contract_line_ids"
widget="section_and_note_one2many"
nolabel="1">
<tree> <tree>
<control>
<create string="Add a line"/>
<create string="Add a section" context="{'default_display_type': 'line_section'}"/>
<create string="Add a note" context="{'default_display_type': 'line_note'}"/>
</control>
<field name="display_type" invisible="1"/>
<field name="sequence" widget="handle" /> <field name="sequence" widget="handle" />
<field name="product_id" /> <field name="product_id" />
<field name="name" /> <field name="name" widget="section_and_note_text"/>
<field name="quantity" /> <field name="quantity" />
<field name="uom_id" /> <field name="uom_id" />
<field name="automatic_price" attrs="{'column_invisible': [('parent.contract_type','=','purchase')]}"/> <field name="automatic_price" attrs="{'column_invisible': [('parent.contract_type','=','purchase')]}"/>

View File

@@ -9,4 +9,14 @@
groups="sales_team.group_sale_salesman" groups="sales_team.group_sale_salesman"
/> />
<record id="contract_contract_form_view" model="ir.ui.view">
<field name="name">contract.contract.form.contract.sale</field>
<field name="model">contract.contract</field>
<field name="inherit_id" ref="contract.contract_contract_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='contract_line_ids']/tree/field[@name='discount']" position="attributes">
<attribute name="groups" add="sale.group_discount_per_so_line" separator=","/>
</xpath>
</field>
</record>
</odoo> </odoo>

View File

@@ -7,7 +7,7 @@
<field name="model">contract.abstract.contract.line</field> <field name="model">contract.abstract.contract.line</field>
<field name="inherit_id" ref="contract.contract_abstract_contract_line_form_view"/> <field name="inherit_id" ref="contract.contract_abstract_contract_line_form_view"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='quantity']" position="before"> <xpath expr="//label[@for='quantity']" position="before">
<field name="qty_type"/> <field name="qty_type"/>
</xpath> </xpath>
<xpath expr="//field[@name='quantity']" position="after"> <xpath expr="//field[@name='quantity']" position="after">

View File

@@ -8,6 +8,16 @@
<xpath expr="//field[@name='code']" position="after"> <xpath expr="//field[@name='code']" position="after">
<field name="skip_zero_qty"/> <field name="skip_zero_qty"/>
</xpath> </xpath>
<xpath expr="//field[@name='contract_line_ids']/tree/field[@name='quantity']" position="before">
<field name="qty_type"/>
<field
name="qty_formula_id"
attrs="{'invisible': [('qty_type', '!=', 'variable')]}"
/>
</xpath>
<xpath expr="//field[@name='contract_line_ids']/tree/field[@name='quantity']" position="attributes">
<attribute name="attrs">{'invisible': [('qty_type', '!=', 'fixed')]}</attribute>
</xpath>
</field> </field>
</record> </record>