[MERGE] forward port of branch saas-3 up to 21b1203
authorChristophe Simonis <chs@odoo.com>
Wed, 29 Oct 2014 18:33:02 +0000 (19:33 +0100)
committerChristophe Simonis <chs@odoo.com>
Wed, 29 Oct 2014 18:33:02 +0000 (19:33 +0100)
1  2 
addons/account/account_invoice.py
addons/account/report/account_partner_balance.py
addons/hr_timesheet_invoice/report/account_analytic_profit.py
addons/product/pricelist.py
addons/product/product.py
addons/product_visible_discount/product_visible_discount.py
addons/sale/wizard/sale_line_invoice.py
addons/sale_margin/sale_margin.py
addons/web_calendar/static/src/js/web_calendar.js

@@@ -222,119 -254,132 +222,129 @@@ class account_invoice(models.Model)
              ('open','Open'),
              ('paid','Paid'),
              ('cancel','Cancelled'),
 -            ],'Status', select=True, readonly=True, track_visibility='onchange',
 -            help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Invoice. \
 -            \n* The \'Pro-forma\' when invoice is in Pro-forma status,invoice does not have an invoice number. \
 -            \n* The \'Open\' status is used when user create invoice,a invoice number is generated.Its in open status till user does not pay invoice. \
 -            \n* The \'Paid\' status is set automatically when the invoice is paid. Its related journal entries may or may not be reconciled. \
 -            \n* The \'Cancelled\' status is used when user cancel invoice.'),
 -        'sent': fields.boolean('Sent', readonly=True, help="It indicates that the invoice has been sent."),
 -        'date_invoice': fields.date('Invoice Date', readonly=True, states={'draft':[('readonly',False)]}, select=True, help="Keep empty to use the current date"),
 -        'date_due': fields.date('Due Date', readonly=True, states={'draft':[('readonly',False)]}, select=True,
 -            help="If you use payment terms, the due date will be computed automatically at the generation "\
 -                "of accounting entries. The payment term may compute several due dates, for example 50% now and 50% in one month, but if you want to force a due date, make sure that the payment term is not set on the invoice. If you keep the payment term and the due date empty, it means direct payment."),
 -        'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}, track_visibility='always'),
 -        'payment_term': fields.many2one('account.payment.term', 'Payment Terms',readonly=True, states={'draft':[('readonly',False)]},
 -            help="If you use payment terms, the due date will be computed automatically at the generation "\
 -                "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
 -                "The payment term may compute several due dates, for example 50% now, 50% in one month."),
 -        'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation(invoice) date.", readonly=True, states={'draft':[('readonly',False)]}),
 -
 -        'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
 -        'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
 -        'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
 -
 -        'move_id': fields.many2one('account.move', 'Journal Entry', readonly=True, select=1, ondelete='restrict', help="Link to the automatically generated Journal Items."),
 -        'amount_untaxed': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Subtotal', track_visibility='always',
 -            store={
 -                'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
 -                'account.invoice.tax': (_get_invoice_tax, None, 20),
 -                'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
 -            },
 -            multi='all'),
 -        'amount_tax': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Tax',
 -            store={
 -                'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
 -                'account.invoice.tax': (_get_invoice_tax, None, 20),
 -                'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
 -            },
 -            multi='all'),
 -        'amount_total': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Total',
 -            store={
 -                'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
 -                'account.invoice.tax': (_get_invoice_tax, None, 20),
 -                'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
 -            },
 -            multi='all'),
 -        'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'),
 -        'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]},
 -                                      domain="[('type', 'in', {'out_invoice': ['sale'], 'out_refund': ['sale_refund'], 'in_refund': ['purchase_refund'], 'in_invoice': ['purchase']}.get(type, [])), ('company_id', '=', company_id)]"),
 -        'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
 -        'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
 -        'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean',
 -            store={
 -                'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
 -                'account.move.line': (_get_invoice_from_line, None, 50),
 -                'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
 -            }, help="It indicates that the invoice has been paid and the journal entry of the invoice has been reconciled with one or several journal entries of payment."),
 -        'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account',
 -            help='Bank Account Number to which the invoice will be paid. A Company bank account if this is a Customer Invoice or Supplier Refund, otherwise a Partner bank account number.', readonly=True, states={'draft':[('readonly',False)]}),
 -        'move_lines':fields.function(_get_lines, type='many2many', relation='account.move.line', string='Entry Lines'),
 -        'residual': fields.function(_amount_residual, digits_compute=dp.get_precision('Account'), string='Balance',
 -            store={
 -                'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line','move_id'], 50),
 -                'account.invoice.tax': (_get_invoice_tax, None, 50),
 -                'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 50),
 -                'account.move.line': (_get_invoice_from_line, None, 50),
 -                'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
 -            },
 -            help="Remaining amount due."),
 -        'payment_ids': fields.function(_compute_lines, relation='account.move.line', type="many2many", string='Payments', groups='base.group_user'),
 -        'move_name': fields.char('Journal Entry', size=64, readonly=True, states={'draft':[('readonly',False)]}),
 -        'user_id': fields.many2one('res.users', 'Salesperson', readonly=True, track_visibility='onchange', states={'draft':[('readonly',False)]}),
 -        'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position', readonly=True, states={'draft':[('readonly',False)]}),
 -        'commercial_partner_id': fields.related('partner_id', 'commercial_partner_id', string='Commercial Entity', type='many2one',
 -                                                relation='res.partner', store=True, readonly=True,
 -                                                help="The commercial entity that will be used on Journal Entries for this invoice")
 -    }
 -    _defaults = {
 -        'type': _get_type,
 -        'state': 'draft',
 -        'journal_id': _get_journal,
 -        'currency_id': _get_currency,
 -        'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.invoice', context=c),
 -        'reference_type': 'none',
 -        'check_total': 0.0,
 -        'internal_number': False,
 -        'user_id': lambda s, cr, u, c: u,
 -        'sent': False,
 -    }
 +        ], string='Status', index=True, readonly=True, default='draft',
 +        track_visibility='onchange', copy=False,
 +        help=" * The 'Draft' status is used when a user is encoding a new and unconfirmed Invoice.\n"
 +             " * The 'Pro-forma' when invoice is in Pro-forma status,invoice does not have an invoice number.\n"
 +             " * The 'Open' status is used when user create invoice,a invoice number is generated.Its in open status till user does not pay invoice.\n"
 +             " * The 'Paid' status is set automatically when the invoice is paid. Its related journal entries may or may not be reconciled.\n"
 +             " * The 'Cancelled' status is used when user cancel invoice.")
 +    sent = fields.Boolean(readonly=True, default=False, copy=False,
 +        help="It indicates that the invoice has been sent.")
 +    date_invoice = fields.Date(string='Invoice Date',
 +        readonly=True, states={'draft': [('readonly', False)]}, index=True,
 +        help="Keep empty to use the current date", copy=False)
 +    date_due = fields.Date(string='Due Date',
 +        readonly=True, states={'draft': [('readonly', False)]}, index=True, copy=False,
 +        help="If you use payment terms, the due date will be computed automatically at the generation "
 +             "of accounting entries. The payment term may compute several due dates, for example 50% "
 +             "now and 50% in one month, but if you want to force a due date, make sure that the payment "
 +             "term is not set on the invoice. If you keep the payment term and the due date empty, it "
 +             "means direct payment.")
 +    partner_id = fields.Many2one('res.partner', string='Partner', change_default=True,
 +        required=True, readonly=True, states={'draft': [('readonly', False)]},
 +        track_visibility='always')
 +    payment_term = fields.Many2one('account.payment.term', string='Payment Terms',
 +        readonly=True, states={'draft': [('readonly', False)]},
 +        help="If you use payment terms, the due date will be computed automatically at the generation "
 +             "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "
 +             "The payment term may compute several due dates, for example 50% now, 50% in one month.")
 +    period_id = fields.Many2one('account.period', string='Force Period',
 +        domain=[('state', '!=', 'done')], copy=False,
 +        help="Keep empty to use the period of the validation(invoice) date.",
 +        readonly=True, states={'draft': [('readonly', False)]})
 +
 +    account_id = fields.Many2one('account.account', string='Account',
 +        required=True, readonly=True, states={'draft': [('readonly', False)]},
 +        help="The partner account used for this invoice.")
 +    invoice_line = fields.One2many('account.invoice.line', 'invoice_id', string='Invoice Lines',
 +        readonly=True, states={'draft': [('readonly', False)]}, copy=True)
 +    tax_line = fields.One2many('account.invoice.tax', 'invoice_id', string='Tax Lines',
 +        readonly=True, states={'draft': [('readonly', False)]}, copy=True)
 +    move_id = fields.Many2one('account.move', string='Journal Entry',
 +        readonly=True, index=True, ondelete='restrict', copy=False,
 +        help="Link to the automatically generated Journal Items.")
 +
 +    amount_untaxed = fields.Float(string='Subtotal', digits=dp.get_precision('Account'),
 +        store=True, readonly=True, compute='_compute_amount', track_visibility='always')
 +    amount_tax = fields.Float(string='Tax', digits=dp.get_precision('Account'),
 +        store=True, readonly=True, compute='_compute_amount')
 +    amount_total = fields.Float(string='Total', digits=dp.get_precision('Account'),
 +        store=True, readonly=True, compute='_compute_amount')
 +
 +    currency_id = fields.Many2one('res.currency', string='Currency',
 +        required=True, readonly=True, states={'draft': [('readonly', False)]},
 +        default=_default_currency, track_visibility='always')
 +    journal_id = fields.Many2one('account.journal', string='Journal',
 +        required=True, readonly=True, states={'draft': [('readonly', False)]},
 +        default=_default_journal,
 +        domain="[('type', 'in', {'out_invoice': ['sale'], 'out_refund': ['sale_refund'], 'in_refund': ['purchase_refund'], 'in_invoice': ['purchase']}.get(type, [])), ('company_id', '=', company_id)]")
 +    company_id = fields.Many2one('res.company', string='Company', change_default=True,
 +        required=True, readonly=True, states={'draft': [('readonly', False)]},
 +        default=lambda self: self.env['res.company']._company_default_get('account.invoice'))
 +    check_total = fields.Float(string='Verification Total', digits=dp.get_precision('Account'),
 +        readonly=True, states={'draft': [('readonly', False)]}, default=0.0)
 +
 +    reconciled = fields.Boolean(string='Paid/Reconciled',
 +        store=True, readonly=True, compute='_compute_reconciled',
 +        help="It indicates that the invoice has been paid and the journal entry of the invoice has been reconciled with one or several journal entries of payment.")
 +    partner_bank_id = fields.Many2one('res.partner.bank', string='Bank Account',
 +        help='Bank Account Number to which the invoice will be paid. A Company bank account if this is a Customer Invoice or Supplier Refund, otherwise a Partner bank account number.',
 +        readonly=True, states={'draft': [('readonly', False)]})
 +
 +    move_lines = fields.Many2many('account.move.line', string='Entry Lines',
 +        compute='_compute_move_lines')
 +    residual = fields.Float(string='Balance', digits=dp.get_precision('Account'),
 +        compute='_compute_residual', store=True,
 +        help="Remaining amount due.")
 +    payment_ids = fields.Many2many('account.move.line', string='Payments',
 +        compute='_compute_payments')
 +    move_name = fields.Char(string='Journal Entry', readonly=True,
 +        states={'draft': [('readonly', False)]}, copy=False)
 +    user_id = fields.Many2one('res.users', string='Salesperson', track_visibility='onchange',
 +        readonly=True, states={'draft': [('readonly', False)]},
 +        default=lambda self: self.env.user)
 +    fiscal_position = fields.Many2one('account.fiscal.position', string='Fiscal Position',
 +        readonly=True, states={'draft': [('readonly', False)]})
 +    commercial_partner_id = fields.Many2one('res.partner', string='Commercial Entity',
 +        related='partner_id.commercial_partner_id', store=True, readonly=True,
 +        help="The commercial entity that will be used on Journal Entries for this invoice")
 +
      _sql_constraints = [
 -        ('number_uniq', 'unique(number, company_id, journal_id, type)', 'Invoice Number must be unique per Company!'),
 +        ('number_uniq', 'unique(number, company_id, journal_id, type)',
 +            'Invoice Number must be unique per Company!'),
      ]
  
 +    @api.model
 +    def fields_view_get(self, view_id=None, view_type=False, toolbar=False, submenu=False):
 +        context = self._context
