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('swerp.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: self.env['procurement.group'].run_scheduler( use_new_cursor=self._cr.dbname, company_id=company.id) new_cr.close() return {}
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('swerp.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')
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_f = Form(self.env['stock.warehouse']) wh_f.name = 'WareHouse PickPackShip' wh_f.code = 'whpps' wh_f.reception_steps = 'two_steps' wh_f.delivery_steps = 'pick_pack_ship' wh_pps = wh_f.save() # 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.type = 'product' p_f.categ_id = self.env.ref('product.product_category_1') p_f.list_price = 100.0 p_f.standard_price = 70.0 with p_f.seller_ids.new() as seller: seller.name = supplier_crossdock p_f.route_ids.add(wh_pps.crossdock_route_id) cross_shop_product = p_f.save() # 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.ref('base.res_partner_4') so_form.warehouse_id = wh_pps with mute_logger('swerp.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 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('swerp.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 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('swerp.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() with Form(self.env['mrp.bom']) as bom: bom.product_tmpl_id = product_template_slidermobile0 # 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 send_email(self, message, *args, **kwargs): with this.registry.cursor() as cr, mute_logger('swerp.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 _update(self, inactivity_period): 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('swerp.sql_db'): presence.write(values)
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('swerp.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_30_stripe_form_management(self): self.assertEqual(self.stripe.environment, 'test', 'test without test environment') # typical data posted by Stripe after client has successfully paid stripe_post_data = { u'amount': 470000, 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-1' }, 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.0, 'acquirer_id': self.stripe.id, 'currency_id': self.currency_euro.id, 'reference': 'SO100-1', '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') stripe_post_data['metadata']['reference'] = u'SO100-2' # reset tx tx = self.env['payment.transaction'].create({ 'amount': 4700.0, 'acquirer_id': self.stripe.id, 'currency_id': self.currency_euro.id, 'reference': 'SO100-2', 'partner_name': 'Norbert Buyer', 'partner_country_id': self.country_france.id }) # 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('swerp.addons.payment_stripe.models.payment'): with mute_logger('swerp.addons.payment_stripe_sca.models.payment'): tx.form_feedback(stripe_post_data, 'stripe') # check state self.assertEqual( tx.state, 'cancel', 'Stipe: erroneous validation did not put tx into error state')
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('swerp.sql_db'): field2.name = field1.name
def test_create_from_ui(self): """ Simulation of sales coming from the interface, even after closing the session """ # 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 = self.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 = self.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 = self.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('swerp.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_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.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 product = self.env.ref('product.product_product_3') vals = { 'name': 'Delivery order for procurement', 'partner_id': self.ref('base.res_partner_2'), '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('swerp.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 _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('swerp.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_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('swerp.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')