Browse Source

Merge pull request #35 from Tecnativa/9.0-contract_variable_quantity

[ADD] contract_variable_quantity:
pull/36/head
Rafael Blasco 8 years ago
committed by GitHub
parent
commit
85a13aa53a
  1. 76
      contract_variable_quantity/README.rst
  2. 4
      contract_variable_quantity/__init__.py
  3. 21
      contract_variable_quantity/__openerp__.py
  4. 188
      contract_variable_quantity/i18n/es.po
  5. BIN
      contract_variable_quantity/images/formula_form.png
  6. 4
      contract_variable_quantity/models/__init__.py
  7. 66
      contract_variable_quantity/models/contract.py
  8. 3
      contract_variable_quantity/security/ir.model.access.csv
  9. BIN
      contract_variable_quantity/static/description/icon.png
  10. 301
      contract_variable_quantity/static/description/icon.svg
  11. 4
      contract_variable_quantity/tests/__init__.py
  12. 60
      contract_variable_quantity/tests/test_contract_variable_quantity.py
  13. 90
      contract_variable_quantity/views/contract_view.xml

76
contract_variable_quantity/README.rst

@ -0,0 +1,76 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
=================================================
Variable quantity in contract recurrent invoicing
=================================================
With this module, you will be able to define in recurring contracts some
lines with variable quantity according to a provided formula.
Configuration
=============
#. Go to Sales > Configuration > Contracts > Formulas (quantity).
#. Define any formula based on Python code that stores at some moment a
float/integer value of the quantity to invoice in the variable 'result'.
You can use these variables to compute your formula:
* *env*: Environment variable for getting other models.
* *context*: Current context dictionary.
* *user*: Current user.
* *line*: Contract recurring invoice line that triggers this formula.
* *contract*: Contract whose line belongs to.
* *invoice*: Invoice (header) being created.
.. figure:: images/formula_form.png
:alt: Formula form
:width: 600 px
Usage
=====
To use this module, you need to:
#. Go to Sales -> Contracts and select or create a new contract.
#. Check *Generate recurring invoices automatically*.
#. Add a new recurring invoicing line.
#. Select "Variable quantity" in column "Qty. type".
#. Select one of the possible formulas to use (previously created).
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/110/9.0
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/contract/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

4
contract_variable_quantity/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models

21
contract_variable_quantity/__openerp__.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# © 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Variable quantity in contract recurrent invoicing',
'version': '9.0.1.0.0',
'category': 'Contract Management',
'license': 'AGPL-3',
'author': "Tecnativa,"
"Odoo Community Association (OCA)",
'website': 'https://www.tecnativa.com',
'depends': [
'contract',
],
'data': [
'security/ir.model.access.csv',
'views/contract_view.xml',
],
'installable': True,
}

188
contract_variable_quantity/i18n/es.po

