mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
Merge pull request #12 from dreispt/master
Remove files from deprecated project
This commit is contained in:
19
.coveragerc
19
.coveragerc
@@ -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:
|
||||
56
.gitignore
vendored
56
.gitignore
vendored
@@ -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
|
||||
44
.travis.yml
44
.travis.yml
@@ -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 ""
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
Before Width: | Height: | 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
|
||||
|
Binary file not shown.
|
Before Width: | Height: | 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"'
|
||||
Reference in New Issue
Block a user