Пример #1
0
    def unlink(self):
        """Override to:
        - Clean up the variants that use any of the values in self:
            - Remove the value from the variant if the value belonged to an
                attribute line with only one value.
            - Unlink or archive all related variants.
        - Archive the value if unlink is not possible.

        Archiving is typically needed when the value is referenced elsewhere
        (on a variant that can't be deleted, on a sales order line, ...).
        """
        # Directly remove the values from the variants for lines that had single
        # value (counting also the values that are archived).
        single_values = self.filtered(lambda ptav: len(ptav.attribute_line_id.product_template_value_ids) == 1)
        for ptav in single_values:
            ptav.ptav_product_variant_ids.write({'product_template_attribute_value_ids': [(3, ptav.id, 0)]})
        # Try to remove the variants before deleting to potentially remove some
        # blocking references.
        self.ptav_product_variant_ids._unlink_or_archive()
        # Now delete or archive the values.
        ptav_to_archive = self.env['product.template.attribute.value']
        for ptav in self:
            try:
                with self.env.cr.savepoint(), tools.mute_logger('coffice.sql_db'):
                    super(ProductTemplateAttributeLine, ptav).unlink()
            except Exception:
                # We catch all kind of exceptions to be sure that the operation
                # doesn't fail.
                ptav_to_archive += ptav
        ptav_to_archive.write({'ptav_active': False})
        return True
Пример #2
0
    def _procure_calculation_orderpoint(self):
        with api.Environment.manage():
            # As this function is in a new thread, I need to open a new cursor, because the old one may be closed
            new_cr = self.pool.cursor()
            self = self.with_env(self.env(cr=new_cr))
            scheduler_cron = self.sudo().env.ref(
                'stock.ir_cron_scheduler_action')
            # Avoid to run the scheduler multiple times in the same time
            try:
                with tools.mute_logger('coffice.sql_db'):
                    self._cr.execute(
                        "SELECT id FROM ir_cron WHERE id = %s FOR UPDATE NOWAIT",
                        (scheduler_cron.id, ))
            except Exception:
                _logger.info(
                    'Attempt to run procurement scheduler aborted, as already running'
                )
                self._cr.rollback()
                self._cr.close()
                return {}

            for company in self.env.user.company_ids:
                cids = (self.env.user.company_id
                        | self.env.user.company_ids).ids
                self.env['procurement.group'].with_context(
                    allowed_company_ids=cids).run_scheduler(
                        use_new_cursor=self._cr.dbname, company_id=company.id)
            new_cr.close()
            return {}
Пример #3
0
    def unlink(self):
        """Override to:
        - Archive the line if unlink is not possible.
        - Clean up related values and related variants.

        Archiving is typically needed when the line has values that can't be
        deleted because they are referenced elsewhere (on a variant that can't
        be deleted, on a sales order line, ...).
        """
        # Try to remove the values first to remove some potentially blocking
        # references, which typically works:
        # - For single value lines because the values are directly removed from
        #   the variants.
        # - For values that are present on variants that can be deleted.
        self.product_template_value_ids._only_active().unlink()
        # Keep a reference to the related templates before the deletion.
        templates = self.product_tmpl_id
        # Now delete or archive the lines.
        ptal_to_archive = self.env['product.template.attribute.line']
        for ptal in self:
            try:
                with self.env.cr.savepoint(), tools.mute_logger('coffice.sql_db'):
                    super(ProductTemplateAttributeLine, ptal).unlink()
            except Exception:
                # We catch all kind of exceptions to be sure that the operation
                # doesn't fail.
                ptal_to_archive += ptal
        ptal_to_archive.write({'active': False})
        # For archived lines `_update_product_template_attribute_values` is
        # implicitly called during the `write` above, but for products that used
        # unlinked lines `_create_variant_ids` has to be called manually.
        (templates - ptal_to_archive.product_tmpl_id)._create_variant_ids()
        return True
Пример #4
0
    def _unlink_or_archive(self, check_access=True):
        """Unlink or archive products.
        Try in batch as much as possible because it is much faster.
        Use dichotomy when an exception occurs.
        """

        # Avoid access errors in case the products is shared amongst companies
        # but the underlying objects are not. If unlink fails because of an
        # AccessError (e.g. while recomputing fields), the 'write' call will
        # fail as well for the same reason since the field has been set to
        # recompute.
        if check_access:
            self.check_access_rights('unlink')
            self.check_access_rule('unlink')
            self.check_access_rights('write')
            self.check_access_rule('write')
            self = self.sudo()

        try:
            with self.env.cr.savepoint(), tools.mute_logger('coffice.sql_db'):
                self.unlink()
        except Exception:
            # We catch all kind of exceptions to be sure that the operation
            # doesn't fail.
            if len(self) > 1:
                self[:len(self) // 2]._unlink_or_archive(check_access=False)
                self[len(self) // 2:]._unlink_or_archive(check_access=False)
            else:
                if self.active:
                    # Note: this can still fail if something is preventing
                    # from archiving.
                    # This is the case from existing stock reordering rules.
                    self.write({'active': False})
Пример #5
0
 def test_sale_transaction_mismatch(self):
     """Test that a transaction for the incorrect amount does not validate the SO."""
     # modify order total
     self.order.order_line[0].price_unit = 200.0
     self.transaction._set_transaction_done()
     with mute_logger('coffice.addons.sale.models.payment'):
         self.transaction._post_process_after_done()
     self.assertEqual(
         self.order.state, 'draft',
         'a transaction for an incorrect amount should not validate a quote'
     )
Пример #6
0
 def test_remove_badge_with_give_badge(self):
     self.certification_survey.write({
         'certification_give_badge':
         True,
         'certification_badge_id':
         self.certification_badge.id
     })
     with mute_logger('coffice.sql_db'):
         with self.assertRaises(IntegrityError):
             self.certification_survey.write(
                 {'certification_badge_id': None})
             self.certification_survey.flush(['certification_badge_id'])
Пример #7
0
    def test_sql_constraint_dates(self):
        # The goal is mainly to verify that a human friendly
        # error message is triggered if the date_from is after
        # date_to. Coming from a bug due to the new ORM 13.0

        leave_vals = {
            'name': 'Sick Time Off',
            'holiday_status_id':
            self.env.ref('hr_holidays.holiday_status_cl').id,
            'date_from': datetime.today().strftime('%Y-%m-11 19:00:00'),
            'date_to': datetime.today().strftime('%Y-%m-10 10:00:00'),
            'employee_id': self.ref('hr.employee_admin'),
            'number_of_days': 1,
        }
        with mute_logger('coffice.sql_db'):
            with self.assertRaises(IntegrityError):
                with self.cr.savepoint():
                    self.env['hr.leave'].create(leave_vals)

        leave_vals = {
            'name': 'Sick Time Off',
            'holiday_status_id':
            self.env.ref('hr_holidays.holiday_status_cl').id,
            'date_from': datetime.today().strftime('%Y-%m-10 10:00:00'),
            'date_to': datetime.today().strftime('%Y-%m-11 19:00:00'),
            'employee_id': self.ref('hr.employee_admin'),
            'number_of_days': 1,
        }
        leave = self.env['hr.leave'].create(leave_vals)
        with mute_logger('coffice.sql_db'):
            with self.assertRaises(IntegrityError):  # No ValidationError
                with self.cr.savepoint():
                    leave.write({
                        'date_from':
                        datetime.today().strftime('%Y-%m-11 19:00:00'),
                        'date_to':
                        datetime.today().strftime('%Y-%m-10 10:00:00'),
                    })
Пример #8
0
 def update_records(model, src, field_model='model', field_id='res_id'):
     Model = self.env[model] if model in self.env else None
     if Model is None:
         return
     records = Model.sudo().search([(field_model, '=', 'res.partner'),
                                    (field_id, '=', src.id)])
     try:
         with mute_logger('coffice.sql_db'), self._cr.savepoint(
         ), self.env.clear_upon_failure():
             records.sudo().write({field_id: dst_partner.id})
             records.flush()
     except psycopg2.Error:
         # updating fails, most likely due to a violated unique constraint
         # keeping record with nonexistent partner_id is useless, better delete it
         records.sudo().unlink()
Пример #9
0
 def test_set_same_badge_on_multiple_survey(self):
     self.certification_survey.write({
         'certification_give_badge':
         True,
         'certification_badge_id':
         self.certification_badge.id
     })
     # set the same badge on another survey should fail:
     with mute_logger('coffice.sql_db'):
         with self.assertRaises(IntegrityError):
             self.certification_survey_2.write({
                 'certification_give_badge':
                 True,
                 'certification_badge_id':
                 self.certification_badge.id
             })
             self.certification_survey.flush()
Пример #10
0
 def test_remove_badge_with_give_badge_multi(self):
     self.certification_survey.write({
         'certification_give_badge':
         True,
         'certification_badge_id':
         self.certification_badge.id
     })
     self.certification_survey_2.write({
         'certification_give_badge':
         True,
         'certification_badge_id':
         self.certification_badge_2.id
     })
     surveys = self.env['survey.survey'].browse(
         [self.certification_survey.id, self.certification_survey_2.id])
     with mute_logger('coffice.sql_db'):
         with self.assertRaises(IntegrityError):
             surveys.write({'certification_badge_id': None})
             surveys.flush(['certification_badge_id'])
Пример #11
0
 def test_105_duplicated_translation(self):
     """ Test synchronizing translations with duplicated source """
     # create a category with a French translation
     padawans = self.env['res.partner.category'].create({'name': 'Padawan'})
     self.env['ir.translation'].create({
         'type': 'model',
         'name': 'res.partner.category,name',
         'module': 'base',
         'lang': 'fr_FR',
         'res_id': padawans.id,
         'value': 'Apprenti',
         'state': 'translated',
     })
     # change name and insert a duplicate manually
     padawans.write({'name': 'Padawans'})
     with self.assertRaises(IntegrityError), mute_logger('coffice.sql_db'):
         with self.env.cr.savepoint():
             self.env['ir.translation'].create({
                 'type': 'model',
                 'name': 'res.partner.category,name',
                 'module': 'base',
                 'lang': 'fr_FR',
                 'res_id': padawans.id,
                 'value': 'Apprentis',
                 'state': 'translated',
             })
     self.env['ir.translation'].translate_fields('res.partner.category',
                                                 padawans.id, 'name')
     translations = self.env['ir.translation'].search([
         ('res_id', '=', padawans.id),
         ('name', '=', 'res.partner.category,name'),
         ('lang', '=', 'fr_FR'),
     ])
     self.assertEqual(
         len(translations), 1,
         "Translations were not duplicated after `translate_fields` call")
     self.assertEqual(translations.value, "Apprenti",
                      "The first translation must stay")
Пример #12
0
 def update(self, inactivity_period):
     """ Updates the last_poll and last_presence of the current user
         :param inactivity_period: duration in milliseconds
     """
     presence = self.search([('user_id', '=', self._uid)], limit=1)
     # compute last_presence timestamp
     last_presence = datetime.datetime.now() - datetime.timedelta(
         milliseconds=inactivity_period)
     values = {
         'last_poll': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
     }
     # update the presence or a create a new one
     if not presence:  # create a new presence for the user
         values['user_id'] = self._uid
         values['last_presence'] = last_presence
         self.create(values)
     else:  # update the last_presence if necessary, and write values
         if presence.last_presence < last_presence:
             values['last_presence'] = last_presence
         # Hide transaction serialization errors, which can be ignored, the presence update is not essential
         with tools.mute_logger('coffice.sql_db'):
             presence.write(values)
     # avoid TransactionRollbackError
     self.env.cr.commit()  # TODO : check if still necessary
Пример #13
0
    def test_sale_mrp(self):
        warehouse0 = self.env.ref('stock.warehouse0')
        # In order to test the sale_mrp module in OpenERP, I start by creating a new product 'Slider Mobile'
        # I define product category Mobile Products Sellable.

        with mute_logger('coffice.tests.common.onchange'):
            # Suppress warning on "Changing your cost method" when creating a
            # product category
            pc = Form(self.env['product.category'])
        pc.name = 'Mobile Products Sellable'
        product_category_allproductssellable0 = pc.save()

        uom_unit = self.env.ref('uom.product_uom_unit')

        self.assertIn("seller_ids", self.env['product.template'].fields_get())

        # I define product for Slider Mobile.
        product = Form(self.env['product.template'])

        product.categ_id = product_category_allproductssellable0
        product.list_price = 200.0
        product.name = 'Slider Mobile'
        product.type = 'product'
        product.uom_id = uom_unit
        product.uom_po_id = uom_unit
        product.route_ids.clear()
        product.route_ids.add(warehouse0.manufacture_pull_id.route_id)
        product.route_ids.add(warehouse0.mto_pull_id.route_id)
        product_template_slidermobile0 = product.save()

        std_price_wiz = Form(
            self.env['stock.change.standard.price'].with_context(
                active_id=product_template_slidermobile0.id,
                active_model='product.template'))
        std_price_wiz.new_price = 189
        std_price_wiz.save()

        product_component = Form(self.env['product.product'])
        product_component.name = 'Battery'
        product_product_bettery = product_component.save()

        with Form(self.env['mrp.bom']) as bom:
            bom.product_tmpl_id = product_template_slidermobile0
            with bom.bom_line_ids.new() as line:
                line.product_id = product_product_bettery
                line.product_qty = 4

        # I create a sale order for product Slider mobile
        so_form = Form(self.env['sale.order'])
        so_form.partner_id = self.env.ref('base.res_partner_4')
        with so_form.order_line.new() as line:
            line.product_id = product_template_slidermobile0.product_variant_ids
            line.price_unit = 200
            line.product_uom_qty = 500.0
            line.customer_lead = 7.0
        sale_order_so0 = so_form.save()

        # I confirm the sale order
        sale_order_so0.action_confirm()

        # I verify that a manufacturing order has been generated, and that its name and reference are correct
        mo = self.env['mrp.production'].search(
            [('origin', 'like', sale_order_so0.name)], limit=1)
        self.assertTrue(mo, 'Manufacturing order has not been generated')
Пример #14
0
    def test_constraints(self):
        """The goal of this test is to make sure constraints are correct."""
        with self.assertRaises(
                UserError,
                msg=
                "can't change variants creation mode of attribute used on product"
        ):
            self.ram_attribute.create_variant = 'no_variant'

        with self.assertRaises(UserError,
                               msg="can't delete attribute used on product"):
            self.ram_attribute.unlink()

        with self.assertRaises(
                UserError,
                msg="can't change the attribute of an value used on product"):
            self.ram_32.attribute_id = self.hdd_attribute.id

        with self.assertRaises(UserError,
                               msg="can't delete value used on product"):
            self.ram_32.unlink()

        with self.assertRaises(
                ValidationError,
                msg="can't have attribute without value on product"):
            self.env['product.template.attribute.line'].create({
                'product_tmpl_id':
                self.computer_case.id,
                'attribute_id':
                self.hdd_attribute.id,
                'value_ids': [(6, 0, [])],
            })

        with self.assertRaises(
                ValidationError,
                msg="value attribute must match line attribute"):
            self.env['product.template.attribute.line'].create({
                'product_tmpl_id':
                self.computer_case.id,
                'attribute_id':
                self.ram_attribute.id,
                'value_ids': [(6, 0, [self.ssd_256.id])],
            })

        with self.assertRaises(
                UserError,
                msg="can't change the attribute of an attribute line"):
            self.computer_ssd_attribute_lines.attribute_id = self.hdd_attribute.id

        with self.assertRaises(
                UserError,
                msg="can't change the product of an attribute line"):
            self.computer_ssd_attribute_lines.product_tmpl_id = self.computer_case.id

        with self.assertRaises(
                UserError,
                msg=
                "can't change the value of a product template attribute value"
        ):
            self.computer_ram_attribute_lines.product_template_value_ids[
                0].product_attribute_value_id = self.hdd_1

        with self.assertRaises(
                UserError,
                msg=
                "can't change the product of a product template attribute value"
        ):
            self.computer_ram_attribute_lines.product_template_value_ids[
                0].product_tmpl_id = self.computer_case.id

        with mute_logger('coffice.sql_db'), self.assertRaises(
                IntegrityError,
                msg=
                "can't have two values with the same name for the same attribute"
        ):
            self.env['product.attribute.value'].create({
                'name':
                '32 GB',
                'attribute_id':
                self.ram_attribute.id,
            })
Пример #15
0
 def test_rename_unique(self):
     """ one cannot create two fields with the same name on a given model """
     field1 = self.create_field('x_foo')
     field2 = self.create_field('x_bar')
     with self.assertRaises(IntegrityError), mute_logger('coffice.sql_db'):
         field2.name = field1.name
Пример #16
0
    def test_proc_rule(self):
        # Create a product route containing a stock rule that will
        # generate a move from Stock for every procurement created in Output
        product_route = self.env['stock.location.route'].create({
            'name':
            'Stock -> output route',
            'product_selectable':
            True,
            'rule_ids': [(0, 0, {
                'name':
                'Stock -> output rule',
                'action':
                'pull',
                'picking_type_id':
                self.ref('stock.picking_type_internal'),
                'location_src_id':
                self.ref('stock.stock_location_stock'),
                'location_id':
                self.ref('stock.stock_location_output'),
            })],
        })

        # Set this route on `product.product_product_3`
        self.product.write({'route_ids': [(4, product_route.id)]})

        # Create Delivery Order of 10 `product.product_product_3` from Output -> Customer
        product = self.product
        vals = {
            'name':
            'Delivery order for procurement',
            'partner_id':
            self.partner.id,
            'picking_type_id':
            self.ref('stock.picking_type_out'),
            'location_id':
            self.ref('stock.stock_location_output'),
            'location_dest_id':
            self.ref('stock.stock_location_customers'),
            'move_lines': [(0, 0, {
                'name': '/',
                'product_id': product.id,
                'product_uom': product.uom_id.id,
                'product_uom_qty': 10.00,
                'procure_method': 'make_to_order',
            })],
        }
        pick_output = self.env['stock.picking'].create(vals)
        pick_output.move_lines.onchange_product_id()

        # Confirm delivery order.
        pick_output.action_confirm()

        # I run the scheduler.
        # Note: If purchase if already installed, the method _run_buy will be called due
        # to the purchase demo data. As we update the stock module to run this test, the
        # method won't be an attribute of stock.procurement at this moment. For that reason
        # we mute the logger when running the scheduler.
        with mute_logger('coffice.addons.stock.models.procurement'):
            self.env['procurement.group'].run_scheduler()

        # Check that a picking was created from stock to output.
        moves = self.env['stock.move'].search([
            ('product_id', '=', self.product.id),
            ('location_id', '=', self.ref('stock.stock_location_stock')),
            ('location_dest_id', '=', self.ref('stock.stock_location_output')),
            ('move_dest_ids', 'in', [pick_output.move_lines[0].id])
        ])
        self.assertEqual(
            len(moves.ids), 1,
            "It should have created a picking from Stock to Output with the original picking as destination"
        )
Пример #17
0
    def _update_foreign_keys(self, src_partners, dst_partner):
        """ Update all foreign key from the src_partner to dst_partner. All many2one fields will be updated.
            :param src_partners : merge source res.partner recordset (does not include destination one)
            :param dst_partner : record of destination res.partner
        """
        _logger.debug(
            '_update_foreign_keys for dst_partner: %s for src_partners: %s',
            dst_partner.id, str(src_partners.ids))

        # find the many2one relation to a partner
        Partner = self.env['res.partner']
        relations = self._get_fk_on('res_partner')

        self.flush()

        for table, column in relations:
            if 'base_partner_merge_' in table:  # ignore two tables
                continue

            # get list of columns of current table (exept the current fk column)
            query = "SELECT column_name FROM information_schema.columns WHERE table_name LIKE '%s'" % (
                table)
            self._cr.execute(query, ())
            columns = []
            for data in self._cr.fetchall():
                if data[0] != column:
                    columns.append(data[0])

            # do the update for the current table/column in SQL
            query_dic = {
                'table': table,
                'column': column,
                'value': columns[0],
            }
            if len(columns) <= 1:
                # unique key treated
                query = """
                    UPDATE "%(table)s" as ___tu
                    SET "%(column)s" = %%s
                    WHERE
                        "%(column)s" = %%s AND
                        NOT EXISTS (
                            SELECT 1
                            FROM "%(table)s" as ___tw
                            WHERE
                                "%(column)s" = %%s AND
                                ___tu.%(value)s = ___tw.%(value)s
                        )""" % query_dic
                for partner in src_partners:
                    self._cr.execute(
                        query, (dst_partner.id, partner.id, dst_partner.id))
            else:
                try:
                    with mute_logger('coffice.sql_db'), self._cr.savepoint():
                        query = 'UPDATE "%(table)s" SET "%(column)s" = %%s WHERE "%(column)s" IN %%s' % query_dic
                        self._cr.execute(query, (
                            dst_partner.id,
                            tuple(src_partners.ids),
                        ))

                        # handle the recursivity with parent relation
                        if column == Partner._parent_name and table == 'res_partner':
                            query = """
                                WITH RECURSIVE cycle(id, parent_id) AS (
                                        SELECT id, parent_id FROM res_partner
                                    UNION
                                        SELECT  cycle.id, res_partner.parent_id
                                        FROM    res_partner, cycle
                                        WHERE   res_partner.id = cycle.parent_id AND
                                                cycle.id != cycle.parent_id
                                )
                                SELECT id FROM cycle WHERE id = parent_id AND id = %s
                            """
                            self._cr.execute(query, (dst_partner.id, ))
                            # NOTE JEM : shouldn't we fetch the data ?
                except psycopg2.Error:
                    # updating fails, most likely due to a violated unique constraint
                    # keeping record with nonexistent partner_id is useless, better delete it
                    query = 'DELETE FROM "%(table)s" WHERE "%(column)s" IN %%s' % query_dic
                    self._cr.execute(query, (tuple(src_partners.ids), ))

        self.invalidate_cache()
Пример #18
0
 def test_give_badge_without_badge(self):
     with mute_logger('coffice.sql_db'):
         with self.assertRaises(IntegrityError):
             self.certification_survey.write(
                 {'certification_give_badge': True})
             self.certification_survey.flush(['certification_give_badge'])