diff --git a/addons/event/models/event.py b/addons/event/models/event.py index d55a17de099..f11105d5e03 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -386,9 +386,14 @@ class EventRegistration(models.Model): @api.multi def message_get_suggested_recipients(self): recipients = super(EventRegistration, self).message_get_suggested_recipients() + public_users = self.env['res.users'].sudo() + public_groups = self.env.ref("base.group_public", raise_if_not_found=False) + if public_groups: + public_users = public_groups.sudo().with_context(active_test=False).mapped("users") try: for attendee in self: - if attendee.partner_id: + is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False + if attendee.partner_id and not is_public: attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer')) elif attendee.email: attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email')) diff --git a/addons/l10n_nl/data/account_tax_template.xml b/addons/l10n_nl/data/account_tax_template.xml index 07558101a49..b534e9b7369 100644 --- a/addons/l10n_nl/data/account_tax_template.xml +++ b/addons/l10n_nl/data/account_tax_template.xml @@ -549,7 +549,7 @@ none - + 20 @@ -680,7 +680,7 @@ none - + 20 diff --git a/addons/link_tracker/models/link_tracker.py b/addons/link_tracker/models/link_tracker.py index bdc97778543..c1152d4b43c 100644 --- a/addons/link_tracker/models/link_tracker.py +++ b/addons/link_tracker/models/link_tracker.py @@ -248,19 +248,20 @@ class link_tracker_click(models.Model): vals = { 'link_id': code_rec.link_id.id, - 'create_date': datetime.date.today(), 'ip': ip, 'country_id': country_record.id, - 'mail_stat_id': stat_id } if stat_id: mail_stat = self.env['mail.mail.statistics'].search([('id', '=', stat_id)]) + # It could happen that the related ID is no longer available, but we still want the link to work + if mail_stat.exists(): + vals['mail_stat_id'] = stat_id - if mail_stat.mass_mailing_campaign_id: - vals['mass_mailing_campaign_id'] = mail_stat.mass_mailing_campaign_id.id + if mail_stat.mass_mailing_campaign_id: + vals['mass_mailing_campaign_id'] = mail_stat.mass_mailing_campaign_id.id - if mail_stat.mass_mailing_id: - vals['mass_mailing_id'] = mail_stat.mass_mailing_id.id + if mail_stat.mass_mailing_id: + vals['mass_mailing_id'] = mail_stat.mass_mailing_id.id self.create(vals) diff --git a/addons/payment_ogone/controllers/main.py b/addons/payment_ogone/controllers/main.py index af2e9e05732..a89ff2c63b3 100644 --- a/addons/payment_ogone/controllers/main.py +++ b/addons/payment_ogone/controllers/main.py @@ -48,7 +48,7 @@ class OgoneController(http.Controller): def feedback(self, **kwargs): try: tx = request.env['payment.transaction'].sudo()._ogone_form_get_tx_from_data(kwargs) - tx._ogone_s2s_validate() + tx._ogone_s2s_validate_tree(kwargs) except ValidationError: return 'ko' return 'ok' diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 28f0eb9e1ed..8d13d682f5d 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -663,7 +663,7 @@ class SaleOrderLine(models.Model): if line.state != 'sale' or not line.product_id._need_procurement(): continue qty = 0.0 - for proc in line.procurement_ids: + for proc in line.procurement_ids.filtered(lambda r: r.state != 'cancel'): qty += proc.product_qty if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0: continue diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py index 8453a49b4e3..f921d1eaba6 100644 --- a/addons/stock/models/stock_scrap.py +++ b/addons/stock/models/stock_scrap.py @@ -80,16 +80,17 @@ class StockScrap(models.Model): for scrap in self: moves = scrap._get_origin_moves() or self.env['stock.move'] move = self.env['stock.move'].create(scrap._prepare_move_values()) - quants = self.env['stock.quant'].quants_get_preferred_domain( - move.product_qty, move, - domain=[ - ('qty', '>', 0), - ('lot_id', '=', self.lot_id.id), - ('package_id', '=', self.package_id.id)], - preferred_domain_list=scrap._get_preferred_domain()) - if any([not x[0] for x in quants]): - raise UserError(_('You cannot scrap a move without having available stock for %s. You can correct it with an inventory adjustment.') % move.product_id.name) - self.env['stock.quant'].quants_reserve(quants, move) + if move.product_id.type == 'product': + quants = self.env['stock.quant'].quants_get_preferred_domain( + move.product_qty, move, + domain=[ + ('qty', '>', 0), + ('lot_id', '=', self.lot_id.id), + ('package_id', '=', self.package_id.id)], + preferred_domain_list=scrap._get_preferred_domain()) + if any([not x[0] for x in quants]): + raise UserError(_('You cannot scrap a move without having available stock for %s. You can correct it with an inventory adjustment.') % move.product_id.name) + self.env['stock.quant'].quants_reserve(quants, move) move.action_done() scrap.write({'move_id': move.id, 'state': 'done'}) moves.recalculate_move_state() diff --git a/addons/stock/tests/__init__.py b/addons/stock/tests/__init__.py index bbc9cde189e..06e4428afda 100644 --- a/addons/stock/tests/__init__.py +++ b/addons/stock/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_stock_flow import test_product import test_warehouse import test_shipment +import test_stock_scrap diff --git a/addons/stock/tests/test_stock_scrap.py b/addons/stock/tests/test_stock_scrap.py new file mode 100644 index 00000000000..b5dc521f4b7 --- /dev/null +++ b/addons/stock/tests/test_stock_scrap.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +from openerp.tests.common import TransactionCase +from openerp.exceptions import UserError + + +class TestStockScrap(TransactionCase): + + def setUp(self): + super(TestStockScrap, self).setUp() + self.user_demo = self.env.ref('base.user_demo') + self.scrap_obj = self.env['stock.scrap'] + self.picking_obj = self.env['stock.picking'] + self.stock_loc = self.browse_ref('stock.stock_location_stock') + self.customer_loc = self.browse_ref('stock.stock_location_customers') + + # Stockable product + self.product_stockable = self.env['product.product'].create({ + 'type': 'product', + 'name': 'Stockable Product', + 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_po_id': self.env.ref('product.product_uom_unit').id, + }) + + # Consumable product + self.product_consumable = self.env['product.product'].create({ + 'type': 'consu', + 'name': 'Consumable Product', + 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_po_id': self.env.ref('product.product_uom_unit').id, + }) + + self._update_product_qty(self.product_stockable) + + def _update_product_qty(self, product): + product_qty = self.env['stock.change.product.qty'].create({ + 'location_id': self.stock_loc.id, + 'product_id': product.id, + 'new_quantity': 100.0, + }) + product_qty.change_product_qty() + return product_qty + + def _prepare_picking(self, product): + picking = self.picking_obj.create({ + 'name': 'picking - test', + 'location_id': self.stock_loc.id, + 'location_dest_id': self.customer_loc.id, + 'picking_type_id': self.ref('stock.picking_type_out'), + 'move_lines': [(0, 0, { + 'name': product.name, + 'product_id': product.id, + 'product_uom_qty': 20.0, + 'product_uom': product.uom_id.id, + })] + }) + return picking + + def test_stock_scrap_01(self): + """ Standalone scrap a stockable product """ + with self.assertRaises(UserError): + self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_stockable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'scrap_qty': 120, + }) + self.assertEquals(self.product_stockable.qty_available, 100.0) + + def test_stock_scrap_02(self): + """ Scrap a stockable product from a picking """ + picking = self._prepare_picking(self.product_stockable) + scrap = self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_stockable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'picking_id': picking.id, + 'scrap_qty': 80, + }) + self.assertEquals(scrap.state, 'done') + self.assertEquals(self.product_stockable.qty_available, 20.0) + + def test_stock_scrap_03(self): + """ Standalone scrap a consumable product """ + scrap = self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_consumable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'scrap_qty': 100, + }) + self.assertEquals(scrap.state, 'done') + self.assertEquals(self.product_consumable.qty_available, -100.0) + with self.assertRaises(UserError): + scrap.sudo(self.user_demo).unlink() + + def test_stock_scrap_04(self): + """ Scrap a consumable product from a picking """ + picking = self._prepare_picking(self.product_consumable) + scrap = self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_consumable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'picking_id': picking.id, + 'scrap_qty': 100, + }) + self.assertEquals(scrap.state, 'done') + self.assertEquals(self.product_consumable.qty_available, -100.0) diff --git a/addons/web/static/lib/qweb/qweb-test-widgets.xml b/addons/web/static/lib/qweb/qweb-test-widgets.xml new file mode 100644 index 00000000000..503880bcc34 --- /dev/null +++ b/addons/web/static/lib/qweb/qweb-test-widgets.xml @@ -0,0 +1,26 @@ + + + {"value": "1988-09-16"} + 1988-09-16 + + + {"value": "1988-09-16 14:00:00"} + 1988-09-16 14:00:00 + + + {"value": "1988-09-16 14:00:00"} + 09/16/1988 16:00:00 + + + {"value": "1988-09-16 14:00:00"} + 09/16/1988 + + + {"value": "1988-09-16 01:00:00"} + 09/16/1988 + + + {"value": "1988-09-16 23:00:00"} + 09/17/1988 + + diff --git a/doc/cla/corporate/eficent.md b/doc/cla/corporate/eficent.md index 3723fbf0740..2b5b8c830c4 100644 --- a/doc/cla/corporate/eficent.md +++ b/doc/cla/corporate/eficent.md @@ -14,6 +14,7 @@ List of contributors: Aarón Henríquez ahenriquez@eficent.com https://github.com/aheficent Lois Rilo lois.rilo@eficent.com https://github.com/lreficent -Gisela Mora gisela.mora@eficent.com https://github.com/gmeficent Miquel Raich miquel.raich@eficent.com https://github.com/mreficent Jordi Ballester jordi.ballester@eficent.com https://github.com/jbeficent +Hector Villarreal hector.villarreal@eficent.com https://github.com/hveficent +Roser Garcia roser.garcia@eficent.com https://github.com/rgarnau (up to 2018-05-01) diff --git a/doc/cla/individual/subodhdahal.md b/doc/cla/individual/subodhdahal.md new file mode 100644 index 00000000000..4e4c389bc99 --- /dev/null +++ b/doc/cla/individual/subodhdahal.md @@ -0,0 +1,11 @@ +Nepal, 2018-02-09 + +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, + +Subodh Dahal contact.subodhdahal@gmail.com https://github.com/SubodhDahal diff --git a/doc/howtos/themes.rst b/doc/howtos/themes.rst index 531f5e2c8f0..e2dc46f647b 100644 --- a/doc/howtos/themes.rst +++ b/doc/howtos/themes.rst @@ -614,7 +614,7 @@ The previous code will create the snippet’s content, but we still need to plac .. code-block:: xml