summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-06-05 16:27:49 +0200
committerChristophe Simonis <chs@odoo.com>2018-06-05 16:27:49 +0200
commitaba8c2b8fbfb024652f0a080c657eb2369088eda (patch)
treebbb10ed85d2ae7f0bc1e77234fc1d24d9067de8d /addons
parentfc5e61b63d1357f4c89e68e108de1ade21db724f (diff)
parent1b272a20504e16e0c170ea717f5bcf63d38e7cb5 (diff)
[MERGE] forward port branch 11.0 up to 1b272a20504
Diffstat (limited to 'addons')
-rw-r--r--addons/account/data/mail_template_data.xml2
-rw-r--r--addons/account/models/account_invoice.py2
-rw-r--r--addons/account/models/company.py59
-rw-r--r--addons/account/tests/test_account_move_closed_period.py18
-rw-r--r--addons/account/tests/test_reconciliation.py49
-rw-r--r--addons/account/views/account_view.xml3
-rw-r--r--addons/account_lock/__init__.py3
-rw-r--r--addons/account_lock/__manifest__.py16
-rw-r--r--addons/account_lock/models/__init__.py3
-rw-r--r--addons/account_lock/models/res_company.py14
-rw-r--r--addons/barcodes/static/src/js/barcode_events.js23
-rw-r--r--addons/delivery/models/stock_picking.py3
-rw-r--r--addons/google_calendar/models/google_calendar.py2
-rw-r--r--addons/hw_escpos/escpos/escpos.py4
-rw-r--r--addons/l10n_fr_certification/models/res_company.py3
-rw-r--r--addons/l10n_generic_coa/__init__.py6
-rw-r--r--addons/l10n_generic_coa/__manifest__.py1
-rw-r--r--addons/mass_mailing/models/mail_mail.py2
-rw-r--r--addons/point_of_sale/models/pos_config.py9
-rw-r--r--addons/purchase/data/mail_template_data.xml4
-rw-r--r--addons/purchase/models/purchase.py6
-rw-r--r--addons/sale/data/mail_template_data.xml2
-rw-r--r--addons/sale/models/product_template.py2
-rw-r--r--addons/stock/models/stock_move.py6
-rw-r--r--addons/stock/report/report_stock_forecast.py4
-rw-r--r--addons/stock/tests/test_move.py26
-rw-r--r--addons/stock_account/views/stock_account_views.xml2
-rw-r--r--addons/website/controllers/main.py3
-rw-r--r--addons/website_event/static/src/js/website_event.js13
-rw-r--r--addons/website_sale/views/templates.xml2
30 files changed, 245 insertions, 47 deletions
diff --git a/addons/account/data/mail_template_data.xml b/addons/account/data/mail_template_data.xml
index f461a271452..4bb340a4f5c 100644
--- a/addons/account/data/mail_template_data.xml
+++ b/addons/account/data/mail_template_data.xml
@@ -7,7 +7,7 @@
<!--Email template -->
<record id="email_template_edi_invoice" model="mail.template">
<field name="name">Invoicing: Invoice email</field>
- <field name="email_from">${(object.user_id.email and '%s &lt;%s&gt;' % (object.user_id.name, object.user_id.email) or '')|safe}</field>
+ <field name="email_from">${(object.user_id.email and '&quot;%s&quot; &lt;%s&gt;' % (object.user_id.name, object.user_id.email) or '')|safe}</field>
<field name="subject">${object.company_id.name} Invoice (Ref ${object.number or 'n/a'})</field>
<field name="partner_to">${object.partner_id.id}</field>
<field name="model_id" ref="account.model_account_invoice"/>
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index bc809c577f7..94c1fa485c7 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -1370,8 +1370,6 @@ class AccountInvoice(models.Model):
else:
payment_method = self.env.ref('account.account_payment_method_manual_out')
journal_payment_methods = pay_journal.outbound_payment_method_ids
- if payment_method not in journal_payment_methods:
- raise UserError(_('No appropriate payment method enabled on journal %s') % pay_journal.name)
communication = self.type in ('in_invoice', 'in_refund') and self.reference or self.number
if self.origin:
diff --git a/addons/account/models/company.py b/addons/account/models/company.py
index 8ae4230a7b2..0209ea5c849 100644
--- a/addons/account/models/company.py
+++ b/addons/account/models/company.py
@@ -2,10 +2,12 @@
from datetime import timedelta, datetime
import calendar
+import time
+from dateutil.relativedelta import relativedelta
from odoo import fields, models, api, _
from odoo.exceptions import ValidationError, UserError
-from odoo.exceptions import UserError
+from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT
from odoo.tools.float_utils import float_round, float_is_zero
@@ -63,6 +65,61 @@ Best Regards,'''))
account_setup_coa_done = fields.Boolean(string='Chart of Account Checked', help="Technical field holding the status of the chart of account setup step.")
account_setup_bar_closed = fields.Boolean(string='Setup Bar Closed', help="Technical field set to True when setup bar has been closed by the user.")
+ @api.multi
+ def _check_lock_dates(self, vals):
+ '''Check the lock dates for the current companies. This can't be done in a api.constrains because we need
+ to perform some comparison between new/old values. This method forces the lock dates to be irreversible.
+
+ * You cannot define stricter conditions on advisors than on users. Then, the lock date on advisor must be set
+ after the lock date for users.
+ * You cannot lock a period that is not finished yet. Then, the lock date for advisors must be set after the
+ last day of the previous month.
+ * The new lock date for advisors must be set after the previous lock date.
+
+ :param vals: The values passed to the write method.
+ '''
+ period_lock_date = vals.get('period_lock_date') and\
+ time.strptime(vals['period_lock_date'], DEFAULT_SERVER_DATE_FORMAT)
+ fiscalyear_lock_date = vals.get('fiscalyear_lock_date') and\
+ time.strptime(vals['fiscalyear_lock_date'], DEFAULT_SERVER_DATE_FORMAT)
+
+ previous_month = datetime.strptime(fields.Date.today(), DEFAULT_SERVER_DATE_FORMAT) + relativedelta(months=-1)
+ days_previous_month = calendar.monthrange(previous_month.year, previous_month.month)
+ previous_month = previous_month.replace(day=days_previous_month[1]).timetuple()
+ for company in self:
+ old_fiscalyear_lock_date = company.fiscalyear_lock_date and\
+ time.strptime(company.fiscalyear_lock_date, DEFAULT_SERVER_DATE_FORMAT)
+
+ # The user attempts to remove the lock date for advisors
+ if old_fiscalyear_lock_date and not fiscalyear_lock_date and 'fiscalyear_lock_date' in vals:
+ raise ValidationError(_('The lock date for advisors is irreversible and can\'t be removed.'))
+
+ # The user attempts to set a lock date for advisors prior to the previous one
+ if old_fiscalyear_lock_date and fiscalyear_lock_date and fiscalyear_lock_date < old_fiscalyear_lock_date:
+ raise ValidationError(_('The new lock date for advisors must be set after the previous lock date.'))
+
+ # In case of no new fiscal year in vals, fallback to the oldest
+ if not fiscalyear_lock_date:
+ if old_fiscalyear_lock_date:
+ fiscalyear_lock_date = old_fiscalyear_lock_date
+ else:
+ continue
+
+ # The user attempts to set a lock date for advisors prior to the last day of previous month
+ if fiscalyear_lock_date > previous_month:
+ raise ValidationError(_('You cannot lock a period that is not finished yet. Please make sure that the lock date for advisors is not set after the last day of the previous month.'))
+
+ # In case of no new period lock date in vals, fallback to the one defined in the company
+ if not period_lock_date:
+ if company.period_lock_date:
+ period_lock_date = time.strptime(company.period_lock_date, DEFAULT_SERVER_DATE_FORMAT)
+ else:
+ continue
+
+ # The user attempts to set a lock date for advisors prior to the lock date for users
+ if period_lock_date < fiscalyear_lock_date:
+ raise ValidationError(_('You cannot define stricter conditions on advisors than on users. Please make sure that the lock date on advisor is set before the lock date for users.'))
+
@api.model
def _verify_fiscalyear_last_day(self, company_id, last_day, last_month):
company = self.browse(company_id)
diff --git a/addons/account/tests/test_account_move_closed_period.py b/addons/account/tests/test_account_move_closed_period.py
index 298738a0d6c..5de3e97ac4f 100644
--- a/addons/account/tests/test_account_move_closed_period.py
+++ b/addons/account/tests/test_account_move_closed_period.py
@@ -1,6 +1,8 @@
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
from odoo.osv.orm import except_orm
-from datetime import datetime, timedelta
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+from calendar import monthrange
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo.tests import tagged
@@ -14,14 +16,16 @@ class TestPeriodState(AccountingTestCase):
def setUp(self):
super(TestPeriodState, self).setUp()
self.user_id = self.env.user
- self.day_before_yesterday = datetime.now() - timedelta(2)
- self.yesterday = datetime.now() - timedelta(1)
- self.yesterday_str = self.yesterday.strftime(DEFAULT_SERVER_DATE_FORMAT)
+
+ last_day_month = datetime.now() - relativedelta(months=1)
+ last_day_month = last_day_month.replace(day=monthrange(last_day_month.year, last_day_month.month)[1])
+ self.last_day_month_str = last_day_month.strftime(DEFAULT_SERVER_DATE_FORMAT)
+
#make sure there is no unposted entry
- draft_entries = self.env['account.move'].search([('date', '<=', self.yesterday_str), ('state', '=', 'draft')])
+ draft_entries = self.env['account.move'].search([('date', '<=', self.last_day_month_str), ('state', '=', 'draft')])
if draft_entries:
draft_entries.post()
- self.user_id.company_id.write({'fiscalyear_lock_date': self.yesterday_str})
+ self.user_id.company_id.fiscalyear_lock_date = self.last_day_month_str
self.sale_journal_id = self.env['account.journal'].search([('type', '=', 'sale')])[0]
self.account_id = self.env['account.account'].search([('internal_type', '=', 'receivable')])[0]
@@ -30,7 +34,7 @@ class TestPeriodState(AccountingTestCase):
move = self.env['account.move'].create({
'name': '/',
'journal_id': self.sale_journal_id.id,
- 'date': self.day_before_yesterday.strftime(DEFAULT_SERVER_DATE_FORMAT),
+ 'date': self.last_day_month_str,
'line_ids': [(0, 0, {
'name': 'foo',
'debit': 10,
diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py
index 4a50c812620..1fce897b602 100644
--- a/addons/account/tests/test_reconciliation.py
+++ b/addons/account/tests/test_reconciliation.py
@@ -42,6 +42,12 @@ class TestReconciliation(AccountingTestCase):
self.diff_income_account = self.env['res.users'].browse(self.env.uid).company_id.income_currency_exchange_account_id
self.diff_expense_account = self.env['res.users'].browse(self.env.uid).company_id.expense_currency_exchange_account_id
+ self.inbound_payment_method = self.env['account.payment.method'].create({
+ 'name': 'inbound',
+ 'code': 'IN',
+ 'payment_type': 'inbound',
+ })
+
def create_invoice(self, type='out_invoice', invoice_amount=50, currency_id=None):
#we create an invoice in given currency
invoice = self.account_invoice_model.create({'partner_id': self.partner_agrolait_id,
@@ -709,6 +715,49 @@ class TestReconciliation(AccountingTestCase):
credit_aml.with_context(invoice_id=inv.id).remove_move_reconcile()
self.assertAlmostEquals(inv.residual, 111)
+ def test_revert_payment_and_reconcile(self):
+ payment = self.env['account.payment'].create({
+ 'payment_method_id': self.inbound_payment_method.id,
+ 'payment_type': 'inbound',
+ 'partner_type': 'customer',
+ 'partner_id': self.partner_agrolait_id,
+ 'journal_id': self.bank_journal_usd.id,
+ 'payment_date': '2018-06-04',
+ 'amount': 666,
+ })
+ payment.post()
+
+ self.assertEqual(len(payment.move_line_ids), 2)
+
+ bank_line = payment.move_line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.default_debit_account_id.id)
+ customer_line = payment.move_line_ids - bank_line
+
+ self.assertEqual(len(bank_line), 1)
+ self.assertEqual(len(customer_line), 1)
+ self.assertNotEqual(bank_line.id, customer_line.id)
+
+ self.assertEqual(bank_line.move_id.id, customer_line.move_id.id)
+ move = bank_line.move_id
+
+ # Reversing the payment's move
+ reversed_move_list = move.reverse_moves('2018-06-04')
+ self.assertEqual(len(reversed_move_list), 1)
+ reversed_move = self.env['account.move'].browse(reversed_move_list[0])
+
+ self.assertEqual(len(reversed_move.line_ids), 2)
+
+ # Testing the reconciliation matching between the move lines and their reversed counterparts
+ reversed_bank_line = reversed_move.line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.default_debit_account_id.id)
+ reversed_customer_line = reversed_move.line_ids - reversed_bank_line
+
+ self.assertEqual(len(reversed_bank_line), 1)
+ self.assertEqual(len(reversed_customer_line), 1)
+ self.assertNotEqual(reversed_bank_line.id, reversed_customer_line.id)
+ self.assertEqual(reversed_bank_line.move_id.id, reversed_customer_line.move_id.id)
+
+ self.assertEqual(reversed_bank_line.full_reconcile_id.id, bank_line.full_reconcile_id.id)
+ self.assertEqual(reversed_customer_line.full_reconcile_id.id, customer_line.full_reconcile_id.id)
+
def test_partial_reconcile_currencies_02(self):
####
# Day 1: Invoice Cust/001 to customer (expressed in USD)
diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml
index 712bfe7b1f9..8a925dbdf8c 100644
--- a/addons/account/views/account_view.xml
+++ b/addons/account/views/account_view.xml
@@ -1141,7 +1141,8 @@
<group>
<field name="name"/>
<field name="partner_id"
- domain="['|', ('parent_id', '=', False), ('is_company', '=', True)]"/>
+ domain="['|', ('parent_id', '=', False), ('is_company', '=', True)]"
+ attrs="{'readonly': [('parent_state', '=', 'posted')]}"/>
</group>
<notebook colspan="4">
<page string="Information">
diff --git a/addons/account_lock/__init__.py b/addons/account_lock/__init__.py
new file mode 100644
index 00000000000..cde864bae21
--- /dev/null
+++ b/addons/account_lock/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import models
diff --git a/addons/account_lock/__manifest__.py b/addons/account_lock/__manifest__.py
new file mode 100644
index 00000000000..67e76b49098
--- /dev/null
+++ b/addons/account_lock/__manifest__.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+{
+ 'name' : 'Irreversible Lock Date',
+ 'version' : '1.0',
+ 'category': 'Accounting',
+ 'description': """
+ Make the lock date irreversible:
+
+ * You cannot define stricter conditions on advisors than on users. Then, the lock date on advisor must be set before the lock date for users.
+ * You cannot lock a period that is not finished yet. Then, the lock date for advisors must be set before the last day of the previous month.
+ * The new lock date for advisors must be set after the previous lock date.
+ """,
+ 'depends' : ['account'],
+ 'data': [],
+}
diff --git a/addons/account_lock/models/__init__.py b/addons/account_lock/models/__init__.py
new file mode 100644
index 00000000000..e759f74fc8d
--- /dev/null
+++ b/addons/account_lock/models/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import res_company
diff --git a/addons/account_lock/models/res_company.py b/addons/account_lock/models/res_company.py
new file mode 100644
index 00000000000..5a01046f36b
--- /dev/null
+++ b/addons/account_lock/models/res_company.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, api
+
+
+class ResCompany(models.Model):
+ _inherit = 'res.company'
+
+ @api.multi
+ def write(self, vals):
+ # fiscalyear_lock_date can't be set to a prior date
+ if 'fiscalyear_lock_date' in vals or 'period_lock_date' in vals:
+ self._check_lock_dates(vals)
+ return super(ResCompany, self).write(vals)
diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js
index a2e5808f8be..2d602a36b97 100644
--- a/addons/barcodes/static/src/js/barcode_events.js
+++ b/addons/barcodes/static/src/js/barcode_events.js
@@ -64,12 +64,14 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
'position': 'fixed',
'top': '50%',
'transform': 'translateY(-50%)',
- 'opacity': 0,
+ 'z-index': '-1',
},
});
+ // Avoid to show autocomplete for a non appearing input
+ this.$barcodeInput.attr('autocomplete', 'off');
}
- this.__removeBarcodeField = _.debounce(this._removeBarcodeField, this.inputTimeOut);
+ this.__blurBarcodeInput = _.debounce(this._blurBarcodeInput, this.inputTimeOut);
},
handle_buffered_keys: function() {
@@ -232,7 +234,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
this.max_time_between_keys_in_ms);
}
// if the barcode input doesn't receive keydown for a while, remove it.
- this.__removeBarcodeField();
+ this.__blurBarcodeInput();
}
},
@@ -247,21 +249,22 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
var barcodeValue = this.$barcodeInput.val();
if (barcodeValue.match(this.regexp)) {
core.bus.trigger('barcode_scanned', barcodeValue, $(e.target).parent()[0]);
- this.$barcodeInput.val('');
+ this._blurBarcodeInput();
}
},
/**
- * Remove the temporary input created to store the barcode value.
- * If nothing happens, this input will be removed, so the focus will be lost
- * and the virtual keyboard on mobile devices will be closed.
+ * Removes the value and focus from the barcode input.
+ * If nothing happens, the focus will be lost and
+ * the virtual keyboard on mobile devices will be closed.
*
* @private
*/
- _removeBarcodeField: function () {
+ _blurBarcodeInput: function () {
if (this.$barcodeInput) {
- // Reset the value and remove from the DOM.
- this.$barcodeInput.val('').remove();
+ // Close the virtual keyboard on mobile browsers
+ // FIXME: actually we can't prevent keyboard from opening
+ this.$barcodeInput.val('').blur();
}
},
diff --git a/addons/delivery/models/stock_picking.py b/addons/delivery/models/stock_picking.py
index c071f23c5b2..c57799109a7 100644
--- a/addons/delivery/models/stock_picking.py
+++ b/addons/delivery/models/stock_picking.py
@@ -162,7 +162,8 @@ class StockPicking(models.Model):
if self.carrier_id.free_over and self.sale_id and self.sale_id._compute_amount_total_without_delivery() >= self.carrier_id.amount:
res['exact_price'] = 0.0
self.carrier_price = res['exact_price']
- self.carrier_tracking_ref = res['tracking_number']
+ if res['tracking_number']:
+ self.carrier_tracking_ref = res['tracking_number']
order_currency = self.sale_id.currency_id or self.company_id.currency_id
msg = _("Shipment sent to carrier %s for shipping with tracking number %s<br/>Cost: %.2f %s") % (self.carrier_id.name, self.carrier_tracking_ref, self.carrier_price, order_currency.name)
self.message_post(body=msg)
diff --git a/addons/google_calendar/models/google_calendar.py b/addons/google_calendar/models/google_calendar.py
index df0df9e3388..b9392bec08b 100644
--- a/addons/google_calendar/models/google_calendar.py
+++ b/addons/google_calendar/models/google_calendar.py
@@ -842,7 +842,7 @@ class GoogleCalendar(models.AbstractModel):
try:
# if already deleted from gmail or never created
recs.delete_an_event(current_event[0])
- except Exception as e:
+ except requests.exceptions.HTTPError as e:
if e.response.status_code in (401, 410,):
pass
else:
diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py
index 7159ac3b31e..f31b1619894 100644
--- a/addons/hw_escpos/escpos/escpos.py
+++ b/addons/hw_escpos/escpos/escpos.py
@@ -522,6 +522,10 @@ class Escpos:
# Print Code
if code:
self._raw(code)
+ # We are using type A commands
+ # So we need to add the 'NULL' character
+ # https://github.com/python-escpos/python-escpos/pull/98/files#diff-a0b1df12c7c67e38915adbe469051e2dR444
+ self._raw('\x00')
else:
raise exception.BarcodeCodeError()
diff --git a/addons/l10n_fr_certification/models/res_company.py b/addons/l10n_fr_certification/models/res_company.py
index 97aa993aa7e..f5f962f6fa3 100644
--- a/addons/l10n_fr_certification/models/res_company.py
+++ b/addons/l10n_fr_certification/models/res_company.py
@@ -30,6 +30,9 @@ class ResCompany(models.Model):
if company._is_accounting_unalterable():
sequence_fields = ['l10n_fr_secure_sequence_id']
company._create_secure_sequence(sequence_fields)
+ # fiscalyear_lock_date can't be set to a prior date
+ if 'fiscalyear_lock_date' in vals or 'period_lock_date' in vals:
+ self._check_lock_dates(vals)
return res
def _create_secure_sequence(self, sequence_fields):
diff --git a/addons/l10n_generic_coa/__init__.py b/addons/l10n_generic_coa/__init__.py
index 67dee8c60db..d417dc6807c 100644
--- a/addons/l10n_generic_coa/__init__.py
+++ b/addons/l10n_generic_coa/__init__.py
@@ -1,2 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+
+def uninstall_hook(cr, registry):
+ cr.execute(
+ "DELETE FROM ir_model_data WHERE module = 'l10n_generic_coa'"
+ )
diff --git a/addons/l10n_generic_coa/__manifest__.py b/addons/l10n_generic_coa/__manifest__.py
index 314e13f44ff..2f04d8df530 100644
--- a/addons/l10n_generic_coa/__manifest__.py
+++ b/addons/l10n_generic_coa/__manifest__.py
@@ -24,4 +24,5 @@ Install some generic chart of accounts.
'data/account_invoice_demo.xml',
],
'website': 'https://www.odoo.com/page/accounting',
+ 'uninstall_hook': 'uninstall_hook',
}
diff --git a/addons/mass_mailing/models/mail_mail.py b/addons/mass_mailing/models/mail_mail.py
index 5889bbeede1..537d333cd7b 100644
--- a/addons/mass_mailing/models/mail_mail.py
+++ b/addons/mass_mailing/models/mail_mail.py
@@ -84,7 +84,7 @@ class MailMail(models.Model):
def send_get_email_dict(self, partner=None):
# TDE: temporary addition (mail was parameter) due to semi-new-API
res = super(MailMail, self).send_get_email_dict(partner)
- base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
+ base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url').rstrip('/')
if self.mailing_id and res.get('body') and res.get('email_to'):
emails = tools.email_split(res.get('email_to')[0])
email_to = emails and emails[0] or False
diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py
index 1b41565401d..2ef110a6dae 100644
--- a/addons/point_of_sale/models/pos_config.py
+++ b/addons/point_of_sale/models/pos_config.py
@@ -364,11 +364,12 @@ class PosConfig(models.Model):
@api.multi
def write(self, vals):
- if (self.is_posbox or vals.get('is_posbox')) and (self.iface_customer_facing_display or vals.get('iface_customer_facing_display')):
- facing_display = (self.customer_facing_display_html or vals.get('customer_facing_display_html') or '').strip()
- if not facing_display:
- vals['customer_facing_display_html'] = self._compute_default_customer_html()
result = super(PosConfig, self).write(vals)
+
+ config_display = self.filtered(lambda c: c.is_posbox and c.iface_customer_facing_display and not (c.customer_facing_display_html or '').strip())
+ if config_display:
+ super(PosConfig, config_display).write({'customer_facing_display_html': self._compute_default_customer_html()})
+
self.sudo()._set_fiscal_position()
self.sudo()._check_modules_to_install()
self.sudo()._check_groups_implied()
diff --git a/addons/purchase/data/mail_template_data.xml b/addons/purchase/data/mail_template_data.xml
index d3c075c3ff0..072342afc87 100644
--- a/addons/purchase/data/mail_template_data.xml
+++ b/addons/purchase/data/mail_template_data.xml
@@ -5,7 +5,7 @@
<!--Email template -->
<record id="email_template_edi_purchase" model="mail.template">
<field name="name">RFQ - Send by Email</field>
- <field name="email_from">${(object.create_uid.email and '%s &lt;%s&gt;' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
+ <field name="email_from">${(object.create_uid.email and '&quot;%s&quot; &lt;%s&gt;' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
<field name="partner_to">${object.partner_id.id}</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
@@ -46,7 +46,7 @@ from ${object.company_id.name}.
<!--Email template -->
<record id="email_template_edi_purchase_done" model="mail.template">
<field name="name">Purchase Order - Send by Email</field>
- <field name="email_from">${(object.create_uid.email and '%s &lt;%s&gt;' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
+ <field name="email_from">${(object.create_uid.email and '&quot;%s&quot; &lt;%s&gt;' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
<field name="partner_to">${object.partner_id.id}</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index 35ff379f620..1bdf4b3bf94 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -942,12 +942,12 @@ class ProcurementRule(models.Model):
if domain in cache:
po = cache[domain]
else:
- po = self.env['purchase.order'].search([dom for dom in domain])
+ po = self.env['purchase.order'].sudo().search([dom for dom in domain])
po = po[0] if po else False
cache[domain] = po
if not po:
vals = self._prepare_purchase_order(product_id, product_qty, product_uom, origin, values, partner)
- po = self.env['purchase.order'].create(vals)
+ po = self.env['purchase.order'].sudo().create(vals)
cache[domain] = po
elif not po.origin or origin not in po.origin.split(', '):
if po.origin:
@@ -968,7 +968,7 @@ class ProcurementRule(models.Model):
break
if not po_line:
vals = self._prepare_purchase_order_line(product_id, product_qty, product_uom, values, po, supplier)
- self.env['purchase.order.line'].create(vals)
+ self.env['purchase.order.line'].sudo().create(vals)
def _get_purchase_schedule_date(self, values):
"""Return the datetime value to use as Schedule Date (``date_planned``) for the
diff --git a/addons/sale/data/mail_template_data.xml b/addons/sale/data/mail_template_data.xml
index b1ee2412b94..57d66f904db 100644
--- a/addons/sale/data/mail_template_data.xml
+++ b/addons/sale/data/mail_template_data.xml
@@ -5,7 +5,7 @@
<!--Email template -->
<record id="email_template_edi_sale" model="mail.template">
<field name="name">Sales Order - Send by Email</field>
- <field name="email_from">${(object.user_id.email and '%s &lt;%s&gt;' % (object.user_id.name, object.user_id.email) or '')|safe}</field>
+ <field name="email_from">${(object.user_id.email and '&quot;%s&quot; &lt;%s&gt;' % (object.user_id.name, object.user_id.email) or '')|safe}</field>
<field name="subject">${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })</field>
<field name="partner_to">${object.partner_id.id}</field>
<field name="model_id" ref="sale.model_sale_order"/>
diff --git a/addons/sale/models/product_template.py b/addons/sale/models/product_template.py
index e1ffda478ab..79dffdd719b 100644
--- a/addons/sale/models/product_template.py
+++ b/addons/sale/models/product_template.py
@@ -24,7 +24,7 @@ class ProductTemplate(models.Model):
@api.depends('product_variant_ids.sales_count')
def _sales_count(self):
for product in self:
- product.sales_count = sum([p.sales_count for p in product.product_variant_ids])
+ product.sales_count = sum([p.sales_count for p in product.with_context(active_test=False).product_variant_ids])
@api.multi
def action_view_sales(self):
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index e59af341990..d88fbca0ceb 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -925,11 +925,15 @@ class StockMove(models.Model):
if not move.move_orig_ids:
if move.procure_method == 'make_to_order':
continue
+ # If we don't need any quantity, consider the move assigned.
+ need = move.product_qty - move.reserved_availability
+ if float_is_zero(need, precision_rounding=move.product_id.uom_id.rounding):
+ assigned_moves |= move
+ continue
# Reserve new quants and create move lines accordingly.
available_quantity = self.env['stock.quant']._get_available_quantity(move.product_id, move.location_id)
if available_quantity <= 0:
continue
- need = move.product_qty - move.reserved_availability
taken_quantity = move._update_reserved_quantity(need, available_quantity, move.location_id, strict=False)
if float_is_zero(taken_quantity, precision_rounding=move.product_id.uom_id.rounding):
continue
diff --git a/addons/stock/report/report_stock_forecast.py b/addons/stock/report/report_stock_forecast.py
index 700aa5455de..1ca3b1195b5 100644
--- a/addons/stock/report/report_stock_forecast.py
+++ b/addons/stock/report/report_stock_forecast.py
@@ -66,7 +66,7 @@ class ReportStockForecat(models.Model):
LEFT JOIN
stock_location source_location ON sm.location_id = source_location.id
WHERE
- sm.state IN ('confirmed','assigned','waiting') and
+ sm.state IN ('confirmed','partially_available','assigned','waiting') and
source_location.usage != 'internal' and dest_location.usage = 'internal'
GROUP BY sm.date_expected,sm.product_id, sm.company_id
UNION ALL
@@ -88,7 +88,7 @@ class ReportStockForecat(models.Model):
LEFT JOIN
stock_location dest_location ON sm.location_dest_id = dest_location.id
WHERE
- sm.state IN ('confirmed','assigned','waiting') and
+ sm.state IN ('confirmed','partially_available','assigned','waiting') and
source_location.usage = 'internal' and dest_location.usage != 'internal'
GROUP BY sm.date_expected,sm.product_id, sm.company_id)
as MAIN
diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py
index df57065b8d7..8326328bc95 100644
--- a/addons/stock/tests/test_move.py
+++ b/addons/stock/tests/test_move.py
@@ -925,6 +925,31 @@ class StockMove(TransactionCase):
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.customer_location), 12.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.customer_location)), 12)
+ def test_availability_8(self):
+ """ Test the assignment mechanism when the product quantity is decreased on a partially
+ reserved stock move.
+ """
+ # make some stock
+ self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 3.0)
+ self.assertAlmostEqual(self.product1.qty_available, 3.0)
+
+ move_partial = self.env['stock.move'].create({
+ 'name': 'test_partial',
+ 'location_id': self.stock_location.id,
+ 'location_dest_id': self.customer_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 5.0,
+ })
+
+ move_partial._action_confirm()
+ move_partial._action_assign()
+ self.assertAlmostEqual(self.product1.virtual_available, -2.0)
+ self.assertEqual(move_partial.state, 'partially_available')
+ move_partial.product_uom_qty = 3.0
+ move_partial._action_assign()
+ self.assertEqual(move_partial.state, 'assigned')
+
def test_unreserve_1(self):
""" Check that unreserving a stock move sets the products reserved as available and
set the state back to confirmed.
@@ -3767,4 +3792,3 @@ class StockMove(TransactionCase):
picking.button_validate()
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.customer_location), 2)
-
diff --git a/addons/stock_account/views/stock_account_views.xml b/addons/stock_account/views/stock_account_views.xml
index 656d6ea7364..77747cdea5c 100644
--- a/addons/stock_account/views/stock_account_views.xml
+++ b/addons/stock_account/views/stock_account_views.xml
@@ -44,7 +44,7 @@
<field name="model">stock.return.picking</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='product_return_moves']/tree" position="inside">
- <field name="to_refund" widget="boolean_toggle"/>
+ <field name="to_refund"/>
</xpath>
</field>
</record>
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index 62d862afe35..ead68c28274 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -18,8 +18,9 @@ from odoo import http, models, fields, _
from odoo.http import request
from odoo.tools import pycompat, OrderedSet
from odoo.addons.http_routing.models.ir_http import slug, _guess_mimetype
-from odoo.addons.web.controllers.main import WebClient, Binary, Home
+from odoo.addons.web.controllers.main import WebClient, Binary
from odoo.addons.portal.controllers.portal import pager as portal_pager
+from odoo.addons.portal.controllers.web import Home
logger = logging.getLogger(__name__)
diff --git a/addons/website_event/static/src/js/website_event.js b/addons/website_event/static/src/js/website_event.js
index 3aa8691fca7..ebe38118559 100644
--- a/addons/website_event/static/src/js/website_event.js
+++ b/addons/website_event/static/src/js/website_event.js
@@ -20,8 +20,11 @@ return instance.appendTo($form).then(function () {
odoo.define('website_event.website_event', function (require) {
var ajax = require('web.ajax');
+var core = require('web.core');
var Widget = require('web.Widget');
+var _t = core._t;
+
// Catch registration form event, because of JS for attendee details
var EventRegistrationForm = Widget.extend({
start: function () {
@@ -31,7 +34,6 @@ var EventRegistrationForm = Widget.extend({
.off('click')
.removeClass('a-submit')
.click(function (ev) {
- $(this).attr('disabled', true);
self.on_click(ev);
});
});
@@ -43,15 +45,18 @@ var EventRegistrationForm = Widget.extend({
var $form = $(ev.currentTarget).closest('form');
var $button = $(ev.currentTarget).closest('[type="submit"]');
var post = {};
+ $('#registration_form table').siblings('.alert').remove();
$('#registration_form select').each(function () {
post[$(this).attr('name')] = $(this).val();
});
var tickets_ordered = _.some(_.map(post, function (value, key) { return parseInt(value); }));
if (!tickets_ordered) {
- return $('#registration_form table').after(
- '<div class="alert alert-info">Please select at least one ticket.</div>'
- );
+ $('<div class="alert alert-info"/>')
+ .text(_t('Please select at least one ticket.'))
+ .insertAfter('#registration_form table');
+ return $.Deferred();
} else {
+ $button.attr('disabled', true);
return ajax.jsonRpc($form.attr('action'), 'call', post).then(function (modal) {
var $modal = $(modal);
$modal.find('.modal-body > div').removeClass('container'); // retrocompatibility - REMOVE ME in master / saas-19
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 385234ad9ea..03bb0252b46 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -420,7 +420,7 @@
<div id="o-carousel-product" class="carousel slide" data-ride="carousel" data-interval="0">
<div class="carousel-outer">
<div class="carousel-inner">
- <div t-if="variant_img" class="item active" itemprop="image" t-field="product.product_variant_id.image" t-options="{'widget': 'image', 'class': 'product_detail_img js_variant_img', 'alt-field': 'name', 'zoom': 'image', 'unique': product['__last_update'] + (product.product_variant_id['__last_update'] or '')}"/>
+ <div t-if="variant_img" class="item active" itemprop="image" t-field="product[:1].product_variant_id.image" t-options="{'widget': 'image', 'class': 'product_detail_img js_variant_img', 'alt-field': 'name', 'zoom': 'image', 'unique': product['__last_update'] + (product.product_variant_id['__last_update'] or '')}"/>
<div t-attf-class="item#{'' if variant_img else ' active'}" itemprop="image" t-field="product.image" t-options="{'widget': 'image', 'class': 'product_detail_img', 'alt-field': 'name', 'zoom': 'image', 'unique': product['__last_update']}"/>
<t t-if="len(image_ids)" t-foreach="image_ids" t-as="pimg">
<div class="item" t-field="pimg.image" t-options='{"widget": "image", "class": "product_detail_img", "alt-field": "name", "zoom": "image" }'/>