diff --git a/analytic_hours_block/__openerp__.py b/analytic_hours_block/__openerp__.py index 4a0b8f45..09c78ad7 100644 --- a/analytic_hours_block/__openerp__.py +++ b/analytic_hours_block/__openerp__.py @@ -20,31 +20,35 @@ ############################################################################## { - "name" : "Project Hours Blocks Management", - "description" : """ + "name": "Project Hours Blocks Management", + "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 +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. """, - "version" : "1.2", - "author" : "Camptocamp", - "category" : "Generic Modules/Projects & Services", + "version": "1.3", + "author": "Camptocamp", + "license": 'AGPL-3', + "category": "Generic Modules/Projects & Services", "website": "http://www.camptocamp.com", - "depends" : [ - "account", - "hr_timesheet_invoice", - "analytic" - ], - "init_xml" : [], - "update_xml" : [ - "hours_block_view.xml", - "hours_block_menu.xml", - "report.xml", - "security/hours_block_security.xml", - "security/ir.model.access.csv", - ], + "depends": [ + "account", + "hr_timesheet_invoice", + "analytic" + ], + "data": [ + "hours_block_view.xml", + "hours_block_menu.xml", + "report.xml", + "security/hours_block_security.xml", + "security/ir.model.access.csv", + ], "active": False, "installable": True } diff --git a/analytic_hours_block/hours_block.py b/analytic_hours_block/hours_block.py index e191f3ab..256c2baf 100644 --- a/analytic_hours_block/hours_block.py +++ b/analytic_hours_block/hours_block.py @@ -19,92 +19,75 @@ # ############################################################################## -import time -import string +from openerp.osv import orm, fields -from osv import osv, fields -import netsvc - -############################################################################ -## Add hours blocks on invoice -############################################################################ - -class AccountHoursBlock(osv.osv): +class AccountHoursBlock(orm.Model): _name = "account.hours.block" def _get_last_action(self, cr, uid, ids, name, arg, context=None): - """TODO""" - context = context or {} + """ Return the last analytic line date for an invoice""" res = {} - for block in self.browse(cr, uid, ids): + 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() - if fetch_res: - date = fetch_res[0] - else: - date = False - res[block.id] = date + 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]""" - context = context or {} - if not isinstance(ids, list): - ids=[ids] + if isinstance(ids, (int, long)): + ids = [ids] result = {} aal_obj = self.pool.get('account.analytic.line') - for block in self.browse(cr,uid,ids): - result[block.id] = {'amount_hours_block' : 0.0, - 'amount_hours_block_done' : 0.0, - 'amount_hours_block_delta' : 0.0} + 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: - ## We will now calculate the product_quantity + # 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 current block + # 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,)) - res2 = cr.fetchall() - if res2: - ids2 = [x[0] for x in res2] - else: - ids2 = [] - for line in aal_obj.browse(cr, uid, ids2, context): - if line.product_uom_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 - if factor == 0.0: - factor = 1.0 - else: - factor = 1.0 factor_invoicing = 1.0 if line.to_invoice and line.to_invoice.factor != 0.0: - factor_invoicing = 1.0 - line.to_invoice.factor / 100 + 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): + 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): + for block in self.browse(cr, uid, ids, context=context): result[block.id] = {'amount_hours_block' : 0.0, - 'amount_hours_block_done' : 0.0, - 'amount_hours_block_delta' : 0.0} + 'amount_hours_block_done' : 0.0} # Compute amount bought for line in block.invoice_id.invoice_line: @@ -125,95 +108,193 @@ class AccountHoursBlock(osv.osv): " WHERE aj.id = al.journal_id" " AND aj.type='general'" " AND al.invoice_id = %s", (block.invoice_id.id,)) - res2 = cr.fetchall() - if res2: - ids2 = [x[0] for x in res2] - else: - ids2 = [] + 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, ids2, context): + 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 = {'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] + 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): + def _compute(self, cr, uid, ids, fields, args, context=None): result = {} block_per_types = {} for block in self.browse(cr, uid, ids, context=context): - if not block.type in block_per_types.keys(): - block_per_types[block.type] = [] - block_per_types[block.type].append(block.id) - + 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)) + 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'] + result[block]['amount_hours_block'] - \ + result[block]['amount_hours_block_done'] return result _columns = { - 'amount_hours_block': fields.function(_compute, method=True, type='float', string='Quantity /Amount bought', store=True, - multi='amount_hours_block_delta', - help="Amount bought by the customer. This amount is expressed in the base UoM (factor=1.0)"), - 'amount_hours_block_done': fields.function(_compute, method=True, type='float', string='Quantity / Amount used', store=True, - multi='amount_hours_block_delta', - help="Amount done by the staff. This amount is expressed in the base UoM (factor=1.0)"), - 'amount_hours_block_delta': fields.function(_compute, method=True, type='float', string='Difference', store=True, - multi='amount_hours_block_delta', - help="Difference between bought and used. This amount is expressed in the base UoM (factor=1.0)"), - 'last_action_date': fields.function(_get_last_action, method=True, type='date', string='Last action date', - help="Date of the last analytic line linked to the invoice related to this block hours."), + 'amount_hours_block': fields.function( + _compute, + type='float', + string='Quantity / Amount bought', + store=True, + 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=True, + 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=True, + 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')], 'Type of Block', - required=True, help="Choose if you want a time or amount base block."), + '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=True, readonly=True), - 'user_id': fields.related('invoice_id', 'user_id', type="many2one", relation="res.users", string="Salesman", store=True, readonly=True), - 'partner_id': fields.related('invoice_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True, readonly=True), - 'name': fields.related('invoice_id', 'name', type="char",size=64, string="Description", store=True,readonly=True), - 'number': fields.related('invoice_id', 'number', type="char",size=64, string="Number", store=True,readonly=True), - 'journal_id': fields.related('invoice_id', 'journal_id', type="many2one", relation="account.journal", string="Journal", store=True,readonly=True), - 'period_id': fields.related('invoice_id', 'period_id', type="many2one", relation="account.period", string="Period", store=True,readonly=True), - 'company_id': fields.related('invoice_id', 'company_id', type="many2one", relation="res.company", string="Company", store=True,readonly=True), - 'currency_id': fields.related('invoice_id', 'currency_id', type="many2one", relation="res.currency", string="Currency", store=True,readonly=True), - 'residual': fields.related('invoice_id', 'residual', type="float", string="Residual", store=True,readonly=True), - 'amount_total': fields.related('invoice_id', 'amount_total', type="float", string="Total", store=True,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=True), + 'date_invoice': fields.related( + 'invoice_id', 'date_invoice', + type="date", + string="Invoice Date", + store=True, + readonly=True), + 'user_id': fields.related( + 'invoice_id', 'user_id', + type="many2one", + relation="res.users", + string="Salesman", + store=True, + readonly=True), + 'partner_id': fields.related( + 'invoice_id', 'partner_id', + type="many2one", + relation="res.partner", + string="Partner", + store=True, + readonly=True), + 'name': fields.related( + 'invoice_id', 'name', + type="char", + size=64, + string="Description", + store=True, + readonly=True), + 'number': fields.related( + 'invoice_id', 'number', + type="char", + size=64, + string="Number", + store=True, + readonly=True), + 'journal_id': fields.related( + 'invoice_id', 'journal_id', + type="many2one", + relation="account.journal", + string="Journal", + store=True, + readonly=True), + 'period_id': fields.related( + 'invoice_id', 'period_id', + type="many2one", + relation="account.period", + string="Period", + store=True, + readonly=True), + 'company_id': fields.related( + 'invoice_id', 'company_id', + type="many2one", + relation="res.company", + string="Company", + store=True, + readonly=True), + 'currency_id': fields.related( + 'invoice_id', 'currency_id', + type="many2one", + relation="res.currency", + string="Currency", + store=True, + readonly=True), + 'residual': fields.related( + 'invoice_id', 'residual', + type="float", + string="Residual", + store=True, + readonly=True), + 'amount_total': fields.related( + 'invoice_id', 'amount_total', + type="float", + string="Total", + store=True, + 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=True), } -AccountHoursBlock() - -class AccountInvoice(osv.osv): +############################################################################ +## Add hours blocks on invoice +############################################################################ +class AccountInvoice(orm.Model): _inherit = 'account.invoice' + _columns = { - 'account_hours_block_ids': fields.one2many('account.hours.block', 'invoice_id', 'Hours Block') + 'account_hours_block_ids': fields.one2many( + 'account.hours.block', + 'invoice_id', + string='Hours Block') } - -AccountInvoice() diff --git a/analytic_hours_block/report/hours_block.py b/analytic_hours_block/report/hours_block.py index ad459fd2..906c694b 100644 --- a/analytic_hours_block/report/hours_block.py +++ b/analytic_hours_block/report/hours_block.py @@ -20,31 +20,26 @@ ############################################################################## import time -from report import report_sxw +from openerp.report import report_sxw 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) + super(account_hours_block, self).__init__(cr, uid, name, context=context) self.localcontext.update({ 'time': time, - 'format_date': self._get_and_change_date_format_for_swiss, 'analytic_lines': self._get_analytic_lines, }) self.context = context def _get_analytic_lines(self, hours_block): al_pool = self.pool.get('account.analytic.line') - al_ids = al_pool.search(self.cr, self.uid, - [['invoice_id', '=', hours_block.invoice_id.id]], - order='date desc') - res = al_pool.browse(self.cr, self.uid, al_ids) - return res - - def _get_and_change_date_format_for_swiss(self, date_to_format): - date_formatted = '' - if date_to_format: - date_formatted = strptime(date_to_format, '%Y-%m-%d').strftime('%d.%m.%Y') - return date_formatted + al_ids = al_pool.search( + self.cr, + self.uid, + [('invoice_id', '=', hours_block.invoice_id.id)], + 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) diff --git a/analytic_hours_block/report/hours_block.rml b/analytic_hours_block/report/hours_block.rml index 8fe879a8..1b66efa9 100644 --- a/analytic_hours_block/report/hours_block.rml +++ b/analytic_hours_block/report/hours_block.rml @@ -184,7 +184,7 @@ Invoice Date : - [[ o.date_invoice and format_date(o.date_invoice) or '' ]] + [[ o.date_invoice and formatLang(o.date_invoice, date=True) or '' ]] @@ -243,7 +243,7 @@ [[ repeatIn(analytic_lines(o),'l') ]] - [[ l.date and format_date(l.date) or '' ]] + [[ l.date and formatLang(l.date, date=True) or '' ]] [[ l.name or '' ]]