diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 229df089c63..1b983dff050 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -44,8 +44,9 @@ class AccountInvoice(models.Model): @api.one @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'currency_id', 'company_id', 'date_invoice', 'type') def _compute_amount(self): + round_curr = self.currency_id.round self.amount_untaxed = sum(line.price_subtotal for line in self.invoice_line_ids) - self.amount_tax = sum(line.amount for line in self.tax_line_ids) + self.amount_tax = sum(round_curr(line.amount) for line in self.tax_line_ids) self.amount_total = self.amount_untaxed + self.amount_tax amount_total_company_signed = self.amount_total amount_untaxed_signed = self.amount_untaxed diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js index 8655f0b0a2b..fa77674a6b9 100644 --- a/addons/mail/static/src/js/client_action.js +++ b/addons/mail/static/src/js/client_action.js @@ -658,7 +658,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, { }); }, destroy: function() { - this.$buttons.off().destroy(); + if (this.$buttons) { + this.$buttons.off().destroy(); + } this._super.apply(this, arguments); }, }); diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index bc5c440e35e..516fc2b6293 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -284,7 +284,7 @@ class StockMove(models.Model): quants = quant_obj.quants_get_preferred_domain(move.product_qty, move, domain=main_domain, preferred_domain_list=preferred_domain_list) self.env['stock.quant'].quants_move(quants, move, move.location_dest_id) else: - for movelot in move.move_lot_ids: + for movelot in move.active_move_lot_ids: if float_compare(movelot.quantity_done, 0, precision_rounding=rounding) > 0: if not movelot.lot_id: raise UserError(_('You need to supply a lot/serial number.')) diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py index 14eb7a65b31..eed76496be8 100644 --- a/addons/mrp/tests/test_workorder_operation.py +++ b/addons/mrp/tests/test_workorder_operation.py @@ -80,7 +80,6 @@ class TestWorkOrderProcess(common.TransactionCase): # -------------------------------------------------------------- # Process cutting operation... # --------------------------------------------------------- - finished_lot =self.env['stock.production.lot'].create({'product_id': production_table.product_id.id}) workorders[0].write({'final_lot_id': finished_lot.id}) workorders[0].button_start() @@ -148,6 +147,119 @@ class TestWorkOrderProcess(common.TransactionCase): self.assertEqual(finished_quant.consumed_quant_ids, consume_quants) + def test_00b_workorder_process(self): + """ Testing consume quants and produced quants with workorder """ + dining_table = self.env.ref("mrp.product_product_computer_desk") + product_table_sheet = self.env.ref('mrp.product_product_computer_desk_head') + product_table_leg = self.env.ref('mrp.product_product_computer_desk_leg') + product_bolt = self.env.ref('mrp.product_product_computer_desk_bolt') + + production_table = self.env['mrp.production'].create({ + 'product_id': dining_table.id, + 'product_qty': 2.0, + 'product_uom_id': dining_table.uom_id.id, + 'bom_id': self.ref("mrp.mrp_bom_desk") + }) + + # Set tracking lot on finish and consume products. + dining_table.tracking = 'lot' + product_table_sheet.tracking = 'lot' + product_table_leg.tracking = 'lot' + product_bolt.tracking = "lot" + + # Initial inventory of product sheet, lags and bolt + lot_sheet = self.env['stock.production.lot'].create({'product_id': product_table_sheet.id}) + lot_leg = self.env['stock.production.lot'].create({'product_id': product_table_leg.id}) + lot_bolt = self.env['stock.production.lot'].create({'product_id': product_bolt.id}) + + # Initialize inventory + # -------------------- + inventory = self.env['stock.inventory'].create({ + 'name': 'Inventory Product Table', + 'filter': 'partial', + 'line_ids': [(0, 0, { + 'product_id': product_table_sheet.id, + 'product_uom_id': product_table_sheet.uom_id.id, + 'product_qty': 20, + 'prod_lot_id': lot_sheet.id, + 'location_id': self.source_location_id + }), (0, 0, { + 'product_id': product_table_leg.id, + 'product_uom_id': product_table_leg.uom_id.id, + 'product_qty': 20, + 'prod_lot_id': lot_leg.id, + 'location_id': self.source_location_id + }), (0, 0, { + 'product_id': product_bolt.id, + 'product_uom_id': product_bolt.uom_id.id, + 'product_qty': 20, + 'prod_lot_id': lot_bolt.id, + 'location_id': self.source_location_id + })] + }) + inventory.action_done() + + # Create work order + production_table.button_plan() + # Check Work order created or not + self.assertEqual(len(production_table.workorder_ids), 3) + + # --------------------------------------------------------- + # Process all workorder and check it state. + # ---------------------------------------------------------- + + workorders = production_table.workorder_ids + self.assertEqual(workorders[0].state, 'ready', "First workorder state should be ready.") + self.assertEqual(workorders[1].state, 'pending') + self.assertEqual(workorders[2].state, 'pending') + + # -------------------------------------------------------------- + # Process cutting operation... + # --------------------------------------------------------- + finished_lot = self.env['stock.production.lot'].create({'product_id': production_table.product_id.id}) + workorders[0].write({'final_lot_id': finished_lot.id, 'qty_producing': 1.0}) + workorders[0].button_start() + workorders[0].active_move_lot_ids[0].write({'lot_id': lot_sheet.id, 'quantity_done': 1}) + self.assertEqual(workorders[0].state, 'progress') + workorders[0].record_production() + + move_table_sheet = production_table.move_raw_ids.filtered(lambda x : x.product_id == product_table_sheet) + self.assertEqual(move_table_sheet.quantity_done, 1) + + # -------------------------------------------------------------- + # Process drilling operation ... + # --------------------------------------------------------- + workorders[1].button_start() + workorders[1].qty_producing = 1.0 + workorders[1].active_move_lot_ids[0].write({'lot_id': lot_leg.id, 'quantity_done': 4}) + workorders[1].record_production() + move_leg = production_table.move_raw_ids.filtered(lambda x : x.product_id == product_table_leg) + #self.assertEqual(workorders[1].state, 'done') + self.assertEqual(move_leg.quantity_done, 4) + + # -------------------------------------------------------------- + # Process fitting operation ... + # --------------------------------------------------------- + workorders[2].button_start() + workorders[2].qty_producing = 1.0 + move_lot = workorders[2].active_move_lot_ids[0] + move_lot.write({'lot_id': lot_bolt.id, 'quantity_done': 4}) + move_table_bolt = production_table.move_raw_ids.filtered(lambda x : x.product_id.id == product_bolt.id) + workorders[2].record_production() + self.assertEqual(move_table_bolt.quantity_done, 4) + + # Change the quantity of the production order to 1 + wiz = self.env['change.production.qty'].create({'mo_id': production_table.id , + 'product_qty': 1.0}) + wiz.change_prod_qty() + # --------------------------------------------------------------- + # Check consume quants and produce quants after posting inventory + # --------------------------------------------------------------- + production_table.post_inventory() + self.assertEqual(sum(move_table_sheet.quant_ids.mapped('qty')), 1, "Wrong quantity of consumed product %s" % move_table_sheet.product_id.name) + self.assertEqual(sum(move_leg.quant_ids.mapped('qty')), 4, "Wrong quantity of consumed product %s" % move_leg.product_id.name) + self.assertEqual(sum(move_table_bolt.quant_ids.mapped('qty')), 4, "Wrong quantity of consumed product %s" % move_table_bolt.product_id.name) + def test_01_without_workorder(self): """ Testing consume quants and produced quants without workorder """ unit = self.ref("product.product_uom_unit") diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py index 463c30c0a7c..e9588516a0c 100644 --- a/addons/mrp/wizard/change_production_qty.py +++ b/addons/mrp/wizard/change_production_qty.py @@ -4,6 +4,7 @@ from odoo import api, fields, models, _ from odoo.addons import decimal_precision as dp from odoo.exceptions import UserError +from odoo.tools import float_is_zero import math class ChangeProductionQty(models.TransientModel): @@ -37,6 +38,7 @@ class ChangeProductionQty(models.TransientModel): @api.multi def change_prod_qty(self): + precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') for wizard in self: production = wizard.mo_id produced = sum(production.move_finished_ids.mapped('quantity_done')) @@ -64,11 +66,14 @@ class ChangeProductionQty(models.TransientModel): wo.duration_expected = (operation.workcenter_id.time_start + operation.workcenter_id.time_stop + cycle_number * operation.time_cycle * 100.0 / operation.workcenter_id.time_efficiency) + quantity = wo.qty_production - wo.qty_produced if production.product_id.tracking == 'serial': - quantity = 1.0 + quantity = 1.0 if float_is_zero(quantity, precision_digits=precision) else 0.0 else: - quantity = wo.qty_production - wo.qty_produced quantity = quantity if (quantity > 0) else 0 + if float_is_zero(quantity, precision_digits=precision): + wo.final_lot_id = False + wo.active_move_lot_ids.unlink() wo.qty_producing = quantity if wo.qty_produced < wo.qty_production and wo.state == 'done': wo.state = 'progress' @@ -81,6 +86,6 @@ class ChangeProductionQty(models.TransientModel): moves_finished = production.move_finished_ids.filtered(lambda move: move.operation_id == operation) #TODO: code does nothing, unless maybe by_products? moves_raw.mapped('move_lot_ids').write({'workorder_id': wo.id}) (moves_finished + moves_raw).write({'workorder_id': wo.id}) - if wo.move_raw_ids.filtered(lambda x: x.product_id.tracking != 'none') and not wo.active_move_lot_ids: + if quantity > 0 and wo.move_raw_ids.filtered(lambda x: x.product_id.tracking != 'none') and not wo.active_move_lot_ids: wo._generate_lot_ids() return {} diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 043188b6393..ccbdf40edd8 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1484,7 +1484,7 @@ exports.Orderline = Backbone.Model.extend({ var sign = 1; if(base < 0){ base = -base; - var sign = -1; + sign = -1; } var incl_fixed_amount = 0.0; @@ -1531,8 +1531,8 @@ exports.Orderline = Backbone.Model.extend({ }); return { taxes: taxes_vals, - total_excluded: round_pr(total_excluded, currency_rounding_bak), - total_included: round_pr(total_included, currency_rounding_bak) + total_excluded: sign * round_pr(total_excluded, currency_rounding_bak), + total_included: sign * round_pr(total_included, currency_rounding_bak) }; }, get_all_prices: function(){ diff --git a/addons/web_editor/static/src/js/summernote.js b/addons/web_editor/static/src/js/summernote.js index d28279d57ed..fe2ff837142 100644 --- a/addons/web_editor/static/src/js/summernote.js +++ b/addons/web_editor/static/src/js/summernote.js @@ -550,6 +550,13 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) { so = 0; eo = 1; } + + var parentNode = sc && sc.parentNode; + if (parentNode && sc.tagName === 'BR') { + sc = parentNode; + ec = parentNode; + } + return { sc: sc, so: so, @@ -1169,9 +1176,9 @@ $.summernote.pluginEvents.visible = function (event, editor, layoutInfo) { if (!r) return; if (!r.isCollapsed()) { - if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) { + if ((dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) && dom.node(r.sc) !== dom.node(r.ec)) { remove_table_content(r); - r = range.create(r.ec, 0).select(); + r = range.create(r.ec, 0); } r.select(); } diff --git a/addons/website_sale_delivery/static/src/js/website_sale_delivery.js b/addons/website_sale_delivery/static/src/js/website_sale_delivery.js index 0775b7657cc..12710db3b4c 100644 --- a/addons/website_sale_delivery/static/src/js/website_sale_delivery.js +++ b/addons/website_sale_delivery/static/src/js/website_sale_delivery.js @@ -1,7 +1,6 @@ $(document).ready(function () { var $pay_button = $('.oe_sale_acquirer_button button'); - $pay_button.prop('disabled', false); // When choosing an delivery carrier, update the quotation and the acquirers. Disable the 'Pay // Now' button to avoid being redirected to payment acquier if the delivery carrier update is diff --git a/doc/cla/individual/IOAyman.md b/doc/cla/individual/IOAyman.md new file mode 100644 index 00000000000..96a37ff862c --- /dev/null +++ b/doc/cla/individual/IOAyman.md @@ -0,0 +1,11 @@ +Algeria, 10-19-2017 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Ayman Nedjmeddine theycallmethedr@gmail.com https://github.com/IOAyman diff --git a/odoo/addons/base/res/res_partner_view.xml b/odoo/addons/base/res/res_partner_view.xml index 073632dd4d1..249029075fb 100644 --- a/odoo/addons/base/res/res_partner_view.xml +++ b/odoo/addons/base/res/res_partner_view.xml @@ -334,7 +334,8 @@ - + @@ -348,8 +349,8 @@ - - + +