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. |
@ -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