@ -0,0 +1,188 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * contract_variable_quantity
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 9.0c\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-09 16:50+0000\n"
"PO-Revision-Date: 2016-09-09 16:50+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "<code>\n"
" result = env['product.product'].search_count([('sale_ok', '=', True)])\n"
" </code>"
msgstr "<code>\n"
" result = env['product.product'].search_count([('sale_ok', '=', True)])\n"
" </code>"
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "<i>context</i>: Current context dictionary."
msgstr "<i>context</i>: Diccionario de contexto actual."
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "<i>contract</i>: Contract whose line belongs to."
msgstr "<i>contract</i>: Contrato al que pertenece esta línea."
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "<i>env</i>: Environment variable for getting other models."
msgstr "<i>env</i>: Variable de entorno para obtener otros modelos."
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "<i>invoice</i>: Invoice (header) being created."
msgstr "<i>invoice</i>: Factura (cabecera) siendo creada."
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "<i>line</i>: Contract recurring invoice line that triggers this formula."
msgstr "<i>line</i>: Línea del contrato de facturación recurrente que lanza esta fórmula."
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "<i>user</i>: Current user."
msgstr "<i>user</i>: Usuario actual."
#. module: contract_variable_quantity
#: model:ir.model,name:contract_variable_quantity.model_account_analytic_account
msgid "Analytic Account"
msgstr "Cuenta analítica"
#. module: contract_variable_quantity
#: model:ir.actions.act_window,help:contract_variable_quantity.action_contract_quantity_formula
msgid "Click to create a new formula for variable quantities."
msgstr "Pulse para crear una nueva fórmula para cantidades variables."
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_contract_line_qty_formula_code
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "Code"
msgstr "Código"
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_contract_line_qty_formula_create_uid
msgid "Created by"
msgstr "Creado por"
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_contract_line_qty_formula_create_date
msgid "Created on"
msgstr "Creado en"
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_contract_line_qty_formula_display_name
msgid "Display Name"
msgstr "Nombre mostrado"
#. module: contract_variable_quantity
#: code:addons/contract_variable_quantity/models/contract.py:64
#, python-format
msgid "Error evaluating code.\n"
"Details: %s"
msgstr "Error evaluando el código.\n"
"Detalles: %s"
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "Example of Python code"
msgstr "Ejemplo del código Python"
#. module: contract_variable_quantity
#: selection:account.analytic.invoice.line,qty_type:0
msgid "Fixed quantity"
msgstr "Cantidad fija"
#. module: contract_variable_quantity
#: model:ir.actions.act_window,name:contract_variable_quantity.action_contract_quantity_formula
#: model:ir.ui.menu,name:contract_variable_quantity.menu_contract_quantity_formula
msgid "Formulas (quantity)"
msgstr "Fórmulas (cantidad)"
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "Help with Python expressions."
msgstr "Ayuda con las expresiones Python."
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_contract_line_qty_formula_id
msgid "ID"
msgstr "ID (identificación)"
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_contract_line_qty_formula___last_update
msgid "Last Modified on"
msgstr "Última modificación en"
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_contract_line_qty_formula_write_uid
msgid "Last Updated by"
msgstr "Última actualización de"
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_contract_line_qty_formula_write_date
msgid "Last Updated on"
msgstr "Última actualización en"
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_contract_line_qty_formula_name
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "Name"
msgstr "Nombre"
#. module: contract_variable_quantity
#: code:addons/contract_variable_quantity/models/contract.py:66
#, python-format
msgid "No valid result returned."
msgstr "No se devuelve un valor válido."
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_account_analytic_invoice_line_qty_formula_id
msgid "Qty. formula"
msgstr "Formula ctdad."
#. module: contract_variable_quantity
#: model:ir.model.fields,field_description:contract_variable_quantity.field_account_analytic_invoice_line_qty_type
msgid "Qty. type"
msgstr "Tipo ctdad."
#. module: contract_variable_quantity
#: selection:account.analytic.invoice.line,qty_type:0
msgid "Variable quantity"
msgstr "Cantidad variable"
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "You can use these variables to compute your formula:"
msgstr "Puede usar estas variables para calcular su fórmula:"
#. module: contract_variable_quantity
#: model:ir.ui.view,arch_db:contract_variable_quantity.view_contract_line_qty_formula_form
msgid "You have to insert valid Python code block that stores at some moment a float/integer value of the quantity to invoice in the variable 'result'."
msgstr "Debe insertar un bloque de código Python que almacene en algún momento un valor entero o decimal de la cantidad a facturar en la variable 'result'."
#. module: contract_variable_quantity
#: model:ir.model,name:contract_variable_quantity.model_account_analytic_invoice_line
msgid "account.analytic.invoice.line"
msgstr "account.analytic.invoice.line"
#. module: contract_variable_quantity
#: model:ir.model,name:contract_variable_quantity.model_contract_line_formula
msgid "contract.line.formula"
msgstr "contract.line.formula"
#. module: contract_variable_quantity
#: model:ir.model,name:contract_variable_quantity.model_contract_line_qty_formula
msgid "contract.line.qty.formula"
msgstr "contract.line.qty.formula"

BIN
contract_variable_quantity/images/formula_form.png

After

Width: 903  |  Height: 639  |  Size: 56 KiB

4
contract_variable_quantity/models/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import contract

66
contract_variable_quantity/models/contract.py

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# © 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import _, api, fields, models, exceptions
from openerp.tools.safe_eval import safe_eval
class AccountAnalyticAccount(models.Model):
_inherit = "account.analytic.account"
@api.model
def _prepare_invoice_line(self, line, invoice_id):
vals = super(AccountAnalyticAccount, self)._prepare_invoice_line(
line, invoice_id)
if line.qty_type == 'variable':
eval_context = {
'env': self.env,
'context': self.env.context,
'user': self.env.user,
'line': line,
'contract': line.analytic_account_id,
'invoice': self.env['account.invoice'].browse(invoice_id),
}
safe_eval(line.qty_formula_id.code.strip(), eval_context,
mode="exec", nocopy=True) # nocopy for returning result
vals['quantity'] = eval_context.get('result', 0)
return vals
class AccountAnalyticInvoiceLine(models.Model):
_inherit = 'account.analytic.invoice.line'
qty_type = fields.Selection(
selection=[
('fixed', 'Fixed quantity'),
('variable', 'Variable quantity'),
], required=True, default='fixed', string="Qty. type")
qty_formula_id = fields.Many2one(
comodel_name="contract.line.qty.formula", string="Qty. formula")
class ContractLineFormula(models.Model):
_name = 'contract.line.qty.formula'
name = fields.Char(required=True)
code = fields.Text(required=True, default="result = 0")
@api.constrains('code')
def _check_code(self):
eval_context = {
'env': self.env,
'context': self.env.context,
'user': self.env.user,
'line': self.env['account.analytic.invoice.line'],
'contract': self.env['account.analytic.account'],
'invoice': self.env['account.invoice'],
}
try:
safe_eval(
self.code.strip(), eval_context, mode="exec", nocopy=True)
except Exception as e:
raise exceptions.ValidationError(
_('Error evaluating code.\nDetails: %s') % e)
if 'result' not in eval_context:
raise exceptions.ValidationError(_('No valid result returned.'))

3
contract_variable_quantity/security/ir.model.access.csv

@ -0,0 +1,3 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"contract_line_qty_formula_manager","Recurring formula manager","model_contract_line_qty_formula","base.group_sale_manager",1,1,1,1
"contract_line_qty_formula_user","Recurring formula user","model_contract_line_qty_formula","base.group_sale_salesman",1,0,0,0

BIN
contract_variable_quantity/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 5.1 KiB

301
contract_variable_quantity/static/description/icon.svg

