Daniel Reis
10 years ago
48 changed files with 1 additions and 3680 deletions
-
19.coveragerc
-
56.gitignore
-
44.travis.yml
-
2README.md
-
22__unported__/account_analytic_analysis_recurring/__init__.py
-
48__unported__/account_analytic_analysis_recurring/__openerp__.py
-
129__unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring.pot
-
209__unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring.py
-
16__unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring_cron.xml
-
44__unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring_view.xml
-
24__unported__/analytic_hours_block/__init__.py
-
59__unported__/analytic_hours_block/__openerp__.py
-
426__unported__/analytic_hours_block/hours_block.py
-
24__unported__/analytic_hours_block/hours_block_data.xml
-
25__unported__/analytic_hours_block/hours_block_menu.xml
-
172__unported__/analytic_hours_block/hours_block_view.xml
-
470__unported__/analytic_hours_block/i18n/analytic_hours_block.pot
-
38__unported__/analytic_hours_block/product.py
-
18__unported__/analytic_hours_block/product_view.xml
-
33__unported__/analytic_hours_block/project.py
-
19__unported__/analytic_hours_block/project_view.xml
-
12__unported__/analytic_hours_block/report.xml
-
21__unported__/analytic_hours_block/report/__init__.py
-
53__unported__/analytic_hours_block/report/hours_block.py
-
263__unported__/analytic_hours_block/report/hours_block.rml
-
11__unported__/analytic_hours_block/security/hours_block_security.xml
-
3__unported__/analytic_hours_block/security/ir.model.access.csv
-
5__unported__/project_sla/__init__.py
-
132__unported__/project_sla/__openerp__.py
-
69__unported__/project_sla/analytic_account.py
-
24__unported__/project_sla/analytic_account_view.xml
-
296__unported__/project_sla/i18n/project_sla.pot
-
BIN__unported__/project_sla/images/10_sla_contract.png
-
BIN__unported__/project_sla/images/20_sla_definition.png
-
BIN__unported__/project_sla/images/30_sla_controlled.png
-
75__unported__/project_sla/m2m.py
-
29__unported__/project_sla/project_issue.py
-
60__unported__/project_sla/project_issue_view.xml
-
86__unported__/project_sla/project_sla.py
-
322__unported__/project_sla/project_sla_control.py
-
18__unported__/project_sla/project_sla_control_data.xml
-
25__unported__/project_sla/project_sla_control_view.xml
-
138__unported__/project_sla/project_sla_demo.xml
-
48__unported__/project_sla/project_sla_view.xml
-
20__unported__/project_sla/project_view.xml
-
8__unported__/project_sla/security/ir.model.access.csv
-
BIN__unported__/project_sla/static/src/img/icon.png
-
66__unported__/project_sla/test/project_sla.yml
@ -1,19 +0,0 @@ |
|||||
# Config file .coveragerc |
|
||||
|
|
||||
[report] |
|
||||
omit = |
|
||||
/usr/* |
|
||||
*/bin/* |
|
||||
*/lib/* |
|
||||
*/odoo/* |
|
||||
*/openerp/* |
|
||||
*/tests/* |
|
||||
*__init__.py |
|
||||
|
|
||||
# Regexes for lines to exclude from consideration |
|
||||
exclude_lines = |
|
||||
# Have to re-enable the standard pragma |
|
||||
pragma: no cover |
|
||||
|
|
||||
# Don't complain about null context checking |
|
||||
if context is None: |
|
@ -1,56 +0,0 @@ |
|||||
# Byte-compiled / optimized / DLL files |
|
||||
__pycache__/ |
|
||||
*.py[cod] |
|
||||
|
|
||||
# C extensions |
|
||||
*.so |
|
||||
|
|
||||
# Distribution / packaging |
|
||||
.Python |
|
||||
env/ |
|
||||
bin/ |
|
||||
build/ |
|
||||
develop-eggs/ |
|
||||
dist/ |
|
||||
eggs/ |
|
||||
lib/ |
|
||||
lib64/ |
|
||||
parts/ |
|
||||
sdist/ |
|
||||
var/ |
|
||||
*.egg-info/ |
|
||||
.installed.cfg |
|
||||
*.egg |
|
||||
|
|
||||
# Installer logs |
|
||||
pip-log.txt |
|
||||
pip-delete-this-directory.txt |
|
||||
|
|
||||
# Unit test / coverage reports |
|
||||
htmlcov/ |
|
||||
.tox/ |
|
||||
.coverage |
|
||||
.cache |
|
||||
nosetests.xml |
|
||||
coverage.xml |
|
||||
|
|
||||
# Translations |
|
||||
*.mo |
|
||||
|
|
||||
# Pycharm |
|
||||
.idea |
|
||||
|
|
||||
# Mr Developer |
|
||||
.mr.developer.cfg |
|
||||
.project |
|
||||
.pydevproject |
|
||||
|
|
||||
# Rope |
|
||||
.ropeproject |
|
||||
|
|
||||
# Sphinx documentation |
|
||||
docs/_build/ |
|
||||
|
|
||||
# Backup files |
|
||||
*~ |
|
||||
*.swp |
|
@ -1,44 +0,0 @@ |
|||||
# Config file .travis.yml |
|
||||
|
|
||||
language: python |
|
||||
|
|
||||
python: |
|
||||
# - "pypy" # not supported by odoo 8 |
|
||||
# - "3.4" # not supported by odoo 8 |
|
||||
# - "3.3" # not supported by odoo 8 |
|
||||
- "2.7" |
|
||||
# - "2.6" # not supported by odoo 8 |
|
||||
|
|
||||
env: |
|
||||
- ODOO="https://github.com/savoirfairelinux/odoo/archive/setuptools-addons.tar.gz" # Temp until https://github.com/odoo/odoo/issues/185 or https://github.com/odoo/odoo/issues/441 is fixed |
|
||||
# - ODOO="https://github.com/odoo/odoo/archive/master.tar.gz" |
|
||||
# - ODOO="https://github.com/OCA/OCB/archive/master.zip" |
|
||||
|
|
||||
# Need coveralls for coverage reports |
|
||||
# Need flake8 for pep8 testing |
|
||||
# Manually get PyChart |
|
||||
# Install tested version of odoo (official or ocb) |
|
||||
# Get modules from other repos which have dependencies (in this case travel requires modules from lp:partner-contact-management and lp:openerp-hr |
|
||||
install: |
|
||||
- pip install coveralls flake8 |
|
||||
- pip install http://download.gna.org/pychart/PyChart-1.39.tar.gz |
|
||||
- pip install ${ODOO} |
|
||||
|
|
||||
# Create databae |
|
||||
# Pre-install modules and dependencies |
|
||||
before_script: |
|
||||
- createdb test |
|
||||
|
|
||||
# Test with flake, ignore F401 for __init__.py files, use a max length of 120 |
|
||||
# Run tests with coverage |
|
||||
# Only test modules in repo (list populated by directories in repo) |
|
||||
# Preload modules before testing to only run tests of repo's modules |
|
||||
# Include current directory and dependent repos in addons-path as well as official addons |
|
||||
script: |
|
||||
- flake8 . --max-line-length=120 --exclude=__unported__ --filename=__init__.py --ignore=F401 |
|
||||
- flake8 . --max-line-length=120 --exclude=__unported__,__init__.py |
|
||||
- odoo.py -d test --stop-after-init --init=$(python -c 'import os; print(",".join(x for x in os.listdir(".") if os.path.isdir(x) and not x.startswith(".") and x != "__unported__"))') --addons-path=$(pwd),`python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"`/addons |
|
||||
- coverage run $(which odoo.py) -d test --test-enable --log-level=test --stop-after-init --init=$(python -c 'import os; print(",".join(x for x in os.listdir(".") if os.path.isdir(x) and not x.startswith(".") and x != "__unported__"))') --addons-path=$(pwd),`python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"`/addons |
|
||||
|
|
||||
after_success: |
|
||||
coveralls |
|
@ -1,3 +1,3 @@ |
|||||
**IMPORTANT** |
|
||||
|
# Warning: DEPRECATED! # |
||||
|
|
||||
These modules are now available from the [Project & Service Management](https://github.com/OCA/project-service) repository. |
These modules are now available from the [Project & Service Management](https://github.com/OCA/project-service) repository. |
@ -1,22 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# OpenERP, Open Source Management Solution |
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>) |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
import account_analytic_analysis_recurring |
|
@ -1,48 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# OpenERP, Open Source Management Solution |
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
|
|
||||
{ |
|
||||
'name': 'Contracts Management recurring', |
|
||||
'version': '0.1', |
|
||||
'category': 'Other', |
|
||||
'description': """ |
|
||||
This module add a new feature in contracts to manage recurring invoice |
|
||||
======================================================================================= |
|
||||
|
|
||||
This is a backport of the new V8 feature available in trunk and saas. With the V8 release this module will be deprecated. |
|
||||
It also add a little feature, you can use #START# and #END# in the contract line to automatically insert the dates of the invoiced period. |
|
||||
|
|
||||
Backport done By Yannick Buron. |
|
||||
""", |
|
||||
'author': 'OpenERP SA', |
|
||||
'website': 'http://openerp.com', |
|
||||
'depends': ['base', 'account_analytic_analysis'], |
|
||||
'data': [ |
|
||||
'account_analytic_analysis_recurring_cron.xml', |
|
||||
'account_analytic_analysis_recurring_view.xml', |
|
||||
], |
|
||||
'demo': [''], |
|
||||
'test':[], |
|
||||
'installable': False, |
|
||||
'images': [], |
|
||||
} |
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
|
@ -1,129 +0,0 @@ |
|||||
# Translation of OpenERP Server. |
|
||||
# This file contains the translation of the following modules: |
|
||||
# * account_analytic_analysis_recurring |
|
||||
# |
|
||||
msgid "" |
|
||||
msgstr "" |
|
||||
"Project-Id-Version: OpenERP Server 7.0\n" |
|
||||
"Report-Msgid-Bugs-To: \n" |
|
||||
"POT-Creation-Date: 2014-02-21 11:41+0000\n" |
|
||||
"PO-Revision-Date: 2014-02-21 11:41+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: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.invoice.line,price_subtotal:0 |
|
||||
msgid "Sub Total" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.account,recurring_rule_type:0 |
|
||||
msgid "Recurrency" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.invoice.line,price_unit:0 |
|
||||
msgid "Unit Price" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: view:account.analytic.account:0 |
|
||||
msgid ". create invoices" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: view:account.analytic.account:0 |
|
||||
msgid "Account Analytic Lines" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.account,recurring_invoice_line_ids:0 |
|
||||
msgid "Invoice Lines" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.invoice.line,uom_id:0 |
|
||||
msgid "Unit of Measure" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: selection:account.analytic.account,recurring_rule_type:0 |
|
||||
msgid "Day(s)" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: help:account.analytic.account,recurring_rule_type:0 |
|
||||
msgid "Invoice automatically repeat at specified interval" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.invoice.line,product_id:0 |
|
||||
msgid "Product" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.invoice.line,name:0 |
|
||||
msgid "Description" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.account,recurring_interval:0 |
|
||||
msgid "Repeat Every" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: view:account.analytic.account:0 |
|
||||
msgid "Recurring Invoices" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.account,recurring_invoices:0 |
|
||||
msgid "Generate recurring invoices automatically" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: selection:account.analytic.account,recurring_rule_type:0 |
|
||||
msgid "Year(s)" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: selection:account.analytic.account,recurring_rule_type:0 |
|
||||
msgid "Week(s)" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.invoice.line,quantity:0 |
|
||||
msgid "Quantity" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: model:ir.model,name:account_analytic_analysis_recurring.model_account_analytic_invoice_line |
|
||||
msgid "account.analytic.invoice.line" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.account,recurring_next_date:0 |
|
||||
msgid "Date of Next Invoice" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: field:account.analytic.invoice.line,analytic_account_id:0 |
|
||||
#: model:ir.model,name:account_analytic_analysis_recurring.model_account_analytic_account |
|
||||
msgid "Analytic Account" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: selection:account.analytic.account,recurring_rule_type:0 |
|
||||
msgid "Month(s)" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: account_analytic_analysis_recurring |
|
||||
#: help:account.analytic.account,recurring_interval:0 |
|
||||
msgid "Repeat every (Days/Week/Month/Year)" |
|
||||
msgstr "" |
|
||||
|
|
||||
|
|
@ -1,209 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# OpenERP, Open Source Management Solution |
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
from dateutil.relativedelta import relativedelta |
|
||||
import datetime |
|
||||
import logging |
|
||||
import time |
|
||||
|
|
||||
from openerp.osv import osv, fields |
|
||||
from openerp.osv.orm import intersect, except_orm |
|
||||
import openerp.tools |
|
||||
from openerp.tools.translate import _ |
|
||||
|
|
||||
from openerp.addons.decimal_precision import decimal_precision as dp |
|
||||
|
|
||||
_logger = logging.getLogger(__name__) |
|
||||
|
|
||||
class account_analytic_invoice_line(osv.osv): |
|
||||
_name = "account.analytic.invoice.line" |
|
||||
|
|
||||
def _amount_line(self, cr, uid, ids, prop, unknow_none, unknow_dict, context=None): |
|
||||
res = {} |
|
||||
for line in self.browse(cr, uid, ids, context=context): |
|
||||
res[line.id] = line.quantity * line.price_unit |
|
||||
if line.analytic_account_id.pricelist_id: |
|
||||
cur = line.analytic_account_id.pricelist_id.currency_id |
|
||||
res[line.id] = self.pool.get('res.currency').round(cr, uid, cur, res[line.id]) |
|
||||
return res |
|
||||
|
|
||||
_columns = { |
|
||||
'product_id': fields.many2one('product.product','Product',required=True), |
|
||||
'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account'), |
|
||||
'name': fields.text('Description', required=True), |
|
||||
'quantity': fields.float('Quantity', required=True), |
|
||||
'uom_id': fields.many2one('product.uom', 'Unit of Measure',required=True), |
|
||||
'price_unit': fields.float('Unit Price', required=True), |
|
||||
'price_subtotal': fields.function(_amount_line, string='Sub Total', type="float",digits_compute= dp.get_precision('Account')), |
|
||||
} |
|
||||
_defaults = { |
|
||||
'quantity' : 1, |
|
||||
} |
|
||||
|
|
||||
def product_id_change(self, cr, uid, ids, product, uom_id, qty=0, name='', partner_id=False, price_unit=False, pricelist_id=False, company_id=None, context=None): |
|
||||
context = context or {} |
|
||||
uom_obj = self.pool.get('product.uom') |
|
||||
company_id = company_id or False |
|
||||
context.update({'company_id': company_id, 'force_company': company_id, 'pricelist_id': pricelist_id}) |
|
||||
|
|
||||
if not product: |
|
||||
return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}} |
|
||||
if partner_id: |
|
||||
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) |
|
||||
if part.lang: |
|
||||
context.update({'lang': part.lang}) |
|
||||
|
|
||||
result = {} |
|
||||
res = self.pool.get('product.product').browse(cr, uid, product, context=context) |
|
||||
result.update({'name':res.partner_ref or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': res.list_price or 0.0}) |
|
||||
if res.description: |
|
||||
result['name'] += '\n'+res.description |
|
||||
|
|
||||
res_final = {'value':result} |
|
||||
if result['uom_id'] != res.uom_id.id: |
|
||||
selected_uom = uom_obj.browse(cr, uid, result['uom_id'], context=context) |
|
||||
new_price = uom_obj._compute_price(cr, uid, res.uom_id.id, res_final['value']['price_unit'], result['uom_id']) |
|
||||
res_final['value']['price_unit'] = new_price |
|
||||
return res_final |
|
||||
|
|
||||
|
|
||||
class account_analytic_account(osv.osv): |
|
||||
_name = "account.analytic.account" |
|
||||
_inherit = "account.analytic.account" |
|
||||
|
|
||||
_columns = { |
|
||||
'recurring_invoice_line_ids': fields.one2many('account.analytic.invoice.line', 'analytic_account_id', 'Invoice Lines'), |
|
||||
'recurring_invoices' : fields.boolean('Generate recurring invoices automatically'), |
|
||||
'recurring_rule_type': fields.selection([ |
|
||||
('daily', 'Day(s)'), |
|
||||
('weekly', 'Week(s)'), |
|
||||
('monthly', 'Month(s)'), |
|
||||
('yearly', 'Year(s)'), |
|
||||
], 'Recurrency', help="Invoice automatically repeat at specified interval"), |
|
||||
'recurring_interval': fields.integer('Repeat Every', help="Repeat every (Days/Week/Month/Year)"), |
|
||||
'recurring_next_date': fields.date('Date of Next Invoice'), |
|
||||
} |
|
||||
|
|
||||
_defaults = { |
|
||||
'recurring_interval': 1, |
|
||||
'recurring_next_date': lambda *a: time.strftime('%Y-%m-%d'), |
|
||||
'recurring_rule_type':'monthly' |
|
||||
} |
|
||||
|
|
||||
def onchange_recurring_invoices(self, cr, uid, ids, recurring_invoices, date_start=False, context=None): |
|
||||
value = {} |
|
||||
if date_start and recurring_invoices: |
|
||||
value = {'value': {'recurring_next_date': date_start}} |
|
||||
return value |
|
||||
|
|
||||
def _prepare_invoice(self, cr, uid, contract, context=None): |
|
||||
context = context or {} |
|
||||
|
|
||||
inv_obj = self.pool.get('account.invoice') |
|
||||
journal_obj = self.pool.get('account.journal') |
|
||||
fpos_obj = self.pool.get('account.fiscal.position') |
|
||||
lang_obj = self.pool.get('res.lang') |
|
||||
|
|
||||
if not contract.partner_id: |
|
||||
raise osv.except_osv(_('No Customer Defined!'),_("You must first select a Customer for Contract %s!") % contract.name ) |
|
||||
|
|
||||
fpos = contract.partner_id.property_account_position or False |
|
||||
journal_ids = journal_obj.search(cr, uid, [('type', '=','sale'),('company_id', '=', contract.company_id.id or False)], limit=1) |
|
||||
if not journal_ids: |
|
||||
raise osv.except_osv(_('Error!'), |
|
||||
_('Please define a sale journal for the company "%s".') % (contract.company_id.name or '', )) |
|
||||
|
|
||||
partner_payment_term = contract.partner_id.property_payment_term and contract.partner_id.property_payment_term.id or False |
|
||||
|
|
||||
|
|
||||
inv_data = { |
|
||||
'reference': contract.code or False, |
|
||||
'account_id': contract.partner_id.property_account_receivable.id, |
|
||||
'type': 'out_invoice', |
|
||||
'partner_id': contract.partner_id.id, |
|
||||
'currency_id': contract.partner_id.property_product_pricelist.id or False, |
|
||||
'journal_id': len(journal_ids) and journal_ids[0] or False, |
|
||||
'date_invoice': contract.recurring_next_date, |
|
||||
'origin': contract.name, |
|
||||
'fiscal_position': fpos and fpos.id, |
|
||||
'payment_term': partner_payment_term, |
|
||||
'company_id': contract.company_id.id or False, |
|
||||
} |
|
||||
invoice_id = inv_obj.create(cr, uid, inv_data, context=context) |
|
||||
|
|
||||
for line in contract.recurring_invoice_line_ids: |
|
||||
|
|
||||
res = line.product_id |
|
||||
account_id = res.property_account_income.id |
|
||||
if not account_id: |
|
||||
account_id = res.categ_id.property_account_income_categ.id |
|
||||
account_id = fpos_obj.map_account(cr, uid, fpos, account_id) |
|
||||
|
|
||||
taxes = res.taxes_id or False |
|
||||
tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes) |
|
||||
|
|
||||
if 'old_date' in context: |
|
||||
lang_ids = lang_obj.search(cr, uid, [('code', '=', contract.partner_id.lang)], context=context) |
|
||||
format = lang_obj.browse(cr, uid, lang_ids, context=context)[0].date_format |
|
||||
line.name = line.name.replace('#START#', context['old_date'].strftime(format)) |
|
||||
line.name = line.name.replace('#END#', context['next_date'].strftime(format)) |
|
||||
|
|
||||
invoice_line_vals = { |
|
||||
'name': line.name, |
|
||||
'account_id': account_id, |
|
||||
'account_analytic_id': contract.id, |
|
||||
'price_unit': line.price_unit or 0.0, |
|
||||
'quantity': line.quantity, |
|
||||
'uos_id': line.uom_id.id or False, |
|
||||
'product_id': line.product_id.id or False, |
|
||||
'invoice_id' : invoice_id, |
|
||||
'invoice_line_tax_id': [(6, 0, tax_id)], |
|
||||
} |
|
||||
self.pool.get('account.invoice.line').create(cr, uid, invoice_line_vals, context=context) |
|
||||
|
|
||||
inv_obj.button_compute(cr, uid, [invoice_id], context=context) |
|
||||
return invoice_id |
|
||||
|
|
||||
def recurring_create_invoice(self, cr, uid, automatic=False, context=None): |
|
||||
context = context or {} |
|
||||
current_date = time.strftime('%Y-%m-%d') |
|
||||
|
|
||||
contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True)]) |
|
||||
for contract in self.browse(cr, uid, contract_ids, context=context): |
|
||||
|
|
||||
next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d") |
|
||||
interval = contract.recurring_interval |
|
||||
if contract.recurring_rule_type == 'daily': |
|
||||
old_date = next_date-relativedelta(days=+interval) |
|
||||
new_date = next_date+relativedelta(days=+interval) |
|
||||
elif contract.recurring_rule_type == 'weekly': |
|
||||
old_date = next_date-relativedelta(weeks=+interval) |
|
||||
new_date = next_date+relativedelta(weeks=+interval) |
|
||||
else: |
|
||||
old_date = next_date+relativedelta(months=+interval) |
|
||||
new_date = next_date+relativedelta(months=+interval) |
|
||||
|
|
||||
context['old_date'] = old_date |
|
||||
context['next_date'] = datetime.datetime.strptime(contract.recurring_next_date or current_date,"%Y-%m-%d") |
|
||||
invoice_id = self._prepare_invoice(cr, uid, contract, context=context) |
|
||||
|
|
||||
self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context) |
|
||||
return True |
|
||||
|
|
@ -1,16 +0,0 @@ |
|||||
<?xml version="1.0" encoding='UTF-8'?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<record model="ir.cron" id="account_analytic_cron_for_invoice"> |
|
||||
<field name="name">Generate Recurring Invoices from Contracts</field> |
|
||||
<field name="interval_number">1</field> |
|
||||
<field name="interval_type">days</field> |
|
||||
<field name="numbercall">-1</field> |
|
||||
<field name="model" eval="'account.analytic.account'"/> |
|
||||
<field name="function" eval="'recurring_create_invoice'"/> |
|
||||
<field name="args" eval="'()'"/> |
|
||||
</record> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,44 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<record id="account_analytic_account_recurring_form_form" model="ir.ui.view"> |
|
||||
<field name="name">account.analytic.account.invoice.recurring.form.inherit</field> |
|
||||
<field name="model">account.analytic.account</field> |
|
||||
<field name="inherit_id" ref="account_analytic_analysis.account_analytic_account_form_form"/> |
|
||||
<field eval="40" name="priority"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<group name='invoice_on_timesheets' position='after'> |
|
||||
<separator string="Recurring Invoices" attrs="{'invisible': [('recurring_invoices','!=',True)]}"/> |
|
||||
<div> |
|
||||
<field name="recurring_invoices" on_change="onchange_recurring_invoices(recurring_invoices, date_start)" class="oe_inline"/> |
|
||||
<label for="recurring_invoices" /> |
|
||||
<button class="oe_link" name="recurring_create_invoice" attrs="{'invisible': [('recurring_invoices','!=',True)]}" string=". create invoices" type="object" groups="base.group_no_one"/> |
|
||||
</div> |
|
||||
<group attrs="{'invisible': [('recurring_invoices','!=',True)]}"> |
|
||||
<label for="recurring_interval"/> |
|
||||
<div> |
|
||||
<field name="recurring_interval" class="oe_inline" attrs="{'required': [('recurring_invoices', '=', True)]}"/> |
|
||||
<field name="recurring_rule_type" class="oe_inline" attrs="{'required': [('recurring_invoices', '=', True)]}"/> |
|
||||
</div> |
|
||||
<field name="recurring_next_date"/> |
|
||||
</group> |
|
||||
<label for="recurring_invoice_line_ids" attrs="{'invisible': [('recurring_invoices','=',False)]}"/> |
|
||||
<div attrs="{'invisible': [('recurring_invoices','=',False)]}"> |
|
||||
<field name="recurring_invoice_line_ids"> |
|
||||
<tree string="Account Analytic Lines" editable="bottom"> |
|
||||
<field name="product_id" on_change="product_id_change(product_id, uom_id, quantity, name, parent.partner_id, price_unit, parent.pricelist_id, parent.company_id)"/> |
|
||||
<field name="name"/> |
|
||||
<field name="quantity"/> |
|
||||
<field name="uom_id"/> |
|
||||
<field name="price_unit"/> |
|
||||
<field name="price_subtotal"/> |
|
||||
</tree> |
|
||||
</field> |
|
||||
</div> |
|
||||
</group> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,24 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Author: Vincent Renaville, ported by Joel Grand-Guillaume |
|
||||
# Copyright 2010-2012 Camptocamp SA |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
import hours_block |
|
||||
import report |
|
||||
import product |
|
||||
import project |
|
@ -1,59 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Author: Vincent Renaville, ported by Joel Grand-Guillaume |
|
||||
# Copyright 2010-2012 Camptocamp SA |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
{ |
|
||||
"name": "Project Hours Blocks Management", |
|
||||
"version": "1.5", |
|
||||
"category": "Generic Modules/Projects & Services", |
|
||||
"description": """ |
|
||||
Project Hours Blocks Management |
|
||||
=============================== |
|
||||
|
|
||||
This module allows you to handle hours blocks, |
|
||||
to follow for example the user support contracts. |
|
||||
This means, you sell a product of type "hours block" |
|
||||
then you input the spent hours on the hours block and |
|
||||
you can track and follow how much has been used. |
|
||||
|
|
||||
""", |
|
||||
"author": "Camptocamp", |
|
||||
"license": 'AGPL-3', |
|
||||
"website": "http://www.camptocamp.com", |
|
||||
"depends": [ |
|
||||
"account", |
|
||||
"hr_timesheet_invoice", |
|
||||
"analytic", |
|
||||
"project", |
|
||||
], |
|
||||
"data": [ |
|
||||
"report.xml", |
|
||||
"hours_block_view.xml", |
|
||||
"hours_block_data.xml", |
|
||||
"hours_block_menu.xml", |
|
||||
"product_view.xml", |
|
||||
"project_view.xml", |
|
||||
"report.xml", |
|
||||
"security/hours_block_security.xml", |
|
||||
"security/ir.model.access.csv", |
|
||||
], |
|
||||
"active": False, |
|
||||
"installable": False |
|
||||
} |
|
@ -1,426 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Author: Vincent Renaville, ported by Joel Grand-Guillaume |
|
||||
# Copyright 2010-2012 Camptocamp SA |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
from openerp.osv import orm, fields |
|
||||
|
|
||||
|
|
||||
class AccountHoursBlock(orm.Model): |
|
||||
_name = "account.hours.block" |
|
||||
_inherit = ['mail.thread'] |
|
||||
|
|
||||
def _get_last_action(self, cr, uid, ids, name, arg, context=None): |
|
||||
""" Return the last analytic line date for an invoice""" |
|
||||
res = {} |
|
||||
for block in self.browse(cr, uid, ids, context=context): |
|
||||
cr.execute("SELECT max(al.date) FROM account_analytic_line AS al" |
|
||||
" WHERE al.invoice_id = %s", (block.invoice_id.id,)) |
|
||||
fetch_res = cr.fetchone() |
|
||||
res[block.id] = fetch_res[0] if fetch_res else False |
|
||||
return res |
|
||||
|
|
||||
def _compute_hours(self, cr, uid, ids, fields, args, context=None): |
|
||||
"""Return a dict of [id][fields]""" |
|
||||
if isinstance(ids, (int, long)): |
|
||||
ids = [ids] |
|
||||
result = {} |
|
||||
aal_obj = self.pool.get('account.analytic.line') |
|
||||
for block in self.browse(cr, uid, ids, context=context): |
|
||||
result[block.id] = {'amount_hours_block': 0.0, |
|
||||
'amount_hours_block_done': 0.0} |
|
||||
# Compute hours bought |
|
||||
for line in block.invoice_id.invoice_line: |
|
||||
hours_bought = 0.0 |
|
||||
if line.product_id and line.product_id.is_in_hours_block: |
|
||||
# We will now calculate the product_quantity |
|
||||
factor = line.uos_id.factor |
|
||||
if factor == 0.0: |
|
||||
factor = 1.0 |
|
||||
amount = line.quantity |
|
||||
hours_bought += (amount / factor) |
|
||||
result[block.id]['amount_hours_block'] += hours_bought |
|
||||
|
|
||||
# Compute hours spent |
|
||||
hours_used = 0.0 |
|
||||
# Get ids of analytic line generated from |
|
||||
# timesheet associated to the current block |
|
||||
cr.execute("SELECT al.id " |
|
||||
"FROM account_analytic_line AS al, " |
|
||||
" account_analytic_journal AS aj " |
|
||||
"WHERE aj.id = al.journal_id " |
|
||||
"AND aj.type = 'general' " |
|
||||
"AND al.invoice_id = %s", (block.invoice_id.id,)) |
|
||||
res_line_ids = cr.fetchall() |
|
||||
line_ids = [l[0] for l in res_line_ids] if res_line_ids else [] |
|
||||
for line in aal_obj.browse(cr, uid, line_ids, context=context): |
|
||||
factor = 1.0 |
|
||||
if line.product_uom_id and line.product_uom_id.factor != 0.0: |
|
||||
factor = line.product_uom_id.factor |
|
||||
factor_invoicing = 1.0 |
|
||||
if line.to_invoice and line.to_invoice.factor != 0.0: |
|
||||
factor_invoicing = 1.0 - line.to_invoice.factor / 100 |
|
||||
hours_used += ((line.unit_amount / factor) * factor_invoicing) |
|
||||
result[block.id]['amount_hours_block_done'] = hours_used |
|
||||
return result |
|
||||
|
|
||||
def _compute_amount(self, cr, uid, ids, fields, args, context=None): |
|
||||
if context is None: |
|
||||
context = {} |
|
||||
result = {} |
|
||||
aal_obj = self.pool.get('account.analytic.line') |
|
||||
pricelist_obj = self.pool.get('product.pricelist') |
|
||||
for block in self.browse(cr, uid, ids, context=context): |
|
||||
result[block.id] = {'amount_hours_block': 0.0, |
|
||||
'amount_hours_block_done': 0.0} |
|
||||
|
|
||||
# Compute amount bought |
|
||||
for line in block.invoice_id.invoice_line: |
|
||||
amount_bought = 0.0 |
|
||||
if line.product_id: |
|
||||
## We will now calculate the product_quantity |
|
||||
factor = line.uos_id.factor |
|
||||
if factor == 0.0: |
|
||||
factor = 1.0 |
|
||||
amount = line.quantity * line.price_unit |
|
||||
amount_bought += (amount / factor) |
|
||||
result[block.id]['amount_hours_block'] += amount_bought |
|
||||
|
|
||||
# Compute total amount |
|
||||
# Get ids of analytic line generated from timesheet associated to current block |
|
||||
cr.execute("SELECT al.id FROM account_analytic_line AS al," |
|
||||
" account_analytic_journal AS aj" |
|
||||
" WHERE aj.id = al.journal_id" |
|
||||
" AND aj.type='general'" |
|
||||
" AND al.invoice_id = %s", (block.invoice_id.id,)) |
|
||||
res_line_ids = cr.fetchall() |
|
||||
line_ids = [l[0] for l in res_line_ids] if res_line_ids else [] |
|
||||
total_amount = 0.0 |
|
||||
for line in aal_obj.browse(cr, uid, line_ids, context=context): |
|
||||
factor_invoicing = 1.0 |
|
||||
if line.to_invoice and line.to_invoice.factor != 0.0: |
|
||||
factor_invoicing = 1.0 - line.to_invoice.factor / 100 |
|
||||
|
|
||||
ctx = dict(context, uom=line.product_uom_id.id) |
|
||||
amount = pricelist_obj.price_get( |
|
||||
cr, uid, |
|
||||
[line.account_id.pricelist_id.id], |
|
||||
line.product_id.id, |
|
||||
line.unit_amount or 1.0, |
|
||||
line.account_id.partner_id.id or False, |
|
||||
ctx)[line.account_id.pricelist_id.id] |
|
||||
total_amount += amount * line.unit_amount * factor_invoicing |
|
||||
result[block.id]['amount_hours_block_done'] += total_amount |
|
||||
|
|
||||
return result |
|
||||
|
|
||||
def _compute(self, cr, uid, ids, fields, args, context=None): |
|
||||
result = {} |
|
||||
block_per_types = {} |
|
||||
for block in self.browse(cr, uid, ids, context=context): |
|
||||
block_per_types.setdefault(block.type, []).append(block.id) |
|
||||
|
|
||||
for block_type in block_per_types: |
|
||||
if block_type: |
|
||||
func = getattr(self, "_compute_%s" % block_type) |
|
||||
result.update(func(cr, uid, ids, fields, args, context=context)) |
|
||||
|
|
||||
for block in result: |
|
||||
result[block]['amount_hours_block_delta'] = \ |
|
||||
result[block]['amount_hours_block'] - \ |
|
||||
result[block]['amount_hours_block_done'] |
|
||||
return result |
|
||||
|
|
||||
def _get_analytic_line(self, cr, uid, ids, context=None): |
|
||||
invoice_ids = [] |
|
||||
an_lines_obj = self.pool.get('account.analytic.line') |
|
||||
block_obj = self.pool.get('account.hours.block') |
|
||||
for line in an_lines_obj.browse(cr, uid, ids, context=context): |
|
||||
if line.invoice_id: |
|
||||
invoice_ids.append(line.invoice_id.id) |
|
||||
return block_obj.search( |
|
||||
cr, uid, [('invoice_id', 'in', invoice_ids)], context=context) |
|
||||
|
|
||||
def _get_invoice(self, cr, uid, ids, context=None): |
|
||||
block_ids = set() |
|
||||
inv_obj = self.pool.get('account.invoice') |
|
||||
for invoice in inv_obj.browse(cr, uid, ids, context=context): |
|
||||
block_ids.update([inv.id for inv in invoice.account_hours_block_ids]) |
|
||||
return list(block_ids) |
|
||||
|
|
||||
def action_send_block(self, cr, uid, ids, context=None): |
|
||||
"""Open a form to send by email. Return an action dict.""" |
|
||||
|
|
||||
assert len(ids) == 1, '''\ |
|
||||
This option should only be used for a single ID at a time.''' |
|
||||
|
|
||||
ir_model_data = self.pool.get('ir.model.data') |
|
||||
|
|
||||
try: |
|
||||
template_id = ir_model_data.get_object_reference( |
|
||||
cr, uid, 'analytic_hours_block', 'email_template_hours_block' |
|
||||
)[1] |
|
||||
except ValueError: |
|
||||
template_id = False |
|
||||
|
|
||||
try: |
|
||||
compose_form_id = ir_model_data.get_object_reference( |
|
||||
cr, uid, 'mail', 'email_compose_message_wizard_form' |
|
||||
)[1] |
|
||||
except ValueError: |
|
||||
compose_form_id = False |
|
||||
|
|
||||
ctx = { |
|
||||
'default_model': self._name, |
|
||||
'default_res_id': ids[0], |
|
||||
'default_use_template': bool(template_id), |
|
||||
'default_template_id': template_id, |
|
||||
'default_composition_mode': 'comment', |
|
||||
} |
|
||||
return { |
|
||||
'type': 'ir.actions.act_window', |
|
||||
'view_type': 'form', |
|
||||
'view_mode': 'form', |
|
||||
'res_model': 'mail.compose.message', |
|
||||
'views': [(compose_form_id, 'form')], |
|
||||
'view_id': compose_form_id, |
|
||||
'target': 'new', |
|
||||
'context': ctx, |
|
||||
} |
|
||||
|
|
||||
_recompute_triggers = { |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: |
|
||||
ids, ['invoice_id', 'type'], 10), |
|
||||
'account.invoice': (_get_invoice, ['analytic_line_ids'], 10), |
|
||||
'account.analytic.line': ( |
|
||||
_get_analytic_line, |
|
||||
['product_uom_id', 'unit_amount', 'to_invoice', 'invoice_id'], |
|
||||
10), |
|
||||
} |
|
||||
|
|
||||
_columns = { |
|
||||
'amount_hours_block': fields.function( |
|
||||
_compute, |
|
||||
type='float', |
|
||||
string='Quantity / Amount bought', |
|
||||
store=_recompute_triggers, |
|
||||
multi='amount_hours_block_delta', |
|
||||
help="Amount bought by the customer. " |
|
||||
"This amount is expressed in the base Unit of Measure " |
|
||||
"(factor=1.0)"), |
|
||||
'amount_hours_block_done': fields.function( |
|
||||
_compute, |
|
||||
type='float', |
|
||||
string='Quantity / Amount used', |
|
||||
store=_recompute_triggers, |
|
||||
multi='amount_hours_block_delta', |
|
||||
help="Amount done by the staff. " |
|
||||
"This amount is expressed in the base Unit of Measure " |
|
||||
"(factor=1.0)"), |
|
||||
'amount_hours_block_delta': fields.function( |
|
||||
_compute, |
|
||||
type='float', |
|
||||
string='Difference', |
|
||||
store=_recompute_triggers, |
|
||||
multi='amount_hours_block_delta', |
|
||||
help="Difference between bought and used. " |
|
||||
"This amount is expressed in the base Unit of Measure " |
|
||||
"(factor=1.0)"), |
|
||||
'last_action_date': fields.function( |
|
||||
_get_last_action, |
|
||||
type='date', |
|
||||
string='Last action date', |
|
||||
help="Date of the last analytic line linked to the invoice " |
|
||||
"related to this block hours."), |
|
||||
'close_date': fields.date('Closed Date'), |
|
||||
'invoice_id': fields.many2one( |
|
||||
'account.invoice', |
|
||||
'Invoice', |
|
||||
ondelete='cascade', |
|
||||
required=True), |
|
||||
'type': fields.selection( |
|
||||
[('hours', 'Hours'), |
|
||||
('amount', 'Amount')], |
|
||||
string='Type of Block', |
|
||||
required=True, |
|
||||
help="The block is based on the quantity of hours " |
|
||||
"or on the amount."), |
|
||||
|
|
||||
# Invoices related infos |
|
||||
'date_invoice': fields.related( |
|
||||
'invoice_id', 'date_invoice', |
|
||||
type="date", |
|
||||
string="Invoice Date", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['date_invoice'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'user_id': fields.related( |
|
||||
'invoice_id', 'user_id', |
|
||||
type="many2one", |
|
||||
relation="res.users", |
|
||||
string="Salesman", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['user_id'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'partner_id': fields.related( |
|
||||
'invoice_id', 'partner_id', |
|
||||
type="many2one", |
|
||||
relation="res.partner", |
|
||||
string="Partner", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['partner_id'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'name': fields.related( |
|
||||
'invoice_id', 'name', |
|
||||
type="char", |
|
||||
string="Description", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['name'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'number': fields.related( |
|
||||
'invoice_id', 'number', |
|
||||
type="char", |
|
||||
string="Number", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['number'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'journal_id': fields.related( |
|
||||
'invoice_id', 'journal_id', |
|
||||
type="many2one", |
|
||||
relation="account.journal", |
|
||||
string="Journal", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['journal_id'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'period_id': fields.related( |
|
||||
'invoice_id', 'period_id', |
|
||||
type="many2one", |
|
||||
relation="account.period", |
|
||||
string="Period", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['period_id'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'company_id': fields.related( |
|
||||
'invoice_id', 'company_id', |
|
||||
type="many2one", |
|
||||
relation="res.company", |
|
||||
string="Company", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['company_id'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'currency_id': fields.related( |
|
||||
'invoice_id', 'currency_id', |
|
||||
type="many2one", |
|
||||
relation="res.currency", |
|
||||
string="Currency", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['currency_id'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'residual': fields.related( |
|
||||
'invoice_id', 'residual', |
|
||||
type="float", |
|
||||
string="Residual", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['residual'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'amount_total': fields.related( |
|
||||
'invoice_id', 'amount_total', |
|
||||
type="float", |
|
||||
string="Total", |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['amount_total'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
'department_id': fields.related( |
|
||||
'invoice_id', 'department_id', |
|
||||
type='many2one', |
|
||||
relation='hr.department', |
|
||||
string='Department', |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['department_id'], 10), |
|
||||
}, |
|
||||
readonly=True), |
|
||||
|
|
||||
'state': fields.related( |
|
||||
'invoice_id', 'state', |
|
||||
type='selection', |
|
||||
selection=[ |
|
||||
('draft', 'Draft'), |
|
||||
('proforma', 'Pro-forma'), |
|
||||
('proforma2', 'Pro-forma'), |
|
||||
('open', 'Open'), |
|
||||
('paid', 'Paid'), |
|
||||
('cancel', 'Cancelled'), |
|
||||
], |
|
||||
string='State', |
|
||||
readonly=True, |
|
||||
store={ |
|
||||
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids, |
|
||||
['invoice_id'], 10), |
|
||||
'account.invoice': (_get_invoice, ['state'], 10), |
|
||||
}), |
|
||||
} |
|
||||
|
|
||||
|
|
||||
############################################################################ |
|
||||
## Add hours blocks on invoice |
|
||||
############################################################################ |
|
||||
class AccountInvoice(orm.Model): |
|
||||
_inherit = 'account.invoice' |
|
||||
|
|
||||
_columns = { |
|
||||
'account_hours_block_ids': fields.one2many( |
|
||||
'account.hours.block', |
|
||||
'invoice_id', |
|
||||
string='Hours Block') |
|
||||
} |
|
@ -1,24 +0,0 @@ |
|||||
<?xml version="1.0" ?> |
|
||||
<openerp> |
|
||||
<!-- Mail template are declared in a NOUPDATE block |
|
||||
so users can freely customize/delete them --> |
|
||||
<data noupdate="1"> |
|
||||
<record id="email_template_hours_block" model="email.template"> |
|
||||
<field name="name">Hours Block - Send by Email</field> |
|
||||
<field name="email_from">${(object.user_id.email or object.company_id.email or 'noreply@localhost')|safe}</field> |
|
||||
<field name="subject">${object.company_id.name} Hours Block (Ref ${object.number or 'n/a'})</field> |
|
||||
<field name="email_recipients">${object.partner_id.id}</field> |
|
||||
<field name="model_id" ref="analytic_hours_block.model_account_hours_block"/> |
|
||||
<field name="auto_delete" eval="True"/> |
|
||||
<field name="report_template" ref="block_hours_report"/> |
|
||||
<field name="report_name">Hours_Block_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field> |
|
||||
<field name="lang">${object.partner_id.lang}</field> |
|
||||
<field name="body_html"><![CDATA[ |
|
||||
<p>Hello ${object.partner_id.name},</p> |
|
||||
|
|
||||
<p>Please find attached your Hours Block Report.</p> |
|
||||
<p>Best regards.</p> |
|
||||
]]></field> |
|
||||
</record> |
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,25 +0,0 @@ |
|||||
<?xml version="1.0" ?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<!-- |
|
||||
Hours block menu |
|
||||
--> |
|
||||
<record model="ir.actions.act_window" id="action_all_block_hour"> |
|
||||
<field name="context">{'search_default_running': 1, 'search_default_group_department_id': 1}</field> |
|
||||
<field name="name">Hours Blocks</field> |
|
||||
<field name="res_model">account.hours.block</field> |
|
||||
<field name="view_type">form</field> |
|
||||
<field name="view_mode">tree,form</field> |
|
||||
<field eval="False" name="view_id"/> |
|
||||
</record> |
|
||||
|
|
||||
<menuitem |
|
||||
name="Hours Block" |
|
||||
parent="account.menu_finance_receivables" |
|
||||
id="action_all_block_hour_account" |
|
||||
action="action_all_block_hour" /> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
||||
|
|
@ -1,172 +0,0 @@ |
|||||
<?xml version="1.0" ?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<!-- |
|
||||
Hours block search form |
|
||||
--> |
|
||||
<record id="view_account_invoice_filter" model="ir.ui.view"> |
|
||||
<field name="name">account.hours.block.select</field> |
|
||||
<field name="model">account.hours.block</field> |
|
||||
<field name="arch" type="xml"> |
|
||||
<search string="Search Invoice"> |
|
||||
<group col="7" colspan="4"> |
|
||||
<filter name="draft" icon="terp-document-new" string="Draft" domain="[('state','=','draft')]" help="Draft Hours Blocks"/> |
|
||||
<filter name="running" icon="terp-dolar" string="Running" domain="[('close_date','=',False),('state','<>','draft')]" help="All Running Hours Block"/> |
|
||||
<separator orientation="vertical"/> |
|
||||
<filter name="overdue" icon="terp-dolar_ok!" string="Overdue" domain="[('amount_hours_block_delta','<','0.0')]" help="Overdue Hours Block"/> |
|
||||
<separator orientation="vertical"/> |
|
||||
<field name="number"/> |
|
||||
<field name="partner_id"/> |
|
||||
<field name="department_id" string="Department"/> |
|
||||
<field name="user_id" select="1" widget="selection" string="Salesman"> |
|
||||
<filter domain="[('user_id','=',uid)]" help="My invoices" icon="terp-personal" separator="1"/> |
|
||||
</field> |
|
||||
|
|
||||
<field name="company_id" widget="selection"/> |
|
||||
</group> |
|
||||
<newline/> |
|
||||
<group expand="0" string="Group By..."> |
|
||||
<filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/> |
|
||||
<filter string="Responsible" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/> |
|
||||
<filter string="Department" icon="terp-personal" domain="[]" context="{'group_by':'department_id'}"/> |
|
||||
<filter string="Invoice State" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/> |
|
||||
</group> |
|
||||
</search> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<!-- |
|
||||
Hours Block View |
|
||||
--> |
|
||||
<record id="hours_block_invoice_form" model="ir.ui.view"> |
|
||||
<field name="name">account.hours.block.form</field> |
|
||||
<field name="model">account.hours.block</field> |
|
||||
<field name="arch" type="xml"> |
|
||||
<form string="Hours Blocks" version="7.0"> |
|
||||
<header> |
|
||||
<button name="action_send_block" type="object" string="Send by Email" class="oe_highlight"/> |
|
||||
</header> |
|
||||
<sheet> |
|
||||
<h1> |
|
||||
<field name="invoice_id" placeholder="Choose an invoice..."/> |
|
||||
<label for="type" string="Based on:" class="oe_inline"/> |
|
||||
<field name="type" class="oe_inline"/> |
|
||||
</h1> |
|
||||
|
|
||||
<group> |
|
||||
<field name="last_action_date" /> |
|
||||
<field name="close_date" /> |
|
||||
</group> |
|
||||
|
|
||||
<group> |
|
||||
<separator colspan="4" string="Hours Quantity / Amount"/> |
|
||||
<field name="amount_hours_block" string="Bought"/> |
|
||||
<field name="amount_hours_block_done" string="Used"/> |
|
||||
<field name="amount_hours_block_delta" string="Difference"/> |
|
||||
</group> |
|
||||
|
|
||||
<group> |
|
||||
<separator colspan="4" string="Invoice's related information"/> |
|
||||
<field name="date_invoice"/> |
|
||||
<field name="name"/> |
|
||||
<field name="number"/> |
|
||||
<field name="partner_id" groups="base.group_user"/> |
|
||||
<field name="user_id"/> |
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/> |
|
||||
<field name="department_id" widget="selection"/> |
|
||||
|
|
||||
<field name="journal_id" invisible="1"/> |
|
||||
<field name="period_id" invisible="1" groups="account.group_account_user"/> |
|
||||
|
|
||||
<field name="currency_id"/> |
|
||||
<newline/> |
|
||||
<field name="residual" sum="Residual Amount"/> |
|
||||
<field name="amount_total" sum="Total Amount"/> |
|
||||
<field name="state"/> |
|
||||
</group> |
|
||||
</sheet> |
|
||||
<div class="oe_chatter"> |
|
||||
<field name="message_follower_ids" widget="mail_followers"/> |
|
||||
<field name="message_ids" widget="mail_thread"/> |
|
||||
</div> |
|
||||
</form> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<record model="ir.ui.view" id="invoice_tree_hour_block"> |
|
||||
<field name="name">account.hours.block.tree</field> |
|
||||
<field name="model">account.hours.block</field> |
|
||||
<field name="arch" type="xml"> |
|
||||
<tree colors="blue:state in ('draft');black:state in ('proforma','proforma2','open');gray:state in ('cancel')" string="Invoice"> |
|
||||
<field name="date_invoice"/> |
|
||||
<field name="partner_id" groups="base.group_user"/> |
|
||||
<field name="name"/> |
|
||||
|
|
||||
<field name="amount_hours_block" sum="Quantity of hours bought"/> |
|
||||
<field name="amount_hours_block_done" sum="Quantity of hours used" /> |
|
||||
<field name="amount_hours_block_delta" sum="Quantity of hours difference"/> |
|
||||
<field name="last_action_date" /> |
|
||||
<field name="close_date" /> |
|
||||
|
|
||||
<field name="journal_id" invisible="1"/> |
|
||||
<field name="period_id" invisible="1" groups="account.group_account_user"/> |
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/> |
|
||||
<field name="department_id" widget="selection"/> |
|
||||
<field name="user_id"/> |
|
||||
<field name="currency_id"/> |
|
||||
<field name="residual" sum="Residual Amount"/> |
|
||||
<field name="amount_total" sum="Total Amount"/> |
|
||||
<field name="state"/> |
|
||||
</tree> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
|
|
||||
<!-- |
|
||||
Add related act_window from partner and Hours Block |
|
||||
--> |
|
||||
<act_window name="All blocks hours" |
|
||||
domain="[('partner_id', '=', active_id)]" |
|
||||
res_model="account.hours.block" |
|
||||
src_model="res.partner" |
|
||||
id="act_block_hour_from_partner"/> |
|
||||
|
|
||||
<!-- |
|
||||
Link to invoice on hours block view |
|
||||
--> |
|
||||
<act_window |
|
||||
domain="[('account_hours_block_ids', '=', active_id)]" |
|
||||
id="act_invoice_from_hours_block" |
|
||||
name="Invoice" |
|
||||
res_model="account.invoice" |
|
||||
src_model="account.hours.block" |
|
||||
view_mode="tree,form" |
|
||||
view_type="form"/> |
|
||||
|
|
||||
<!-- |
|
||||
Link to analytic lines on hours block view |
|
||||
--> |
|
||||
<act_window |
|
||||
domain="[('invoice_id.account_hours_block_ids', '=', active_id)]" |
|
||||
id="act_analytic_lines_from_hours_block" |
|
||||
name="Analytic Lines" |
|
||||
res_model="account.analytic.line" |
|
||||
src_model="account.hours.block" |
|
||||
view_mode="tree,form" |
|
||||
view_type="form"/> |
|
||||
|
|
||||
<!-- |
|
||||
Link to hours block on invoice view |
|
||||
--> |
|
||||
<act_window |
|
||||
domain="[('invoice_id', '=', active_id)]" |
|
||||
id="act_hours_block_from_invoice" |
|
||||
name="Hours Block" |
|
||||
res_model="account.hours.block" |
|
||||
src_model="account.invoice" |
|
||||
view_mode="tree,form,calendar,graph" |
|
||||
view_type="form"/> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,470 +0,0 @@ |
|||||
# Translation of OpenERP Server. |
|
||||
# This file contains the translation of the following modules: |
|
||||
# * analytic_hours_block |
|
||||
# |
|
||||
msgid "" |
|
||||
msgstr "" |
|
||||
"Project-Id-Version: OpenERP Server 7.0\n" |
|
||||
"Report-Msgid-Bugs-To: \n" |
|
||||
"POT-Creation-Date: 2014-01-08 12:49+0000\n" |
|
||||
"PO-Revision-Date: 2014-01-08 12:49+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: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Maintenance And Support Summary" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Invoice Date:" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Group By..." |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Bought" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,close_date:0 |
|
||||
msgid "Closed Date" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,message_unread:0 |
|
||||
msgid "Unread Messages" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,company_id:0 |
|
||||
msgid "Company" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,date_invoice:0 |
|
||||
msgid "Invoice Date" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,residual:0 |
|
||||
msgid "Residual" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: help:account.hours.block,amount_hours_block:0 |
|
||||
msgid "Amount bought by the customer. This amount is expressed in the base Unit of Measure (factor=1.0)" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Based on:" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,message_ids:0 |
|
||||
msgid "Messages" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: selection:account.hours.block,type:0 |
|
||||
msgid "Amount" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: selection:account.hours.block,state:0 |
|
||||
msgid "Cancelled" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: help:account.hours.block,message_unread:0 |
|
||||
msgid "If checked new messages require your attention." |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: model:email.template,body_html:analytic_hours_block.email_template_hours_block |
|
||||
msgid "\n" |
|
||||
" Here is your Hours Block Report\n" |
|
||||
" " |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Hours Quantity / Amount" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Remaining hours:" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Quantity of hours bought:" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: help:account.hours.block,message_summary:0 |
|
||||
msgid "Holds the Chatter summary (number of messages, ...). This summary is directly in html format in order to be inserted in kanban views." |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Quantity of hours bought" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
#: field:account.hours.block,partner_id:0 |
|
||||
msgid "Partner" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Quantity of hours difference" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,period_id:0 |
|
||||
msgid "Period" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,state:0 |
|
||||
msgid "State" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,message_follower_ids:0 |
|
||||
msgid "Followers" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Send by Email" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "All Running Hours Block" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,last_action_date:0 |
|
||||
msgid "Last action date" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: selection:account.hours.block,type:0 |
|
||||
msgid "Hours" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Description:" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: help:account.hours.block,type:0 |
|
||||
msgid "The block is based on the quantity of hours or on the amount." |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: model:email.template,report_name:analytic_hours_block.email_template_hours_block |
|
||||
msgid "Hours_Block_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Remaining amount:" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: model:ir.model,name:analytic_hours_block.model_account_hours_block |
|
||||
msgid "account.hours.block" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,amount_hours_block:0 |
|
||||
msgid "Quantity / Amount bought" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: model:ir.actions.report.xml,name:analytic_hours_block.block_hours_report |
|
||||
msgid "Block Hours State" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Choose an invoice..." |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: selection:account.hours.block,state:0 |
|
||||
msgid "Open" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "My invoices" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Draft Hours Blocks" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,currency_id:0 |
|
||||
msgid "Currency" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
#: field:account.hours.block,user_id:0 |
|
||||
msgid "Salesman" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Quantity of hours used" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
#: selection:account.hours.block,state:0 |
|
||||
msgid "Draft" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
#: model:ir.actions.act_window,name:analytic_hours_block.action_all_block_hour |
|
||||
msgid "Hours Blocks" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,type:0 |
|
||||
msgid "Type of Block" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Used" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Total Amount" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: selection:account.hours.block,state:0 |
|
||||
msgid "Paid" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Page" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,message_is_follower:0 |
|
||||
msgid "Is a Follower" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Date" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.invoice,account_hours_block_ids:0 |
|
||||
#: model:ir.actions.act_window,name:analytic_hours_block.act_hours_block_from_invoice |
|
||||
#: model:ir.ui.menu,name:analytic_hours_block.action_all_block_hour_account |
|
||||
msgid "Hours Block" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: help:account.hours.block,last_action_date:0 |
|
||||
msgid "Date of the last analytic line linked to the invoice related to this block hours." |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Report Date:" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: model:email.template,subject:analytic_hours_block.email_template_hours_block |
|
||||
msgid "${object.company_id.name} Hours Block (Ref ${object.number or 'n/a'})" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Invoice's related information" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Search Invoice" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Quantity" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: help:account.hours.block,amount_hours_block_delta:0 |
|
||||
msgid "Difference between bought and used. This amount is expressed in the base Unit of Measure (factor=1.0)" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "0.6cm 27.9cm 20.3cm 27.9cm" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Residual Amount" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Overdue Hours Block" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Amount used:" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,number:0 |
|
||||
msgid "Number" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
#: field:account.hours.block,invoice_id:0 |
|
||||
#: model:ir.actions.act_window,name:analytic_hours_block.act_invoice_from_hours_block |
|
||||
#: model:ir.model,name:analytic_hours_block.model_account_invoice |
|
||||
msgid "Invoice" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: selection:account.hours.block,state:0 |
|
||||
msgid "Pro-forma" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Responsible" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
#: field:account.hours.block,name:0 |
|
||||
msgid "Description" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Amount bought:" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: help:account.hours.block,amount_hours_block_done:0 |
|
||||
msgid "Amount done by the staff. This amount is expressed in the base Unit of Measure (factor=1.0)" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Quantity of hours used:" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Invoicing" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,amount_hours_block_done:0 |
|
||||
msgid "Quantity / Amount used" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,journal_id:0 |
|
||||
msgid "Journal" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Running" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
#: field:account.hours.block,amount_hours_block_delta:0 |
|
||||
msgid "Difference" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: model:ir.actions.act_window,name:analytic_hours_block.act_block_hour_from_partner |
|
||||
msgid "All blocks hours" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: report:account.hours.block:0 |
|
||||
msgid "Deduced" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: model:ir.actions.act_window,name:analytic_hours_block.act_analytic_lines_from_hours_block |
|
||||
msgid "Analytic Lines" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Invoice State" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,message_summary:0 |
|
||||
msgid "Summary" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: help:account.hours.block,message_ids:0 |
|
||||
msgid "Messages and communication history" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: view:account.hours.block:0 |
|
||||
msgid "Overdue" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:account.hours.block,amount_total:0 |
|
||||
msgid "Total" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: field:product.product,is_in_hours_block:0 |
|
||||
msgid "Accounted for hours block?" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: analytic_hours_block |
|
||||
#: help:product.product,is_in_hours_block:0 |
|
||||
msgid "Specify if you want to have invoice lines containing this product to be considered for hours blocks." |
|
||||
msgstr "" |
|
||||
|
|
@ -1,38 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Author: Matthieu Dietrich |
|
||||
# Copyright 2014 Camptocamp SA |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
from openerp.osv import orm, fields |
|
||||
|
|
||||
|
|
||||
class Product(orm.Model): |
|
||||
_name = "product.product" |
|
||||
_inherit = 'product.product' |
|
||||
|
|
||||
_columns = { |
|
||||
'is_in_hours_block': fields.boolean( |
|
||||
'Accounted for hours block?', |
|
||||
help="Specify if you want to have invoice lines " |
|
||||
"containing this product to be considered for hours blocks.") |
|
||||
} |
|
||||
|
|
||||
_defaults = { |
|
||||
'is_in_hours_block': False |
|
||||
} |
|
@ -1,18 +0,0 @@ |
|||||
<?xml version="1.0" ?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<record id="view_product_hours_block_form" model="ir.ui.view"> |
|
||||
<field name="name">product.product.block.form</field> |
|
||||
<field name="model">product.product</field> |
|
||||
<field name="inherit_id" ref="product.product_normal_form_view"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<div name="options" position="inside"> |
|
||||
<field name="is_in_hours_block"/> |
|
||||
<label for="is_in_hours_block"/> |
|
||||
</div> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,33 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
from osv import orm |
|
||||
from openerp.tools.translate import _ |
|
||||
|
|
||||
|
|
||||
class project_project(orm.Model): |
|
||||
_inherit = 'project.project' |
|
||||
|
|
||||
def hours_block_tree_view(self, cr, uid, ids, context): |
|
||||
invoice_line_obj = self.pool.get('account.invoice.line') |
|
||||
hours_block_obj = self.pool.get('account.hours.block') |
|
||||
project = self.browse(cr, uid , ids)[0] |
|
||||
invoice_line_ids = invoice_line_obj.search(cr, uid, [('account_analytic_id', '=', project.analytic_account_id.id)]) |
|
||||
invoice_lines = invoice_line_obj.browse(cr, uid, invoice_line_ids) |
|
||||
invoice_ids = [x.invoice_id.id for x in invoice_lines] |
|
||||
res_ids = hours_block_obj.search(cr, uid, [('invoice_id','in',invoice_ids)]) |
|
||||
domain=False |
|
||||
if res_ids: |
|
||||
domain = [('id', 'in', res_ids)] |
|
||||
else: |
|
||||
raise orm.except_orm(_('Warning'), _("No Hours Block for this project")) |
|
||||
|
|
||||
return { |
|
||||
'name': _('Hours Blocks'), |
|
||||
'domain': domain, |
|
||||
'res_model': 'account.hours.block', |
|
||||
'type': 'ir.actions.act_window', |
|
||||
'view_id': False, |
|
||||
'view_mode': 'tree,form', |
|
||||
'view_type': 'form', |
|
||||
'limit': 80, |
|
||||
'res_id' : res_ids or False, |
|
||||
} |
|
@ -1,19 +0,0 @@ |
|||||
<?xml version="1.0" ?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<record model="ir.ui.view" id="edit_project_hours_block_link"> |
|
||||
<field name="name">project.project.form.hours.block.link</field> |
|
||||
<field name="model">project.project</field> |
|
||||
<field name="inherit_id" ref="project.edit_project"/> |
|
||||
<field name="type">form</field> |
|
||||
<field name="arch" type="xml"> |
|
||||
<xpath expr="//button[@name='attachment_tree_view']" position="after"> |
|
||||
<button name="hours_block_tree_view" string="Hours Block" type="object"/> |
|
||||
</xpath> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,12 +0,0 @@ |
|||||
<?xml version="1.0" encoding="UTF-8"?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<report id="block_hours_report" |
|
||||
string="Block Hours State" |
|
||||
model="account.hours.block" |
|
||||
name="account.hours.block" |
|
||||
rml="analytic_hours_block/report/hours_block.rml"/> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,21 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Author: Vincent Renaville, ported by Joel Grand-Guillaume |
|
||||
# Copyright 2010-2012 Camptocamp SA |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
import hours_block |
|
@ -1,53 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Author: Vincent Renaville, ported by Joel Grand-Guillaume |
|
||||
# Copyright 2010-2013 Camptocamp SA |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
import time |
|
||||
from openerp.report import report_sxw |
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT |
|
||||
|
|
||||
|
|
||||
class account_hours_block(report_sxw.rml_parse): |
|
||||
def __init__(self, cr, uid, name, context=None): |
|
||||
super(account_hours_block, self).__init__(cr, uid, name, context=context) |
|
||||
self.localcontext.update({'time': time, |
|
||||
'date_format': DEFAULT_SERVER_DATE_FORMAT, |
|
||||
'analytic_lines': self._get_analytic_lines, |
|
||||
}) |
|
||||
self.context = context |
|
||||
|
|
||||
def _get_analytic_lines(self, hours_block): |
|
||||
al_pool = self.pool.get('account.analytic.line') |
|
||||
aj_pool = self.pool.get('account.analytic.journal') |
|
||||
tcj_ids = aj_pool.search(self.cr, self.uid, |
|
||||
[('type', '=', 'general')]) |
|
||||
al_ids = al_pool.search(self.cr, |
|
||||
self.uid, |
|
||||
[('invoice_id', '=', hours_block.invoice_id.id), |
|
||||
('journal_id', 'in', tcj_ids), |
|
||||
], |
|
||||
order='date desc', |
|
||||
context=self.context) |
|
||||
return al_pool.browse(self.cr, self.uid, al_ids, context=self.context) |
|
||||
|
|
||||
report_sxw.report_sxw('report.account.hours.block', |
|
||||
'account.hours.block', |
|
||||
'addons/analytic_hours_block/report/hours_block.rml', |
|
||||
parser=account_hours_block) |
|
@ -1,263 +0,0 @@ |
|||||
<?xml version="1.0"?> |
|
||||
<document filename="test.pdf"> |
|
||||
<template pageSize="(595.0,842.0)" title="Test" author="Martin Simon" allowSplitting="20"> |
|
||||
<pageTemplate id="first"> |
|
||||
<frame id="first" x1="35.0" y1="35.0" width="525" height="772"/> |
|
||||
<pageGraphics> |
|
||||
<setFont name="Helvetica-Bold" size="9"/> |
|
||||
|
|
||||
<drawString x="1.0cm" y="28.1cm">[[ company.name ]]</drawString> |
|
||||
<drawString x="17.7cm" y="28.1cm">Maintenance And Support Summary</drawString> |
|
||||
|
|
||||
<setFont name="Helvetica" size="9"/> |
|
||||
<drawString x="1.0cm" y="2cm"> [[ formatLang(time.strftime(date_format), date=True) ]]</drawString> |
|
||||
<drawString x="17.7cm" y="2cm">Page <pageNumber/></drawString> |
|
||||
|
|
||||
<lineMode width="0.7"/> |
|
||||
<lines>0.6cm 27.9cm 20.3cm 27.9cm</lines> |
|
||||
<setFont name="Helvetica" size="8"/> |
|
||||
</pageGraphics> |
|
||||
|
|
||||
</pageTemplate> |
|
||||
</template> |
|
||||
<stylesheet> |
|
||||
<blockTableStyle id="Standard_Outline"> |
|
||||
<blockAlignment value="LEFT"/> |
|
||||
<blockValign value="TOP"/> |
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="Table1"> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#ffffff" start="0,0" stop="-1,-1"/> |
|
||||
<blockAlignment value="LEFT"/> |
|
||||
<blockValign value="TOP"/> |
|
||||
<blockBackground colorName="#e6e6e6" start="0,0" stop="0,-1"/> |
|
||||
<blockBackground colorName="#e6e6e6" start="1,0" stop="1,-1"/> |
|
||||
<blockBackground colorName="#e6e6e6" start="2,0" stop="2,-1"/> |
|
||||
<blockBackground colorName="#e6e6e6" start="0,1" stop="0,-1"/> |
|
||||
<blockBackground colorName="#e6e6e6" start="1,1" stop="1,-1"/> |
|
||||
<blockBackground colorName="#e6e6e6" start="2,1" stop="2,-1"/> |
|
||||
|
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="Table6"> |
|
||||
<blockAlignment value="LEFT"/> |
|
||||
<blockValign value="TOP"/> |
|
||||
<lineStyle kind="LINEBEFORE" colorName="#000000" start="0,0" stop="0,-1"/> |
|
||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="0,0" stop="0,0"/> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/> |
|
||||
<lineStyle kind="LINEBEFORE" colorName="#000000" start="1,0" stop="1,-1"/> |
|
||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="1,0" stop="1,0"/> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/> |
|
||||
<lineStyle kind="LINEBEFORE" colorName="#000000" start="2,0" stop="2,-1"/> |
|
||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="2,0" stop="2,0"/> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/> |
|
||||
<lineStyle kind="LINEBEFORE" colorName="#000000" start="3,0" stop="3,-1"/> |
|
||||
<lineStyle kind="LINEAFTER" colorName="#000000" start="3,0" stop="3,-1"/> |
|
||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="3,0" stop="3,0"/> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/> |
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="Table2"> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,0" stop="-1,0"/> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,1" stop="-1,-1"/> |
|
||||
|
|
||||
<blockValign value="TOP"/> |
|
||||
<blockAlignment value="RIGHT" start="2,1" stop="-1,-1"/> |
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="Table3"> |
|
||||
<blockAlignment value="LEFT"/> |
|
||||
<blockValign value="TOP"/> |
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="Table4"> |
|
||||
<blockAlignment value="LEFT"/> |
|
||||
<blockValign value="TOP"/> |
|
||||
<lineStyle kind="LINEBEFORE" colorName="#000000" start="0,0" stop="0,-1"/> |
|
||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="0,0" stop="0,0"/> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/> |
|
||||
<lineStyle kind="LINEBEFORE" colorName="#000000" start="1,0" stop="1,-1"/> |
|
||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="1,0" stop="1,0"/> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/> |
|
||||
<lineStyle kind="LINEBEFORE" colorName="#000000" start="2,0" stop="2,-1"/> |
|
||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="2,0" stop="2,0"/> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/> |
|
||||
<lineStyle kind="LINEBEFORE" colorName="#000000" start="3,0" stop="3,-1"/> |
|
||||
<lineStyle kind="LINEAFTER" colorName="#000000" start="3,0" stop="3,-1"/> |
|
||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="3,0" stop="3,0"/> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/> |
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="Table7"> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,0" stop="-1,0"/> |
|
||||
</blockTableStyle> |
|
||||
|
|
||||
<initialize> |
|
||||
<paraStyle name="all" alignment="justify"/> |
|
||||
</initialize> |
|
||||
<paraStyle name="P1" fontName="Helvetica-Bold" fontSize="14.0" leading="25" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/> |
|
||||
<paraStyle name="P2" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="6.0"/> |
|
||||
<paraStyle name="P2c" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/> |
|
||||
<paraStyle name="P3" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="6.0"/> |
|
||||
<paraStyle name="P10" fontName="Helvetica" fontSize="8.0" leading="14" spaceBefore="0.0" spaceAfter="6.0" alignment="RIGHT"/> |
|
||||
<paraStyle name="P9" fontName="Helvetica-Bold" alignment="CENTER" fontSize="14.5" leftIndent="-5.0"/> |
|
||||
<paraStyle name="P9b" fontName="Helvetica" fontSize="8" alignment="LEFT"/> |
|
||||
<paraStyle name="P9c" fontName="Helvetica" fontSize="8" alignment="RIGHT"/> |
|
||||
<paraStyle name="P12" fontName="Helvetica" fontSize="8.0" leading="14" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/> |
|
||||
<paraStyle name="P12a" fontName="Helvetica-Bold" fontSize="8.0" alignment="LEFT" spaceBefore="0.0" spaceAfter="6.0"/> |
|
||||
<paraStyle name="P14" rightIndent="17.0" leftIndent="-0.0" fontName="Helvetica" fontSize="8.0" leading="10" spaceBefore="0.0" spaceAfter="6.0"/> |
|
||||
|
|
||||
|
|
||||
<blockTableStyle id="TrLevel6"> |
|
||||
<blockLeftPadding length="60" start="1,0" stop="1,0"/> |
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="TrLevel5"> |
|
||||
<blockLeftPadding length="40" start="1,0" stop="1,0"/> |
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="TrLevel4"> |
|
||||
<blockLeftPadding length="20" start="1,0" stop="1,0"/> |
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="TrLevel3"> |
|
||||
<blockLeftPadding length="0" start="1,0" stop="1,0"/> |
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="TrLevel2"> |
|
||||
<blockLeftPadding length="0" start="1,0" stop="1,0"/> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#777777" start="1,0" stop="1,0"/> |
|
||||
<blockTopPadding length="13" start="0,0" stop="-1,0"/> |
|
||||
<blockBottomPadding length="2" start="0,0" stop="-1,0"/> |
|
||||
<blockFont name="Times-Bold" start="0,0" stop="-1,-1"/> |
|
||||
</blockTableStyle> |
|
||||
<blockTableStyle id="TrLevel1"> |
|
||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,0" stop="-1,0"/> |
|
||||
<blockLeftPadding length="0" start="1,0" stop="1,0"/> |
|
||||
<blockTopPadding length="26" start="0,0" stop="-1,0"/> |
|
||||
<blockBottomPadding length="2" start="0,0" stop="-1,0"/> |
|
||||
<blockFont name="Times-Bold" start="0,0" stop="-1,-1"/> |
|
||||
</blockTableStyle> |
|
||||
|
|
||||
<paraStyle |
|
||||
name="Level5" |
|
||||
fontName="Helvetica" |
|
||||
fontSize="8.0" /> |
|
||||
<paraStyle |
|
||||
name="Level4" |
|
||||
fontName="Helvetica" |
|
||||
fontSize="8.0" /> |
|
||||
<paraStyle |
|
||||
name="Level3" |
|
||||
fontName="Helvetica" |
|
||||
fontSize="8.0" /> |
|
||||
<paraStyle |
|
||||
name="Level2" |
|
||||
firstLineIndent="-0.03cm" |
|
||||
fontName="Helvetica-Bold" |
|
||||
fontSize="8.0" /> |
|
||||
<paraStyle name="Level1" |
|
||||
fontSize="8.0" |
|
||||
fontName="Helvetica-Bold" |
|
||||
/> |
|
||||
<paraStyle name="Caption" fontName="Helvetica" fontSize="10.0" leading="13" spaceBefore="6.0" spaceAfter="6.0"/> |
|
||||
<paraStyle name="Index" fontName="Helvetica"/> |
|
||||
</stylesheet> |
|
||||
<images/> |
|
||||
<story> |
|
||||
|
|
||||
<para style="P2">[[ repeatIn(objects,'o') ]]</para> |
|
||||
<para style="P2">[[ setLang(o.partner_id.lang) ]]</para> |
|
||||
<para style="P1">Maintenance And Support Summary</para> |
|
||||
|
|
||||
<para style="P12a"></para> |
|
||||
<para style="P12a"></para> |
|
||||
<blockTable colWidths="258.0,259.0" style="Table1" repeatRows="1"> |
|
||||
<tr> |
|
||||
<td> |
|
||||
<para style="P12a">Description: </para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P2">[[ o.name ]]</para> |
|
||||
</td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<td> |
|
||||
<para style="P12a">Report Date: </para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P2">[[ formatLang(time.strftime(date_format), date=True) ]]</para> |
|
||||
</td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<td> |
|
||||
<para style="P12a">Invoice Date: </para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P2">[[ o.date_invoice and formatLang(o.date_invoice, date=True) or '' ]]</para> |
|
||||
</td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<td> |
|
||||
<para style="P12a">Amount bought: [[ (o.type == 'amount' or removeParentNode('para')) and '' ]]</para> |
|
||||
<para style="P12a">Quantity of hours bought: [[ (o.type == 'hours' or removeParentNode('para')) and '' ]]</para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P2">[[ o.amount_hours_block ]]</para> |
|
||||
</td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<td> |
|
||||
<para style="P12a">Amount used: [[ (o.type == 'amount' or removeParentNode('para')) and '' ]]</para> |
|
||||
<para style="P12a">Quantity of hours used: [[ (o.type == 'hours' or removeParentNode('para')) and '' ]]</para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P2">[[ round(o.amount_hours_block_done, 2) ]]</para> |
|
||||
</td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<td> |
|
||||
<para style="P12a">Remaining amount: [[ (o.type == 'amount' or removeParentNode('para')) and '' ]]</para> |
|
||||
<para style="P12a">Remaining hours: [[ (o.type == 'hours' or removeParentNode('para')) and '' ]]</para> |
|
||||
|
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P2">[[ o.amount_hours_block and round(o.amount_hours_block_delta, 2) or '' ]]</para> |
|
||||
</td> |
|
||||
</tr> |
|
||||
</blockTable> |
|
||||
<para style="P12a"></para> |
|
||||
<para style="P12a"></para> |
|
||||
<para style="P12a"></para> |
|
||||
<blockTable colWidths="58,305.0,52.0,52.0,52.0" style="Table2" repeatRows="1"> |
|
||||
<tr> |
|
||||
<td> |
|
||||
<para style="P12a">Date</para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P12a">Description</para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P12a">Quantity</para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P12a">Invoicing</para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P12a">Deduced</para> |
|
||||
</td> |
|
||||
</tr> |
|
||||
|
|
||||
<tr> |
|
||||
[[ repeatIn(analytic_lines(o), 'l') ]] |
|
||||
<td> |
|
||||
<para style="P2">[[ l.date if formatLang(l.date, date=True) else '' ]]</para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P2">[[ l.name or '' ]]</para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P2c">[[ round(l.unit_amount, 2) or '0.0' ]]</para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P2c">[[ l.to_invoice.customer_name ]]</para> |
|
||||
</td> |
|
||||
<td> |
|
||||
<para style="P2c">[[ round((l.unit_amount and l.to_invoice) and (l.unit_amount - (l.unit_amount * l.to_invoice.factor) / 100 ), 2) or '0.0' ]]</para> |
|
||||
</td> |
|
||||
</tr> |
|
||||
</blockTable> |
|
||||
|
|
||||
</story> |
|
||||
</document> |
|
||||
|
|
@ -1,11 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<openerp><data> |
|
||||
|
|
||||
<record id="hours_block_comp_rule" model="ir.rule"> |
|
||||
<field name="name">Hours Block multi company rule</field> |
|
||||
<field model="ir.model" name="model_id" ref="model_account_hours_block"/> |
|
||||
<field eval="True" name="global"/> |
|
||||
<field name="domain_force">[]</field> |
|
||||
</record> |
|
||||
|
|
||||
</data></openerp> |
|
@ -1,3 +0,0 @@ |
|||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
|
||||
"access_hours_block_user","account.hours.block","model_account_hours_block","base.group_user",1,0,0,0 |
|
||||
"access_hours_block_invoice_manager","account.hours.block","model_account_hours_block","account.group_account_invoice",1,1,1,1 |
|
@ -1,5 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
import project_sla |
|
||||
import analytic_account |
|
||||
import project_sla_control |
|
||||
import project_issue |
|
@ -1,132 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Copyright (C) 2013 Daniel Reis |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
{ |
|
||||
'name': 'Service Level Agreements', |
|
||||
'summary': 'Define SLAs for your Contracts', |
|
||||
'version': '1.0', |
|
||||
"category": "Project Management", |
|
||||
'description': """\ |
|
||||
Contract SLAs |
|
||||
=============== |
|
||||
|
|
||||
SLAs are assigned to Contracts, on the Analytic Account form, SLA Definition |
|
||||
separator. This is also where new SLA Definitions are created. |
|
||||
|
|
||||
One Contract can have several SLA Definitions attached, allowing for |
|
||||
"composite SLAs". For example, a contract could have a Response Time SLA (time |
|
||||
to start resolution) and a Resolution Time SLA (time to close request). |
|
||||
|
|
||||
|
|
||||
SLA Controlled Documents |
|
||||
======================== |
|
||||
|
|
||||
Only Project Issue documents are made SLA controllable. |
|
||||
However, a framework is made available to easily build extensions to make |
|
||||
other documents models SLA controlled. |
|
||||
|
|
||||
SLA controlled documents have attached information on the list of SLA rules |
|
||||
they should meet (more than one in the case for composite SLAs) and a summary |
|
||||
SLA status: |
|
||||
|
|
||||
* "watching" the service level (it has SLA requirements to meet) |
|
||||
* under "warning" (limit dates are close, special attention is needed) |
|
||||
* "failed" (one on the SLA limits has not been met) |
|
||||
* "achieved" (all SLA limits have been met) |
|
||||
|
|
||||
Transient states, such as "watching" and "warning", are regularly updated by |
|
||||
a hourly scheduled job, that reevaluates the warning and limit dates against |
|
||||
the current time and changes the state when find dates that have been exceeded. |
|
||||
|
|
||||
To decide what SLA Definitions apply for a specific document, first a lookup |
|
||||
is made for a ``analytic_account_id`` field. If not found, then it will |
|
||||
look up for the ``project_id`` and it's corresponding ``analytic_account_id``. |
|
||||
|
|
||||
Specifically, the Service Desk module introduces a Analytic Account field for |
|
||||
Project Issues. This makes it possible for a Service Team (a "Project") to |
|
||||
have a generic SLA, but at the same time allow for some Contracts to have |
|
||||
specific SLAs (such as the case for "premium" service conditions). |
|
||||
|
|
||||
|
|
||||
SLA Definitions and Rules |
|
||||
========================= |
|
||||
|
|
||||
New SLA Definitions are created from the Analytic Account form, SLA Definition |
|
||||
field. |
|
||||
|
|
||||
Each definition can have one or more Rules. |
|
||||
The particular rule to use is decided by conditions, so that you can set |
|
||||
different service levels based on request attributes, such as Priority or |
|
||||
Category. |
|
||||
Each rule condition is evaluated in "sequence" order, and the first onea to met |
|
||||
is the one to be used. |
|
||||
In the simplest case, a single rule with no condition is just what is needed. |
|
||||
|
|
||||
Each rule sets a number of hours until the "limit date", and the number of |
|
||||
hours until a "warning date". The former will be used to decide if the SLA |
|
||||
was achieved, and the later can be used for automatic alarms or escalation |
|
||||
procedures. |
|
||||
|
|
||||
Time will be counted from creation date, until the "Control Date" specified for |
|
||||
the SLA Definition. That would usually be the "Close" (time until resolution) |
|
||||
or the "Open" (time until response) dates. |
|
||||
|
|
||||
The working calendar set in the related Project definitions will be used (see |
|
||||
the "Other Info" tab). If none is defined, a builtin "all days, 8-12 13-17" |
|
||||
default calendar is used. |
|
||||
|
|
||||
A timezone and leave calendars will also used, based on either the assigned |
|
||||
user (document's `user_id`) or on the current user. |
|
||||
|
|
||||
|
|
||||
Setup checklist |
|
||||
=============== |
|
||||
|
|
||||
The basic steps to configure SLAs for a Project are: |
|
||||
|
|
||||
* Set Project's Working Calendar, at Project definitions, "Other Info" tab |
|
||||
* Go to the Project's Analytic Account form; create and set SLA Definitions |
|
||||
* Use the "Reapply SLAs" button on the Analytic Account form |
|
||||
* See Project Issue's calculated SLAs in the new "Service Levels" tab |
|
||||
|
|
||||
|
|
||||
Credits and Contributors |
|
||||
======================== |
|
||||
|
|
||||
* Daniel Reis (https://launchpad.net/~dreis-pt) |
|
||||
* David Vignoni, author of the icon from the KDE 3.x Nuvola icon theme |
|
||||
""", |
|
||||
'author': 'Daniel Reis', |
|
||||
'website': '', |
|
||||
'depends': [ |
|
||||
'project_issue', |
|
||||
], |
|
||||
'data': [ |
|
||||
'project_sla_view.xml', |
|
||||
'project_sla_control_view.xml', |
|
||||
'project_sla_control_data.xml', |
|
||||
'analytic_account_view.xml', |
|
||||
'project_view.xml', |
|
||||
'project_issue_view.xml', |
|
||||
'security/ir.model.access.csv', |
|
||||
], |
|
||||
'demo': ['project_sla_demo.xml'], |
|
||||
'test': ['test/project_sla.yml'], |
|
||||
'installable': False, |
|
||||
} |
|
@ -1,69 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Copyright (C) 2013 Daniel Reis |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
from openerp.osv import fields, orm |
|
||||
|
|
||||
|
|
||||
class AnalyticAccount(orm.Model): |
|
||||
""" Add SLA to Analytic Accounts """ |
|
||||
_inherit = 'account.analytic.account' |
|
||||
_columns = { |
|
||||
'sla_ids': fields.many2many( |
|
||||
'project.sla', string='Service Level Agreement'), |
|
||||
} |
|
||||
|
|
||||
def _reapply_sla(self, cr, uid, ids, recalc_closed=False, context=None): |
|
||||
""" |
|
||||
Force SLA recalculation on open documents that already are subject to |
|
||||
this SLA Definition. |
|
||||
To use after changing a Contract SLA or it's Definitions. |
|
||||
The ``recalc_closed`` flag allows to also recompute closed documents. |
|
||||
""" |
|
||||
ctrl_obj = self.pool.get('project.sla.control') |
|
||||
proj_obj = self.pool.get('project.project') |
|
||||
exclude_states = ['cancelled'] + (not recalc_closed and ['done'] or []) |
|
||||
for contract in self.browse(cr, uid, ids, context=context): |
|
||||
# for each contract, and for each model under SLA control ... |
|
||||
for m_name in set([sla.control_model for sla in contract.sla_ids]): |
|
||||
model = self.pool.get(m_name) |
|
||||
doc_ids = [] |
|
||||
if 'analytic_account_id' in model._columns: |
|
||||
doc_ids += model.search( |
|
||||
cr, uid, |
|
||||
[('analytic_account_id', '=', contract.id), |
|
||||
('state', 'not in', exclude_states)], |
|
||||
context=context) |
|
||||
if 'project_id' in model._columns: |
|
||||
proj_ids = proj_obj.search( |
|
||||
cr, uid, [('analytic_account_id', '=', contract.id)], |
|
||||
context=context) |
|
||||
doc_ids += model.search( |
|
||||
cr, uid, |
|
||||
[('project_id', 'in', proj_ids), |
|
||||
('state', 'not in', exclude_states)], |
|
||||
context=context) |
|
||||
if doc_ids: |
|
||||
docs = model.browse(cr, uid, doc_ids, context=context) |
|
||||
ctrl_obj.store_sla_control(cr, uid, docs, context=context) |
|
||||
return True |
|
||||
|
|
||||
def reapply_sla(self, cr, uid, ids, context=None): |
|
||||
""" Reapply SLAs button action """ |
|
||||
return self._reapply_sla(cr, uid, ids, context=context) |
|
@ -1,24 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<record id="view_account_analytic_account_form_sla" model="ir.ui.view"> |
|
||||
<field name="name">view_account_analytic_account_form_sla</field> |
|
||||
<field name="model">account.analytic.account</field> |
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_account_form"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
|
|
||||
<page name="contract_page" position="after"> |
|
||||
<page name="sla_page" string="Service Level Agreement"> |
|
||||
<field name="sla_ids" nolabel="1"/> |
|
||||
<button name="reapply_sla" string="Reapply" type="object" |
|
||||
help="Reapply the SLAs to all Contract's documents." |
|
||||
groups="project.group_project_manager" /> |
|
||||
</page> |
|
||||
</page> |
|
||||
|
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,296 +0,0 @@ |
|||||
# Translation of OpenERP Server. |
|
||||
# This file contains the translation of the following modules: |
|
||||
# * project_sla |
|
||||
# |
|
||||
msgid "" |
|
||||
msgstr "" |
|
||||
"Project-Id-Version: OpenERP Server 7.0\n" |
|
||||
"Report-Msgid-Bugs-To: \n" |
|
||||
"POT-Creation-Date: 2013-12-19 10:28+0000\n" |
|
||||
"PO-Revision-Date: 2013-12-19 10:28+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: project_sla |
|
||||
#: model:project.sla,name:project_sla.sla_response |
|
||||
msgid "Standard Response Time" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: help:project.sla,control_field_id:0 |
|
||||
msgid "Date field used to check if the SLA was achieved." |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:ir.model,name:project_sla.model_project_issue |
|
||||
msgid "Project Issue" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:ir.model,name:project_sla.model_project_sla_control |
|
||||
msgid "SLA Control Registry" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:project.sla:0 |
|
||||
msgid "Reapply SLA on Contracts" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:project.project:0 |
|
||||
msgid "Administration" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:project.issue:0 |
|
||||
msgid "Priority" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: selection:project.issue,sla_state:0 |
|
||||
#: selection:project.sla.control,sla_state:0 |
|
||||
#: selection:project.sla.controlled,sla_state:0 |
|
||||
msgid "Failed" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.control,sla_warn_date:0 |
|
||||
msgid "Warning Date" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.line,warn_qty:0 |
|
||||
msgid "Hours to Warn" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:project.sla.control:0 |
|
||||
msgid "Service Level" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: selection:project.issue,sla_state:0 |
|
||||
#: selection:project.sla.control,sla_state:0 |
|
||||
#: selection:project.sla.controlled,sla_state:0 |
|
||||
msgid "Watching" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:project.sla:0 |
|
||||
#: field:project.sla,analytic_ids:0 |
|
||||
msgid "Contracts" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla,name:0 |
|
||||
#: field:project.sla.line,name:0 |
|
||||
msgid "Title" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla,active:0 |
|
||||
msgid "Active" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.issue,sla_control_ids:0 |
|
||||
#: field:project.sla.controlled,sla_control_ids:0 |
|
||||
msgid "SLA Control" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.control,sla_achieved:0 |
|
||||
msgid "Achieved?" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:project.issue:0 |
|
||||
#: field:project.issue,sla_state:0 |
|
||||
#: field:project.sla.control,sla_state:0 |
|
||||
#: field:project.sla.controlled,sla_state:0 |
|
||||
msgid "SLA Status" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.line,condition:0 |
|
||||
msgid "Condition" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla,control_model:0 |
|
||||
msgid "For documents" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:project.sla:0 |
|
||||
#: field:project.sla.line,sla_id:0 |
|
||||
msgid "SLA Definition" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:project.sla.line,name:project_sla.sla_response_rule2 |
|
||||
msgid "Response in two business days" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: selection:project.issue,sla_state:0 |
|
||||
#: selection:project.sla.control,sla_state:0 |
|
||||
#: selection:project.sla.controlled,sla_state:0 |
|
||||
msgid "Will Fail" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.control,sla_line_id:0 |
|
||||
msgid "Service Agreement" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.control,doc_id:0 |
|
||||
msgid "Document ID" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.control,locked:0 |
|
||||
msgid "Recalculation disabled" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.control,sla_limit_date:0 |
|
||||
msgid "Limit Date" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: help:project.sla.line,condition:0 |
|
||||
msgid "Apply only if this expression is evaluated to True. The document fields can be accessed using either o, obj or object. Example: obj.priority <= '2'" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:project.sla.line,name:project_sla.sla_resolution_rule1 |
|
||||
msgid "Resolution in two business days" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:account.analytic.account:0 |
|
||||
msgid "Reapply" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.line,limit_qty:0 |
|
||||
msgid "Hours to Limit" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla,sla_line_ids:0 |
|
||||
#: view:project.sla.line:0 |
|
||||
msgid "Definitions" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:project.sla.line,name:project_sla.sla_resolution_rule2 |
|
||||
msgid "Resolution in three business days" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.control,sla_close_date:0 |
|
||||
msgid "Close Date" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:ir.model,name:project_sla.model_project_sla |
|
||||
msgid "project.sla" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla,control_field_id:0 |
|
||||
msgid "Control Date" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:project.sla.line,name:project_sla.sla_response_rule1 |
|
||||
msgid "Response in one business day" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: selection:project.issue,sla_state:0 |
|
||||
#: selection:project.sla.control,sla_state:0 |
|
||||
#: selection:project.sla.controlled,sla_state:0 |
|
||||
msgid "Achieved" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:account.analytic.account:0 |
|
||||
#: field:account.analytic.account,sla_ids:0 |
|
||||
msgid "Service Level Agreement" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:project.sla,name:project_sla.sla_resolution |
|
||||
msgid "Standard Resolution Time" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.line,sequence:0 |
|
||||
msgid "Sequence" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:project.issue:0 |
|
||||
msgid "Service Levels" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:ir.model,name:project_sla.model_account_analytic_account |
|
||||
msgid "Analytic Account" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:project.sla:0 |
|
||||
msgid "Rules" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: help:project.sla.control,locked:0 |
|
||||
msgid "Safeguard manual changes from future automatic recomputations." |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: selection:project.issue,sla_state:0 |
|
||||
#: selection:project.sla.control,sla_state:0 |
|
||||
#: selection:project.sla.controlled,sla_state:0 |
|
||||
msgid "Warning" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:project.issue:0 |
|
||||
msgid "Extra Info" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.control,doc_model:0 |
|
||||
msgid "Document Model" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:ir.model,name:project_sla.model_project_sla_line |
|
||||
msgid "project.sla.line" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: view:account.analytic.account:0 |
|
||||
msgid "Reapply the SLAs to all Contract's documents." |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: field:project.sla.control,sla_start_date:0 |
|
||||
msgid "Start Date" |
|
||||
msgstr "" |
|
||||
|
|
||||
#. module: project_sla |
|
||||
#: model:ir.model,name:project_sla.model_project_sla_controlled |
|
||||
msgid "SLA Controlled Document" |
|
||||
msgstr "" |
|
||||
|
|
Before Width: 773 | Height: 464 | Size: 18 KiB |
Before Width: 915 | Height: 437 | Size: 19 KiB |
Before Width: 873 | Height: 572 | Size: 26 KiB |
@ -1,75 +0,0 @@ |
|||||
""" |
|
||||
Wrapper for OpenERP's cryptic write conventions for x2many fields. |
|
||||
|
|
||||
Example usage: |
|
||||
|
|
||||
import m2m |
|
||||
browse_rec.write({'many_ids: m2m.clear()) |
|
||||
browse_rec.write({'many_ids: m2m.link(99)) |
|
||||
browse_rec.write({'many_ids: m2m.add({'name': 'Monty'})) |
|
||||
browse_rec.write({'many_ids: m2m.replace([98, 99])) |
|
||||
|
|
||||
Since returned values are lists, the can be joined using the plus operator: |
|
||||
|
|
||||
browse_rec.write({'many_ids: m2m.clear() + m2m.link(99)) |
|
||||
|
|
||||
(Source: https://github.com/dreispt/openerp-write2many) |
|
||||
""" |
|
||||
|
|
||||
|
|
||||
def create(values): |
|
||||
""" Create a referenced record """ |
|
||||
assert isinstance(values, dict) |
|
||||
return [(0, 0, values)] |
|
||||
|
|
||||
|
|
||||
def add(values): |
|
||||
""" Intuitive alias for create() """ |
|
||||
return create(values) |
|
||||
|
|
||||
|
|
||||
def write(id, values): |
|
||||
""" Write on referenced record """ |
|
||||
assert isinstance(id, int) |
|
||||
assert isinstance(values, dict) |
|
||||
return [(1, id, values)] |
|
||||
|
|
||||
|
|
||||
def remove(id): |
|
||||
""" Unlink and delete referenced record """ |
|
||||
assert isinstance(id, int) |
|
||||
return [(2, id)] |
|
||||
|
|
||||
|
|
||||
def unlink(id): |
|
||||
""" Unlink but do not delete the referenced record """ |
|
||||
assert isinstance(id, int) |
|
||||
return [(3, id)] |
|
||||
|
|
||||
|
|
||||
def link(id): |
|
||||
""" Link but do not delete the referenced record """ |
|
||||
assert isinstance(id, int) |
|
||||
return [(4, id)] |
|
||||
|
|
||||
|
|
||||
def clear(): |
|
||||
""" Unlink all referenced records (doesn't delete them) """ |
|
||||
return [(5, 0)] |
|
||||
|
|
||||
|
|
||||
def replace(ids): |
|
||||
""" Unlink all current records and replace them with a new list """ |
|
||||
assert isinstance(ids, list) |
|
||||
return [(6, 0, ids)] |
|
||||
|
|
||||
|
|
||||
if __name__ == "__main__": |
|
||||
# Tests: |
|
||||
assert create({'name': 'Monty'}) == [(0, 0, {'name': 'Monty'})] |
|
||||
assert write(99, {'name': 'Monty'}) == [(1, 99, {'name': 'Monty'})] |
|
||||
assert remove(99) == [(2, 99)] |
|
||||
assert unlink(99) == [(3, 99)] |
|
||||
assert clear() == [(5, 0)] |
|
||||
assert replace([97, 98, 99]) == [(6, 0, [97, 98, 99])] |
|
||||
print("Done!") |
|
@ -1,29 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Copyright (C) 2013 Daniel Reis |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
from openerp.osv import orm |
|
||||
|
|
||||
|
|
||||
class ProjectIssue(orm.Model): |
|
||||
""" |
|
||||
Extend Project Issues to be SLA Controlled |
|
||||
""" |
|
||||
_name = 'project.issue' |
|
||||
_inherit = ['project.issue', 'project.sla.controlled'] |
|
@ -1,60 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<!-- Project Issue Form --> |
|
||||
<record id="project_issue_form_view_sla" model="ir.ui.view"> |
|
||||
<field name="name">project_issue_form_view_sla</field> |
|
||||
<field name="model">project.issue</field> |
|
||||
<field name="inherit_id" ref="project_issue.project_issue_form_view"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
|
|
||||
<page string="Extra Info" position="after"> |
|
||||
<page name="sla_page" string="Service Levels" |
|
||||
attrs="{'invisible': [('sla_state', '=', False)]}"> |
|
||||
<group> |
|
||||
<group> |
|
||||
<field name="sla_state" /> |
|
||||
</group> |
|
||||
<group> |
|
||||
<field name="write_date" /> |
|
||||
</group> |
|
||||
</group> |
|
||||
<field name="sla_control_ids"/> |
|
||||
</page> |
|
||||
</page> |
|
||||
|
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<!-- Project Issue List --> |
|
||||
<record model="ir.ui.view" id="project_issue_tree_view_sla"> |
|
||||
<field name="name">project_issue_tree_view_sla</field> |
|
||||
<field name="model">project.issue</field> |
|
||||
<field name="inherit_id" ref="project_issue.project_issue_tree_view"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
|
|
||||
<field name="project_id" position="after"> |
|
||||
<field name="sla_state"/> |
|
||||
</field> |
|
||||
|
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
|
|
||||
<!-- Project Issue Filter --> |
|
||||
<record id="view_project_issue_filter_sdesk" model="ir.ui.view"> |
|
||||
<field name="name">view_project_issue_filter_sdesk</field> |
|
||||
<field name="model">project.issue</field> |
|
||||
<field name="inherit_id" ref="project_issue.view_project_issue_filter"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
|
|
||||
<filter string="Priority" position="after"> |
|
||||
<filter string="SLA Status" context="{'group_by':'sla_state'}" /> |
|
||||
</filter> |
|
||||
|
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,86 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Copyright (C) 2013 Daniel Reis |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
from openerp.osv import fields, orm |
|
||||
|
|
||||
|
|
||||
class SLADefinition(orm.Model): |
|
||||
""" |
|
||||
SLA Definition |
|
||||
""" |
|
||||
_name = 'project.sla' |
|
||||
_description = 'SLA Definition' |
|
||||
_columns = { |
|
||||
'name': fields.char('Title', size=64, required=True, translate=True), |
|
||||
'active': fields.boolean('Active'), |
|
||||
'control_model': fields.char('For documents', size=128, required=True), |
|
||||
'control_field_id': fields.many2one( |
|
||||
'ir.model.fields', 'Control Date', required=True, |
|
||||
domain="[('model_id.model', '=', control_model)," |
|
||||
" ('ttype', 'in', ['date', 'datetime'])]", |
|
||||
help="Date field used to check if the SLA was achieved."), |
|
||||
'sla_line_ids': fields.one2many( |
|
||||
'project.sla.line', 'sla_id', 'Definitions'), |
|
||||
'analytic_ids': fields.many2many( |
|
||||
'account.analytic.account', string='Contracts'), |
|
||||
} |
|
||||
_defaults = { |
|
||||
'active': True, |
|
||||
} |
|
||||
|
|
||||
def _reapply_slas(self, cr, uid, ids, recalc_closed=False, context=None): |
|
||||
""" |
|
||||
Force SLA recalculation on all _open_ Contracts for the selected SLAs. |
|
||||
To use upon SLA Definition modifications. |
|
||||
""" |
|
||||
contract_obj = self.pool.get('account.analytic.account') |
|
||||
for sla in self.browse(cr, uid, ids, context=context): |
|
||||
contr_ids = [x.id for x in sla.analytic_ids if x.state == 'open'] |
|
||||
contract_obj._reapply_sla( |
|
||||
cr, uid, contr_ids, recalc_closed=recalc_closed, |
|
||||
context=context) |
|
||||
return True |
|
||||
|
|
||||
def reapply_slas(self, cr, uid, ids, context=None): |
|
||||
""" Reapply SLAs button action """ |
|
||||
return self._reapply_slas(cr, uid, ids, context=context) |
|
||||
|
|
||||
|
|
||||
class SLARules(orm.Model): |
|
||||
""" |
|
||||
SLA Definition Rule Lines |
|
||||
""" |
|
||||
_name = 'project.sla.line' |
|
||||
_definition = 'SLA Definition Rule Lines' |
|
||||
_order = 'sla_id,sequence' |
|
||||
_columns = { |
|
||||
'sla_id': fields.many2one('project.sla', 'SLA Definition'), |
|
||||
'sequence': fields.integer('Sequence'), |
|
||||
'name': fields.char('Title', size=64, required=True, translate=True), |
|
||||
'condition': fields.char( |
|
||||
'Condition', size=256, help="Apply only if this expression is " |
|
||||
"evaluated to True. The document fields can be accessed using " |
|
||||
"either o, obj or object. Example: obj.priority <= '2'"), |
|
||||
'limit_qty': fields.integer('Hours to Limit'), |
|
||||
'warn_qty': fields.integer('Hours to Warn'), |
|
||||
} |
|
||||
_defaults = { |
|
||||
'sequence': 10, |
|
||||
} |
|
@ -1,322 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Copyright (C) 2013 Daniel Reis |
|
||||
# |
|
||||
# This program is free software: you can redistribute it and/or modify |
|
||||
# it under the terms of the GNU Affero General Public License as |
|
||||
# published by the Free Software Foundation, either version 3 of the |
|
||||
# License, or (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
from openerp.osv import fields, orm |
|
||||
from openerp.tools.safe_eval import safe_eval |
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as DT_FMT |
|
||||
from openerp import SUPERUSER_ID |
|
||||
from datetime import timedelta |
|
||||
from datetime import datetime as dt |
|
||||
import m2m |
|
||||
|
|
||||
import logging |
|
||||
_logger = logging.getLogger(__name__) |
|
||||
|
|
||||
|
|
||||
SLA_STATES = [('5', 'Failed'), ('4', 'Will Fail'), ('3', 'Warning'), |
|
||||
('2', 'Watching'), ('1', 'Achieved')] |
|
||||
|
|
||||
|
|
||||
def safe_getattr(obj, dotattr, default=False): |
|
||||
""" |
|
||||
Follow an object attribute dot-notation chain to find the leaf value. |
|
||||
If any attribute doesn't exist or has no value, just return False. |
|
||||
Checks hasattr ahead, to avoid ORM Browse log warnings. |
|
||||
""" |
|
||||
attrs = dotattr.split('.') |
|
||||
while attrs: |
|
||||
attr = attrs.pop(0) |
|
||||
if attr in obj._model._columns: |
|
||||
try: |
|
||||
obj = getattr(obj, attr) |
|
||||
except AttributeError: |
|
||||
return default |
|
||||
if not obj: |
|
||||
return default |
|
||||
else: |
|
||||
return default |
|
||||
return obj |
|
||||
|
|
||||
|
|
||||
class SLAControl(orm.Model): |
|
||||
""" |
|
||||
SLA Control Registry |
|
||||
Each controlled document (Issue, Claim, ...) will have a record here. |
|
||||
This model concentrates all the logic for Service Level calculation. |
|
||||
""" |
|
||||
_name = 'project.sla.control' |
|
||||
_description = 'SLA Control Registry' |
|
||||
|
|
||||
_columns = { |
|
||||
'doc_id': fields.integer('Document ID', readonly=True), |
|
||||
'doc_model': fields.char('Document Model', size=128, readonly=True), |
|
||||
'sla_line_id': fields.many2one( |
|
||||
'project.sla.line', 'Service Agreement'), |
|
||||
'sla_warn_date': fields.datetime('Warning Date'), |
|
||||
'sla_limit_date': fields.datetime('Limit Date'), |
|
||||
'sla_start_date': fields.datetime('Start Date'), |
|
||||
'sla_close_date': fields.datetime('Close Date'), |
|
||||
'sla_achieved': fields.integer('Achieved?'), |
|
||||
'sla_state': fields.selection(SLA_STATES, string="SLA Status"), |
|
||||
'locked': fields.boolean( |
|
||||
'Recalculation disabled', |
|
||||
help="Safeguard manual changes from future automatic " |
|
||||
"recomputations."), |
|
||||
# Future: perfect SLA manual handling |
|
||||
} |
|
||||
|
|
||||
def write(self, cr, uid, ids, vals, context=None): |
|
||||
""" |
|
||||
Update the related Document's SLA State when any of the SLA Control |
|
||||
lines changes state |
|
||||
""" |
|
||||
res = super(SLAControl, self).write( |
|
||||
cr, uid, ids, vals, context=context) |
|
||||
new_state = vals.get('sla_state') |
|
||||
if new_state: |
|
||||
# just update sla_state without recomputing the whole thing |
|
||||
context = context or {} |
|
||||
context['__sla_stored__'] = 1 |
|
||||
for sla in self.browse(cr, uid, ids, context=context): |
|
||||
doc = self.pool.get(sla.doc_model).browse( |
|
||||
cr, uid, sla.doc_id, context=context) |
|
||||
if doc.sla_state < new_state: |
|
||||
doc.write({'sla_state': new_state}) |
|
||||
return res |
|
||||
|
|
||||
def update_sla_states(self, cr, uid, context=None): |
|
||||
""" |
|
||||
Updates SLA States, given the current datetime: |
|
||||
Only works on "open" sla states (watching, warning and will fail): |
|
||||
- exceeded limit date are set to "will fail" |
|
||||
- exceeded warning dates are set to "warning" |
|
||||
To be used by a scheduled job. |
|
||||
""" |
|
||||
now = dt.now().strftime(DT_FMT) |
|
||||
# SLAs to mark as "will fail" |
|
||||
control_ids = self.search( |
|
||||
cr, uid, |
|
||||
[('sla_state', 'in', ['2', '3']), ('sla_limit_date', '<', now)], |
|
||||
context=context) |
|
||||
self.write(cr, uid, control_ids, {'sla_state': '4'}, context=context) |
|
||||
# SLAs to mark as "warning" |
|
||||
control_ids = self.search( |
|
||||
cr, uid, |
|
||||
[('sla_state', 'in', ['2']), ('sla_warn_date', '<', now)], |
|
||||
context=context) |
|
||||
self.write(cr, uid, control_ids, {'sla_state': '3'}, context=context) |
|
||||
return True |
|
||||
|
|
||||
def _compute_sla_date(self, cr, uid, working_hours, res_uid, |
|
||||
start_date, hours, context=None): |
|
||||
""" |
|
||||
Return a limit datetime by adding hours to a start_date, honoring |
|
||||
a working_time calendar and a resource's (res_uid) timezone and |
|
||||
availability (leaves) |
|
||||
|
|
||||
Currently implemented using a binary search using |
|
||||
_interval_hours_get() from resource.calendar. This is |
|
||||
resource.calendar agnostic, but could be more efficient if |
|
||||
implemented based on it's logic. |
|
||||
|
|
||||
Known issue: the end date can be a non-working time; it would be |
|
||||
best for it to be the latest working time possible. Example: |
|
||||
if working time is 08:00 - 16:00 and start_date is 19:00, the +8h |
|
||||
end date will be 19:00 of the next day, and it should rather be |
|
||||
16:00 of the next day. |
|
||||
""" |
|
||||
assert isinstance(start_date, dt) |
|
||||
assert isinstance(hours, int) and hours >= 0 |
|
||||
|
|
||||
cal_obj = self.pool.get('resource.calendar') |
|
||||
target, step = hours * 3600, 16 * 3600 |
|
||||
lo, hi = start_date, start_date |
|
||||
while target > 0 and step > 60: |
|
||||
hi = lo + timedelta(seconds=step) |
|
||||
check = int(3600 * cal_obj._interval_hours_get( |
|
||||
cr, uid, working_hours, lo, hi, |
|
||||
timezone_from_uid=res_uid, exclude_leaves=False, |
|
||||
context=context)) |
|
||||
if check <= target: |
|
||||
target -= check |
|
||||
lo = hi |
|
||||
else: |
|
||||
step = int(step / 4.0) |
|
||||
return hi |
|
||||
|
|
||||
def _get_computed_slas(self, cr, uid, doc, context=None): |
|
||||
""" |
|
||||
Returns a dict with the computed data for SLAs, given a browse record |
|
||||
for the target document. |
|
||||
|
|
||||
* The SLA used is either from a related analytic_account_id or |
|
||||
project_id, whatever is found first. |
|
||||
* The work calendar is taken from the Project's definitions ("Other |
|
||||
Info" tab -> Working Time). |
|
||||
* The timezone used for the working time calculations are from the |
|
||||
document's responsible User (user_id) or from the current User (uid). |
|
||||
|
|
||||
For the SLA Achieved calculation: |
|
||||
|
|
||||
* Creation date is used to start counting time |
|
||||
* Control date, used to calculate SLA achievement, is defined in the |
|
||||
SLA Definition rules. |
|
||||
""" |
|
||||
def datetime2str(dt_value, fmt): # tolerant datetime to string |
|
||||
return dt_value and dt.strftime(dt_value, fmt) or None |
|
||||
|
|
||||
res = [] |
|
||||
sla_ids = (safe_getattr(doc, 'analytic_account_id.sla_ids') or |
|
||||
safe_getattr(doc, 'project_id.analytic_account_id.sla_ids')) |
|
||||
if not sla_ids: |
|
||||
return res |
|
||||
|
|
||||
for sla in sla_ids: |
|
||||
if sla.control_model != doc._table_name: |
|
||||
continue # SLA not for this model; skip |
|
||||
|
|
||||
for l in sla.sla_line_ids: |
|
||||
eval_context = {'o': doc, 'obj': doc, 'object': doc} |
|
||||
if not l.condition or safe_eval(l.condition, eval_context): |
|
||||
start_date = dt.strptime(doc.create_date, DT_FMT) |
|
||||
res_uid = doc.user_id.id or uid |
|
||||
cal = safe_getattr( |
|
||||
doc, 'project_id.resource_calendar_id.id') |
|
||||
warn_date = self._compute_sla_date( |
|
||||
cr, uid, cal, res_uid, start_date, l.warn_qty, |
|
||||
context=context) |
|
||||
lim_date = self._compute_sla_date( |
|
||||
cr, uid, cal, res_uid, warn_date, |
|
||||
l.limit_qty - l.warn_qty, |
|
||||
context=context) |
|
||||
# evaluate sla state |
|
||||
control_val = getattr(doc, sla.control_field_id.name) |
|
||||
if control_val: |
|
||||
control_date = dt.strptime(control_val, DT_FMT) |
|
||||
if control_date > lim_date: |
|
||||
sla_val, sla_state = 0, '5' # failed |
|
||||
else: |
|
||||
sla_val, sla_state = 1, '1' # achieved |
|
||||
else: |
|
||||
control_date = None |
|
||||
now = dt.now() |
|
||||
if now > lim_date: |
|
||||
sla_val, sla_state = 0, '4' # will fail |
|
||||
elif now > warn_date: |
|
||||
sla_val, sla_state = 0, '3' # warning |
|
||||
else: |
|
||||
sla_val, sla_state = 0, '2' # watching |
|
||||
|
|
||||
res.append( |
|
||||
{'sla_line_id': l.id, |
|
||||
'sla_achieved': sla_val, |
|
||||
'sla_state': sla_state, |
|
||||
'sla_warn_date': datetime2str(warn_date, DT_FMT), |
|
||||
'sla_limit_date': datetime2str(lim_date, DT_FMT), |
|
||||
'sla_start_date': datetime2str(start_date, DT_FMT), |
|
||||
'sla_close_date': datetime2str(control_date, DT_FMT), |
|
||||
'doc_id': doc.id, |
|
||||
'doc_model': sla.control_model}) |
|
||||
break |
|
||||
|
|
||||
if sla_ids and not res: |
|
||||
_logger.warning("No valid SLA rule found for %d, SLA Ids %s" |
|
||||
% (doc.id, repr([x.id for x in sla_ids]))) |
|
||||
return res |
|
||||
|
|
||||
def store_sla_control(self, cr, uid, docs, context=None): |
|
||||
""" |
|
||||
Used by controlled documents to ask for SLA calculation and storage. |
|
||||
``docs`` is a Browse object |
|
||||
""" |
|
||||
# context flag to avoid infinite loops on further writes |
|
||||
context = context or {} |
|
||||
if '__sla_stored__' in context: |
|
||||
return False |
|
||||
else: |
|
||||
context['__sla_stored__'] = 1 |
|
||||
|
|
||||
res = [] |
|
||||
for ix, doc in enumerate(docs): |
|
||||
if ix and ix % 50 == 0: |
|
||||
_logger.info('...%d SLAs recomputed for %s' % (ix, doc._name)) |
|
||||
control = {x.sla_line_id.id: x |
|
||||
for x in doc.sla_control_ids} |
|
||||
sla_recs = self._get_computed_slas(cr, uid, doc, context=context) |
|
||||
# calc sla control lines |
|
||||
if sla_recs: |
|
||||
slas = [] |
|
||||
for sla_rec in sla_recs: |
|
||||
sla_line_id = sla_rec.get('sla_line_id') |
|
||||
if sla_line_id in control: |
|
||||
control_rec = control.get(sla_line_id) |
|
||||
if not control_rec.locked: |
|
||||
slas += m2m.write(control_rec.id, sla_rec) |
|
||||
else: |
|
||||
slas += m2m.add(sla_rec) |
|
||||
else: |
|
||||
slas = m2m.clear() |
|
||||
# calc sla control summary |
|
||||
vals = {'sla_state': None, 'sla_control_ids': slas} |
|
||||
if sla_recs and doc.sla_control_ids: |
|
||||
vals['sla_state'] = max( |
|
||||
x.sla_state for x in doc.sla_control_ids) |
|
||||
# store sla |
|
||||
doc._model.write( # regular users can't write on SLA Control |
|
||||
cr, SUPERUSER_ID, [doc.id], vals, context=context) |
|
||||
return res |
|
||||
|
|
||||
|
|
||||
class SLAControlled(orm.AbstractModel): |
|
||||
""" |
|
||||
SLA Controlled documents: AbstractModel to apply SLA control on Models |
|
||||
""" |
|
||||
_name = 'project.sla.controlled' |
|
||||
_description = 'SLA Controlled Document' |
|
||||
_columns = { |
|
||||
'sla_control_ids': fields.many2many( |
|
||||
'project.sla.control', string="SLA Control", ondelete='cascade'), |
|
||||
'sla_state': fields.selection( |
|
||||
SLA_STATES, string="SLA Status", readonly=True), |
|
||||
} |
|
||||
|
|
||||
def create(self, cr, uid, vals, context=None): |
|
||||
res = super(SLAControlled, self).create(cr, uid, vals, context=context) |
|
||||
docs = self.browse(cr, uid, [res], context=context) |
|
||||
self.pool.get('project.sla.control').store_sla_control( |
|
||||
cr, uid, docs, context=context) |
|
||||
return res |
|
||||
|
|
||||
def write(self, cr, uid, ids, vals, context=None): |
|
||||
res = super(SLAControlled, self).write( |
|
||||
cr, uid, ids, vals, context=context) |
|
||||
docs = [x for x in self.browse(cr, uid, ids, context=context) |
|
||||
if (x.state != 'cancelled') and |
|
||||
(x.state != 'done' or x.sla_state not in ['1', '5'])] |
|
||||
self.pool.get('project.sla.control').store_sla_control( |
|
||||
cr, uid, docs, context=context) |
|
||||
return res |
|
||||
|
|
||||
def unlink(self, cr, uid, ids, context=None): |
|
||||
# Unlink and delete all related Control records |
|
||||
for doc in self.browse(cr, uid, ids, context=context): |
|
||||
vals = [m2m.remove(x.id)[0] for x in doc.sla_control_ids] |
|
||||
doc.write({'sla_control_ids': vals}) |
|
||||
return super(SLAControlled, self).unlink(cr, uid, ids, context=context) |
|
@ -1,18 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<openerp> |
|
||||
<data noupdate="1"> |
|
||||
|
|
||||
<record id="ir_cron_sla_action" model="ir.cron"> |
|
||||
<field name="name">Update SLA States</field> |
|
||||
<field name="priority" eval="100"/> |
|
||||
<field name="interval_number">1</field> |
|
||||
<field name="interval_type">hours</field> |
|
||||
<field name="numbercall">-1</field> |
|
||||
<field name="doall" eval="False"/> |
|
||||
<field name="model">project.sla.control</field> |
|
||||
<field name="function">update_sla_states</field> |
|
||||
<field name="args">()</field> |
|
||||
</record> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,25 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<!-- List view used when the sla_control_ids field |
|
||||
is added to controlled document's form --> |
|
||||
<record id="view_sla_control_tree" model="ir.ui.view"> |
|
||||
<field name="name">view_sla_control_tree</field> |
|
||||
<field name="model">project.sla.control</field> |
|
||||
<field name="arch" type="xml"> |
|
||||
|
|
||||
<tree string="Service Level"> |
|
||||
<field name="sla_line_id"/> |
|
||||
<field name="sla_state"/> |
|
||||
<field name="sla_start_date"/> |
|
||||
<field name="sla_warn_date"/> |
|
||||
<field name="sla_limit_date"/> |
|
||||
<field name="sla_close_date"/> |
|
||||
</tree> |
|
||||
|
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,138 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<!-- Working Time calendar --> |
|
||||
<record id="worktime_9_18" model="resource.calendar"> |
|
||||
<field name="name">Working Days 09-13 14-18</field> |
|
||||
</record> |
|
||||
<record id="worktime 9_18_0M" model="resource.calendar.attendance"> |
|
||||
<field name="dayofweek">0</field> |
|
||||
<field name="name">Monday Morning</field> |
|
||||
<field name="hour_from">9</field> |
|
||||
<field name="hour_to">13</field> |
|
||||
<field name="calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
<record id="worktime 9_18_0A" model="resource.calendar.attendance"> |
|
||||
<field name="dayofweek">0</field> |
|
||||
<field name="name">Monday Afternoon</field> |
|
||||
<field name="hour_from">14</field> |
|
||||
<field name="hour_to">18</field> |
|
||||
<field name="calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
<record id="worktime 9_18_1M" model="resource.calendar.attendance"> |
|
||||
<field name="dayofweek">1</field> |
|
||||
<field name="name">Tuesday Morning</field> |
|
||||
<field name="hour_from">9</field> |
|
||||
<field name="hour_to">13</field> |
|
||||
<field name="calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
<record id="worktime 9_18_1A" model="resource.calendar.attendance"> |
|
||||
<field name="dayofweek">1</field> |
|
||||
<field name="name">Tuesday Afternoon</field> |
|
||||
<field name="hour_from">14</field> |
|
||||
<field name="hour_to">18</field> |
|
||||
<field name="calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
<record id="worktime 9_18_2M" model="resource.calendar.attendance"> |
|
||||
<field name="dayofweek">2</field> |
|
||||
<field name="name">Wednesday Morning</field> |
|
||||
<field name="hour_from">9</field> |
|
||||
<field name="hour_to">13</field> |
|
||||
<field name="calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
<record id="worktime 9_18_2A" model="resource.calendar.attendance"> |
|
||||
<field name="dayofweek">2</field> |
|
||||
<field name="name">Wednesday Afternoon</field> |
|
||||
<field name="hour_from">14</field> |
|
||||
<field name="hour_to">18</field> |
|
||||
<field name="calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
<record id="worktime 9_18_3M" model="resource.calendar.attendance"> |
|
||||
<field name="dayofweek">3</field> |
|
||||
<field name="name">Thursday Morning</field> |
|
||||
<field name="hour_from">9</field> |
|
||||
<field name="hour_to">13</field> |
|
||||
<field name="calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
<record id="worktime 9_18_3A" model="resource.calendar.attendance"> |
|
||||
<field name="dayofweek">3</field> |
|
||||
<field name="name">Thursday Afternoon</field> |
|
||||
<field name="hour_from">14</field> |
|
||||
<field name="hour_to">18</field> |
|
||||
<field name="calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
<record id="worktime 9_18_4M" model="resource.calendar.attendance"> |
|
||||
<field name="dayofweek">4</field> |
|
||||
<field name="name">Friday Morning</field> |
|
||||
<field name="hour_from">9</field> |
|
||||
<field name="hour_to">13</field> |
|
||||
<field name="calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
<record id="worktime 9_18_4A" model="resource.calendar.attendance"> |
|
||||
<field name="dayofweek">4</field> |
|
||||
<field name="name">Friday Afternoon</field> |
|
||||
<field name="hour_from">14</field> |
|
||||
<field name="hour_to">18</field> |
|
||||
<field name="calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
|
|
||||
<!-- Set Project Calendar --> |
|
||||
<record id="project.project_project_1" model="project.project"> |
|
||||
<field name="resource_calendar_id" ref="worktime_9_18" /> |
|
||||
</record> |
|
||||
|
|
||||
<!-- SLA Definition and Rules --> |
|
||||
<record id="sla_resolution" model="project.sla"> |
|
||||
<field name="name">Standard Resolution Time</field> |
|
||||
<field name="control_model">project.issue</field> |
|
||||
<field name="control_field_id" |
|
||||
ref="project_issue.field_project_issue_date_closed"/> |
|
||||
</record> |
|
||||
<record id="sla_resolution_rule1" model="project.sla.line"> |
|
||||
<field name="sla_id" ref="sla_resolution"/> |
|
||||
<field name="sequence">10</field> |
|
||||
<field name="name">Resolution in two business days</field> |
|
||||
<field name="condition">obj.priority <= '2'</field> |
|
||||
<field name="limit_qty">16</field> |
|
||||
<field name="warn_qty">8</field> |
|
||||
</record> |
|
||||
<record id="sla_resolution_rule2" model="project.sla.line"> |
|
||||
<field name="sla_id" ref="sla_resolution"/> |
|
||||
<field name="sequence">20</field> |
|
||||
<field name="name">Resolution in three business days</field> |
|
||||
<field name="condition"></field> |
|
||||
<field name="limit_qty">24</field> |
|
||||
<field name="warn_qty">16</field> |
|
||||
</record> |
|
||||
|
|
||||
<record id="sla_response" model="project.sla"> |
|
||||
<field name="name">Standard Response Time</field> |
|
||||
<field name="control_model">project.issue</field> |
|
||||
<field name="control_field_id" |
|
||||
ref="project_issue.field_project_issue_date_open"/> |
|
||||
</record> |
|
||||
<record id="sla_response_rule1" model="project.sla.line"> |
|
||||
<field name="sla_id" ref="sla_response"/> |
|
||||
<field name="sequence">10</field> |
|
||||
<field name="name">Response in one business day</field> |
|
||||
<field name="condition">obj.priority <= '2'</field> |
|
||||
<field name="limit_qty">8</field> |
|
||||
<field name="warn_qty">4</field> |
|
||||
</record> |
|
||||
<record id="sla_response_rule2" model="project.sla.line"> |
|
||||
<field name="sla_id" ref="sla_response"/> |
|
||||
<field name="sequence">20</field> |
|
||||
<field name="name">Response in two business days</field> |
|
||||
<field name="condition"></field> |
|
||||
<field name="limit_qty">16</field> |
|
||||
<field name="warn_qty">8</field> |
|
||||
</record> |
|
||||
|
|
||||
<!-- Set Contract Resolution SLA Definition --> |
|
||||
<record id="project.project_project_1_account_analytic_account" model="account.analytic.account"> |
|
||||
<field name="sla_ids" eval="[(6, 0, [ref('sla_resolution')])]" /> |
|
||||
</record> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,48 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<record id="view_sla_lines_tree" model="ir.ui.view"> |
|
||||
<field name="name">view_sla_lines_tree</field> |
|
||||
<field name="model">project.sla.line</field> |
|
||||
<field name="arch" type="xml"> |
|
||||
|
|
||||
<tree string="Definitions"> |
|
||||
<field name="sequence"/> |
|
||||
<field name="name"/> |
|
||||
<field name="condition"/> |
|
||||
<field name="limit_qty"/> |
|
||||
<field name="warn_qty"/> |
|
||||
</tree> |
|
||||
|
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<record id="view_sla_form" model="ir.ui.view"> |
|
||||
<field name="name">view_sla_form</field> |
|
||||
<field name="model">project.sla</field> |
|
||||
<field name="arch" type="xml"> |
|
||||
|
|
||||
<form string="SLA Definition"> |
|
||||
<field name="name"/> |
|
||||
<field name="active"/> |
|
||||
<field name="control_model"/> |
|
||||
<field name="control_field_id"/> |
|
||||
<notebook colspan="4"> |
|
||||
<page string="Rules" name="rules_page"> |
|
||||
<field name="sla_line_ids" nolabel="1"/> |
|
||||
</page> |
|
||||
<page string="Contracts" name="contracts_page"> |
|
||||
<field name="analytic_ids" nolabel="1" /> |
|
||||
</page> |
|
||||
</notebook> |
|
||||
<button name="reapply_slas" colspan="2" |
|
||||
string="Reapply SLA on Contracts" |
|
||||
type="object" /> |
|
||||
</form> |
|
||||
|
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,20 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
|
|
||||
<record id="edit_project_sla" model="ir.ui.view"> |
|
||||
<field name="name">edit_project_sla</field> |
|
||||
<field name="model">project.project</field> |
|
||||
<field name="inherit_id" ref="project.edit_project"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
|
|
||||
<!-- make resource calendar always visible --> |
|
||||
<group string="Administration" position="attributes"> |
|
||||
<attribute name="groups"/> |
|
||||
</group> |
|
||||
|
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</data> |
|
||||
</openerp> |
|
@ -1,8 +0,0 @@ |
|||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
|
||||
access_sla_manager,access_sla_manager,model_project_sla,project.group_project_manager,1,1,1,1 |
|
||||
access_sla_user,access_sla_user,model_project_sla,base.group_user,1,0,0,0 |
|
||||
access_sla_lines_manager,access_sla_lines_manager,model_project_sla_line,project.group_project_manager,1,1,1,1 |
|
||||
access_sla_lines_user,access_sla_lines_user,model_project_sla_line,base.group_user,1,0,0,0 |
|
||||
access_sla_control_manager,access_sla_control_manager,model_project_sla_control,project.group_project_manager,1,1,0,0 |
|
||||
access_sla_control_user,access_sla_control_user,model_project_sla_control,base.group_user,1,0,0,0 |
|
||||
access_sla_controlled_manager,access_sla_controlled_manager,model_project_sla_controlled,project.group_project_manager,1,1,1,1 |
|
Before Width: 128 | Height: 128 | Size: 9.3 KiB |
@ -1,66 +0,0 @@ |
|||||
- |
|
||||
Cleanup previous test run |
|
||||
- |
|
||||
!python {model: project.issue}: | |
|
||||
res = self.search(cr, uid, [('name', '=', 'My monitor is flickering')]) |
|
||||
self.unlink(cr, uid, res) |
|
||||
- |
|
||||
Create a new Issue |
|
||||
- |
|
||||
!record {model: project.issue, id: issue1, view: False}: |
|
||||
name: "My monitor is flickering" |
|
||||
project_id: project.project_project_1 |
|
||||
priority: "3" |
|
||||
user_id: base.user_root |
|
||||
partner_id: base.res_partner_2 |
|
||||
email_from: agr@agrolait.com |
|
||||
categ_ids: |
|
||||
- project_issue.project_issue_category_01 |
|
||||
- |
|
||||
Close the Issue |
|
||||
- |
|
||||
!python {model: project.issue}: | |
|
||||
self.case_close(cr, uid, [ref("issue1")]) |
|
||||
- |
|
||||
Force the Issue's Create Date and Close Date |
|
||||
Created friday before opening hour, closed on next monday near closing hour |
|
||||
- |
|
||||
!python {model: project.issue}: | |
|
||||
import time |
|
||||
self.write(cr, uid, [ref("issue1"),], { |
|
||||
'create_date': time.strftime('2013-11-22 06:15:00'), |
|
||||
'date_closed': time.strftime('2013-11-25 16:45:00'), |
|
||||
}) |
|
||||
- |
|
||||
There should be Service Level info generated on the Issue |
|
||||
- |
|
||||
!assert {model: project.issue, id: issue1, string: Issue should have calculated service levels}: |
|
||||
- len(sla_control_ids) == 2 |
|
||||
- |
|
||||
Assign an additional "Response SLA" to the Contract |
|
||||
- |
|
||||
!python {model: account.analytic.account}: | |
|
||||
self.write(cr, uid, [ref('project.project_project_1_account_analytic_account')], |
|
||||
{'sla_ids': [(4, ref('sla_response'))]}) |
|
||||
- |
|
||||
Button to Reapply the SLA Definition |
|
||||
- |
|
||||
!python {model: project.sla}: | |
|
||||
self._reapply_slas(cr, uid, [ref('sla_resolution')], recalc_closed=True) |
|
||||
- |
|
||||
There should be two Service Level lines generated on the Issue |
|
||||
- |
|
||||
!assert {model: project.issue, id: issue1, string: Issue should have two calculated service levels}: |
|
||||
- len(sla_control_ids) == 2 |
|
||||
- |
|
||||
The Issue's Resolution SLA should be "3 business days" |
|
||||
- |
|
||||
!python {model: project.issue}: | |
|
||||
issue = self.browse(cr, uid, ref('issue1')) |
|
||||
for x in issue.sla_control_ids: |
|
||||
print x.sla_line_id.name |
|
||||
if x.sla_line_id.id == ref("sla_resolution_rule2"): |
|
||||
assert x.sla_achieved == 1, "Issue resolution SLA should be achieved" |
|
||||
break |
|
||||
else: |
|
||||
assert False, 'Issue Resolution SLA should be "3 business days"' |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue