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('odoo.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') ]) self.assertEqual(len(translations), 1, "Translations were not duplicated after `translate_fields` call") self.assertEqual(translations.value, "Apprenti", "The first translation must stay")
def test_proc_rule(self): # Create a product route containing a procurement 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, 'pull_ids': [(0, 0, { 'name': 'Stock -> output rule', 'action': 'move', '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.env.ref('product.product_product_3').write({ 'route_ids': [(4, product_route.id)]}) # Create Delivery Order of 10 `product.product_product_3` from Output -> Customer default_get_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get())) default_get_vals.update({ 'name': 'Delivery order for procurement', 'partner_id': self.ref('base.res_partner_2'), 'picking_type_id': self.ref('stock.picking_type_out'), 'move_lines': [(0, 0, { 'product_id': self.ref('product.product_product_3'), 'product_uom_qty': 10.00, 'procure_method': 'make_to_order', })], }) pick_output = self.env['stock.picking'].new(default_get_vals) pick_output.onchange_picking_type() pick_output.move_lines.onchange_product_id() pick_output.update({ 'location_id': self.ref('stock.stock_location_output'), 'location_dest_id': self.ref('stock.stock_location_customers'), }) vals = pick_output._convert_to_write(pick_output._cache) pick_output = self.env['stock.picking'].create(vals) # 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('odoo.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.ref('product.product_product_3')), ('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")
def create_variant_ids(self): Product = self.env["product.product"] AttributeValues = self.env['product.attribute.value'] variants_to_create = [] variants_to_activate = [] variants_to_unlink = [] for tmpl_id in self.with_context(active_test=False): # adding an attribute with only one value should not recreate product # write this attribute on every product to make sure we don't lose them variant_alone = tmpl_id.attribute_line_ids.filtered(lambda line: line.attribute_id.create_variant and len(line.value_ids) == 1).mapped('value_ids') for value_id in variant_alone: updated_products = tmpl_id.product_variant_ids.filtered(lambda product: value_id.attribute_id not in product.mapped('attribute_value_ids.attribute_id')) updated_products.write({'attribute_value_ids': [(4, value_id.id)]}) # iterator of n-uple of product.attribute.value *ids* variant_matrix = [ AttributeValues.browse(value_ids) for value_ids in itertools.product(*(line.value_ids.ids for line in tmpl_id.attribute_line_ids if line.value_ids[:1].attribute_id.create_variant)) ] # get the value (id) sets of existing variants existing_variants = {frozenset(variant.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant).ids) for variant in tmpl_id.product_variant_ids} # -> for each value set, create a recordset of values to create a # variant for if the value set isn't already a variant for value_ids in variant_matrix: if set(value_ids.ids) not in existing_variants: variants_to_create.append({ 'product_tmpl_id': tmpl_id.id, 'attribute_value_ids': [(6, 0, value_ids.ids)] }) # check product for product_id in tmpl_id.product_variant_ids: if not product_id.active and product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant) in variant_matrix: variants_to_activate.append(product_id) elif product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant) not in variant_matrix: variants_to_unlink.append(product_id) if variants_to_activate: Product.concat(*variants_to_activate).write({'active': True}) # create new products if variants_to_create: Product.create(variants_to_create) # unlink or inactive product for variant in variants_to_unlink: try: with self._cr.savepoint(), tools.mute_logger('odoo.sql_db'): variant.unlink() # We catch all kind of exception to be sure that the operation doesn't fail. except (psycopg2.Error, except_orm): variant.write({'active': False}) pass return True
def create_variant_ids(self): Product = self.env["product.product"] for tmpl_id in self.with_context(active_test=False): # list of values combination existing_variants = [set(variant.attribute_value_ids.ids) for variant in tmpl_id.product_variant_ids] variant_matrix = itertools.product( *( line.value_ids for line in tmpl_id.attribute_line_ids if line.value_ids and line.value_ids[0].attribute_id.create_variant ) ) variant_matrix = map( lambda record_list: reduce(lambda x, y: x + y, record_list, self.env["product.attribute.value"]), variant_matrix, ) to_create_variants = filter(lambda rec_set: set(rec_set.ids) not in existing_variants, variant_matrix) # adding an attribute with only one value should not recreate product # write this attribute on every product to make sure we don't lose them variant_alone = tmpl_id.attribute_line_ids.filtered(lambda line: len(line.value_ids) == 1).mapped( "value_ids" ) for value_id in variant_alone: updated_products = tmpl_id.product_variant_ids.filtered( lambda product: value_id.attribute_id not in product.mapped("attribute_value_ids.attribute_id") ) updated_products.write({"attribute_value_ids": [(4, value_id.id)]}) # check product variants_to_activate = self.env["product.product"] variants_to_unlink = self.env["product.product"] for product_id in tmpl_id.product_variant_ids: if not product_id.active and product_id.attribute_value_ids in variant_matrix: variants_to_activate |= product_id elif product_id.attribute_value_ids not in variant_matrix: variants_to_unlink |= product_id if variants_to_activate: variants_to_activate.write({"active": True}) # create new product for variant_ids in to_create_variants: new_variant = Product.create( {"product_tmpl_id": tmpl_id.id, "attribute_value_ids": [(6, 0, variant_ids.ids)]} ) # unlink or inactive product for variant in variants_to_activate: try: with self._cr.savepoint(), tools.mute_logger("openerp.sql_db"): variant.unlink() # We catch all kind of exception to be sure that the operation doesn't fail. except (psycopg2.Error, except_orm): variant.write({"active": False}) pass return True
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('odoo.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.standard_price = 189.0 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() 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')
def test_00_dropship(self): # Create a vendor supplier_dropship = self.env['res.partner'].create({'name': 'Vendor of Dropshipping test'}) # Create new product without any routes drop_shop_product = self.env['product.product'].create({ 'name': "Pen drive", 'type': "product", 'categ_id': self.env.ref('product.product_category_1').id, 'lst_price': 100.0, 'standard_price': 0.0, 'uom_id': self.env.ref('uom.product_uom_unit').id, 'uom_po_id': self.env.ref('uom.product_uom_unit').id, 'seller_ids': [(0, 0, { 'delay': 1, 'name': supplier_dropship.id, 'min_qty': 2.0 })] }) # Create a sales order with a line of 200 PCE incoming shipment, with route_id drop shipping so_form = Form(self.env['sale.order']) so_form.partner_id = self.env.ref('base.res_partner_2') so_form.payment_term_id = self.env.ref('account.account_payment_term') with mute_logger('odoo.tests.common.onchange'): # otherwise complains that there's not enough inventory and # apparently that's normal according to @jco and @sle with so_form.order_line.new() as line: line.product_id = drop_shop_product line.product_uom_qty = 200 line.price_unit = 1.00 line.route_id = self.env.ref('stock_dropshipping.route_drop_shipping') sale_order_drp_shpng = so_form.save() # Confirm sales order sale_order_drp_shpng.action_confirm() # Check the sales order created a procurement group which has a procurement of 200 pieces self.assertTrue(sale_order_drp_shpng.procurement_group_id, 'SO should have procurement group') # Check a quotation was created to a certain vendor and confirm so it becomes a confirmed purchase order purchase = self.env['purchase.order'].search([('partner_id', '=', supplier_dropship.id)]) self.assertTrue(purchase, "an RFQ should have been created by the scheduler") purchase.button_confirm() self.assertEquals(purchase.state, 'purchase', 'Purchase order should be in the approved state') self.assertEquals(len(purchase.ids), 1, 'There should be one picking') # Send the 200 pieces purchase.picking_ids.move_lines.quantity_done = purchase.picking_ids.move_lines.product_qty purchase.picking_ids.button_validate() # Check one move line was created in Customers location with 200 pieces move_line = self.env['stock.move.line'].search([ ('location_dest_id', '=', self.env.ref('stock.stock_location_customers').id), ('product_id', '=', drop_shop_product.id)]) self.assertEquals(len(move_line.ids), 1, 'There should be exactly one move line')
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('odoo.sql_db'), self._cr.savepoint(): return records.sudo().write({field_id: dst_partner.id}) except psycopg2.Error: # updating fails, most likely due to a violated unique constraint # keeping record with nonexistent partner_id is useless, better delete it return records.sudo().unlink()
def send_email(self, message, *args, **kwargs): with this.registry.cursor() as cr, mute_logger('odoo.sql_db'): try: # try ro aquire lock (no wait) on notification (should fail) cr.execute("SELECT email_status FROM mail_message_res_partner_needaction_rel WHERE id = %s FOR UPDATE NOWAIT", [notif.id]) except psycopg2.OperationalError: # record already locked by send, all good bounce_deferred.append(True) else: # this should trigger psycopg2.extensions.TransactionRollbackError in send(). # Only here to simulate the initial use case # If the record is lock, this line would create a deadlock since we are in the same thread # In practice, the update will wait the end of the send() transaction and set the notif as bounce, as expeced cr.execute("UPDATE mail_message_res_partner_needaction_rel SET email_status='bounce' WHERE id = %s", [notif.id]) return message['Message-Id']
def safe_duplicate_create(self, vals): """ Create benefit but silently abord if it already exists in the database (according to the _unique constraints). @return: new record if it didn't exist, empty recordset otherwise. """ try: with mute_logger('odoo.sql_db'), self.env.cr.savepoint(): # Any database call will be run inside a postgresql savepoint which is # rolledback if an exception occurs allowing to make subsequent calls in the transaction. # (Otherwise InternalError is raised by any subsequent database operations) return self.create(vals) except IntegrityError as err: if 'hr_benefit__unique' not in err.pgerror: raise err return self.env['hr.benefit']
def _cron_plan_compute(self): mrp_cron = self.sudo().env.ref('mrp_mrp.ir_cron_azi_mrp_action') try: with tools.mute_logger('odoo.sql_db'): self._cr.execute("SELECT id FROM ir_cron WHERE id = %s FOR UPDATE NOWAIT", (mrp_cron.id,)) except Exception: _logger.info('Attempt to run mrp aborted (may be already running)') _logger.info('Cron %s exception: %s' % (mrp_cron.name, Exception.message)) return {} _logger.info('Starting mrp calculation from cron') new_cr = self.pool.cursor() self._run_mrp_api( use_new_cursor=new_cr.dbname, company_id=self.env.user.company_id.id) return {}
def test_defaults(self): # Create some default value for some model, for all users. ir_values = self.env['ir.values'] ir_values.set_default('res.partner', 'ref', 'X11') ir_values.set_default('res.partner.title', 'shortcut', 'Mr', condition='name=Mister') # Retrieve them: ds is a list of triplets (id, name, value) ds = ir_values.get_defaults('res.partner') d = next((d for d in ds if d[1] == 'ref'), None) self.assertTrue(d, "At least one value should be retrieved for this model.") self.assertEqual(d[2], 'X11', "Can't retrieve the created default value.") ds = ir_values.get_defaults('res.partner.title') d = next((d for d in ds if d[1] == 'shortcut'), None) self.assertFalse(d, "No value should be retrieved, the condition is not met.") ds = ir_values.get_defaults('res.partner.title', condition="name=Miss") d = next((d for d in ds if d[1] == 'shortcut'), None) self.assertFalse(d, "No value should be retrieved, the condition is not met.") ds = ir_values.get_defaults('res.partner.title', condition="name=Mister") d = next((d for d in ds if d[1] == 'shortcut'), None) self.assertTrue(d, "At least one value should be retrieved.") self.assertEqual(d[2], 'Mr', "Can't retrieve the created default value.") # Do it again but for a specific user. ir_values.set_default('res.partner', 'ref', '007', for_all_users=False) # Retrieve it and check it is the one for the current user. ds = ir_values.get_defaults('res.partner') d = next((d for d in ds if d[1] == 'ref'), None) self.assertTrue(d, "At least one value should be retrieved for this model.") self.assertEqual(d[2], '007', "Can't retrieve the created default value.") # create valid but unusable defaults, a ValidationError should not be thrown with tools.mute_logger('odoo.addons.base.ir.ir_values'): ir_values.set_default('unknown_model', 'unknown_field', 42) ir_values.set_default('res.partner', 'unknown_field', 42) # create invalid defaults with self.assertRaises(ValidationError): ir_values.set_default('res.partner', 'lang', 'some_LANG') with self.assertRaises(ValidationError): ir_values.set_default('res.partner', 'credit_limit', 'foo')
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('procurement.ir_cron_scheduler_action') # Avoid to run the scheduler multiple times in the same time try: with tools.mute_logger('odoo.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 {} self.env['procurement.order']._procure_orderpoint_confirm( use_new_cursor=new_cr.dbname, company_id=self.env.user.company_id.id) new_cr.close() return {}
def _procure_calculation_all(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 = registry(self._cr.dbname).cursor() self = self.with_env(self.env(cr=new_cr)) # TDE FIXME scheduler_cron = self.sudo().env.ref('procurement.ir_cron_scheduler_action') # Avoid to run the scheduler multiple times in the same time try: with tools.mute_logger('openerp.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 {} Procurement = self.env['procurement.order'] for company in self.env.user.company_ids: Procurement.run_scheduler(use_new_cursor=self._cr.dbname, company_id=company.id) # close the new cursor self._cr.close() return {}
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('odoo.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")
def _procure_calculation_products(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 = registry(self._cr.dbname).cursor() self = self.with_env(self.env(cr=new_cr)) # TDE FIXME 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('odoo.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 {} if self.group_id: self.individual_procurement() self._cr.commit() else: products = self.env['product.product'] for item in self.item_ids: products |= item.product_id ProcurementGroup = self.env['procurement.group'] ProcurementGroup.with_context( product_ids=products.ids).run_scheduler( use_new_cursor=self._cr.dbname, company_id=self.env.user.company_id) self._cr.close() return {}
def split(self, template, clear_attribute_value=False): relations = self._shared_table() for table, column, column2 in relations: query_dic = {'table': table, 'column': column, 'column_t': column2} try: with mute_logger('odoo.sql_db'), self._cr.savepoint(): query = 'UPDATE "%(table)s" SET %(column_t)s = %%s WHERE %(column)s=%%s' % query_dic self._cr.execute(query, ( template.id, self.id, )) 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"=%%s' % query_dic self._cr.execute(query, (self.id, )) if clear_attribute_value: query = 'DELETE FROM product_attribute_value_product_product_rel WHERE product_attribute_value_id=%s AND product_product_id=%s' % ( clear_attribute_value.id, self.id) self._cr.execute(query, ())
def run_scheduler(self): with api.Environment.manage(): new_cr = self.pool.cursor() self = self.with_env(self.env(cr=new_cr)) cron = self.sudo().env.ref('auto_rop.ir_cron_calculate_rop_action') try: with tools.mute_logger('odoo.sql_db'): self._cr.execute( "SELECT id FROM ir_cron WHERE id = %s FOR UPDATE NOWAIT", (cron.id, )) except Exception: _logger.info( "Attempt to run auto rop scheduler aborted, as already running" ) self._cr.rollback() self._cr.close() return {} self.env['stock.warehouse.orderpoint'].run_scheduler( use_new_cursor=self._cr.dbname) new_cr.close() return {}
def test_redirect_form_values(self): tx = self.create_transaction(flow="redirect") with mute_logger('odoo.addons.payment.models.payment_transaction'): processing_values = tx._get_processing_values() form_info = self._extract_values_from_html_form( processing_values['redirect_form_html']) form_inputs = form_info['inputs'] self.assertEqual(form_info['action'], self.sips.sips_test_url) self.assertEqual(form_inputs['InterfaceVersion'], self.sips.sips_version) return_url = self._build_url(SipsController._return_url) notify_url = self._build_url(SipsController._notify_url) self.assertEqual(form_inputs['Data'], f'amount=111111|currencyCode=978|merchantId=dummy_mid|normalReturnUrl={return_url}|' \ f'automaticResponseUrl={notify_url}|transactionReference={self.reference}|' \ f'statementReference={self.reference}|keyVersion={self.sips.sips_key_version}|' \ f'returnContext={json.dumps(dict(reference=self.reference))}' ) self.assertEqual( form_inputs['Seal'], '4d7cc67c0168e8ce11c25fbe1937231c644861e320702ab544022b032b9eb4a2')
def test_80_permission(self): self.action.write({ 'state': 'code', 'code': """record.write({'date': datetime.date.today()})""", }) user_demo = self.env.ref("base.user_demo") self_demo = self.action.with_user(user_demo.id) # can write on contact partner self.test_partner.type = "contact" self.test_partner.with_user(user_demo.id).check_access_rule("write") self_demo.with_context(self.context).run() self.assertEqual(self.test_partner.date, date.today()) # but can not write on private address self.test_partner.type = "private" with self.assertRaises(AccessError): self.test_partner.with_user(user_demo.id).check_access_rule("write") # nor execute a server action on it with self.assertRaises(AccessError), mute_logger('odoo.addons.base.models.ir_actions'): self_demo.with_context(self.context).run()
def setUpClass(cls): super(TestLeadAssignCommon, cls).setUpClass() cls._switch_to_multi_membership() cls._switch_to_auto_assign() # don't mess with existing teams, deactivate them to make tests repeatable cls.sales_teams = cls.sales_team_1 + cls.sales_team_convert cls.members = cls.sales_team_1_m1 + cls.sales_team_1_m2 + cls.sales_team_1_m3 + cls.sales_team_convert_m1 + cls.sales_team_convert_m2 cls.env['crm.team'].search([('id', 'not in', cls.sales_teams.ids) ]).write({'active': False}) # don't mess with existing leads, unlink those assigned to users used here to make tests # repeatable (archive is not sufficient because of lost leads) with mute_logger('odoo.models.unlink'): cls.env['crm.lead'].with_context(active_test=False).search([ '|', ('team_id', '=', False), ('user_id', 'in', cls.sales_teams.member_ids.ids) ]).unlink() cls.bundle_size = 50 cls.env['ir.config_parameter'].set_param( 'crm.assignment.commit.bundle', '%s' % cls.bundle_size) cls.env['ir.config_parameter'].set_param('crm.assignment.delay', '0')
def _procure_calculation_all2(self): with api.Environment.manage(): _logger.info("hola entre back") # As this function is in a new thread, i need to open a new cursor, because the old one may be closed new_cr = registry(self._cr.dbname).cursor() self = self.with_env(self.env(cr=new_cr)) # TDE FIXME scheduler_cron = self.sudo().env.ref('procurement.ir_cron_scheduler_action') # Avoid to run the scheduler multiple times in the same time try: with tools.mute_logger('odoo.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 {} Procurement = self.env['procurement.order'] #for company in self.env.user.company_ids: Procurement.run_scheduler(use_new_cursor=self._cr.dbname, company_id=False) # close the new cursor self._cr.close() return {}
def test_bad_template_does_not_break(self): """Create template with error (obaject) to test error.""" self.env["mail.message.custom.subject"].create({ "name": "Test bad template 1", "model_id": self.env.ref("base.model_res_partner").id, "subtype_ids": [(6, 0, [self.env.ref("mail.mt_comment").id])], "subject_template": "${obaject.number_a} and something", "position": "append_after", }) # Send message in partner with mute_logger("odoo.addons.mail.models.mail_render_mixin"): mail_message_1 = self.partner_1.message_post( body="Test", subtype_xmlid="mail.mt_comment", subject="Test", ) # Get message and check subject # No exception should be raised but subject should remain as original. self.assertEqual(mail_message_1.subject, "Test")
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('odoo.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 {}
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.strftime(DEFAULT_SERVER_DATETIME_FORMAT) self.create(values) else: # update the last_presence if necessary, and write values if datetime.datetime.strptime(presence.last_presence, DEFAULT_SERVER_DATETIME_FORMAT) < last_presence: values['last_presence'] = last_presence.strftime(DEFAULT_SERVER_DATETIME_FORMAT) # Hide transaction serialization errors, which can be ignored, the presence update is not essential with tools.mute_logger('openerp.sql_db'): presence.write(values) # avoid TransactionRollbackError self.env.cr.commit() # TODO : check if still necessary
def test_redirect_form_values(self): return_url = self._build_url(BuckarooController._return_url) expected_values = { 'Brq_websitekey': self.buckaroo.buckaroo_website_key, 'Brq_amount': str(self.amount), 'Brq_currency': self.currency.name, 'Brq_invoicenumber': self.reference, 'Brq_signature': 'dacc220c3087edcc1200a38a6db0191c823e7f69', 'Brq_return': return_url, 'Brq_returncancel': return_url, 'Brq_returnerror': return_url, 'Brq_returnreject': return_url, 'Brq_culture': 'en-US', } tx_sudo = self.create_transaction(flow='redirect') with mute_logger('odoo.addons.payment.models.payment_transaction'): processing_values = tx_sudo._get_processing_values() form_info = self._extract_values_from_html_form(processing_values['redirect_form_html']) self.assertEqual(form_info['action'], "https://testcheckout.buckaroo.nl/html/") self.assertDictEqual(expected_values, form_info['inputs'], "Buckaroo: invalid inputs specified in the redirect form.")
def test_01_load(self): """ Admin user can import data, but the demo user cannot """ fields = ( 'id', 'name', 'perm_read', 'perm_write', 'perm_create', 'perm_unlink', ) data = [ ('access_res_users_test', 'res.users test', '1', '0', '0', '0'), ('access_res_users_test2', 'res.users test2', '1', '1', '1', '1'), ] self.has_button_import(user=self.env.user) with mute_logger('odoo.sql_db'): res = self.Access.load(fields, data) self.assertEqual(res['ids'], False) self.assertEqual(len(res['messages']), 2) self.assertEqual( res['messages'][0]['message'], "Missing required value for the field 'Object' (model_id)") self.assertEqual( res['messages'][1]['message'], "Missing required value for the field 'Object' (model_id)") self.has_button_import(falsify=True, user=self.user_test) res2 = self.Access.sudo(self.user_test).load(fields, data) self.assertEqual(res2['ids'], None) self.assertEqual(len(res2['messages']), 1) self.assertEqual( res2['messages'][0]['message'], 'User (ID: %s) is not allowed to import data in ' 'model ir.model.access.' % self.user_test.id)
def test_unicity(self): """ Archived memberships should be removed when detecting duplicates. Creating duplicates should raise unicity constraint. Note: redoing the data set to avoid clashing with SavepointCase as we expect a db-level assert """ sales_team_1_m1 = self.env['crm.team.member'].create({ 'user_id': self.user_sales_leads.id, 'crm_team_id': self.sales_team_1.id, }) sales_team_1_m1.write({'active': False}) sales_team_1_m1.flush_recordset() sales_team_1_m2 = self.env['crm.team.member'].create({ 'user_id': self.user_sales_leads.id, 'crm_team_id': self.sales_team_1.id, }) found = self.env['crm.team.member'].search([ ('user_id', '=', self.user_sales_leads.id), ('crm_team_id', '=', self.sales_team_1.id), ]) self.assertEqual(found, sales_team_1_m2) with self.assertRaises( exceptions.UserError), mute_logger('odoo.sql_db'): self.env['crm.team.member'].create({ 'user_id': self.user_sales_leads.id, 'crm_team_id': self.sales_team_1.id, })
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() to_unlink = self._filter_to_unlink() to_archive = self - to_unlink to_archive.write({'active': False}) self = to_unlink try: with self.env.cr.savepoint(), tools.mute_logger('odoo.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})
def test_login_login_is_lowercased(self): """ It should verify the login is set to lowercase on login """ rec_id = self._new_record() # We have to commit this cursor, because `_login` uses a fresh cursor self.env.cr.commit() with mute_logger("odoo.addons.auth_ldap.models.res_company_ldap"): res_id = self.model_obj._login( self.env.registry.db_name, self.login.upper(), "password", {"interactive": True}, ) # Now clean up our mess to preserve idempotence with api.Environment.manage(): with registry(self.env.registry.db_name).cursor() as new_cr: new_cr.execute("DELETE FROM res_users WHERE \ login='******'" % self.login.lower()) new_cr.commit() self.assertEqual( rec_id.id, res_id, "Login with with uppercase chars was not \ successful", )
def test_redirect_form_values(self): tx = self.create_transaction(flow="redirect") with mute_logger('odoo.addons.payment.models.payment_transaction'): processing_values = tx._get_processing_values() form_info = self._extract_values_from_html_form( processing_values['redirect_form_html']) form_inputs = form_info['inputs'] self.assertEqual(form_info['action'], self.sips.sips_test_url) self.assertEqual(form_inputs['InterfaceVersion'], self.sips.sips_version) return_url = self._build_url(SipsController._return_url) notify_url = self._build_url(SipsController._webhook_url) self.assertEqual( form_inputs['Data'], f'amount=111111|currencyCode=978|merchantId=dummy_mid|normalReturnUrl={return_url}|' f'automaticResponseUrl={notify_url}|transactionReference={self.reference}|' f'statementReference={self.reference}|keyVersion={self.sips.sips_key_version}|' f'returnContext={json.dumps(dict(reference=self.reference))}', ) self.assertEqual( form_inputs['Seal'], '99d1d2d46a841de7fe313ac0b2d13a9e42cad50b444d35bf901879305818d9b2')
def _check_xml(self): """Mute warnings about views which are common during migration""" with mute_logger("odoo.addons.base.models.ir_ui_view"): return View._check_xml._original_method(self)
""" Only moderators can modify pending messages """ self.group_public.write({ 'email_send': True, 'moderation': True, 'channel_partner_ids': [(4, self.partner_employee.id)], 'moderator_ids': [(4, self.user_employee.id)], }) self.message.write({'model': 'mail.channel', 'res_id': self.group_public.id, 'moderation_status': 'pending_moderation'}) self.message.with_user(self.user_employee).write({'moderation_status': 'accepted'}) def test_mail_message_access_write_crash_moderation(self): self.message.write({'model': 'mail.channel', 'res_id': self.group_public.id, 'moderation_status': 'pending_moderation'}) with self.assertRaises(AccessError): self.message.with_user(self.user_employee).write({'moderation_status': 'accepted'}) @mute_logger('openerp.addons.mail.models.mail_mail') def test_mark_all_as_read(self): self.user_employee.notification_type = 'inbox' emp_partner = self.user_employee.partner_id.with_user(self.user_employee) group_private = self.env['mail.channel'].with_context({ 'mail_create_nolog': True, 'mail_create_nosubscribe': True, 'mail_channel_noautofollow': True, }).create({ 'name': 'Private', 'description': 'Private James R.', 'public': 'private', 'alias_name': 'private', 'alias_contact': 'followers'} ).with_context({'mail_create_nosubscribe': False})
def test_create_from_ui(self): """ Simulation of sales coming from the interface, even after closing the session """ def compute_tax(product, price, qty=1, taxes=None): if not taxes: taxes = product.taxes_id.filtered( lambda t: t.company_id.id == self.env.user.id) currency = self.pos_config.pricelist_id.currency_id res = taxes.compute_all(price, currency, qty, product=product) untax = res['total_excluded'] return untax, sum(tax.get('amount', 0.0) for tax in res['taxes']) # I click on create a new session button self.pos_config.open_session_cb(check_coa=False) current_session = self.pos_config.current_session_id num_starting_orders = len(current_session.order_ids) untax, atax = compute_tax(self.led_lamp, 0.9) carrot_order = { 'data': { 'amount_paid': untax + atax, 'amount_return': 0, 'amount_tax': atax, 'amount_total': untax + atax, 'creation_date': fields.Datetime.to_string(fields.Datetime.now()), 'fiscal_position_id': False, 'pricelist_id': self.pos_config.available_pricelist_ids[0].id, 'lines': [[ 0, 0, { 'discount': 0, 'id': 42, 'pack_lot_ids': [], 'price_unit': 0.9, 'product_id': self.led_lamp.id, 'price_subtotal': 0.9, 'price_subtotal_incl': 1.04, 'qty': 1, 'tax_ids': [(6, 0, self.led_lamp.taxes_id.ids)] } ]], 'name': 'Order 00042-003-0014', 'partner_id': False, 'pos_session_id': current_session.id, 'sequence_number': 2, 'statement_ids': [[ 0, 0, { 'amount': untax + atax, 'name': fields.Datetime.now(), 'payment_method_id': self.cash_payment_method.id } ]], 'uid': '00042-003-0014', 'user_id': self.env.uid }, 'id': '00042-003-0014', 'to_invoice': False } untax, atax = compute_tax(self.whiteboard_pen, 1.2) zucchini_order = { 'data': { 'amount_paid': untax + atax, 'amount_return': 0, 'amount_tax': atax, 'amount_total': untax + atax, 'creation_date': fields.Datetime.to_string(fields.Datetime.now()), 'fiscal_position_id': False, 'pricelist_id': self.pos_config.available_pricelist_ids[0].id, 'lines': [[ 0, 0, { 'discount': 0, 'id': 3, 'pack_lot_ids': [], 'price_unit': 1.2, 'product_id': self.whiteboard_pen.id, 'price_subtotal': 1.2, 'price_subtotal_incl': 1.38, 'qty': 1, 'tax_ids': [(6, 0, self.whiteboard_pen.taxes_id.ids)] } ]], 'name': 'Order 00043-003-0014', 'partner_id': False, 'pos_session_id': current_session.id, 'sequence_number': self.pos_config.journal_id.id, 'statement_ids': [[ 0, 0, { 'amount': untax + atax, 'name': fields.Datetime.now(), 'payment_method_id': self.credit_payment_method.id } ]], 'uid': '00043-003-0014', 'user_id': self.env.uid }, 'id': '00043-003-0014', 'to_invoice': False } untax, atax = compute_tax(self.newspaper_rack, 1.28) newspaper_rack_order = { 'data': { 'amount_paid': untax + atax, 'amount_return': 0, 'amount_tax': atax, 'amount_total': untax + atax, 'creation_date': fields.Datetime.to_string(fields.Datetime.now()), 'fiscal_position_id': False, 'pricelist_id': self.pos_config.available_pricelist_ids[0].id, 'lines': [[ 0, 0, { 'discount': 0, 'id': 3, 'pack_lot_ids': [], 'price_unit': 1.28, 'product_id': self.newspaper_rack.id, 'price_subtotal': 1.28, 'price_subtotal_incl': 1.47, 'qty': 1, 'tax_ids': [[6, False, self.newspaper_rack.taxes_id.ids]] } ]], 'name': 'Order 00044-003-0014', 'partner_id': False, 'pos_session_id': current_session.id, 'sequence_number': self.pos_config.journal_id.id, 'statement_ids': [[ 0, 0, { 'amount': untax + atax, 'name': fields.Datetime.now(), 'payment_method_id': self.bank_payment_method.id } ]], 'uid': '00044-003-0014', 'user_id': self.env.uid }, 'id': '00044-003-0014', 'to_invoice': False } # I create an order on an open session self.PosOrder.create_from_ui([carrot_order]) self.assertEqual(num_starting_orders + 1, len(current_session.order_ids), "Submitted order not encoded") # I close the session current_session.action_pos_session_closing_control() self.assertEqual(current_session.state, 'closed', "Session was not properly closed") self.assertFalse(self.pos_config.current_session_id, "Current session not properly recomputed") # I keep selling after the session is closed with mute_logger('odoo.addons.point_of_sale.models.pos_order'): self.PosOrder.create_from_ui( [zucchini_order, newspaper_rack_order]) rescue_session = self.PosSession.search([('config_id', '=', self.pos_config.id), ('state', '=', 'opened'), ('rescue', '=', True)]) self.assertEqual( len(rescue_session), 1, "One (and only one) rescue session should be created for orphan orders" ) self.assertIn("(RESCUE FOR %s)" % current_session.name, rescue_session.name, "Rescue session is not linked to the previous one") self.assertEqual(len(rescue_session.order_ids), 2, "Rescue session does not contain both orders") # I close the rescue session rescue_session.action_pos_session_closing_control() self.assertEqual(rescue_session.state, 'closed', "Rescue session was not properly closed")
def test_30_stripe_form_management(self): stripe = self.env["payment.acquirer"].browse(self.stripe_id) self.assertEqual(stripe.environment, "test", "test without test environment") # typical data posted by Stripe after client has successfully paid stripe_post_data = { u"amount": 4700, u"amount_refunded": 0, u"application_fee": None, u"balance_transaction": u"txn_172xfnGMfVJxozLwssrsQZyT", u"captured": True, u"created": 1446529775, u"currency": u"eur", u"customer": None, u"description": None, u"destination": None, u"dispute": None, u"failure_code": None, u"failure_message": None, u"fraud_details": {}, u"id": u"ch_172xfnGMfVJxozLwEjSfpfxD", u"invoice": None, u"livemode": False, u"metadata": {u"reference": u"SO100"}, u"object": u"charge", u"paid": True, u"receipt_email": None, u"receipt_number": None, u"refunded": False, u"refunds": { u"data": [], u"has_more": False, u"object": u"list", u"total_count": 0, u"url": u"/v1/charges/ch_172xfnGMfVJxozLwEjSfpfxD/refunds", }, u"shipping": None, u"source": { u"address_city": None, u"address_country": None, u"address_line1": None, u"address_line1_check": None, u"address_line2": None, u"address_state": None, u"address_zip": None, u"address_zip_check": None, u"brand": u"Visa", u"country": u"US", u"customer": None, u"cvc_check": u"pass", u"dynamic_last4": None, u"exp_month": 2, u"exp_year": 2022, u"fingerprint": u"9tJA9bUEuvEb3Ell", u"funding": u"credit", u"id": u"card_172xfjGMfVJxozLw1QO6gYNM", u"last4": u"4242", u"metadata": {}, u"name": u"*****@*****.**", u"object": u"card", u"tokenization_method": None, }, u"statement_descriptor": None, u"status": u"succeeded", } tx = self.env["payment.transaction"].create( { "amount": 4700, "acquirer_id": self.stripe_id, "currency_id": self.currency_euro.id, "reference": "SO100", "partner_name": "Norbert Buyer", "partner_country_id": self.country_france_id, } ) # validate it tx.form_feedback(stripe_post_data, "stripe") self.assertEqual(tx.state, "done", "Stripe: validation did not put tx into done state") self.assertEqual(tx.acquirer_reference, stripe_post_data.get("id"), "Stripe: validation did not update tx id") # reset tx tx.write({"state": "draft", "date_validate": False, "acquirer_reference": False}) # simulate an error stripe_post_data["status"] = "error" stripe_post_data.update( { u"error": { u"message": u"Your card's expiration year is invalid.", u"code": u"invalid_expiry_year", u"type": u"card_error", u"param": u"exp_year", } } ) with mute_logger("openerp.addons.payment_stripe.models.payment_transaction"): tx.form_feedback(stripe_post_data, "stripe") # check state self.assertEqual(tx.state, "error", "Stipe: erroneous validation did not put tx into error state")
def test_30_stripe_form_management(self): stripe = self.env['payment.acquirer'].browse(self.stripe_id) self.assertEqual(stripe.environment, 'test', 'test without test environment') # typical data posted by Stripe after client has successfully paid stripe_post_data = { u'amount': 4700, u'amount_refunded': 0, u'application_fee': None, u'balance_transaction': u'txn_172xfnGMfVJxozLwssrsQZyT', u'captured': True, u'created': 1446529775, u'currency': u'eur', u'customer': None, u'description': None, u'destination': None, u'dispute': None, u'failure_code': None, u'failure_message': None, u'fraud_details': {}, u'id': u'ch_172xfnGMfVJxozLwEjSfpfxD', u'invoice': None, u'livemode': False, u'metadata': {u'reference': u'SO100'}, u'object': u'charge', u'paid': True, u'receipt_email': None, u'receipt_number': None, u'refunded': False, u'refunds': {u'data': [], u'has_more': False, u'object': u'list', u'total_count': 0, u'url': u'/v1/charges/ch_172xfnGMfVJxozLwEjSfpfxD/refunds'}, u'shipping': None, u'source': {u'address_city': None, u'address_country': None, u'address_line1': None, u'address_line1_check': None, u'address_line2': None, u'address_state': None, u'address_zip': None, u'address_zip_check': None, u'brand': u'Visa', u'country': u'US', u'customer': None, u'cvc_check': u'pass', u'dynamic_last4': None, u'exp_month': 2, u'exp_year': 2022, u'fingerprint': u'9tJA9bUEuvEb3Ell', u'funding': u'credit', u'id': u'card_172xfjGMfVJxozLw1QO6gYNM', u'last4': u'4242', u'metadata': {}, u'name': u'*****@*****.**', u'object': u'card', u'tokenization_method': None}, u'statement_descriptor': None, u'status': u'succeeded'} tx = self.env['payment.transaction'].create({ 'amount': 4700, 'acquirer_id': self.stripe_id, 'currency_id': self.currency_euro.id, 'reference': 'SO100', 'partner_name': 'Norbert Buyer', 'partner_country_id': self.country_france_id}) # validate it tx.form_feedback(stripe_post_data, 'stripe') self.assertEqual(tx.state, 'done', 'Stripe: validation did not put tx into done state') self.assertEqual(tx.acquirer_reference, stripe_post_data.get('id'), 'Stripe: validation did not update tx id') # reset tx tx.write({'state': 'draft', 'date_validate': False, 'acquirer_reference': False}) # simulate an error stripe_post_data['status'] = 'error' stripe_post_data.update({u'error': {u'message': u"Your card's expiration year is invalid.", u'code': u'invalid_expiry_year', u'type': u'card_error', u'param': u'exp_year'}}) with mute_logger('openerp.addons.payment_stripe.models.payment_transaction'): tx.form_feedback(stripe_post_data, 'stripe') # check state self.assertEqual(tx.state, 'error', 'Stipe: erroneous validation did not put tx into error state')
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('odoo.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()
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') 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('odoo.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),))
def test_pay_logged_in_another_company(self): """User pays for an amount in another company.""" # for another res.partner than the user's one route_values = self._prepare_pay_values( partner=self.user_company_b.partner_id) # Log in as user from Company A self.authenticate(self.user_company_a.login, self.user_company_a.login) # Pay in company B route_values['company_id'] = self.company_b.id tx_context = self.get_tx_checkout_context(**route_values) for key, val in tx_context.items(): if key in route_values: if key == 'access_token': continue # access_token was modified due to the change of partner. elif key == 'partner_id': # The partner is replaced by the partner of the user paying. self.assertEqual(val, self.user_company_a.partner_id.id) else: self.assertEqual(val, route_values[key]) available_acquirers = self.env['payment.acquirer'].sudo().browse( tx_context['acquirer_ids']) self.assertIn(self.acquirer_company_b, available_acquirers) self.assertEqual(available_acquirers.company_id, self.company_b) validation_values = { k: tx_context[k] for k in [ 'amount', 'currency_id', 'reference_prefix', 'partner_id', 'access_token', 'landing_route', ] } validation_values.update({ 'flow': 'direct', 'payment_option_id': self.acquirer_company_b.id, 'tokenization_requested': False, }) with mute_logger('odoo.addons.payment.models.payment_transaction'): processing_values = self.get_processing_values(**validation_values) tx_sudo = self._get_tx(processing_values['reference']) # Tx values == given values self.assertEqual(tx_sudo.acquirer_id.id, self.acquirer_company_b.id) self.assertEqual(tx_sudo.amount, self.amount) self.assertEqual(tx_sudo.currency_id.id, self.currency.id) self.assertEqual(tx_sudo.partner_id.id, self.user_company_a.partner_id.id) self.assertEqual(tx_sudo.reference, self.reference) self.assertEqual(tx_sudo.company_id, self.company_b) # processing_values == given values self.assertEqual(processing_values['acquirer_id'], self.acquirer_company_b.id) self.assertEqual(processing_values['amount'], self.amount) self.assertEqual(processing_values['currency_id'], self.currency.id) self.assertEqual(processing_values['partner_id'], self.user_company_a.partner_id.id) self.assertEqual(processing_values['reference'], self.reference)
def test_crud_rights(self): Post = self.env['forum.post'] Vote = self.env['forum.post.vote'] self.user_portal.karma = 500 self.user_employee.karma = 500 # create some posts self.admin_post = self.post self.portal_post = Post.sudo(self.user_portal).create({ 'name': 'Post from Portal User', 'content': 'I am not a bird.', 'forum_id': self.forum.id, }) self.employee_post = Post.sudo(self.user_employee).create({ 'name': 'Post from Employee User', 'content': 'I am not a bird.', 'forum_id': self.forum.id, }) # vote on some posts self.employee_vote_on_admin_post = Vote.sudo(self.user_employee).create({ 'post_id': self.admin_post.id, 'vote': '1', }) self.portal_vote_on_admin_post = Vote.sudo(self.user_portal).create({ 'post_id': self.admin_post.id, 'vote': '1', }) self.admin_vote_on_portal_post = Vote.create({ 'post_id': self.portal_post.id, 'vote': '1', }) self.admin_vote_on_employee_post = Vote.create({ 'post_id': self.employee_post.id, 'vote': '1', }) # One should not be able to modify someone else's vote with self.assertRaises(UserError): self.admin_vote_on_portal_post.sudo(self.user_employee).write({ 'vote': '-1', }) with self.assertRaises(UserError): self.admin_vote_on_employee_post.sudo(self.user_portal).write({ 'vote': '-1', }) # One should not be able to give his vote to someone else self.employee_vote_on_admin_post.sudo(self.user_employee).write({ 'user_id': 1, }) self.assertEqual(self.employee_vote_on_admin_post.user_id, self.user_employee, 'User employee should not be able to give its vote ownership to someone else') # One should not be able to change his vote's post to a post of his own (would be self voting) with self.assertRaises(UserError): self.employee_vote_on_admin_post.sudo(self.user_employee).write({ 'post_id': self.employee_post.id, }) # One should not be able to give his vote to someone else self.portal_vote_on_admin_post.sudo(self.user_portal).write({ 'user_id': 1, }) self.assertEqual(self.portal_vote_on_admin_post.user_id, self.user_portal, 'User portal should not be able to give its vote ownership to someone else') # One should not be able to change his vote's post to a post of his own (would be self voting) with self.assertRaises(UserError): self.portal_vote_on_admin_post.sudo(self.user_portal).write({ 'post_id': self.portal_post.id, }) # One should not be able to vote for its own post with self.assertRaises(UserError): Vote.sudo(self.user_employee).create({ 'post_id': self.employee_post.id, 'vote': '1', }) # One should not be able to vote for its own post with self.assertRaises(UserError): Vote.sudo(self.user_portal).create({ 'post_id': self.portal_post.id, 'vote': '1', }) with mute_logger('odoo.sql_db'): with self.assertRaises(IntegrityError): with self.cr.savepoint(): # One should not be able to vote more than once on a same post Vote.sudo(self.user_employee).create({ 'post_id': self.admin_post.id, 'vote': '1', }) with self.assertRaises(IntegrityError): with self.cr.savepoint(): # One should not be able to vote more than once on a same post Vote.sudo(self.user_employee).create({ 'post_id': self.admin_post.id, 'vote': '1', }) # One should not be able to create a vote for someone else new_employee_vote = Vote.sudo(self.user_employee).create({ 'post_id': self.portal_post.id, 'user_id': 1, 'vote': '1', }) self.assertEqual(new_employee_vote.user_id, self.user_employee, 'Creating a vote for someone else should not be allowed. It should create it for yourself instead') # One should not be able to create a vote for someone else new_portal_vote = Vote.sudo(self.user_portal).create({ 'post_id': self.employee_post.id, 'user_id': 1, 'vote': '1', }) self.assertEqual(new_portal_vote.user_id, self.user_portal, 'Creating a vote for someone else should not be allowed. It should create it for yourself instead')
def create_variant_ids(self): Product = self.env["product.product"] variants_to_create = [] variants_to_activate = [] variants_to_unlink = [] for tmpl_id in self.with_context(active_test=False): # adding an attribute with only one value should not recreate product # write this attribute on every product to make sure we don't lose them variant_alone = tmpl_id.attribute_line_ids.filtered(lambda line: line.attribute_id.create_variant == 'always' and len(line.value_ids) == 1).mapped('value_ids') for value_id in variant_alone: updated_products = tmpl_id.product_variant_ids.filtered(lambda product: value_id.attribute_id not in product.mapped('attribute_value_ids.attribute_id')) updated_products.write({'attribute_value_ids': [(4, value_id.id)]}) # Determine which product variants need to be created based on the attribute # configuration. If any attribute is set to generate variants dynamically, skip the # process. # Technical note: if there is no attribute, a variant is still created because # 'not any([])' and 'set([]) not in set([])' are True. if not any(attrib.create_variant == 'dynamic' for attrib in tmpl_id.mapped('attribute_line_ids.attribute_id')): # Iterator containing all possible attribute values combination # The iterator is used to avoid MemoryError in case of a huge number of combination. all_variants = itertools.product(*( line.value_ids.ids for line in tmpl_id.attribute_line_ids if line.value_ids[:1].attribute_id.create_variant != 'no_variant' )) # Set containing existing attribute values combination existing_variants = { frozenset(variant.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant != 'no_variant').ids) for variant in tmpl_id.product_variant_ids } # For each possible variant, create if it doesn't exist yet. for value_ids in all_variants: value_ids = frozenset(value_ids) if value_ids not in existing_variants: variants_to_create.append({ 'product_tmpl_id': tmpl_id.id, 'attribute_value_ids': [(6, 0, list(value_ids))], }) if len(variants_to_create) > 1000: raise UserError(_( 'The number of variants to generate is too high. ' 'You should either not generate variants for each combination or generate them on demand from the sales order. ' 'To do so, open the form view of attributes and change the mode of *Create Variants*.')) # Check existing variants if any needs to be activated or unlinked. # - if the product is not active and has valid attributes and attribute values, it # should be activated # - if the product does not have valid attributes or attribute values, it should be # deleted valid_value_ids = tmpl_id.mapped('attribute_line_ids.value_ids').filtered( lambda v: v.attribute_id.create_variant != 'no_variant' ) valid_attribute_ids = valid_value_ids.mapped('attribute_id') for product_id in tmpl_id.product_variant_ids: if product_id._has_valid_attributes(valid_attribute_ids, valid_value_ids): if not product_id.active: variants_to_activate.append(product_id) else: variants_to_unlink.append(product_id) if variants_to_activate: Product.concat(*variants_to_activate).write({'active': True}) # create new products if variants_to_create: Product.create(variants_to_create) # unlink or inactive product for variant in variants_to_unlink: try: with self._cr.savepoint(), tools.mute_logger('odoo.sql_db'): variant.unlink() # We catch all kind of exception to be sure that the operation doesn't fail. except (psycopg2.Error, except_orm): variant.write({'active': False}) pass return True
def create_variant_ids(self): Product = self.env["product.product"] for tmpl_id in self.with_context(active_test=False): # Handle the variants for each template separately. This will be # less efficient when called on a lot of products with few variants # but it is better when there's a lot of variants on one template. variants_to_create = [] variants_to_activate = self.env['product.product'] variants_to_unlink = self.env['product.product'] # adding an attribute with only one value should not recreate product # write this attribute on every product to make sure we don't lose them variant_alone = tmpl_id._get_valid_product_template_attribute_lines().filtered(lambda line: line.attribute_id.create_variant == 'always' and len(line.value_ids) == 1).mapped('value_ids') for value_id in variant_alone: updated_products = tmpl_id.product_variant_ids.filtered(lambda product: value_id.attribute_id not in product.mapped('attribute_value_ids.attribute_id')) updated_products.write({'attribute_value_ids': [(4, value_id.id)]}) # Determine which product variants need to be created based on the attribute # configuration. If any attribute is set to generate variants dynamically, skip the # process. # Technical note: if there is no attribute, a variant is still created because # 'not any([])' and 'set([]) not in set([])' are True. if not tmpl_id.has_dynamic_attributes(): # Iterator containing all possible `product.attribute.value` combination # The iterator is used to avoid MemoryError in case of a huge number of combination. all_variants = itertools.product(*( line.value_ids.ids for line in tmpl_id.valid_product_template_attribute_line_wnva_ids )) # Set containing existing `product.attribute.value` combination existing_variants = { frozenset(variant.attribute_value_ids.ids) for variant in tmpl_id.product_variant_ids } # For each possible variant, create if it doesn't exist yet. for value_ids in all_variants: value_ids = frozenset(value_ids) if value_ids not in existing_variants: variants_to_create.append({ 'product_tmpl_id': tmpl_id.id, 'attribute_value_ids': [(6, 0, list(value_ids))], }) if len(variants_to_create) > 1000: raise UserError(_( 'The number of variants to generate is too high. ' 'You should either not generate variants for each combination or generate them on demand from the sales order. ' 'To do so, open the form view of attributes and change the mode of *Create Variants*.')) # Check existing variants if any needs to be activated or unlinked. # - if the product is not active and has valid attributes and attribute values, it # should be activated # - if the product does not have valid attributes or attribute values, it should be # deleted valid_value_ids = tmpl_id.valid_product_attribute_value_wnva_ids valid_attribute_ids = tmpl_id.valid_product_attribute_wnva_ids for product_id in tmpl_id.product_variant_ids: if product_id._has_valid_attributes(valid_attribute_ids, valid_value_ids): if not product_id.active: variants_to_activate += product_id else: variants_to_unlink += product_id if variants_to_activate: variants_to_activate.write({'active': True}) # create new products if variants_to_create: Product.create(variants_to_create) # unlink or inactive product # try in batch first because it is much faster try: with self._cr.savepoint(), tools.mute_logger('odoo.sql_db'): variants_to_unlink.unlink() except Exception: # fall back to one by one if batch is not possible for variant in variants_to_unlink: try: with self._cr.savepoint(), tools.mute_logger('odoo.sql_db'): variant.unlink() # We catch all kind of exception to be sure that the operation doesn't fail. except Exception: # Note: this can still fail if something is preventing from archiving. # This is the case from existing stock reordering rules. variant.write({'active': False}) # prefetched o2m have to be reloaded (because of active_test) # (eg. product.template: product_variant_ids) # We can't rely on existing invalidate_cache because of the savepoint. self.invalidate_cache() return True
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('odoo.sql_db'): field2.name = field1.name
def test01_basic(self): """Fails immediately on missing product.""" doc = self.create_tutorial('order02.csv') with tools.mute_logger('odoo.addons.edi.models.edi_issues'): self.assertFalse(doc.action_execute())
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 holiday_status_paid_time_off = self.env['hr.leave.type'].create({ 'name': 'Paid Time Off', 'allocation_type': 'fixed', 'validation_type': 'both', 'validity_start': time.strftime('%Y-%m-01'), 'responsible_id': self.env.ref('base.user_admin').id, }) self.env['hr.leave.allocation'].create({ 'name': 'Paid Time off for David', 'holiday_status_id': holiday_status_paid_time_off.id, 'number_of_days': 20, 'employee_id': self.ref('hr.employee_admin'), 'state': 'validate', }) leave_vals = { 'name': 'Sick Time Off', 'holiday_status_id': holiday_status_paid_time_off.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('odoo.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': holiday_status_paid_time_off.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('odoo.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'), })
def _test_flow(self, flow): """ Simulate the given online payment flow and tests the tx values at each step. :param str flow: The online payment flow to test ('direct', 'redirect', or 'token') :return: The transaction created by the payment flow :rtype: recordset of `payment.transaction` """ self.reference = f"Test Transaction ({flow} - {self.partner.name})" route_values = self._prepare_pay_values() # /payment/pay tx_context = self.get_tx_checkout_context(**route_values) for key, val in tx_context.items(): if key in route_values: self.assertEqual(val, route_values[key]) # Route values are taken from tx_context result of /pay route to correctly simulate the flow route_values = { k: tx_context[k] for k in [ 'amount', 'currency_id', 'reference_prefix', 'partner_id', 'access_token', 'landing_route', ] } route_values.update({ 'flow': flow, 'payment_option_id': self.acquirer.id, 'tokenization_requested': False, }) if flow == 'token': route_values['payment_option_id'] = self.create_token().id with mute_logger('odoo.addons.payment.models.payment_transaction'): processing_values = self.get_processing_values(**route_values) tx_sudo = self._get_tx(processing_values['reference']) # Tx values == given values self.assertEqual(tx_sudo.acquirer_id.id, self.acquirer.id) self.assertEqual(tx_sudo.amount, self.amount) self.assertEqual(tx_sudo.currency_id.id, self.currency.id) self.assertEqual(tx_sudo.partner_id.id, self.partner.id) self.assertEqual(tx_sudo.reference, self.reference) # processing_values == given values self.assertEqual(processing_values['acquirer_id'], self.acquirer.id) self.assertEqual(processing_values['amount'], self.amount) self.assertEqual(processing_values['currency_id'], self.currency.id) self.assertEqual(processing_values['partner_id'], self.partner.id) self.assertEqual(processing_values['reference'], self.reference) # Verify computed values not provided, but added during the flow self.assertIn("tx_id=", tx_sudo.landing_route) self.assertIn("access_token=", tx_sudo.landing_route) if flow == 'redirect': # In redirect flow, we verify the rendering of the dummy test form redirect_form_info = self._extract_values_from_html_form( processing_values['redirect_form_html']) # Test content of rendered dummy redirect form self.assertEqual(redirect_form_info['action'], 'dummy') # Public user since we didn't authenticate with a specific user self.assertEqual(redirect_form_info['inputs']['user_id'], str(self.user.id)) self.assertEqual(redirect_form_info['inputs']['view_id'], str(self.dummy_acquirer.redirect_form_view_id.id)) return tx_sudo
def test_00_crossdock(self): # Create a supplier supplier_crossdock = self.env['res.partner'].create( {'name': "Crossdocking supplier"}) # I first create a warehouse with pick-pack-ship and reception in 2 steps wh_pps = self.env['stock.warehouse'].create({ 'name': 'WareHouse PickPackShip', 'code': 'whpps', 'reception_steps': 'two_steps', 'delivery_steps': 'pick_pack_ship', }) # Check that cross-dock route is active self.assertTrue( wh_pps.crossdock_route_id.active, "Crossdock route should be active when reception_steps is not in 'single_step'" ) p_f = Form(self.env['product.template']) p_f.name = 'PCE' p_f.detailed_type = 'product' p_f.categ_id = self.env.ref('product.product_category_1') p_f.list_price = 100.0 with p_f.seller_ids.new() as seller: seller.partner_id = supplier_crossdock p_f.route_ids.add(wh_pps.crossdock_route_id) cross_shop_product = p_f.save() p_f.standard_price = 70.0 # Create a sales order with a line of 100 PCE incoming shipment with route_id crossdock shipping so_form = Form(self.env['sale.order']) so_form.partner_id = self.env['res.partner'].create( {'name': 'My Test Partner'}) so_form.warehouse_id = wh_pps with mute_logger('odoo.tests.common.onchange'): # otherwise complains that there's not enough inventory and # apparently that's normal according to @jco and @sle with so_form.order_line.new() as line: line.product_id = cross_shop_product.product_variant_ids line.product_uom_qty = 100.0 sale_order_crossdock = so_form.save() # Confirm sales order sale_order_crossdock.action_confirm() # Run the scheduler self.env['procurement.group'].run_scheduler() # Check a quotation was created for the created supplier and confirm it po = self.env['purchase.order'].search([('partner_id', '=', supplier_crossdock.id), ('state', '=', 'draft')]) self.assertTrue(po, "an RFQ should have been created by the scheduler") po.button_confirm()
def test_mail_server_priorities(self): """Test if we choose the right mail server to send an email. Priorities are 1. Forced mail server (e.g.: in mass mailing) - If the "from_filter" of the mail server match the notification email use the notifications email in the "From header" - Otherwise spoof the "From" (because we force the mail server but we don't know which email use to send it) 2. A mail server for which the "from_filter" match the "From" header 3. A mail server for which the "from_filter" match the domain of the "From" header 4. The mail server used for notifications 5. A mail server without "from_filter" (and so spoof the "From" header because we do not know for which email address it can be used) """ # sanity checks self.assertTrue(self.env['ir.mail_server']._get_default_from_address(), 'Notifications email must be set for testing') self.assertTrue( self.env['ir.mail_server']._get_default_bounce_address(), 'Bounce email must be set for testing') mail_server, mail_from = self.env['ir.mail_server']._find_mail_server( email_from='*****@*****.**') self.assertEqual(mail_server, self.server_user) self.assertEqual(mail_from, '*****@*****.**') mail_server, mail_from = self.env['ir.mail_server']._find_mail_server( email_from='"Name [email protected]" <*****@*****.**>') self.assertEqual(mail_server, self.server_user, 'Must extract email from full name') self.assertEqual(mail_from, '"Name [email protected]" <*****@*****.**>', 'Must keep the given mail from') # Should not be case sensitive mail_server, mail_from = self.env['ir.mail_server']._find_mail_server( email_from='*****@*****.**') self.assertEqual(mail_server, self.server_user, 'Mail from is case insensitive') self.assertEqual(mail_from, '*****@*****.**', 'Should not change the mail from') mail_server, mail_from = self.env['ir.mail_server']._find_mail_server( email_from='*****@*****.**') self.assertEqual(mail_server, self.server_domain) self.assertEqual(mail_from, '*****@*****.**') # Cover a different condition that the "email case insensitive" test mail_server, mail_from = self.env['ir.mail_server']._find_mail_server( email_from='*****@*****.**') self.assertEqual(mail_server, self.server_domain, 'Domain is case insensitive') self.assertEqual(mail_from, '*****@*****.**', 'Domain is case insensitive') mail_server, mail_from = self.env['ir.mail_server']._find_mail_server( email_from='"Test" <test@unknown_domain.com>') self.assertEqual(mail_server, self.server_notification, 'Should take the notification email') self.assertEqual(mail_from, '*****@*****.**') # remove the notifications email to simulate a mis-configured Odoo database # so we do not have the choice, we have to spoof the FROM # (otherwise we can not send the email) self.env['ir.config_parameter'].sudo().set_param( 'mail.catchall.domain', False) with mute_logger('odoo.addons.base.models.ir_mail_server'): mail_server, mail_from = self.env[ 'ir.mail_server']._find_mail_server( email_from='test@unknown_domain.com') self.assertEqual( mail_server.from_filter, False, 'No notifications email set, must be forced to spoof the FROM') self.assertEqual(mail_from, 'test@unknown_domain.com')
def test_create_or_update_with_errors(self): request = fake_request(form_data={}, method='POST') form = self.get_form('cms.form.res.partner', req=request) with mute_logger('odoo.sql_db'): values = form.form_process_POST({}) self.assertTrue('_integrity' in values['errors'])
def test_create_from_ui(self): """ Simulation of sales coming from the interface, even after closing the session """ def compute_tax(product, price, qty=1, taxes=None): if not taxes: taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.user.id) currency = self.pos_config.pricelist_id.currency_id res = taxes.compute_all(price, currency, qty, product=product) untax = res['total_excluded'] return untax, sum(tax.get('amount', 0.0) for tax in res['taxes']) # I click on create a new session button self.pos_config.open_session_cb() current_session = self.pos_config.current_session_id num_starting_orders = len(current_session.order_ids) untax, atax = compute_tax(self.led_lamp, 0.9) carrot_order = {'data': {'amount_paid': untax + atax, 'amount_return': 0, 'amount_tax': atax, 'amount_total': untax + atax, 'creation_date': fields.Datetime.to_string(fields.Datetime.now()), 'fiscal_position_id': False, 'pricelist_id': self.pos_config.available_pricelist_ids[0].id, 'lines': [[0, 0, {'discount': 0, 'id': 42, 'pack_lot_ids': [], 'price_unit': 0.9, 'product_id': self.led_lamp.id, 'price_subtotal': 0.9, 'price_subtotal_incl': 1.04, 'qty': 1, 'tax_ids': [(6, 0, self.led_lamp.taxes_id.ids)]}]], 'name': 'Order 00042-003-0014', 'partner_id': False, 'pos_session_id': current_session.id, 'sequence_number': 2, 'statement_ids': [[0, 0, {'account_id': self.env.user.partner_id.property_account_receivable_id.id, 'amount': untax + atax, 'journal_id': self.pos_config.journal_ids[0].id, 'name': fields.Datetime.now(), 'statement_id': current_session.statement_ids[0].id}]], 'uid': '00042-003-0014', 'user_id': self.env.uid}, 'id': '00042-003-0014', 'to_invoice': False} untax, atax = compute_tax(self.whiteboard_pen, 1.2) zucchini_order = {'data': {'amount_paid': untax + atax, 'amount_return': 0, 'amount_tax': atax, 'amount_total': untax + atax, 'creation_date': fields.Datetime.to_string(fields.Datetime.now()), 'fiscal_position_id': False, 'pricelist_id': self.pos_config.available_pricelist_ids[0].id, 'lines': [[0, 0, {'discount': 0, 'id': 3, 'pack_lot_ids': [], 'price_unit': 1.2, 'product_id': self.whiteboard_pen.id, 'price_subtotal': 1.2, 'price_subtotal_incl': 1.38, 'qty': 1, 'tax_ids': [(6, 0, self.whiteboard_pen.taxes_id.ids)]}]], 'name': 'Order 00043-003-0014', 'partner_id': False, 'pos_session_id': current_session.id, 'sequence_number': self.pos_config.journal_id.id, 'statement_ids': [[0, 0, {'account_id': self.env.user.partner_id.property_account_receivable_id.id, 'amount': untax + atax, 'journal_id': self.pos_config.journal_ids[0].id, 'name': fields.Datetime.now(), 'statement_id': current_session.statement_ids[0].id}]], 'uid': '00043-003-0014', 'user_id': self.env.uid}, 'id': '00043-003-0014', 'to_invoice': False} untax, atax = compute_tax(self.newspaper_rack, 1.28) newspaper_rack_order = {'data': {'amount_paid': untax + atax, 'amount_return': 0, 'amount_tax': atax, 'amount_total': untax + atax, 'creation_date': fields.Datetime.to_string(fields.Datetime.now()), 'fiscal_position_id': False, 'pricelist_id': self.pos_config.available_pricelist_ids[0].id, 'lines': [[0, 0, {'discount': 0, 'id': 3, 'pack_lot_ids': [], 'price_unit': 1.28, 'product_id': self.newspaper_rack.id, 'price_subtotal': 1.28, 'price_subtotal_incl': 1.47, 'qty': 1, 'tax_ids': [[6, False, self.newspaper_rack.taxes_id.ids]]}]], 'name': 'Order 00044-003-0014', 'partner_id': False, 'pos_session_id': current_session.id, 'sequence_number': self.pos_config.journal_id.id, 'statement_ids': [[0, 0, {'account_id': self.env.user.partner_id.property_account_receivable_id.id, 'amount': untax + atax, 'journal_id': self.pos_config.journal_ids[0].id, 'name': fields.Datetime.now(), 'statement_id': current_session.statement_ids[0].id}]], 'uid': '00044-003-0014', 'user_id': self.env.uid}, 'id': '00044-003-0014', 'to_invoice': False} # I create an order on an open session self.PosOrder.create_from_ui([carrot_order]) self.assertEqual(num_starting_orders + 1, len(current_session.order_ids), "Submitted order not encoded") # I resubmit the same order self.PosOrder.create_from_ui([carrot_order]) self.assertEqual(num_starting_orders + 1, len(current_session.order_ids), "Resubmitted order was not skipped") # I close the session current_session.action_pos_session_closing_control() self.assertEqual(current_session.state, 'closed', "Session was not properly closed") self.assertFalse(self.pos_config.current_session_id, "Current session not properly recomputed") # I keep selling after the session is closed with mute_logger('odoo.addons.point_of_sale.models.pos_order'): self.PosOrder.create_from_ui([zucchini_order, newspaper_rack_order]) rescue_session = self.PosSession.search([ ('config_id', '=', self.pos_config.id), ('state', '=', 'opened'), ('rescue', '=', True) ]) self.assertEqual(len(rescue_session), 1, "One (and only one) rescue session should be created for orphan orders") self.assertIn("(RESCUE FOR %s)" % current_session.name, rescue_session.name, "Rescue session is not linked to the previous one") self.assertEqual(len(rescue_session.order_ids), 2, "Rescue session does not contain both orders") # I close the rescue session rescue_session.action_pos_session_closing_control() self.assertEqual(rescue_session.state, 'closed', "Rescue session was not properly closed")