# -*- 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 . # ############################################################################## 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: # 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=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", string="Description", store=True, readonly=True), 'number': fields.related( 'invoice_id', 'number', type="char", 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), } ############################################################################ ## 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') }