@ -0,0 +1,301 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg2"
sodipodi:docname="icon.svg"
viewBox="0 0 128 128"
version="1.1"
inkscape:version="0.91 r13725"
width="128"
height="128"
inkscape:export-filename="icon.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4">
<linearGradient
id="linearGradient4306">
<stop
id="stop4308"
style="stop-color:#dedc37"
offset="0" />
<stop
id="stop4310"
style="stop-color:#f8fa8d"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3727">
<stop
id="stop3729"
style="stop-color:#f3f360"
offset="0" />
<stop
id="stop3731"
style="stop-color:#cbc630"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3717">
<stop
id="stop3719"
style="stop-color:#e4e240"
offset="0" />
<stop
id="stop3725"
style="stop-color:#d4d237"
offset=".47047" />
<stop
id="stop3721"
style="stop-color:#f8f44e"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3590">
<stop
id="stop3592"
style="stop-color:#b8b852"
offset="0" />
<stop
id="stop3594"
style="stop-color:#dbd649"
offset="1" />
</linearGradient>
<filter
inkscape:menu-tooltip="In and out glow with a possible offset and colorizable flood"
inkscape:menu="Shadows and Glows"
inkscape:label="Cutout Glow"
style="color-interpolation-filters:sRGB;"
id="filter3869">
<feOffset
dy="3"
dx="3"
id="feOffset3871" />
<feGaussianBlur
stdDeviation="3"
result="blur"
id="feGaussianBlur3873" />
<feFlood
flood-color="rgb(0,0,0)"
flood-opacity="1"
result="flood"
id="feFlood3875" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite"
id="feComposite3877" />
<feBlend
in="blur"
in2="composite"
mode="normal"
id="feBlend3879" />
</filter>
</defs>
<sodipodi:namedview
id="base"
bordercolor="#666666"
inkscape:pageshadow="2"
inkscape:window-y="24"
pagecolor="#ffffff"
inkscape:window-height="1056"
inkscape:window-maximized="1"
inkscape:zoom="4.9954589"
inkscape:window-x="65"
showgrid="false"
borderopacity="1.0"
inkscape:current-layer="layer1"
inkscape:cx="73.86699"
inkscape:cy="46.4832"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1855"
inkscape:pageopacity="0.0"
inkscape:document-units="px" />
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(-319.71,-475.23)">
<g
id="g3620"
transform="matrix(2.2116072,0,0,2.2116072,-246.9006,-632.55508)">
<path
style="fill:#ffffff"
sodipodi:nodetypes="sssssssss"
inkscape:connector-curvature="0"
d="m 298.70134,500.89595 -38.10593,0 c -2.4922,0 -4.39684,1.9053 -4.39684,4.39684 l 0,38.10527 c 0,2.4922 1.90464,4.39749 4.39684,4.39749 l 38.10593,0 c 2.49154,0 4.39683,-1.90529 4.39683,-4.39749 l 0,-38.10527 c 0,-2.49154 -1.90529,-4.39684 -4.39683,-4.39684 z"
id="path3618" />
<path
id="path3497"
d="m 298.70134,500.89595 -38.10593,0 c -2.4922,0 -4.39684,1.9053 -4.39684,4.39684 l 0,38.10527 c 0,2.4922 1.90464,4.39749 4.39684,4.39749 l 38.10593,0 c 2.49154,0 4.39683,-1.90529 4.39683,-4.39749 l 0,-38.10527 c 0,-2.49154 -1.90529,-4.39684 -4.39683,-4.39684 z m 1.4643,42.50211 c 0,0.88068 -0.58624,1.46561 -1.4643,1.46561 l -38.10593,0 c -0.87937,0 -1.46561,-0.58624 -1.46561,-1.46561 l 0,-38.10527 c 0,-0.88002 0.58624,-1.46561 1.46561,-1.46561 l 38.10593,0 c 0.88002,0 1.4643,0.58624 1.4643,1.46561 l 0,38.10527 z"
inkscape:connector-curvature="0" />
<path
id="path3499"
d="m 291.81296,519.06955 0.14787,-0.73216 c 0.14721,-0.43968 -0.14787,-0.87936 -0.58625,-1.02527 -0.43968,-0.14722 -0.88002,0.14722 -1.02593,0.58625 l -0.14721,0.73215 c -0.87937,-0.14656 -1.61218,0 -1.75874,0.43968 l -3.51812,13.33642 4.54274,1.17184 3.51682,-13.33642 c 0.14721,-0.29378 -0.29247,-0.87937 -1.17118,-1.17249 z"
inkscape:connector-curvature="0" />
<path
id="path3501"
d="m 294.01138,520.09548 c -0.14787,0 -0.441,0.14721 -0.441,0.29246 l -1.75873,6.59526 0.7328,0.14722 1.75874,-6.59526 c 0.001,-0.14656 0.001,-0.43968 -0.29181,-0.43968 z"
inkscape:connector-curvature="0" />
<path
id="path3503"
d="m 286.24363,536.80411 c 0,0 1.17249,-1.02593 2.05251,-1.90529 l -2.93057,-0.73346 c 0.29116,1.17314 0.87806,2.63875 0.87806,2.63875 z"
inkscape:connector-curvature="0" />
<path
id="path3505"
d="m 285.36361,536.07065 -0.29247,0 c -0.73281,0 -1.46496,0.14722 -4.39749,0.14722 -0.29247,0 -0.58625,-0.14722 -0.88003,-0.43969 -0.43902,-0.29312 -0.7328,-0.58624 -1.31905,-0.58624 l 0,0 c -0.43968,0 -0.88002,0.29247 -1.90529,0.73215 -0.43969,0.29312 -1.17249,0.58625 -1.46562,0.73346 0,-0.14787 0,-0.58624 0.14657,-0.73346 0.14721,-0.58624 0.14721,-1.17183 0,-1.4643 -0.14657,-0.14722 -0.29247,-0.14722 -0.43969,-0.14722 l 0,0 c -0.87937,0 -1.75873,1.02528 -2.78466,1.90464 -0.58494,0.5869 -1.31775,1.31906 -1.61087,1.31906 l 0,0 c -0.29312,-0.43969 0.73281,-2.4922 1.02593,-3.3696 0.58625,-1.46562 0.87937,-1.75874 0.43969,-2.05252 -0.14722,-0.14721 -0.29313,-0.14721 -0.73281,0 -0.58625,0.43969 -1.31905,1.02462 -1.9053,1.75874 -1.31905,1.17183 -2.6381,2.49285 -3.66403,2.05186 -0.14721,-0.14657 -0.43968,0 -0.58624,0.14917 -0.14657,0.14722 0,0.43969 0.14721,0.5856 1.46496,0.73215 2.93057,-0.73347 4.54275,-2.19777 0.43968,-0.29247 0.7328,-0.73215 1.17249,-1.02462 -0.14657,0.14656 -0.14657,0.29247 -0.29313,0.43968 -0.87936,1.9053 -1.46495,3.3696 -1.02527,4.10307 0.14656,0.14721 0.29312,0.29115 0.58624,0.29115 l 0,0 c 0.58625,0 1.31971,-0.73215 2.19842,-1.61151 0.73281,-0.73347 1.46562,-1.46562 2.05186,-1.61283 0,0.14721 0,0.43968 -0.14656,0.73346 -0.14722,0.58624 -0.14722,1.17183 0,1.46561 0.14591,0.14329 0.29247,0.29051 0.43903,0.29051 0.43968,0 1.02593,-0.29247 1.9053,-0.87937 0.43968,-0.29378 1.31905,-0.73346 1.46561,-0.73346 l 0,0 c 0.29247,0 0.58624,0.14721 0.87937,0.43968 0.29312,0.29378 0.7328,0.58625 1.31905,0.58625 3.07713,0 3.66534,0 4.54274,-0.14657 l 0.29312,0 c 0.14657,0 0.29247,-0.14721 0.29247,-0.44099 6.6e-4,-0.29116 0.29378,-0.29116 6.6e-4,-0.29116 z"
inkscape:connector-curvature="0" />
<rect
id="rect3507"
height="2.9312253"
width="29.312252"
y="510.569"
x="264.99225" />
<rect
id="rect3509"
height="2.9305711"
width="18.467373"
y="516.43146"
x="264.99225" />
<rect
id="rect3511"
height="2.9312253"
width="18.467373"
y="523.75952"
x="264.99225" />
</g>
<g
id="g3513"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3515"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3517"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3519"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3521"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3523"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3525"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3527"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3529"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3531"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3533"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3535"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3537"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3539"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
id="g3541"
transform="matrix(0.65429135,0,0,0.65429135,252.06849,472.60499)" />
<g
transform="scale(1.0411221,0.9605021)"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:25.9893322px;line-height:125%;font-family:'Liberation Sans Narrow';-inkscape-font-specification:'Liberation Sans Narrow Bold Condensed';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text3390">
<path
d="m 326.62742,615.79017 q 0,-1.56089 0.25381,-2.86797 0.26649,-1.31977 0.79947,-2.25884 0.53299,-0.95175 1.34515,-1.48474 0.82486,-0.53298 1.95428,-0.53298 1.12942,0 2.01773,0.58374 0.8883,0.58375 1.38322,1.89083 0,-0.26649 0.0127,-0.60913 0.0127,-0.35532 0.0381,-0.67257 0.0254,-0.32994 0.0508,-0.58375 0.0381,-0.2538 0.0508,-0.34263 l 2.8172,0 q -0.0254,0.45684 -0.0508,1.2817 -0.0127,0.82486 -0.0127,1.87814 l 0,15.96415 -2.90603,0 0,-5.71055 q 0,-0.35532 0.0127,-0.69796 0.0127,-0.34263 0.0127,-0.6345 0,0 0.0254,-0.63451 l -0.0254,0 q -0.50761,1.33246 -1.45936,1.94159 -0.93907,0.59643 -2.20808,0.59643 -1.06597,0 -1.84007,-0.53298 -0.77409,-0.53298 -1.2817,-1.48474 -0.5076,-0.95176 -0.74871,-2.24615 -0.24112,-1.30708 -0.24112,-2.84258 z m 7.77904,-0.0761 q 0,-1.37054 -0.21573,-2.25884 -0.20305,-0.88831 -0.54568,-1.4086 -0.32994,-0.53299 -0.76141,-0.73603 -0.43146,-0.21573 -0.86292,-0.21573 -0.55837,0 -1.00252,0.26649 -0.43146,0.26649 -0.73603,0.83755 -0.29187,0.57105 -0.45684,1.45936 -0.15228,0.88831 -0.15228,2.13194 0,4.66995 2.32229,4.66995 0.43146,0 0.86292,-0.22842 0.44416,-0.22842 0.78679,-0.77409 0.34263,-0.54568 0.54568,-1.45937 0.21573,-0.91368 0.21573,-2.28421 z"
style="fill:#808080"
id="path3409" />
<path
d="m 343.16263,622.87124 q -1.29439,0 -1.99235,-0.85023 -0.68526,-0.86293 -0.68526,-2.60147 l 0,-8.09629 -1.43398,0 0,-2.41112 1.57357,0 0.91369,-3.22329 1.84006,0 0,3.22329 2.13194,0 0,2.41112 -2.13194,0 0,7.13184 q 0,1.00252 0.30456,1.48474 0.31726,0.46953 0.96445,0.46953 0.26649,0 0.48222,-0.0508 0.22843,-0.0508 0.50761,-0.1269 l 0,2.20808 q -0.54568,0.21573 -1.1548,0.31725 -0.59644,0.11421 -1.31977,0.11421 z"
style="fill:#808080"
id="path3411" />
<path
d="m 353.18781,623.36616 q -0.40608,1.20556 -0.82486,2.08118 -0.41877,0.87561 -0.92637,1.44667 -0.49492,0.58374 -1.12942,0.86292 -0.62182,0.27919 -1.45937,0.27919 -0.45684,0 -0.93906,-0.0381 -0.46954,-0.0381 -0.901,-0.12691 l 0,-2.53802 q 0.20304,0.0381 0.49491,0.0634 0.30457,0.0381 0.50761,0.0381 0.43146,0 0.7614,-0.11422 0.34264,-0.11421 0.60913,-0.3807 0.26649,-0.26649 0.49491,-0.71064 0.24112,-0.44416 0.45685,-1.11673 l 0.19035,-0.60913 -4.45423,-13.5911 3.09639,0 1.76392,6.43388 q 0,0 0.1269,0.46954 l 0.20304,0.7614 0.22843,0.88831 0.21573,0.88831 q 0.10152,0.43146 0.16497,0.7614 0.0761,0.32995 0.11421,0.46954 0.0254,-0.13959 0.0888,-0.46954 l 0.16497,-0.73602 0.19035,-0.86293 0.20305,-0.87562 0.16497,-0.74871 q 0.0761,-0.34264 0.1269,-0.49492 l 1.6624,-6.48464 3.05832,0 -4.45423,14.45403 z"
style="fill:#808080"
id="path3413" />
<path
d="m 364.60891,611.95776 0,-2.8299 10.68506,0 0,2.8299 -10.68506,0 z m 0,6.99224 0,-2.80451 10.68506,0 0,2.80451 -10.68506,0 z"
style="fill:#808080"
id="path3415" />
<path
d="m 390.62362,622.64282 -2.62686,-4.97452 -2.63954,4.97452 -3.12176,0 4.12428,-7.09377 -3.92124,-6.63692 3.15983,0 2.39843,4.4923 2.39843,-4.4923 3.17253,0 -3.92124,6.59885 4.14966,7.13184 -3.17252,0 z"
style="fill:#808080"
id="path3417" />
<path
d="m 407.3111,615.42215 0,5.17757 -2.34767,0 0,-5.17757 -4.14966,0 0,-2.84258 4.14966,0 0,-5.17756 2.34767,0 0,5.17756 4.18774,0 0,2.84258 -4.18774,0 z"
style="fill:#808080"
id="path3419" />
<path
d="m 425.57217,623.36616 q -0.40609,1.20556 -0.82486,2.08118 -0.41877,0.87561 -0.92638,1.44667 -0.49491,0.58374 -1.12942,0.86292 -0.62181,0.27919 -1.45936,0.27919 -0.45684,0 -0.93907,-0.0381 -0.46953,-0.0381 -0.90099,-0.12691 l 0,-2.53802 q 0.20304,0.0381 0.49491,0.0634 0.30456,0.0381 0.5076,0.0381 0.43147,0 0.76141,-0.11422 0.34263,-0.11421 0.60912,-0.3807 0.2665,-0.26649 0.49492,-0.71064 0.24111,-0.44416 0.45684,-1.11673 l 0.19035,-0.60913 -4.45422,-13.5911 3.09638,0 1.76393,6.43388 q 0,0 0.1269,0.46954 l 0.20304,0.7614 0.22842,0.88831 0.21573,0.88831 q 0.10152,0.43146 0.16497,0.7614 0.0761,0.32995 0.11422,0.46954 0.0254,-0.13959 0.0888,-0.46954 l 0.16497,-0.73602 0.19035,-0.86293 0.20304,-0.87562 0.16497,-0.74871 q 0.0761,-0.34264 0.1269,-0.49492 l 1.66241,-6.48464 3.05831,0 -4.45422,14.45403 z"
style="fill:#808080"
id="path3421" />
</g>
</g>
<metadata
id="metadata499">
<rdf:RDF>
<cc:Work>
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" />
<dc:publisher>
<cc:Agent
rdf:about="http://openclipart.org/">
<dc:title>Openclipart</dc:title>
</cc:Agent>
</dc:publisher>
<dc:title>Pile of Golden Coins</dc:title>
<dc:date>2010-04-09T03:27:45</dc:date>
<dc:description>A pile of hypothetical golden coins, drawn in Inkscape.</dc:description>
<dc:source>https://openclipart.org/detail/43969/pile-of-golden-coins-by-j_alves</dc:source>
<dc:creator>
<cc:Agent>
<dc:title>J_Alves</dc:title>
</cc:Agent>
</dc:creator>
<dc:subject>
<rdf:Bag>
<rdf:li>coin</rdf:li>
<rdf:li>currency</rdf:li>
<rdf:li>gold</rdf:li>
<rdf:li>money</rdf:li>
<rdf:li>thaler</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/publicdomain/zero/1.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
</svg>

4
contract_variable_quantity/tests/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import test_contract_variable_quantity

60
contract_variable_quantity/tests/test_contract_variable_quantity.py

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
# © 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp.tests import common
from openerp import exceptions
class TestContractVariableQuantity(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(TestContractVariableQuantity, cls).setUpClass()
cls.partner = cls.env['res.partner'].create({
'name': 'Test partner',
})
cls.product = cls.env['product.product'].create({
'name': 'Test product',
})
cls.contract = cls.env['account.analytic.account'].create({
'name': 'Test Contract',
'partner_id': cls.partner.id,
'pricelist_id': cls.partner.property_product_pricelist.id,
'recurring_invoices': True,
})
cls.formula = cls.env['contract.line.qty.formula'].create({
'name': 'Test formula',
# For testing each of the possible variables
'code': 'env["res.users"]\n'
'context.get("lang")\n'
'user.id\n'
'line.qty_type\n'
'contract.id\n'
'invoice.id\n'
'result = 12',
})
cls.contract_line = cls.env['account.analytic.invoice.line'].create({
'analytic_account_id': cls.contract.id,
'product_id': cls.product.id,
'name': 'Test',
'qty_type': 'variable',
'qty_formula_id': cls.formula.id,
'quantity': 1,
'uom_id': cls.product.uom_id.id,
'price_unit': 100,
'discount': 50,
})
def test_check_invalid_code(self):
with self.assertRaises(exceptions.ValidationError):
self.formula.code = "sdsds"
def test_check_no_return_value(self):
with self.assertRaises(exceptions.ValidationError):
self.formula.code = "user.id"
def test_check_variable_quantity(self):
self.contract._create_invoice(self.contract)
invoice = self.env['account.invoice'].search(
[('contract_id', '=', self.contract.id)])
self.assertEqual(invoice.invoice_line_ids[0].quantity, 12)

90
contract_variable_quantity/views/contract_view.xml

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_analytic_account_recurring_form_form" model="ir.ui.view">
<field name="model">account.analytic.account</field>
<field name="inherit_id" ref="contract.account_analytic_account_recurring_form_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='recurring_invoice_line_ids']//field[@name='quantity']" position="before">
<field name="qty_type"/>
</xpath>
<xpath expr="//field[@name='recurring_invoice_line_ids']//field[@name='quantity']" position="after">
<field name="qty_formula_id"
attrs="{'required': [('qty_type', '=', 'variable')], 'invisible': [('qty_type', '!=', 'variable')]}"
/>
</xpath>
<xpath expr="//field[@name='recurring_invoice_line_ids']//field[@name='quantity']" position="attributes">
<attribute name="attrs">{'required': [('qty_type', '=', 'fixed')], 'invisible': [('qty_type', '!=', 'fixed')]}</attribute>
</xpath>
</field>
</record>
<record id="view_contract_line_qty_formula_tree" model="ir.ui.view">
<field name="model">contract.line.qty.formula</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
</tree>
</field>
</record>
<record id="view_contract_line_qty_formula_form" model="ir.ui.view">
<field name="model">contract.line.qty.formula</field>
<field name="arch" type="xml">
<form >
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="Name"/>
</h1>
</div>
<group string="Code">
<div style="margin-top: 4px;">
<field name="code" nolabel="1"/>
<h3>Help with Python expressions.</h3>
<p>You have to insert valid Python code block that stores at some moment a float/integer value of the quantity to invoice in the variable 'result'.</p>
<p>You can use these variables to compute your formula:</p>
<ul>
<li><i>env</i>: Environment variable for getting other models.</li>
<li><i>context</i>: Current context dictionary.</li>
<li><i>user</i>: Current user.</li>
<li><i>line</i>: Contract recurring invoice line that triggers this formula.</li>
<li><i>contract</i>: Contract whose line belongs to.</li>
<li><i>invoice</i>: Invoice (header) being created.</li>
</ul>
<div>
<p>Example of Python code</p>
<code>
result = env['product.product'].search_count([('sale_ok', '=', True)])
</code>
</div>
</div>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_contract_quantity_formula" model="ir.actions.act_window">
<field name="name">Formulas (quantity)</field>
<field name="res_model">contract.line.qty.formula</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new formula for variable quantities.
</p>
</field>
</record>
<menuitem id="contract.menu_config_contract"
name="Contracts"
sequence="5"
parent="base.menu_sale_config"
/>
<menuitem id="menu_contract_quantity_formula"
action="action_contract_quantity_formula"
parent="contract.menu_config_contract"
/>
</odoo>
Loading…
Cancel
Save