[MERGE] forward port branch saas-11 up to 8411b89

This commit is contained in:
Christophe Simonis
2016-10-19 17:56:52 +02:00
17 changed files with 189 additions and 35 deletions
+4
View File
@@ -736,6 +736,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
@@ -743,6 +746,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,
@@ -374,6 +374,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)
@api.one
@api.constrains('amount')
+36 -18
View File
@@ -12,6 +12,9 @@ from openerp.tools.misc import formatLang
from openerp.exceptions import UserError, RedirectWarning, ValidationError
import openerp.addons.decimal_precision as dp
import logging
_logger = logging.getLogger(__name__)
# mapping invoice type to journal type
TYPE2JOURNAL = {
@@ -544,6 +547,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 = {}
@@ -551,16 +573,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.
@@ -575,6 +588,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
@@ -1306,15 +1320,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
@@ -446,7 +446,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
@@ -147,7 +147,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
@@ -168,7 +168,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
@@ -54,7 +54,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:
+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;
@@ -1896,7 +1896,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') +
@@ -195,10 +195,7 @@ 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:
ProductUOM = self.env['product.uom']
product_qty = ProductUOM._compute_qty_obj(
line.product_uom_id, line.product_qty, line.product_id.uom_po_id)
@@ -208,6 +205,9 @@ class PurchaseOrder(models.Model):
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)
@@ -262,6 +262,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
@@ -141,6 +141,24 @@ class TestViewSaving(common.TransactionCase):
Company = self.registry('res.company')
View = self.registry('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(
@@ -152,6 +170,9 @@ class TestViewSaving(common.TransactionCase):
View.save(self.cr, self.uid, res_id=self.view_id, value=replacement,
xpath='/div/div[2]')
# the xml_id of the view should be flagged as 'noupdate'
self.assertTrue(imd.noupdate)
company = Company.browse(self.cr, self.uid, 1)
self.assertEqual(company.name, "Acme Corporation")
self.assertEqual(company.phone, "+12 3456789")
@@ -47,8 +47,8 @@ div#blog_angle_down a:hover {
top: 20%;
margin-right: auto;
margin-left: auto;
left: 0;
right: 0;
left: 15px;
right: 15px;
}
.blog_title h1 {
font-weight: bold;
@@ -40,8 +40,8 @@ div#blog_angle_down
top: 20%
margin-right: auto
margin-left: auto
left: 0
right: 0
left: 15px
right: 15px
h1
font-weight: bold
+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()