[MERGE] forward port branch saas-12 up to 00f4547

This commit is contained in:
Christophe Simonis
2016-10-19 18:09:53 +02:00
18 changed files with 191 additions and 36 deletions
+4
View File
@@ -773,6 +773,9 @@ class AccountTax(models.Model):
else:
total_included += tax_amount
# Keep base amount used for the current tax
tax_base = base
if tax.include_base_amount:
base += tax_amount
@@ -780,6 +783,7 @@ class AccountTax(models.Model):
'id': tax.id,
'name': tax.with_context(**{'lang': partner.lang} if partner else {}).name,
'amount': tax_amount,
'base': tax_base,
'sequence': tax.sequence,
'account_id': tax.account_id.id,
'refund_account_id': tax.refund_account_id.id,
@@ -366,6 +366,7 @@ class AccountBankStatementLine(models.Model):
journal_entry_ids = fields.One2many('account.move', 'statement_line_id', 'Journal Entries', copy=False, readonly=True)
amount_currency = fields.Monetary(help="The amount expressed in an optional other currency if it is a multi-currency entry.")
currency_id = fields.Many2one('res.currency', string='Currency', help="The optional other currency if it is a multi-currency entry.")
state = fields.Selection(related='statement_id.state' , string='Status', readonly=True)
move_name = fields.Char(string='Journal Entry Name', readonly=True,
default=False, copy=False,
help="Technical field holding the number given to the journal entry, automatically set when the statement line is reconciled then stored to set the same number again if the line is cancelled, set to draft and re-processed again.")
+36 -18
View File
@@ -12,6 +12,9 @@ from odoo.tools.misc import formatLang
from odoo.exceptions import UserError, RedirectWarning, ValidationError
import odoo.addons.decimal_precision as dp
import logging
_logger = logging.getLogger(__name__)
# mapping invoice type to journal type
TYPE2JOURNAL = {
@@ -592,6 +595,25 @@ class AccountInvoice(models.Model):
else:
return self.env.ref('account.invoice_form').id
def _prepare_tax_line_vals(self, line, tax):
""" Prepare values to create an account.invoice.tax line
The line parameter is an account.invoice.line, and the
tax parameter is the output of account.tax.compute_all().
"""
vals = {
'invoice_id': self.id,
'name': tax['name'],
'tax_id': tax['id'],
'amount': tax['amount'],
'base': tax['base'],
'manual': False,
'sequence': tax['sequence'],
'account_analytic_id': tax['analytic'] and line.account_analytic_id.id or False,
'account_id': self.type in ('out_invoice', 'in_invoice') and (tax['account_id'] or line.account_id.id) or (tax['refund_account_id'] or line.account_id.id),
}
return vals
@api.multi
def get_taxes_values(self):
tax_grouped = {}
@@ -599,16 +621,7 @@ class AccountInvoice(models.Model):
price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = line.invoice_line_tax_ids.compute_all(price_unit, self.currency_id, line.quantity, line.product_id, self.partner_id)['taxes']
for tax in taxes:
val = {
'invoice_id': self.id,
'name': tax['name'],
'tax_id': tax['id'],
'amount': tax['amount'],
'manual': False,
'sequence': tax['sequence'],
'account_analytic_id': tax['analytic'] and line.account_analytic_id.id or False,
'account_id': self.type in ('out_invoice', 'in_invoice') and (tax['account_id'] or line.account_id.id) or (tax['refund_account_id'] or line.account_id.id),
}
val = self._prepare_tax_line_vals(line, tax)
# If the taxes generate moves on the same financial account as the invoice line,
# propagate the analytic account from the invoice line to the tax line.
@@ -623,6 +636,7 @@ class AccountInvoice(models.Model):
tax_grouped[key] = val
else:
tax_grouped[key]['amount'] += val['amount']
tax_grouped[key]['base'] += val['base']
return tax_grouped
@api.multi
@@ -1332,15 +1346,19 @@ class AccountInvoiceTax(models.Model):
_order = 'sequence'
def _compute_base_amount(self):
tax_grouped = {}
for invoice in self.mapped('invoice_id'):
tax_grouped[invoice.id] = invoice.get_taxes_values()
for tax in self:
base = 0.0
for line in tax.invoice_id.invoice_line_ids:
if tax.tax_id in line.invoice_line_tax_ids:
price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
base += (line.invoice_line_tax_ids - tax.tax_id).compute_all(
price_unit, line.invoice_id.currency_id, line.quantity, line.product_id, line.invoice_id.partner_id
)['base']
tax.base = base
key = self.env['account.tax'].browse(tax.tax_id.id).get_grouping_key({
'tax_id': tax.tax_id.id,
'account_id': tax.account_id.id,
'account_analytic_id': tax.account_analytic_id.id,
})
if tax.invoice_id and key in tax_grouped[tax.invoice_id.id]:
tax.base = tax_grouped[tax.invoice_id.id][key]['base']
else:
_logger.warning('Tax Base Amount not computable probably due to a change in an underlying tax (%s).', tax.tax_id.name)
invoice_id = fields.Many2one('account.invoice', string='Invoice', ondelete='cascade', index=True)
name = fields.Char(string='Tax Description', required=True)
@@ -41,3 +41,65 @@ class TestAccountSupplierInvoice(AccountingTestCase):
#I cancel the account move which is in posted state and verifies that it gives warning message
with self.assertRaises(Warning):
invoice.move_id.button_cancel()
def test_supplier_invoice2(self):
tax_fixed = self.env['account.tax'].create({
'sequence': 10,
'name': 'Tax 10.0 (Fixed)',
'amount': 10.0,
'amount_type': 'fixed',
'include_base_amount': True,
})
tax_percent_included_base_incl = self.env['account.tax'].create({
'sequence': 20,
'name': 'Tax 50.0% (Percentage of Price Tax Included)',
'amount': 50.0,
'amount_type': 'division',
'include_base_amount': True,
})
tax_percentage = self.env['account.tax'].create({
'sequence': 30,
'name': 'Tax 20.0% (Percentage of Price)',
'amount': 20.0,
'amount_type': 'percent',
'include_base_amount': False,
})
analytic_account = self.env['account.analytic.account'].create({
'name': 'test account',
})
# Should be changed by automatic on_change later
invoice_account = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1).id
invoice_line_account = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_expenses').id)], limit=1).id
invoice = self.env['account.invoice'].create({'partner_id': self.env.ref('base.res_partner_2').id,
'account_id': invoice_account,
'type': 'in_invoice',
})
invoice_line = self.env['account.invoice.line'].create({'product_id': self.env.ref('product.product_product_4').id,
'quantity': 5.0,
'price_unit': 100.0,
'invoice_id': invoice.id,
'name': 'product that cost 100',
'account_id': invoice_line_account,
'invoice_line_tax_ids': [(6, 0, [tax_fixed.id, tax_percent_included_base_incl.id, tax_percentage.id])],
'account_analytic_id': analytic_account.id,
})
invoice.compute_taxes()
# check that Initially supplier bill state is "Draft"
self.assertTrue((invoice.state == 'draft'), "Initially vendor bill state is Draft")
#change the state of invoice to open by clicking Validate button
invoice.signal_workflow('invoice_open')
# Check if amount and corresponded base is correct for all tax scenarios given on a computational base
# Keep in mind that tax amount can be changed by the user at any time before validating (based on the invoice and tax laws applicable)
invoice_tax = invoice.tax_line_ids.sorted(key=lambda r: r.sequence)
self.assertEquals(invoice_tax.mapped('amount'), [50.0, 550.0, 220.0])
self.assertEquals(invoice_tax.mapped('base'), [500.0, 550.0, 1100.0])
#I cancel the account move which is in posted state and verifies that it gives warning message
with self.assertRaises(Warning):
invoice.move_id.button_cancel()
+1 -1
View File
@@ -447,7 +447,7 @@
<span class="fa fa-copy fa-4x"></span>
<h3><strong>Contracts &amp; Subscriptions</strong></h3>
<p>Manage time &amp; material contracts or fixed-price recurring subscriptions.</p>
<a t-att-href="prepare_backend_url('base.open_module_tree', 'form', 'account_analytic_analysis')"
<a t-att-href="prepare_backend_url('base.open_module_tree', 'form', 'sale_contract')"
class="btn odoo_purple">
<span class="fa fa-arrow-circle-o-down"/> Install Now
</a>
+2 -2
View File
@@ -153,7 +153,7 @@ class AccountAssetAsset(models.Model):
else:
if self.method == 'linear':
amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids))
if self.prorata and self.category_id.type == 'purchase':
if self.prorata:
amount = amount_to_depr / self.method_number
if sequence == 1:
days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1
@@ -174,7 +174,7 @@ class AccountAssetAsset(models.Model):
while depreciation_date <= end_date:
depreciation_date = date(depreciation_date.year, depreciation_date.month, depreciation_date.day) + relativedelta(months=+self.method_period)
undone_dotation_number += 1
if self.prorata and self.category_id.type == 'purchase':
if self.prorata:
undone_dotation_number += 1
return undone_dotation_number
@@ -53,7 +53,8 @@
<attribute name="options">{'reload_on_button': true}</attribute>
</field>
<xpath expr="//field[@name='bank_account_id']" position="after">
<button name="button_cancel_reconciliation" attrs="{'invisible': [('journal_entry_ids', '=', [])]}" string="Cancel" type="object" icon="fa-undo text-warning"/>
<field name="state" invisible="1"/>
<button name="button_cancel_reconciliation" attrs="{'invisible': ['|',('journal_entry_ids', '=', []), ('state', '=', 'confirm')]}" string="Cancel" type="object" icon="fa fa-undo text-warning"/>
</xpath>
</field>
</record>
@@ -198,7 +198,14 @@ class BaseActionRule(models.Model):
# subscribe followers
if self.act_followers and hasattr(records, 'message_subscribe'):
records.message_subscribe(self.act_followers.ids)
followers = self.env['mail.followers'].sudo().search(
[('res_model', '=', records._name),
('res_id', 'in', records.ids),
('partner_id', 'in', self.act_followers.ids),
]
)
if not len(followers) == len(self.act_followers):
records.message_subscribe(self.act_followers.ids)
# execute server actions
if self.server_action_ids:
+1 -1
View File
@@ -1372,7 +1372,7 @@ class Meeting(models.Model):
if not self._context.get('dont_notify'):
if len(meeting.alarm_ids) > 0 or values.get('alarm_ids'):
partners_to_notify = meeting.partner_ids.ids
event_attendees_changes = attendees_create and attendees_create[real_ids[0]]
event_attendees_changes = attendees_create and real_ids and attendees_create[real_ids[0]]
if event_attendees_changes:
partners_to_notify.append(event_attendees_changes['removed_partners'].ids)
self.env['calendar.alarm_manager'].notify_next_alarm(partners_to_notify)
+1 -1
View File
@@ -213,5 +213,5 @@ class Employee(models.Model):
('date_from', '<=', today_end),
('date_to', '>=', today_start),
('type', '=', 'remove')
], ['employee_id'])
])
return [('id', 'in', holidays.mapped('employee_id').ids)]
+4
View File
@@ -410,6 +410,10 @@ var PosDB = core.Class.extend({
}
}
// Only necessary when we store a new, validated order. Orders
// that where already stored should already have been removed.
this.remove_unpaid_order(order);
orders.push({id: order_id, data: order});
this.save('orders',orders);
return order_id;
@@ -1895,7 +1895,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
}
// if the change is too large, it's probably an input error, make the user confirm.
if (!force_validation && (order.get_total_with_tax() * 1000 < order.get_total_paid())) {
if (!force_validation && order.get_total_with_tax() > 0 && (order.get_total_with_tax() * 1000 < order.get_total_paid())) {
this.gui.show_popup('confirm',{
title: _t('Please Confirm Large Amount'),
body: _t('Are you sure that the customer wants to pay') +
+4 -3
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from ast import literal_eval
from odoo import models, fields, api
@@ -29,7 +29,7 @@ class pos_cache(models.Model):
prod_ctx = prod_ctx.sudo(self.compute_user_id.id)
res = prod_ctx.read(self.get_product_fields())
datas = {
'cache': cPickle.dumps(res),
'cache': base64.encodestring(cPickle.dumps(res)),
}
self.write(datas)
@@ -49,7 +49,8 @@ class pos_cache(models.Model):
self.product_fields = str(fields)
self.refresh_cache()
return cPickle.loads(self.cache)
cache = base64.decodestring(self.cache)
return cPickle.loads(cache)
class pos_config(models.Model):
@@ -194,16 +194,16 @@ class PurchaseOrder(models.Model):
taxes_ids = line.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == requisition.company_id).ids
# Compute quantity and price_unit
if requisition.type_id.quantity_copy != 'copy':
product_qty = 0
price_unit = line.price_unit
elif line.product_uom_id != line.product_id.uom_po_id:
if line.product_uom_id != line.product_id.uom_po_id:
product_qty = line.product_uom_id._compute_quantity(line.product_qty, line.product_id.uom_po_id)
price_unit = line.product_uom_id._compute_price(line.price_unit, line.product_id.uom_po_id)
else:
product_qty = line.product_qty
price_unit = line.price_unit
if requisition.type_id.quantity_copy != 'copy':
product_qty = 0
# Compute price_unit in appropriate currency
if requisition.company_id.currency_id != currency:
price_unit = requisition.company_id.currency_id.compute(price_unit, currency)
@@ -258,6 +258,24 @@ class PurchaseOrder(models.Model):
return result
class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"
@api.onchange('product_qty', 'product_uom')
def _onchange_quantity(self):
res = super(PurchaseOrderLine, self)._onchange_quantity()
if self.order_id.requisition_id:
for line in self.order_id.requisition_id.line_ids:
if line.product_id == self.product_id:
if line.product_uom_id != self.product_uom:
self.price_unit = self.env['product.uom']._compute_price(
line.product_uom_id.id, line.price_unit, to_uom_id=self.product_uom.id)
else:
self.price_unit = line.price_unit
break
return res
class ProductTemplate(models.Model):
_inherit = 'product.template'
@@ -430,6 +430,7 @@ var FormWidget = Widget.extend(InvisibilityChangerMixin, {
this.process_modifiers();
this._super();
this.$el.addClass(this.node.attrs["class"] || "");
this.$el.attr('style', this.node.attrs.style);
},
destroy: function() {
$.fn.tooltip('destroy');
+21
View File
@@ -139,6 +139,24 @@ class TestViewSaving(common.TransactionCase):
Company = self.env['res.company']
View = self.env['ir.ui.view']
# create a view with an xmlid, like the file import would
with self.env.norecompute():
self.view_id = View.create(self.cr, self.uid, {
'name': "Test View",
'type': 'qweb',
'arch': ET.tostring(self.arch, encoding='utf-8').decode('utf-8')
})
imd = self.env['ir.model.data'].create({
'module': 'website',
'name': 'test_view',
'model': 'ir.ui.view',
'res_id': self.view_id,
})
# the xml_id of the view should not be flagged as 'noupdate'
self.assertEqual(View.browse(self.cr, self.uid, self.view_id).model_data_id, imd)
self.assertFalse(imd.noupdate)
replacement = ET.tostring(h.DIV(
h.H3("Column 2"),
h.UL(
@@ -149,6 +167,9 @@ class TestViewSaving(common.TransactionCase):
), encoding='utf-8')
self.view_id.save(value=replacement, xpath='/div/div[2]')
# the xml_id of the view should be flagged as 'noupdate'
self.assertTrue(imd.noupdate)
company = Company.browse(1)
self.assertEqual(company.name, "Acme Corporation")
self.assertEqual(company.phone, "+12 3456789")
+12 -3
View File
@@ -229,10 +229,11 @@ actual arch.
for view, view_wo_lang in zip(self, self.with_context(lang=None)):
view_wo_lang.arch = view.arch_base
@api.depends('write_date')
def _compute_model_data_id(self):
# get the last ir_model_data record corresponding to self
# get the first ir_model_data record corresponding to self
domain = [('model', '=', 'ir.ui.view'), ('res_id', 'in', self.ids)]
for data in self.env['ir.model.data'].search_read(domain, ['res_id']):
for data in self.env['ir.model.data'].search_read(domain, ['res_id'], order='id desc'):
view = self.browse(data['res_id'])
view.model_data_id = data['id']
@@ -343,7 +344,15 @@ actual arch.
values['name'] = "%s %s" % (values.get('model'), values['type'])
self.clear_caches()
return super(View, self).create(self._compute_defaults(values))
if 'install_mode_data' in self._context:
# the view is created from a data file by installing a module; delay
# the recomputation of field 'model_data_id' until its xmlid record
# is created
with self.env.norecompute():
return super(View, self).create(self._compute_defaults(values))
else:
return super(View, self).create(self._compute_defaults(values))
@api.multi
def write(self, vals):
+8
View File
@@ -12,6 +12,14 @@ from odoo.tests import common
from odoo.tools import mute_logger
class ViewXMLID(common.TransactionCase):
def test_model_data_id(self):
""" Check whether views know their xmlid record. """
view = self.env.ref('base.view_company_form')
self.assertTrue(view)
self.assertTrue(view.model_data_id)
self.assertEqual(view.model_data_id.complete_name, 'base.view_company_form')
class ViewCase(common.TransactionCase):
def setUp(self):
super(ViewCase, self).setUp()