diff --git a/addons/account/controllers/portal.py b/addons/account/controllers/portal.py index f67ae00286e..4303f972f93 100644 --- a/addons/account/controllers/portal.py +++ b/addons/account/controllers/portal.py @@ -9,17 +9,19 @@ from odoo.tools import consteq class PortalAccount(CustomerPortal): - - def _prepare_portal_layout_values(self): - values = super(PortalAccount, self)._prepare_portal_layout_values() + + def _get_account_invoice_domain(self): partner = request.env.user.partner_id - - invoice_count = request.env['account.invoice'].search_count([ + domain = [ ('type', 'in', ['out_invoice', 'out_refund']), ('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]), ('state', 'in', ['open', 'paid', 'cancel']) - ]) + ] + return domain + def _prepare_portal_layout_values(self): + values = super(PortalAccount, self)._prepare_portal_layout_values() + invoice_count = request.env['account.invoice'].search_count(self._get_account_invoice_domain()) values['invoice_count'] = invoice_count return values @@ -62,11 +64,7 @@ class PortalAccount(CustomerPortal): partner = request.env.user.partner_id AccountInvoice = request.env['account.invoice'] - domain = [ - ('type', 'in', ['out_invoice', 'out_refund']), - ('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]), - ('state', 'in', ['open', 'paid', 'cancelled']) - ] + domain = self._get_account_invoice_domain() searchbar_sortings = { 'date': {'label': _('Invoice Date'), 'order': 'date_invoice desc'}, diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js index bfd2e48972e..79724846cbc 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_action.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_action.js @@ -359,7 +359,11 @@ var ManualAction = StatementAction.extend({ title: self.title, time: Date.now()-self.time, }); - self._openFirstLine(); + if(!_.any(result.updated, function (handle) { + return self.model.getLine(handle).mode !== 'inactive'; + })) { + self._openFirstLine(); + } }); }, }); diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index b77882323a3..249e8b20862 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -501,7 +501,7 @@ var StatementModel = BasicModel.extend({ var amount = line.balance.amount; var amount_str = _.str.sprintf('%.2f', Math.abs(amount)); amount_str = (amount > '0' ? '-' : '+') + amount_str; - if (line.balance.amount_currency) { + if (line.balance.currency_id && line.balance.amount_currency) { var amount_currency = line.balance.amount_currency; var amount_currency_str = _.str.sprintf('%.2f', Math.abs(amount_currency)); amount_str += '|' + (amount_currency > '0' ? '-' : '+') + amount_currency_str; @@ -821,14 +821,13 @@ var StatementModel = BasicModel.extend({ amount: total, amount_str: field_utils.format.monetary(Math.abs(total), {}, formatOptions), currency_id: isOtherCurrencyId, - amount_currency: isOtherCurrencyId ? amount_currency : false, + amount_currency: isOtherCurrencyId ? amount_currency : total, amount_currency_str: isOtherCurrencyId ? field_utils.format.monetary(Math.abs(amount_currency), {}, { currency_id: isOtherCurrencyId }) : false, account_code: self.accounts[line.st_line.open_balance_account_id], }; - line.balance.type = (isOtherCurrencyId && amount_currency || line.balance.amount) ? - (line.balance.amount > 0 && line.st_line.partner_id ? 0 : -1) : 1; + line.balance.type = line.balance.amount_currency ? (line.balance.amount_currency > 0 && line.st_line.partner_id ? 0 : -1) : 1; }); }, /** @@ -1295,7 +1294,7 @@ var ManualModel = StatementModel.extend({ return this._super(line).then(function () { var props = _.reject(line.reconciliation_proposition, 'invalid'); line.balance.type = -1; - if (!line.balance.amount && props.length) { + if (!line.balance.amount_currency && props.length) { line.balance.type = 1; } else if(_.any(props, function (prop) {return prop.amount > 0;}) && _.any(props, function (prop) {return prop.amount < 0;})) { diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index bea6f5873d9..91139aa0926 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -363,7 +363,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { .attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line})); } - if ((state.balance.amount !== 0 || line.partial_reconcile) && props.length === 1 && + if ((state.balance.amount_currency !== 0 || line.partial_reconcile) && props.length === 1 && line.already_paid === false && ( (state.st_line.amount > 0 && state.st_line.amount < props[0].amount) || @@ -425,7 +425,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { } }); } - this.$('.create .add_line').toggle(!!state.balance.amount); + this.$('.create .add_line').toggle(!!state.balance.amount_currency); }, //-------------------------------------------------------------------------- diff --git a/addons/account/static/src/xml/account_reconciliation.xml b/addons/account/static/src/xml/account_reconciliation.xml index 20ef80c45c8..94bef90a6f5 100644 --- a/addons/account/static/src/xml/account_reconciliation.xml +++ b/addons/account/static/src/xml/account_reconciliation.xml @@ -124,13 +124,13 @@ - + - Create Write-offOpen balanceChoose counterpart - - + Create Write-offOpen balanceChoose counterpart + + diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index d292c83a8df..93e4a2f365b 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -448,7 +448,10 @@ var move_lines_for_manual_reconciliation = { '[7,12,"",0,6]': [ {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-08", 'date': "2017-02-08", 'total_amount_str': "$ 11,000.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0004: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 17, 'credit': 11000.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 11,000.00", 'debit': 0.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, {'account_type': "receivable", 'account_id': [7, "101200 Account Receivable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 1,000.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0005: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 19, 'credit': 1000.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 1,000.00", 'debit': 0.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 180.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "BILL/2017/0003: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 21, 'credit': 180.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 180.00", 'debit': 0.0, 'account_code': "101200", 'ref': "fddfgfdgfdgsdfg", 'already_paid': false}, + {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency': 100, 'amount_currency_str': "100.00 €", 'currency_id': 1, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 170.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0003: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 21, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 170.00", 'debit': 170.0, 'account_code': "101200", 'ref': "INV fddfgfdgfdgsdfg", 'already_paid': false}, + {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency': 100, 'amount_currency_str': "100.00 €", 'currency_id': 1, 'date_maturity': "2017-02-10", 'date': "2017-02-10", 'total_amount_str': "$ 180.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "BILL/2017/0003: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 22, 'credit': 180.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 180.00", 'debit': 0.0, 'account_code': "101200", 'ref': "fddfgfdgfdgsdfg", 'already_paid': false}, + {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency': 170, 'amount_currency_str': "170.00 €", 'currency_id': 1, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 100.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0003: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 23, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 100.00", 'debit': 100.0, 'account_code': "101200", 'ref': "INV fddfgfdgfdgsdfg", 'already_paid': false}, + {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency': 180, 'amount_currency_str': "180.00 €", 'currency_id': 1, 'date_maturity': "2017-02-10", 'date': "2017-02-10", 'total_amount_str': "$ 100.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "BILL/2017/0003: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 24, 'credit': 100.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 100.00", 'debit': 0.0, 'account_code': "101200", 'ref': "fddfgfdgfdgsdfg", 'already_paid': false}, ], '[284,8,"",0,6]': [ {'account_type': "receivable", 'account_id': [284, "111100 Account Payable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-08", 'date': "2017-02-08", 'total_amount_str': "$ 11,000.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0004: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 17, 'credit': 11000.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 11,000.00", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false}, @@ -1117,7 +1120,7 @@ QUnit.module('account', { testUtils.addMockEnvironment(clientAction, { 'data': this.params.data, }); - clientAction.appendTo($('body')); + clientAction.appendTo($('#qunit-fixture')); clientAction.$('button.o_automatic_reconciliation').trigger('click'); @@ -1141,7 +1144,7 @@ QUnit.module('account', { }); QUnit.test('Reconciliation manual', function (assert) { - assert.expect(8); + assert.expect(13); var clientAction = new ReconciliationClientAction.ManualAction(null, this.params.options); @@ -1171,6 +1174,23 @@ QUnit.module('account', { assert.strictEqual(clientAction.$('.accounting_view:first .o_no_valid:visible').length, 1, "should display the skip button"); + clientAction.$('.accounting_view:eq(1) thead td:first').trigger('click'); + clientAction.$('.o_reconciliation_line:eq(1) [data-line-id="21"] .cell_label').trigger('click'); + clientAction.$('.o_reconciliation_line:eq(1) [data-line-id="22"] .cell_label').trigger('click'); + + assert.strictEqual(clientAction.$('.o_reconciliation_line:eq(1) tfoot tr').length, 0, "should not display the 'Write-off' line because the balance is null in Euro"); + assert.strictEqual(clientAction.$('.o_reconciliation_line:eq(1) .o_reconcile:visible').length, 1, "should display 'Reconcile' button in green"); + + clientAction.$('.o_reconciliation_line:eq(1) .o_reconcile:visible').trigger('click'); + + assert.strictEqual(clientAction.$('.o_reconciliation_line[data-mode!="inactive"]').length, 1, "should have only one line open"); + + clientAction.$('.o_reconciliation_line:eq(1) [data-line-id="23"] .cell_label').trigger('click'); + clientAction.$('.o_reconciliation_line:eq(1) [data-line-id="24"] .cell_label').trigger('click'); + + assert.strictEqual(clientAction.$('.o_reconciliation_line:eq(1) tfoot tr').length, 1, "should display the 'Write-off' line because the balance is not null in Euro"); + assert.strictEqual(clientAction.$('.o_reconciliation_line:eq(1) .o_validate:visible').length, 1, "should display 'Reconcile' button"); + clientAction.destroy(); }); diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml index 92c5e5db026..e34beadc322 100644 --- a/addons/account/views/account_invoice_view.xml +++ b/addons/account/views/account_invoice_view.xml @@ -252,7 +252,7 @@ - + diff --git a/addons/hr/views/hr_views.xml b/addons/hr/views/hr_views.xml index aa84a081a01..fc75dbe232e 100644 --- a/addons/hr/views/hr_views.xml +++ b/addons/hr/views/hr_views.xml @@ -297,7 +297,7 @@ [('id','in',active_ids)] tree - + diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py index 3f2a6471c86..ac778e6be8f 100644 --- a/addons/l10n_fr_fec/wizard/account_fr_fec.py +++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py @@ -43,8 +43,8 @@ class AccountFrFec(models.TransientModel): '-' AS PieceRef, %s AS PieceDate, '/' AS EcritureLib, - replace(CASE WHEN COALESCE(sum(aml.balance), 0) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Debit, - replace(CASE WHEN COALESCE(sum(aml.balance), 0) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Credit, + replace(CASE WHEN COALESCE(sum(aml.balance), 0) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit, + replace(CASE WHEN COALESCE(sum(aml.balance), 0) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit, '' AS EcritureLet, '' AS DateLet, %s AS ValidDate, @@ -142,8 +142,8 @@ class AccountFrFec(models.TransientModel): '-' AS PieceRef, %s AS PieceDate, '/' AS EcritureLib, - replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Debit, - replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Credit, + replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit, + replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit, '' AS EcritureLet, '' AS DateLet, %s AS ValidDate, @@ -227,8 +227,8 @@ class AccountFrFec(models.TransientModel): '-' AS PieceRef, %s AS PieceDate, '/' AS EcritureLib, - replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Debit, - replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Credit, + replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit, + replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit, '' AS EcritureLet, '' AS DateLet, %s AS ValidDate, @@ -289,14 +289,14 @@ class AccountFrFec(models.TransientModel): AS PieceRef, TO_CHAR(am.date, 'YYYYMMDD') AS PieceDate, CASE WHEN aml.name IS NULL THEN '/' ELSE replace(replace(aml.name, '|', '/'), '\t', '') END AS EcritureLib, - replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '999999999999999D99') END, '.', ',') AS Debit, - replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '999999999999999D99') END, '.', ',') AS Credit, + replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '000000000000000D99') END, '.', ',') AS Debit, + replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '000000000000000D99') END, '.', ',') AS Credit, CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet, CASE WHEN aml.full_reconcile_id IS NULL THEN '' ELSE TO_CHAR(rec.create_date, 'YYYYMMDD') END AS DateLet, TO_CHAR(am.date, 'YYYYMMDD') AS ValidDate, CASE WHEN aml.amount_currency IS NULL OR aml.amount_currency = 0 THEN '' - ELSE replace(to_char(aml.amount_currency, '999999999999999D99'), '.', ',') + ELSE replace(to_char(aml.amount_currency, '000000000000000D99'), '.', ',') END AS Montantdevise, CASE WHEN aml.currency_id IS NULL THEN '' ELSE rc.name END AS Idevise FROM diff --git a/addons/mail/static/src/js/many2many_tags_email.js b/addons/mail/static/src/js/many2many_tags_email.js index f10d7d76701..7e1fb260a0c 100644 --- a/addons/mail/static/src/js/many2many_tags_email.js +++ b/addons/mail/static/src/js/many2many_tags_email.js @@ -48,7 +48,7 @@ BasicModel.include({ def = self._rpc({ model: list.model, method: 'name_get', - args: list.res_ids, + args: [list.res_ids], context: record.getContext({fieldName: fieldName}), }); } diff --git a/addons/mail/static/src/xml/activity.xml b/addons/mail/static/src/xml/activity.xml index 6b5aaabe5e8..da3e63772a6 100644 --- a/addons/mail/static/src/xml/activity.xml +++ b/addons/mail/static/src/xml/activity.xml @@ -17,7 +17,7 @@
- +
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js index f4043e8b174..9cf98987778 100644 --- a/addons/mail/static/tests/chatter_tests.js +++ b/addons/mail/static/tests/chatter_tests.js @@ -1273,7 +1273,7 @@ QUnit.test('fieldmany2many tags email', function (assert) { }, mockRPC: function (route, args) { if (route === "/web/dataset/call_kw/partner_type/name_get") { - assert.deepEqual(args.args, nameGottenIds.shift(), + assert.deepEqual(args.args[0], nameGottenIds.shift(), "partner with email should be name_get'ed"); } else if (args.method ==='read' && args.model === 'partner_type') { diff --git a/addons/mass_mailing/static/src/less/themes/theme_default.less b/addons/mass_mailing/static/src/less/themes/theme_default.less index cfd1264e0c7..adfa4bba5ce 100644 --- a/addons/mass_mailing/static/src/less/themes/theme_default.less +++ b/addons/mass_mailing/static/src/less/themes/theme_default.less @@ -50,6 +50,7 @@ // ===== Layout ===== .o_layout { overflow: hidden; + box-sizing: content-box; width: 94%; min-height: 100%; background-color: @o_mm_def_color_alpha; diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 10d8a7311a6..f9436f66870 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -13,7 +13,9 @@ class StockMoveLine(models.Model): workorder_id = fields.Many2one('mrp.workorder', 'Work Order') production_id = fields.Many2one('mrp.production', 'Production Order') lot_produced_id = fields.Many2one('stock.production.lot', 'Finished Lot') - lot_produced_qty = fields.Float('Quantity Finished Product', help="Informative, not used in matching") + lot_produced_qty = fields.Float( + 'Quantity Finished Product', digits=dp.get_precision('Product Unit of Measure'), + help="Informative, not used in matching") done_wo = fields.Boolean('Done for Work Order', default=True, help="Technical Field which is False when temporarily filled in in work order") # TDE FIXME: naming done_move = fields.Boolean('Move Done', related='move_id.is_done', store=True) # TDE FIXME: naming diff --git a/addons/mrp/views/mrp_routing_views.xml b/addons/mrp/views/mrp_routing_views.xml index 190ff768e96..e3425b66201 100644 --- a/addons/mrp/views/mrp_routing_views.xml +++ b/addons/mrp/views/mrp_routing_views.xml @@ -85,7 +85,7 @@ - + diff --git a/addons/phone_validation/__manifest__.py b/addons/phone_validation/__manifest__.py index dc21476150f..2ebc99f3ead 100644 --- a/addons/phone_validation/__manifest__.py +++ b/addons/phone_validation/__manifest__.py @@ -16,6 +16,7 @@ formatting. This module applies this feature to Leads and Contacts.""", 'data': [ + 'views/res_company_view.xml' ], 'depends': ['base'], } diff --git a/addons/phone_validation/models/res_company.py b/addons/phone_validation/models/res_company.py index 66d68225246..2ee2c18d7bd 100644 --- a/addons/phone_validation/models/res_company.py +++ b/addons/phone_validation/models/res_company.py @@ -10,7 +10,7 @@ class ResCompany(models.Model): phone_international_format = fields.Selection([ ('no_prefix', 'No prefix'), ('prefix', 'Add international prefix'), - ],string="Local Numbers", default="no_prefix", + ], string="Local Numbers", default="prefix", help="Always encode phone numbers using international format. Otherwise " "numbers coming from the company's country are nationaly formatted. " "International numbers are always using international format." diff --git a/addons/phone_validation/views/res_company_view.xml b/addons/phone_validation/views/res_company_view.xml new file mode 100644 index 00000000000..d788e5aa7fe --- /dev/null +++ b/addons/phone_validation/views/res_company_view.xml @@ -0,0 +1,17 @@ + + + + + res.company.view.form.phone_validation + res.company + + + + + + + + diff --git a/addons/product/views/product_template_views.xml b/addons/product/views/product_template_views.xml index 1cd7f1a166c..ed4e2ca54af 100644 --- a/addons/product/views/product_template_views.xml +++ b/addons/product/views/product_template_views.xml @@ -89,7 +89,7 @@
    -
  • Price:
  • +
  • Price:
diff --git a/addons/sale_stock/__manifest__.py b/addons/sale_stock/__manifest__.py index bc8f0d777d1..3e09974e3f5 100644 --- a/addons/sale_stock/__manifest__.py +++ b/addons/sale_stock/__manifest__.py @@ -25,7 +25,7 @@ You can choose flexible invoicing methods: * *Before Delivery*: A Draft invoice is created and must be paid before delivery """, 'website': 'https://www.odoo.com/page/warehouse', - 'depends': ['sale', 'stock_account'], + 'depends': ['sale_management', 'stock_account'], 'data': [ 'security/sale_stock_security.xml', 'security/ir.model.access.csv', diff --git a/addons/stock/models/procurement.py b/addons/stock/models/procurement.py index cb1072efe50..38b9af72a2b 100644 --- a/addons/stock/models/procurement.py +++ b/addons/stock/models/procurement.py @@ -78,7 +78,6 @@ class ProcurementRule(models.Model): data = self._get_stock_move_values(product_id, product_qty, product_uom, location_id, name, origin, values, group_id) # Since action_confirm launch following procurement_group we should activate it. move = self.env['stock.move'].sudo().create(data) - move._assign_picking() move._action_confirm() return True diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 05e4bb2b60d..21e41caa10f 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -183,6 +183,10 @@ class Route(models.Model): 'domain': [('route_ids', 'in', self.ids)], } + @api.onchange('warehouse_selectable') + def _onchange_warehouse_selectable(self): + if not self.warehouse_selectable: + self.warehouse_ids = [(5, 0, 0)] class PushedFlow(models.Model): _name = "stock.location.path" diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index a8a32347882..b0cf820005b 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -17,6 +17,7 @@ import operator import os import re import sys +import tempfile import time import zlib @@ -24,6 +25,7 @@ import werkzeug import werkzeug.utils import werkzeug.wrappers import werkzeug.wsgi +from collections import OrderedDict from werkzeug.urls import url_decode, iri_to_uri from xml.etree import ElementTree @@ -41,6 +43,7 @@ from odoo.http import content_disposition, dispatch_rpc, request, \ serialize_exception as _serialize_exception, Response from odoo.exceptions import AccessError, UserError from odoo.models import check_method_name +from odoo.service import db _logger = logging.getLogger(__name__) @@ -155,10 +158,10 @@ def module_installed(environment): # TODO The following code should move to ir.module.module.list_installed_modules() Modules = environment['ir.module.module'] domain = [('state','=','installed'), ('name','in', loadable)] - modules = { - module.name: module.dependencies_id.mapped('name') + modules = OrderedDict( + (module.name, module.dependencies_id.mapped('name')) for module in Modules.search(domain) - } + ) sorted_modules = topological_sort(modules) return sorted_modules @@ -740,12 +743,15 @@ class Database(http.Controller): @http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False) def restore(self, master_pwd, backup_file, name, copy=False): try: - data = base64.b64encode(backup_file.read()) - dispatch_rpc('db', 'restore', [master_pwd, name, data, str2bool(copy)]) + with tempfile.NamedTemporaryFile(delete=False) as data_file: + backup_file.save(data_file) + db.restore_db(name, data_file.name, str2bool(copy)) return http.local_redirect('/web/database/manager') except Exception as e: error = "Database restore error: %s" % (str(e) or repr(e)) return self._render_template(error=error) + finally: + os.unlink(data_file.name) @http.route('/web/database/change_password', type='http', auth="none", methods=['POST'], csrf=False) def change_password(self, master_pwd, master_pwd_new): diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js index 99534708a94..1bdd9e70a69 100644 --- a/addons/web/static/src/js/core/utils.js +++ b/addons/web/static/src/js/core/utils.js @@ -23,6 +23,22 @@ var utils = { throw new Error("AssertionError"); } }, + /** + * Check if the value is a bin_size or not. + * If not, compute an approximate size out of the base64 encoded string. + * + * @param {string} value original format + * @return {string} bin_size (human-readable) + */ + binaryToBinsize: function (value) { + if (!this.is_bin_size(value)) { + // Computing approximate size out of base64 encoded string + // http://en.wikipedia.org/wiki/Base64#MIME + return this.human_size(value.length / 1.37); + } + // already bin_size + return value; + }, /** * Confines a value inside an interval * @@ -132,7 +148,7 @@ var utils = { size /= 1024; ++i; } - return size.toFixed(2) + ' ' + units[i]; + return size.toFixed(2) + ' ' + units[i].trim(); }, /** * Insert "thousands" separators in the provided number (which is actually diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index 2534f8ae90a..8bb9afca065 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -44,6 +44,11 @@ var AbstractField = Widget.extend({ navigation_move: '_onNavigationMove', }, + /** + * fields can extend the context, e.g. binary fields add {bin_size: true} + */ + context: null, + /** * An object representing fields to be fetched by the model eventhough not present in the view * This object contains "field name" as key and an object as value. diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index d0662fe7cef..f3e66cee421 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -1094,6 +1094,7 @@ var AbstractFieldBinary = AbstractField.extend({ }, 'click .o_clear_file_button': 'on_clear', }), + context: { bin_size: true }, init: function (parent, name, record) { this._super.apply(this, arguments); this.fields = record.fields; @@ -1312,25 +1313,6 @@ var FieldBinaryFile = AbstractFieldBinary.extend({ }, }); -var FieldBinaryDownloadLink = AbstractFieldBinary.extend({ - template: 'FieldBinaryDownloadLink', - events: _.extend({}, AbstractFieldBinary.prototype.events, { - 'click': function (ev) { - ev.stopPropagation(); - }, - }), - supportedFieldTypes: ['binary'], - init: function () { - this._super.apply(this, arguments); - this.filename = _t("Binary File"); - if (this.nodeOptions.filename && this.recordData[this.nodeOptions.filename]) { - this.filename = this.recordData[this.nodeOptions.filename]; - } - this.url = "data:application/octet-stream;base64," + this.value; - this.text = _t("Download"); - }, -}); - var PriorityWidget = AbstractField.extend({ // the current implementation of this widget makes it // only usable for fields of type selection @@ -2561,7 +2543,6 @@ return { TranslatableFieldMixin: TranslatableFieldMixin, DebouncedField: DebouncedField, FieldEmail: FieldEmail, - FieldBinaryDownloadLink: FieldBinaryDownloadLink, FieldBinaryFile: FieldBinaryFile, FieldBinaryImage: FieldBinaryImage, FieldBoolean: FieldBoolean, diff --git a/addons/web/static/src/js/fields/field_registry.js b/addons/web/static/src/js/fields/field_registry.js index 85b4fc1187f..6692663d5f9 100644 --- a/addons/web/static/src/js/fields/field_registry.js +++ b/addons/web/static/src/js/fields/field_registry.js @@ -33,7 +33,6 @@ registry .add('email', basic_fields.FieldEmail) .add('phone', basic_fields.FieldPhone) .add('url', basic_fields.UrlWidget) - .add('download_link', basic_fields.FieldBinaryDownloadLink) .add('image', basic_fields.FieldBinaryImage) .add('binary', basic_fields.FieldBinaryFile) .add('monetary', basic_fields.FieldMonetary) diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js index 812a7f380f7..5ac180b1de1 100644 --- a/addons/web/static/src/js/fields/field_utils.js +++ b/addons/web/static/src/js/fields/field_utils.js @@ -28,17 +28,17 @@ var _t = core._t; //------------------------------------------------------------------------------ /** + * Convert binary to bin_size * - * @param {string} value base64 representation of the binary + * @param {string} value base64 representation of the binary (might be already a bin_size!) * @param {Object} [field] * a description of the field (note: this parameter is ignored) * @param {Object} [options] additional options (note: this parameter is ignored) + * + * @returns {string} bin_size (which is human-readable) */ function formatBinary(value, field, options) { - if (!value) { - return ''; - } - return utils.human_size(value.length / 1.37); // base64 estimated size: http://en.wikipedia.org/wiki/Base64#MIME + return utils.binaryToBinsize(value); } /** diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js index 72ec816efdd..948d3a1c73a 100644 --- a/addons/web/static/src/js/services/data_manager.js +++ b/addons/web/static/src/js/services/data_manager.js @@ -387,6 +387,10 @@ return core.Class.extend({ attrs.fieldDependencies = attrs.Widget.prototype.fieldDependencies; } + if (attrs.Widget.prototype.context) { + attrs.context = attrs.Widget.prototype.context; + } + return attrs; }, /** diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 3621b7c1227..a6979776c99 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -33,7 +33,7 @@ odoo.define('web.BasicModel', function (require) { * isOpen: {boolean}, * loadMoreOffset: {integer}, * limit: {integer}, - * model: {string, + * model: {string}, * offset: {integer}, * openGroupByDefault: {boolean}, * orderedBy: {Object[]}, @@ -1854,7 +1854,7 @@ var BasicModel = AbstractModel.extend({ model: record.model, method: 'read', args: [[record.res_id], fieldNames], - context: _.extend({}, record.getContext(), {bin_size: true}), + context: record.getContext(), }) .then(function (result) { if (result.length === 0) { @@ -2374,7 +2374,7 @@ var BasicModel = AbstractModel.extend({ }); record.data[fieldName] = list.id; if (!fieldInfo.__no_fetch) { - var def = self._readUngroupedList(list).then(function () { + var def = self._readUngroupedList(list).then(function (list) { return $.when( self._fetchX2ManysBatched(list), self._fetchReferencesBatched(list) @@ -3040,10 +3040,33 @@ var BasicModel = AbstractModel.extend({ * the resource in the localData object. * * @param {Object} params - * @param {Object} [params.fieldsInfo={}] contains the fieldInfo of each field - * @param {Array} [params.fieldNames] the name of fields to load, the list - * of all fields by default + * @param {Object} [params.aggregateValues={}] + * @param {Object} [params.context={}] context of the action + * @param {integer} [params.count=0] number of record being manipulated + * @param {Object|Object[]} [params.data={}|[]] data of the record + * @param {*[]} [params.domain=[]] * @param {Object} params.fields contains the description of each field + * @param {Object} [params.fieldsInfo={}] contains the fieldInfo of each field + * @param {Object[]} [params.fieldNames] the name of fields to load, the list + * of all fields by default + * @param {string[]} [params.groupedBy=[]] + * @param {boolean} [params.isOpen] + * @param {integer} params.limit max number of records shown on screen (pager size) + * @param {string} params.modelName + * @param {integer} [params.offset] + * @param {boolean} [params.openGroupByDefault] + * @param {Object[]} [params.orderedBy=[]] + * @param {integer[]} [params.orderedResIDs] + * @param {string} [params.parentID] model name ID of the parent model + * @param {Object} [params.rawContext] + * @param {[type]} [params.ref] + * @param {string} [params.relationField] + * @param {integer|null} [params.res_id] actual id of record in the server + * @param {integer[]} [params.res_ids] context in which the data point is used, from a list of res_id + * @param {boolean} [params.static=false] + * @param {string} [params.type='record'|'list'] + * @param {[type]} [params.value] + * @param {string} [params.viewType] the type of the view, e.g. 'list' or 'form' * @returns {Object} the resource created */ _makeDataPoint: function (params) { @@ -3069,13 +3092,23 @@ var BasicModel = AbstractModel.extend({ id: {type: 'integer'}, }, params.fields); + // datapoint context is extended by fields widgets + var context = _.extend({}, params.context); + if (params.fieldsInfo && params.viewType) { + _.each(params.fieldsInfo[params.viewType], function (field) { + if (field.context) { + _.extend(context, field.context); + } + }); + } + var dataPoint = { _cache: type === 'list' ? {} : undefined, _changes: null, _domains: {}, _rawChanges: {}, aggregateValues: params.aggregateValues || {}, - context: params.context || {}, + context: context, count: params.count || res_ids.length, data: data, domain: params.domain || [], diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index 9409a51b7e0..779121de3e9 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -693,6 +693,7 @@ var FormRenderer = BasicRenderer.extend({ var self = this; var $headers = $('