diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index e2f8e6eaeaa..648744d1c26 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -167,7 +167,7 @@ class AccountAccount(models.Model):
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
default = dict(default or {})
- default.update(code=_("%s (copy)") % (self.code or ''))
+ default.setdefault('code', _("%s (copy)") % (self.code or ''))
return super(AccountAccount, self).copy(default)
@api.multi
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index e21847878e2..48a3b65b6bf 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -392,7 +392,15 @@ class account_payment(models.Model):
#Reconcile with the invoices
if self.payment_difference_handling == 'reconcile' and self.payment_difference:
writeoff_line = self._get_shared_move_line_vals(0, 0, 0, move.id, False)
- debit_wo, credit_wo, amount_currency_wo, currency_id = aml_obj.with_context(date=self.payment_date).compute_amount_fields(self.payment_difference, self.currency_id, self.company_id.currency_id, invoice_currency)
+ amount_currency_wo, currency_id = aml_obj.with_context(date=self.payment_date).compute_amount_fields(self.payment_difference, self.currency_id, self.company_id.currency_id, invoice_currency)[2:]
+ # the writeoff debit and credit must be computed from the invoice residual in company currency
+ # minus the payment amount in company currency, and not from the payment difference in the payment currency
+ # to avoid loss of precision during the currency rate computations. See revision 20935462a0cabeb45480ce70114ff2f4e91eaf79 for a detailed example.
+ total_residual_company_signed = sum(invoice.residual_company_signed for invoice in self.invoice_ids)
+ total_payment_company_signed = self.currency_id.with_context(date=self.payment_date).compute(self.amount, self.company_id.currency_id)
+ amount_wo = total_residual_company_signed - total_payment_company_signed
+ debit_wo = amount_wo > 0 and amount_wo or 0.0
+ credit_wo = amount_wo < 0 and -amount_wo or 0.0
writeoff_line['name'] = _('Counterpart')
writeoff_line['account_id'] = self.writeoff_account_id.id
writeoff_line['debit'] = debit_wo
diff --git a/addons/account/wizard/account_invoice_refund.py b/addons/account/wizard/account_invoice_refund.py
index afc3c0609ef..c0c2a0a14f5 100644
--- a/addons/account/wizard/account_invoice_refund.py
+++ b/addons/account/wizard/account_invoice_refund.py
@@ -75,7 +75,7 @@ class AccountInvoiceRefund(models.TransientModel):
for tmpline in refund.move_id.line_ids:
if tmpline.account_id.id == inv.account_id.id:
to_reconcile_lines += tmpline
- to_reconcile_lines.reconcile()
+ to_reconcile_lines.filtered(lambda l: l.reconciled == False).reconcile()
if mode == 'modify':
invoice = inv.read(
['name', 'type', 'number', 'reference',
diff --git a/addons/account_analytic_default/models/account_analytic_default.py b/addons/account_analytic_default/models/account_analytic_default.py
index 85ec2199f0f..9ef2add29bc 100644
--- a/addons/account_analytic_default/models/account_analytic_default.py
+++ b/addons/account_analytic_default/models/account_analytic_default.py
@@ -65,7 +65,10 @@ class AccountInvoiceLine(models.Model):
return res
def _set_additional_fields(self, invoice):
- rec = self.env['account.analytic.default'].account_get(self.product_id.id, self.invoice_id.partner_id.id, self.env.uid,
- fields.Date.today(), company_id=self.company_id.id)
- self.account_analytic_id = rec.analytic_id.id
+ if not self.account_analytic_id:
+ rec = self.env['account.analytic.default'].account_get(
+ self.product_id.id, self.invoice_id.partner_id.id, self.env.uid,
+ fields.Date.today(), company_id=self.company_id.id)
+ if rec:
+ self.account_analytic_id = rec.analytic_id.id
super(AccountInvoiceLine, self)._set_additional_fields(invoice)
diff --git a/addons/event/views/event_views.xml b/addons/event/views/event_views.xml
index 0eb0e01b9d3..c660e5e5594 100644
--- a/addons/event/views/event_views.xml
+++ b/addons/event/views/event_views.xml
@@ -244,7 +244,7 @@
-
+
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 9cf1c22e482..55446e37263 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -12,18 +12,17 @@ from odoo.tools.safe_eval import safe_eval
EXPRESSION_PATTERN = re.compile('(\$\{.+?\})')
-def _reopen(self, res_id, model):
+def _reopen(self, res_id, model, context=None):
+ # save original model in context, because selecting the list of available
+ # templates requires a model in context
+ context = dict(context or {}, default_model=model)
return {'type': 'ir.actions.act_window',
'view_mode': 'form',
'view_type': 'form',
'res_id': res_id,
'res_model': self._name,
'target': 'new',
- # save original model in context, because selecting the list of available
- # templates requires a model in context
- 'context': {
- 'default_model': model,
- },
+ 'context': context,
}
@@ -409,7 +408,7 @@ class MailComposer(models.TransientModel):
# generate the saved template
record.write({'template_id': template.id})
record.onchange_template_id_wrapper()
- return _reopen(self, record.id, record.model)
+ return _reopen(self, record.id, record.model, context=self._context)
#------------------------------------------------------
# Template rendering
diff --git a/addons/payment_transfer/i18n/payment_transfer.pot b/addons/payment_transfer/i18n/payment_transfer.pot
index a22512d6633..5995c6f8dd1 100644
--- a/addons/payment_transfer/i18n/payment_transfer.pot
+++ b/addons/payment_transfer/i18n/payment_transfer.pot
@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0c\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2016-09-07 08:57+0000\n"
-"PO-Revision-Date: 2016-09-07 08:57+0000\n"
+"POT-Creation-Date: 2016-12-14 14:52+0000\n"
+"PO-Revision-Date: 2016-12-14 14:52+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -15,6 +15,18 @@ msgstr ""
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
+#. module: payment_transfer
+#: code:addons/payment_transfer/models/payment_acquirer.py:32
+#, python-format
+msgid "
\n"
+"
Please use the following transfer details
\n"
+"
%(bank_title)s
\n"
+"%(bank_accounts)s\n"
+"
Communication
\n"
+"
Please use the order name as communication reference.
\n"
+"
"
+msgstr ""
+
#. module: payment_transfer
#: code:addons/payment_transfer/models/payment.py:63
#, python-format
diff --git a/addons/payment_transfer/models/payment.py b/addons/payment_transfer/models/payment.py
index c61b12b1ceb..4a19e336f3f 100644
--- a/addons/payment_transfer/models/payment.py
+++ b/addons/payment_transfer/models/payment.py
@@ -25,13 +25,13 @@ class TransferPaymentAcquirer(models.Model):
accounts = journals.mapped('bank_account_id').name_get()
bank_title = _('Bank Accounts') if len(accounts) > 1 else _('Bank Account')
bank_accounts = ''.join([''] + ['- %s
' % name for id, name in accounts] + ['
'])
- post_msg = '''
+ post_msg = _('''
Please use the following transfer details
%(bank_title)s
%(bank_accounts)s
Communication
Please use the order name as communication reference.
-
''' % {
+
''') % {
'bank_title': bank_title,
'bank_accounts': bank_accounts,
}
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 4731d30afb1..9a10c8a9812 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -53,36 +53,29 @@ class PosOrder(models.Model):
}
# This deals with orders that belong to a closed session. In order
- # to recover from this we:
- # - assign the order to another compatible open session
- # - if that doesn't exist, create a new one
+ # to recover from this situation we create a new rescue session,
+ # making it obvious that something went wrong.
+ # A new, separate, rescue session is preferred for every such recovery,
+ # to avoid adding unrelated orders to live sessions.
def _get_valid_session(self, order):
PosSession = self.env['pos.session']
closed_session = PosSession.browse(order['pos_session_id'])
- open_session = PosSession.search(
- [('state', '=', 'opened'),
- ('config_id', '=', closed_session.config_id.id),
- ('user_id', '=', closed_session.user_id.id)],
- limit=1, order="start_at DESC")
_logger.warning('session %s (ID: %s) was closed but received order %s (total: %s) belonging to it',
closed_session.name,
closed_session.id,
order['name'],
order['amount_total'])
+ _logger.warning('attempting to create recovery session for saving order %s', order['name'])
+ new_session = PosSession.create({
+ 'config_id': closed_session.config_id.id,
+ 'name': _('(RESCUE FOR %(session)s)') % {'session': closed_session.name},
+ 'rescue': True, # avoid conflict with live sessions
+ })
+ # bypass opening_control (necessary when using cash control)
+ new_session.action_pos_session_open()
- if open_session:
- _logger.warning('using session %s (ID: %s) for order %s instead',
- open_session.name,
- open_session.id,
- order['name'])
- return open_session
- else:
- _logger.warning('attempting to create new session for order %s', order['name'])
- new_session = PosSession.create({'config_id': closed_session.config_id.id})
- # bypass opening_control (necessary when using cash control)
- new_session.action_pos_session_open()
- return new_session
+ return new_session
def _match_payment_to_invoice(self, order):
account_precision = self.env['decimal.precision'].precision_get('Account')
@@ -408,7 +401,7 @@ class PosOrder(models.Model):
# set name based on the sequence specified on the config
session = self.env['pos.session'].browse(values['session_id'])
values['name'] = session.config_id.sequence_id._next()
- values.setdefault('session_id', session.config_id.pricelist_id.id)
+ values.setdefault('pricelist_id', session.config_id.pricelist_id.id)
else:
# fallback on any pos.order sequence
values['name'] = self.env['ir.sequence'].next_by_code('pos.order')
diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py
index 5c6afff5dd8..d30c96ac0f3 100644
--- a/addons/point_of_sale/models/pos_session.py
+++ b/addons/point_of_sale/models/pos_session.py
@@ -165,6 +165,8 @@ class PosSession(models.Model):
pos_config.sudo().write({'journal_ids': [(6, 0, journals.ids)]})
pos_name = self.env['ir.sequence'].with_context(ctx).next_by_code('pos.session')
+ if values.get('name'):
+ pos_name += ' ' + values['name']
statements = []
ABS = self.env['account.bank.statement']
@@ -269,3 +271,33 @@ class PosSession(models.Model):
'target': 'self',
'url': '/pos/web/',
}
+
+ @api.multi
+ def open_cashbox(self):
+ self.ensure_one()
+ context = dict(self._context)
+ balance_type = context.get('balance') or 'start'
+ context['bank_statement_id'] = self.cash_register_id.id
+ context['balance'] = balance_type
+ context['default_pos_id'] = self.config_id.id
+
+ action = {
+ 'name': _('Cash Control'),
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'account.bank.statement.cashbox',
+ 'view_id': self.env.ref('account.view_account_bnk_stmt_cashbox').id,
+ 'type': 'ir.actions.act_window',
+ 'context': context,
+ 'target': 'new'
+ }
+
+ cashbox_id = None
+ if balance_type == 'start':
+ cashbox_id = self.cash_register_id.cashbox_start_id.id
+ else:
+ cashbox_id = self.cash_register_id.cashbox_end_id.id
+ if cashbox_id:
+ action['res_id'] = cashbox_id
+
+ return action
diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js
index 15cf0fdef6b..f0755fa7573 100644
--- a/addons/point_of_sale/static/src/js/gui.js
+++ b/addons/point_of_sale/static/src/js/gui.js
@@ -114,7 +114,7 @@ var Gui = core.Class.extend({
this.current_screen.hide();
}
this.current_screen = screen;
- this.current_screen.show();
+ this.current_screen.show(refresh);
}
},
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index b34e9c32290..bc4f2a2fd2e 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -1075,7 +1075,8 @@ exports.Orderline = Backbone.Model.extend({
init_from_JSON: function(json) {
this.product = this.pos.db.get_product_by_id(json.product_id);
if (!this.product) {
- console.error('ERROR: attempting to recover product not available in the point of sale');
+ console.error('ERROR: attempting to recover product ID', json.product_id,
+ 'not available in the point of sale. Correct the product or clean the browser cache.');
}
this.set_product_lot(this.product)
this.price = json.price_unit;
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index bb30e7b7943..5f61deb5e0c 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -272,8 +272,9 @@ var ScaleScreenWidget = ScreenWidget.extend({
});
this.$('.next,.buy-product').click(function(){
- self.order_product();
self.gui.show_screen(self.next_screen);
+ // add product *after* switching screen to scroll properly
+ self.order_product();
});
queue.schedule(function(){
@@ -964,10 +965,12 @@ var ProductScreenWidget = ScreenWidget.extend({
}
},
- show: function(){
+ show: function(reset){
this._super();
- this.product_categories_widget.reset_category();
- this.numpad.state.reset();
+ if (reset) {
+ this.product_categories_widget.reset_category();
+ this.numpad.state.reset();
+ }
},
close: function(){
diff --git a/addons/point_of_sale/views/pos_session_view.xml b/addons/point_of_sale/views/pos_session_view.xml
index 0b6d29a0973..47f8cec724b 100644
--- a/addons/point_of_sale/views/pos_session_view.xml
+++ b/addons/point_of_sale/views/pos_session_view.xml
@@ -48,18 +48,18 @@
Money Out
-
-