++        def get_view_id(xid, name):
++            try:
++                return self.env['ir.model.data'].xmlid_to_res_id('account.' + xid, raise_if_not_found=True)
++            except ValueError:
++                try:
++                    return self.env['ir.ui.view'].search([('name', '=', name)], limit=1).id
++                except Exception:
++                    return False    # view not found
 -
 -    def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
 -        journal_obj = self.pool.get('account.journal')
 -        if context is None:
 -            context = {}
 -
 -        if context.get('active_model', '') in ['res.partner'] and context.get('active_ids', False) and context['active_ids']:
 -            partner = self.pool[context['active_model']].read(cr, uid, context['active_ids'], ['supplier','customer'])[0]
 +        if context.get('active_model') == 'res.partner' and context.get('active_ids'):
 +            partner = self.env['res.partner'].browse(context['active_ids'])[0]
              if not view_type:
-                 view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.tree')]).id
 -                try:
 -                    view_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'invoice_tree')[1]
 -                except ValueError:
 -                    view_id = self.pool.get('ir.ui.view').search(cr, uid, [('name', '=', 'account.invoice.tree')], limit=1)
++                view_id = get_view_id('invoice_tree', 'account.invoice.tree')
                  view_type = 'tree'
 -            if view_type == 'form':
 -                if partner['supplier'] and not partner['customer']:
 -                    try:
 -                        view_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'invoice_supplier_form')[1]
 -                    except ValueError:
 -                        view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name', '=', 'account.invoice.supplier.form')], limit=1)
 -                elif partner['customer'] and not partner['supplier']:
 -                    try:
 -                        view_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'invoice_form')[1]
 -                    except ValueError:
 -                        view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name', '=', 'account.invoice.form')], limit=1)
 -        if view_id and isinstance(view_id, (list, tuple)):
 -            view_id = view_id[0]
 -        res = super(account_invoice,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
 -
 -        type = context.get('journal_type', False)
 +            elif view_type == 'form':
 +                if partner.supplier and not partner.customer:
-                     view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.supplier.form')]).id
++                    view_id = get_view_id('invoice_supplier_form', 'account.invoice.supplier.form')
 +                elif partner.customer and not partner.supplier:
-                     view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.form')]).id
++                    view_id = get_view_id('invoice_form', 'account.invoice.form')
 +
 +        res = super(account_invoice, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
 +
 +        # adapt selection of field journal_id
          for field in res['fields']:
              if field == 'journal_id' and type:
 -                journal_select = journal_obj._name_search(cr, uid, '', [('type', '=', type)], context=context, limit=None, name_get_uid=1)
 +                journal_select = self.env['account.journal']._name_search('', [('type', '=', type)], name_get_uid=1)
                  res['fields'][field]['selection'] = journal_select
  
          doc = etree.XML(res['arch'])
Simple merge
@@@ -1107,17 -877,34 +1107,45 @@@ class product_product(osv.osv)
                  'res_id': product.product_tmpl_id.id,
                  'target': 'new'}
  
 +    def create(self, cr, uid, vals, context=None):
 +        if context is None:
 +            context = {}
 +        ctx = dict(context or {}, create_product_product=True)
 +        return super(product_product, self).create(cr, uid, vals, context=ctx)
 +
 +
 +
 +    def need_procurement(self, cr, uid, ids, context=None):
 +        return False
 +
+     def _compute_uos_qty(self, cr, uid, ids, uom, qty, uos, context=None):
+         '''
+         Computes product's invoicing quantity in UoS from quantity in UoM.
+         Takes into account the
+         :param uom: Source unit
+         :param qty: Source quantity
+         :param uos: Target UoS unit.
+         '''
+         if not uom or not qty or not uos:
+             return qty
+         uom_obj = self.pool['product.uom']
+         product_id = ids[0] if isinstance(ids, (list, tuple)) else ids
+         product = self.browse(cr, uid, product_id, context=context)
+         if isinstance(uos, (int, long)):
+             uos = uom_obj.browse(cr, uid, uos, context=context)
+         if isinstance(uom, (int, long)):
+             uom = uom_obj.browse(cr, uid, uom, context=context)
+         if product.uos_id:  # Product has UoS defined
+             # We cannot convert directly between units even if the units are of the same category
+             # as we need to apply the conversion coefficient which is valid only between quantities
+             # in product's default UoM/UoS
+             qty_default_uom = uom_obj._compute_qty_obj(cr, uid, uom, qty, product.uom_id)  # qty in product's default UoM
+             qty_default_uos = qty_default_uom * product.uos_coeff
+             return uom_obj._compute_qty_obj(cr, uid, product.uos_id, qty_default_uos, uos)
+         else:
+             return uom_obj._compute_qty_obj(cr, uid, uom, qty, uos)
  
  class product_packaging(osv.osv):
      _name = "product.packaging"
@@@ -77,11 -72,10 +77,11 @@@ class sale_order_line(osv.osv)
                  price=result['price_unit']
              else:
                  return res
+             uom = result.get('product_uom', uom)
              product = product_obj.browse(cr, uid, product, context)
 -            list_price = pricelist_obj.price_get(cr, uid, [pricelist],
 -                    product.id, qty or 1.0, partner_id, {'uom': uom,'date': date_order })
 +            pricelist_context = dict(context, uom=uom, date=date_order)
 +            list_price = pricelist_obj.price_rule_get(cr, uid, [pricelist],
 +                    product.id, qty or 1.0, partner_id, context=pricelist_context)
  
              so_pricelist = pricelist_obj.browse(cr, uid, pricelist, context=context)
  
Simple merge