def test_recurrence_week_day(self): form = Form(self.env['project.task']) form.name = 'test recurring task' form.project_id = self.project_recurring form.recurring_task = True form.repeat_unit = 'week' form.mon = False form.tue = False form.wed = False form.thu = False form.fri = False form.sat = False form.sun = False with self.assertRaises(ValidationError), self.cr.savepoint(): form.save()
def test_in_refund_line_onchange_partner_1(self): move_form = Form(self.invoice) move_form.partner_id = self.partner_b move_form.payment_reference = 'turlututu' move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'partner_id': self.partner_b.id, }, { **self.product_line_vals_2, 'partner_id': self.partner_b.id, }, { **self.tax_line_vals_1, 'partner_id': self.partner_b.id, }, { **self.tax_line_vals_2, 'partner_id': self.partner_b.id, }, { **self.term_line_vals_1, 'name': 'turlututu', 'partner_id': self.partner_b.id, 'account_id': self.partner_b.property_account_payable_id.id, 'price_unit': -338.4, 'price_subtotal': -338.4, 'price_total': -338.4, 'amount_currency': 338.4, 'debit': 338.4, }, { **self.term_line_vals_1, 'name': 'turlututu', 'partner_id': self.partner_b.id, 'account_id': self.partner_b.property_account_payable_id.id, 'price_unit': -789.6, 'price_subtotal': -789.6, 'price_total': -789.6, 'amount_currency': 789.6, 'debit': 789.6, 'date_maturity': fields.Date.from_string('2019-02-28'), }, ], { **self.move_vals, 'partner_id': self.partner_b.id, 'payment_reference': 'turlututu', 'fiscal_position_id': self.fiscal_pos_a.id, 'invoice_payment_term_id': self.pay_terms_b.id, 'amount_untaxed': 960.0, 'amount_tax': 168.0, 'amount_total': 1128.0, }) # Remove lines and recreate them to apply the fiscal position. move_form = Form(self.invoice) move_form.invoice_line_ids.remove(0) move_form.invoice_line_ids.remove(0) with move_form.invoice_line_ids.new() as line_form: line_form.product_id = self.product_a with move_form.invoice_line_ids.new() as line_form: line_form.product_id = self.product_b move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'account_id': self.product_b.property_account_expense_id.id, 'partner_id': self.partner_b.id, 'tax_ids': self.tax_purchase_b.ids, }, { **self.product_line_vals_2, 'partner_id': self.partner_b.id, 'price_total': 184.0, 'tax_ids': self.tax_purchase_b.ids, }, { **self.tax_line_vals_1, 'name': self.tax_purchase_b.name, 'partner_id': self.partner_b.id, 'tax_line_id': self.tax_purchase_b.id, }, { **self.term_line_vals_1, 'name': 'turlututu', 'account_id': self.partner_b.property_account_payable_id.id, 'partner_id': self.partner_b.id, 'price_unit': -331.2, 'price_subtotal': -331.2, 'price_total': -331.2, 'amount_currency': 331.2, 'debit': 331.2, }, { **self.term_line_vals_1, 'name': 'turlututu', 'account_id': self.partner_b.property_account_payable_id.id, 'partner_id': self.partner_b.id, 'price_unit': -772.8, 'price_subtotal': -772.8, 'price_total': -772.8, 'amount_currency': 772.8, 'debit': 772.8, 'date_maturity': fields.Date.from_string('2019-02-28'), }, ], { **self.move_vals, 'partner_id': self.partner_b.id, 'payment_reference': 'turlututu', 'fiscal_position_id': self.fiscal_pos_a.id, 'invoice_payment_term_id': self.pay_terms_b.id, 'amount_untaxed': 960.0, 'amount_tax': 144.0, 'amount_total': 1104.0, })
def test_in_refund_line_onchange_cash_rounding_1(self): move_form = Form(self.invoice) # Add a cash rounding having 'add_invoice_line'. move_form.invoice_cash_rounding_id = self.cash_rounding_a move_form.save() # The cash rounding does nothing as the total is already rounded. self.assertInvoiceValues(self.invoice, [ self.product_line_vals_1, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, self.term_line_vals_1, ], self.move_vals) move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: line_form.price_unit = 799.99 move_form.save() self.assertInvoiceValues(self.invoice, [ { 'name': 'add_invoice_line', 'product_id': False, 'account_id': self.cash_rounding_a.profit_account_id.id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': 1.0, 'discount': 0.0, 'price_unit': 0.01, 'price_subtotal': 0.01, 'price_total': 0.01, 'tax_ids': [], 'tax_line_id': False, 'currency_id': self.company_data['currency'].id, 'amount_currency': -0.01, 'debit': 0.0, 'credit': 0.01, 'date_maturity': False, 'tax_exigible': True, }, { **self.product_line_vals_1, 'price_unit': 799.99, 'price_subtotal': 799.99, 'price_total': 919.99, 'amount_currency': -799.99, 'credit': 799.99, }, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, self.term_line_vals_1, ], self.move_vals) move_form = Form(self.invoice) # Change the cash rounding to one having 'biggest_tax'. move_form.invoice_cash_rounding_id = self.cash_rounding_b move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'price_unit': 799.99, 'price_subtotal': 799.99, 'price_total': 919.99, 'amount_currency': -799.99, 'credit': 799.99, }, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, { 'name': '%s (rounding)' % self.tax_purchase_a.name, 'product_id': False, 'account_id': self.company_data['default_account_tax_purchase'].id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': 1.0, 'discount': 0.0, 'price_unit': -0.04, 'price_subtotal': -0.04, 'price_total': -0.04, 'tax_ids': [], 'tax_line_id': self.tax_purchase_a.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': 0.04, 'debit': 0.04, 'credit': 0.0, 'date_maturity': False, 'tax_exigible': True, }, { **self.term_line_vals_1, 'price_unit': -1127.95, 'price_subtotal': -1127.95, 'price_total': -1127.95, 'amount_currency': 1127.95, 'debit': 1127.95, }, ], { **self.move_vals, 'amount_untaxed': 959.99, 'amount_tax': 167.96, 'amount_total': 1127.95, })
def test_subcontracting_account_backorder(self): """ This test uses tracked (serial and lot) component and tracked (serial) finished product The original subcontracting production order will be split into 4 backorders. This test ensure the extra cost asked from the subcontractor is added correctly on all the finished product valuation layer. Not only the first one. """ todo_nb = 4 self.comp2.tracking = 'lot' self.comp1.tracking = 'serial' self.comp2.standard_price = 100 self.finished.tracking = 'serial' self.env.ref( 'product.product_category_all').property_cost_method = 'fifo' # Create a receipt picking from the subcontractor picking_form = Form(self.env['stock.picking']) picking_form.picking_type_id = self.env.ref('stock.picking_type_in') picking_form.partner_id = self.subcontractor_partner1 with picking_form.move_ids_without_package.new() as move: move.product_id = self.finished move.product_uom_qty = todo_nb picking_receipt = picking_form.save() # Mimic the extra cost on the po line picking_receipt.move_lines.price_unit = 50 picking_receipt.action_confirm() # We should be able to call the 'record_components' button self.assertTrue(picking_receipt.display_action_record_components) # Check the created manufacturing order mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)]) wh = picking_receipt.picking_type_id.warehouse_id lot_comp2 = self.env['stock.production.lot'].create({ 'name': 'lot_comp2', 'product_id': self.comp2.id, 'company_id': self.env.company.id, }) serials_finished = [] serials_comp1 = [] for i in range(todo_nb): serials_finished.append(self.env['stock.production.lot'].create({ 'name': 'serial_fin_%s' % i, 'product_id': self.finished.id, 'company_id': self.env.company.id, })) serials_comp1.append(self.env['stock.production.lot'].create({ 'name': 'serials_comp1_%s' % i, 'product_id': self.comp1.id, 'company_id': self.env.company.id, })) for i in range(todo_nb): action = picking_receipt.action_record_components() mo = self.env['mrp.production'].browse(action['res_id']) mo_form = Form(mo.with_context(**action['context']), view=action['view_id']) mo_form.lot_producing_id = serials_finished[i] with mo_form.move_line_raw_ids.edit(0) as ml: self.assertEqual(ml.product_id, self.comp1) ml.lot_id = serials_comp1[i] with mo_form.move_line_raw_ids.edit(1) as ml: self.assertEqual(ml.product_id, self.comp2) ml.lot_id = lot_comp2 mo = mo_form.save() mo.subcontracting_record_component() # We should not be able to call the 'record_components' button picking_receipt.button_validate() f_layers = self.finished.stock_valuation_layer_ids self.assertEqual(len(f_layers), 4) for layer in f_layers: self.assertEqual(layer.value, 100 + 50)
def test_in_refund_line_onchange_business_fields_1(self): move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: # Current price_unit is 800. # We set quantity = 4, discount = 50%, price_unit = 400. The debit/credit fields don't change because (4 * 400) * 0.5 = 800. line_form.quantity = 4 line_form.discount = 50 line_form.price_unit = 400 move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'quantity': 4, 'discount': 50.0, 'price_unit': 400.0, }, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, self.term_line_vals_1, ], self.move_vals) move_form = Form(self.invoice) with move_form.line_ids.edit(2) as line_form: # Reset field except the discount that becomes 100%. # /!\ The modification is made on the accounting tab. line_form.quantity = 1 line_form.discount = 100 line_form.price_unit = 800 move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'discount': 100.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'amount_currency': 0.0, 'credit': 0.0, }, self.product_line_vals_2, { **self.tax_line_vals_1, 'price_unit': 24.0, 'price_subtotal': 24.0, 'price_total': 24.0, 'amount_currency': -24.0, 'credit': 24.0, }, self.tax_line_vals_2, { **self.term_line_vals_1, 'price_unit': -208.0, 'price_subtotal': -208.0, 'price_total': -208.0, 'amount_currency': 208.0, 'debit': 208.0, }, ], { **self.move_vals, 'amount_untaxed': 160.0, 'amount_tax': 48.0, 'amount_total': 208.0, })
def test_intercom_lot_pull(self): """Use warehouse of comany a to resupply warehouse of company b. Check pull rule works correctly in two companies and moves are unchained from inter-company transit location.""" customer_location = self.env.ref('stock.stock_location_customers') supplier_location = self.env.ref('stock.stock_location_suppliers') intercom_location = self.env.ref('stock.stock_location_inter_wh') intercom_location.write({'active': True}) partner = self.env['res.partner'].create({'name': 'Deco Addict'}) self.warehouse_a.resupply_wh_ids = [(6, 0, [self.warehouse_b.id])] resupply_route = self.env['stock.location.route'].search([ ('supplier_wh_id', '=', self.warehouse_b.id), ('supplied_wh_id', '=', self.warehouse_a.id) ]) self.assertTrue(resupply_route, "Resupply route not found") product_lot = self.env['product.product'].create({ 'type': 'product', 'tracking': 'lot', 'name': 'product lot', 'route_ids': [(4, resupply_route.id), (4, self.env.ref('stock.route_warehouse0_mto').id)], }) move_sup_to_whb = self.env['stock.move'].create({ 'company_id': self.company_b.id, 'name': 'from_supplier_to_whb', 'location_id': supplier_location.id, 'location_dest_id': self.warehouse_b.lot_stock_id.id, 'product_id': product_lot.id, 'product_uom': product_lot.uom_id.id, 'product_uom_qty': 1.0, 'picking_type_id': self.warehouse_b.in_type_id.id, }) move_sup_to_whb._action_confirm() move_line_1 = move_sup_to_whb.move_line_ids[0] move_line_1.lot_name = 'lot b' move_line_1.qty_done = 1.0 move_sup_to_whb._action_done() lot_b = move_line_1.lot_id picking_out = self.env['stock.picking'].create({ 'company_id': self.company_a.id, 'partner_id': partner.id, 'picking_type_id': self.warehouse_a.out_type_id.id, 'location_id': self.stock_location_a.id, 'location_dest_id': customer_location.id, }) move_wha_to_cus = self.env['stock.move'].create({ 'name': "WH_A to Customer", 'product_id': product_lot.id, 'product_uom_qty': 1, 'product_uom': product_lot.uom_id.id, 'picking_id': picking_out.id, 'location_id': self.stock_location_a.id, 'location_dest_id': customer_location.id, 'warehouse_id': self.warehouse_a.id, 'procure_method': 'make_to_order', 'company_id': self.company_a.id, }) picking_out.action_confirm() move_whb_to_transit = self.env['stock.move'].search([ ('location_id', '=', self.stock_location_b.id), ('product_id', '=', product_lot.id) ]) move_transit_to_wha = self.env['stock.move'].search([ ('location_id', '=', intercom_location.id), ('product_id', '=', product_lot.id) ]) self.assertTrue(move_whb_to_transit, "No move created by pull rule") self.assertTrue(move_transit_to_wha, "No move created by pull rule") self.assertTrue(move_wha_to_cus in move_transit_to_wha.move_dest_ids, "Moves are not chained") self.assertFalse( move_transit_to_wha in move_whb_to_transit.move_dest_ids, "Chained move created in transit location") self.assertEqual(move_wha_to_cus.state, "waiting") self.assertEqual(move_transit_to_wha.state, "waiting") self.assertEqual(move_whb_to_transit.state, "confirmed") (move_wha_to_cus + move_whb_to_transit + move_transit_to_wha).picking_id.action_assign() self.assertEqual(move_wha_to_cus.state, "waiting") self.assertEqual(move_transit_to_wha.state, "assigned") self.assertEqual(move_whb_to_transit.state, "assigned") res_dict = move_whb_to_transit.picking_id.button_validate() self.assertEqual(res_dict.get('res_model'), 'stock.immediate.transfer') wizard = Form(self.env[res_dict['res_model']].with_context( res_dict['context'])).save() wizard.process() self.assertEqual( self.env['stock.quant']._get_available_quantity( product_lot, intercom_location, lot_b), 1.0) with self.assertRaises(UserError): move_transit_to_wha.picking_id.button_validate() move_line_2 = move_transit_to_wha.move_line_ids[0] move_line_2.lot_name = 'lot a' move_line_2.qty_done = 1.0 move_transit_to_wha._action_done() lot_a = move_line_2.lot_id move_wha_to_cus._action_assign() self.assertEqual(move_wha_to_cus.state, "assigned") res_dict = move_wha_to_cus.picking_id.button_validate() self.assertEqual(res_dict.get('res_model'), 'stock.immediate.transfer') wizard = Form(self.env[res_dict['res_model']].with_context( res_dict['context'])).save() wizard.process() self.assertEqual( self.env['stock.quant']._get_available_quantity( product_lot, customer_location, lot_a), 1.0) self.assertEqual(lot_a.company_id, self.company_a) self.assertEqual(lot_a.name, 'lot a') self.assertEqual(lot_b.company_id, self.company_b) self.assertEqual(lot_b.name, 'lot b')
def _import_fattura_pa(self, tree, invoice): """ Decodes a fattura_pa invoice into an invoice. :param tree: the fattura_pa tree to decode. :param invoice: the invoice to update or an empty recordset. :returns: the invoice where the fattura_pa data was imported. """ invoices = self.env['account.move'] first_run = True # possible to have multiple invoices in the case of an invoice batch, the batch itself is repeated for every invoice of the batch for body_tree in tree.xpath('//FatturaElettronicaBody'): if not first_run or not invoice: # make sure all the iterations create a new invoice record (except the first which could have already created one) invoice = self.env['account.move'] first_run = False # Type must be present in the context to get the right behavior of the _default_journal method (account.move). # journal_id must be present in the context to get the right behavior of the _default_account method (account.move.line). elements = tree.xpath('//CessionarioCommittente//IdCodice') company = elements and self.env['res.company'].search( [('vat', 'ilike', elements[0].text)], limit=1) if not company: elements = tree.xpath( '//CessionarioCommittente//CodiceFiscale') company = elements and self.env['res.company'].search( [('l10n_it_codice_fiscale', 'ilike', elements[0].text)], limit=1) if not company: # Only invoices with a correct VAT or Codice Fiscale can be imported _logger.warning( 'No company found with VAT or Codice Fiscale like %r.', elements[0].text) continue # Refund type. # TD01 == invoice # TD02 == advance/down payment on invoice # TD03 == advance/down payment on fee # TD04 == credit note # TD05 == debit note # TD06 == fee # For unsupported document types, just assume in_invoice, and log that the type is unsupported elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento') move_type = 'in_invoice' if elements and elements[0].text and elements[0].text == 'TD04': move_type = 'in_refund' elif elements and elements[0].text and elements[0].text != 'TD01': _logger.info( 'Document type not managed: %s. Invoice type is set by default.', elements[0].text) # Setup the context for the Invoice Form invoice_ctx = invoice.with_company(company) \ .with_context(default_move_type=move_type) # move could be a single record (editing) or be empty (new). with Form(invoice_ctx) as invoice_form: message_to_log = [] # Partner (first step to avoid warning 'Warning! You must first select a partner.'). <1.2> elements = tree.xpath('//CedentePrestatore//IdCodice') partner = elements and self.env['res.partner'].search([ '&', ('vat', 'ilike', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False) ], limit=1) if not partner: elements = tree.xpath('//CedentePrestatore//CodiceFiscale') if elements: codice = elements[0].text domains = [[('l10n_it_codice_fiscale', '=', codice)]] if re.match(r'^[0-9]{11}$', codice): domains.append([('l10n_it_codice_fiscale', '=', 'IT' + codice)]) elif re.match(r'^IT[0-9]{11}$', codice): domains.append([ ('l10n_it_codice_fiscale', '=', self.env['res.partner']. _l10n_it_normalize_codice_fiscale(codice)) ]) partner = elements and self.env['res.partner'].search( AND([ OR(domains), OR([[('company_id', '=', company.id)], [('company_id', '=', False)]]) ]), limit=1) if not partner: elements = tree.xpath('//DatiTrasmissione//Email') partner = elements and self.env['res.partner'].search( [ '&', '|', ('email', '=', elements[0].text), ('l10n_it_pec_email', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False) ], limit=1) if partner: invoice_form.partner_id = partner else: message_to_log.append("%s<br/>%s" % ( _("Vendor not found, useful informations from XML file:" ), invoice._compose_info_message(tree, './/CedentePrestatore'))) # Numbering attributed by the transmitter. <1.1.2> elements = tree.xpath('//ProgressivoInvio') if elements: invoice_form.payment_reference = elements[0].text elements = body_tree.xpath('.//DatiGeneraliDocumento//Numero') if elements: invoice_form.ref = elements[0].text # Currency. <2.1.1.2> elements = body_tree.xpath('.//DatiGeneraliDocumento/Divisa') if elements: currency_str = elements[0].text currency = self.env.ref('base.%s' % currency_str.upper(), raise_if_not_found=False) if currency != self.env.company.currency_id and currency.active: invoice_form.currency_id = currency # Date. <2.1.1.3> elements = body_tree.xpath('.//DatiGeneraliDocumento/Data') if elements: date_str = elements[0].text date_obj = datetime.strptime( date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT) invoice_form.invoice_date = date_obj # Dati Bollo. <2.1.1.6> elements = body_tree.xpath( './/DatiGeneraliDocumento/DatiBollo/ImportoBollo') if elements: invoice_form.l10n_it_stamp_duty = float(elements[0].text) # List of all amount discount (will be add after all article to avoid to have a negative sum) discount_list = [] percentage_global_discount = 1.0 # Global discount. <2.1.1.8> discount_elements = body_tree.xpath( './/DatiGeneraliDocumento/ScontoMaggiorazione') total_discount_amount = 0.0 if discount_elements: for discount_element in discount_elements: discount_line = discount_element.xpath('.//Tipo') discount_sign = -1 if discount_line and discount_line[0].text == 'SC': discount_sign = 1 discount_percentage = discount_element.xpath( './/Percentuale') if discount_percentage and discount_percentage[0].text: percentage_global_discount *= 1 - float( discount_percentage[0].text ) / 100 * discount_sign discount_amount_text = discount_element.xpath( './/Importo') if discount_amount_text and discount_amount_text[ 0].text: discount_amount = float(discount_amount_text[0]. text) * discount_sign * -1 discount = {} discount["seq"] = 0 if discount_amount < 0: discount["name"] = _('GLOBAL DISCOUNT') else: discount["name"] = _('GLOBAL EXTRA CHARGE') discount["amount"] = discount_amount discount["tax"] = [] discount_list.append(discount) # Comment. <2.1.1.11> elements = body_tree.xpath('.//DatiGeneraliDocumento//Causale') for element in elements: invoice_form.narration = '%s%s\n' % (invoice_form.narration or '', element.text) # Informations relative to the purchase order, the contract, the agreement, # the reception phase or invoices previously transmitted # <2.1.2> - <2.1.6> for document_type in [ 'DatiOrdineAcquisto', 'DatiContratto', 'DatiConvenzione', 'DatiRicezione', 'DatiFattureCollegate' ]: elements = body_tree.xpath('.//DatiGenerali/' + document_type) if elements: for element in elements: message_to_log.append( "%s %s<br/>%s" % (document_type, _("from XML file:"), invoice._compose_info_message(element, '.'))) # Dati DDT. <2.1.8> elements = body_tree.xpath('.//DatiGenerali/DatiDDT') if elements: message_to_log.append( "%s<br/>%s" % (_("Transport informations from XML file:"), invoice._compose_info_message( body_tree, './/DatiGenerali/DatiDDT'))) # Due date. <2.4.2.5> elements = body_tree.xpath( './/DatiPagamento/DettaglioPagamento/DataScadenzaPagamento' ) if elements: date_str = elements[0].text date_obj = datetime.strptime( date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT) invoice_form.invoice_date_due = fields.Date.to_string( date_obj) # Total amount. <2.4.2.6> elements = body_tree.xpath('.//ImportoPagamento') amount_total_import = 0 for element in elements: amount_total_import += float(element.text) if amount_total_import: message_to_log.append( _("Total amount from the XML File: %s") % (amount_total_import)) # Bank account. <2.4.2.13> if invoice_form.move_type not in ('out_invoice', 'in_refund'): elements = body_tree.xpath( './/DatiPagamento/DettaglioPagamento/IBAN') if elements: if invoice_form.partner_id and invoice_form.partner_id.commercial_partner_id: bank = self.env['res.partner.bank'].search([ ('acc_number', '=', elements[0].text), ('partner_id.id', '=', invoice_form.partner_id. commercial_partner_id.id) ]) else: bank = self.env['res.partner.bank'].search([ ('acc_number', '=', elements[0].text) ]) if bank: invoice_form.partner_bank_id = bank else: message_to_log.append("%s<br/>%s" % ( _("Bank account not found, useful informations from XML file:" ), invoice._compose_multi_info_message( body_tree, [ './/DatiPagamento//Beneficiario', './/DatiPagamento//IstitutoFinanziario', './/DatiPagamento//IBAN', './/DatiPagamento//ABI', './/DatiPagamento//CAB', './/DatiPagamento//BIC', './/DatiPagamento//ModalitaPagamento' ]))) else: elements = body_tree.xpath( './/DatiPagamento/DettaglioPagamento') if elements: message_to_log.append("%s<br/>%s" % ( _("Bank account not found, useful informations from XML file:" ), invoice._compose_info_message( body_tree, './/DatiPagamento'))) # Invoice lines. <2.2.1> elements = body_tree.xpath('.//DettaglioLinee') if elements: for element in elements: with invoice_form.invoice_line_ids.new( ) as invoice_line_form: # Sequence. line_elements = element.xpath('.//NumeroLinea') if line_elements: invoice_line_form.sequence = int( line_elements[0].text) * 2 # Product. line_elements = element.xpath('.//Descrizione') if line_elements: invoice_line_form.name = " ".join( line_elements[0].text.split()) elements_code = element.xpath('.//CodiceArticolo') if elements_code: for element_code in elements_code: type_code = element_code.xpath( './/CodiceTipo')[0] code = element_code.xpath( './/CodiceValore')[0] if type_code.text == 'EAN': product = self.env[ 'product.product'].search([ ('barcode', '=', code.text) ]) if product: invoice_line_form.product_id = product break if partner: product_supplier = self.env[ 'product.supplierinfo'].search([ ('name', '=', partner.id), ('product_code', '=', code.text) ]) if product_supplier and product_supplier.product_id: invoice_line_form.product_id = product_supplier.product_id break if not invoice_line_form.product_id: for element_code in elements_code: code = element_code.xpath( './/CodiceValore')[0] product = self.env[ 'product.product'].search( [('default_code', '=', code.text)], limit=1) if product: invoice_line_form.product_id = product break # Price Unit. line_elements = element.xpath('.//PrezzoUnitario') if line_elements: invoice_line_form.price_unit = float( line_elements[0].text) # Quantity. line_elements = element.xpath('.//Quantita') if line_elements: invoice_line_form.quantity = float( line_elements[0].text) else: invoice_line_form.quantity = 1 # Taxes tax_element = element.xpath('.//AliquotaIVA') natura_element = element.xpath('.//Natura') invoice_line_form.tax_ids.clear() if tax_element and tax_element[0].text: percentage = float(tax_element[0].text) if natura_element and natura_element[0].text: l10n_it_kind_exoneration = natura_element[ 0].text tax = self.env['account.tax'].search( [ ('company_id', '=', invoice_form.company_id.id), ('amount_type', '=', 'percent'), ('type_tax_use', '=', 'purchase'), ('amount', '=', percentage), ('l10n_it_kind_exoneration', '=', l10n_it_kind_exoneration), ], limit=1) else: tax = self.env['account.tax'].search( [ ('company_id', '=', invoice_form.company_id.id), ('amount_type', '=', 'percent'), ('type_tax_use', '=', 'purchase'), ('amount', '=', percentage), ], limit=1) l10n_it_kind_exoneration = '' if tax: invoice_line_form.tax_ids.add(tax) else: if l10n_it_kind_exoneration: message_to_log.append( _("Tax not found with percentage: %s and exoneration %s for the article: %s" ) % (percentage, l10n_it_kind_exoneration, invoice_line_form.name)) else: message_to_log.append( _("Tax not found with percentage: %s for the article: %s" ) % (percentage, invoice_line_form.name)) # Discount in cascade mode. # if 3 discounts : -10% -50€ -20% # the result must be : (((price -10%)-50€) -20%) # Generic form : (((price -P1%)-A1€) -P2%) # It will be split in two parts: fix amount and pourcent amount # example: (((((price - A1€) -P2%) -A3€) -A4€) -P5€) # pourcent: 1-(1-P2)*(1-P5) # fix amount: A1*(1-P2)*(1-P5)+A3*(1-P5)+A4*(1-P5) (we must take account of all # percentage present after the fix amount) line_elements = element.xpath( './/ScontoMaggiorazione') total_discount_amount = 0.0 total_discount_percentage = percentage_global_discount if line_elements: for line_element in line_elements: discount_line = line_element.xpath( './/Tipo') discount_sign = -1 if discount_line and discount_line[ 0].text == 'SC': discount_sign = 1 discount_percentage = line_element.xpath( './/Percentuale') if discount_percentage and discount_percentage[ 0].text: pourcentage_actual = 1 - float( discount_percentage[0].text ) / 100 * discount_sign total_discount_percentage *= pourcentage_actual total_discount_amount *= pourcentage_actual discount_amount = line_element.xpath( './/Importo') if discount_amount and discount_amount[ 0].text: total_discount_amount += float( discount_amount[0].text ) * discount_sign * -1 # Save amount discount. if total_discount_amount != 0: discount = {} discount[ "seq"] = invoice_line_form.sequence + 1 if total_discount_amount < 0: discount["name"] = _( 'DISCOUNT: %s', invoice_line_form.name) else: discount["name"] = _( 'EXTRA CHARGE: %s', invoice_line_form.name) discount["amount"] = total_discount_amount discount["tax"] = [] for tax in invoice_line_form.tax_ids: discount["tax"].append(tax) discount_list.append(discount) invoice_line_form.discount = ( 1 - total_discount_percentage) * 100 # Apply amount discount. for discount in discount_list: with invoice_form.invoice_line_ids.new( ) as invoice_line_form_discount: invoice_line_form_discount.tax_ids.clear() invoice_line_form_discount.sequence = discount["seq"] invoice_line_form_discount.name = discount["name"] invoice_line_form_discount.price_unit = discount[ "amount"] new_invoice = invoice_form.save() new_invoice.l10n_it_send_state = "other" elements = body_tree.xpath('.//Allegati') if elements: for element in elements: name_attachment = element.xpath( './/NomeAttachment')[0].text attachment_64 = str.encode( element.xpath('.//Attachment')[0].text) attachment_64 = self.env['ir.attachment'].create({ 'name': name_attachment, 'datas': attachment_64, 'type': 'binary', }) # default_res_id is had to context to avoid facturx to import his content # no_new_invoice to prevent from looping on the message_post that would create a new invoice without it new_invoice.with_context( no_new_invoice=True, default_res_id=new_invoice.id).message_post( body=(_("Attachment from XML")), attachment_ids=[attachment_64.id]) for message in message_to_log: new_invoice.message_post(body=message) invoices += new_invoice return invoices
def _create_other_currency_config(cls): (cls.other_currency.rate_ids | cls.company_currency.rate_ids).unlink() cls.env['res.currency.rate'].create({ 'rate': 0.5, 'currency_id': cls.other_currency.id, 'name': datetime.today().date(), }) other_cash_journal = cls.env['account.journal'].create({ 'name': 'Cash Other', 'type': 'cash', 'company_id': cls.company.id, 'code': 'CSHO', 'sequence': 10, 'currency_id': cls.other_currency.id }) other_invoice_journal = cls.env['account.journal'].create({ 'name': 'Customer Invoice Other', 'type': 'sale', 'company_id': cls.company.id, 'code': 'INVO', 'sequence': 11, 'currency_id': cls.other_currency.id }) other_sales_journal = cls.env['account.journal'].create({ 'name': 'PoS Sale Other', 'type': 'sale', 'code': 'POSO', 'company_id': cls.company.id, 'sequence': 12, 'currency_id': cls.other_currency.id }) other_pricelist = cls.env['product.pricelist'].create({ 'name': 'Public Pricelist Other', 'currency_id': cls.other_currency.id, }) other_cash_payment_method = cls.env['pos.payment.method'].create({ 'name': 'Cash Other', 'receivable_account_id': cls.pos_receivable_account.id, 'is_cash_count': True, 'cash_journal_id': other_cash_journal.id, }) other_bank_payment_method = cls.env['pos.payment.method'].create({ 'name': 'Bank Other', 'receivable_account_id': cls.pos_receivable_account.id, }) new_config = Form(cls.env['pos.config']) new_config.name = 'Shop Other' new_config.invoice_journal_id = other_invoice_journal new_config.journal_id = other_sales_journal new_config.use_pricelist = True new_config.available_pricelist_ids.clear() new_config.available_pricelist_ids.add(other_pricelist) new_config.pricelist_id = other_pricelist new_config.payment_method_ids.clear() new_config.payment_method_ids.add(other_cash_payment_method) new_config.payment_method_ids.add(other_bank_payment_method) config = new_config.save() return config
def _create_taxes(cls): """ Create taxes tax7: 7%, excluded in product price tax10: 10%, included in product price tax21: 21%, included in product price """ def create_tag(name): return cls.env['account.account.tag'].create({ 'name': name, 'applicability': 'taxes', 'country_id': cls.env.company.country_id.id }) cls.tax_tag_invoice_base = create_tag('Invoice Base tag') cls.tax_tag_invoice_tax = create_tag('Invoice Tax tag') cls.tax_tag_refund_base = create_tag('Refund Base tag') cls.tax_tag_refund_tax = create_tag('Refund Tax tag') def create_tax(percentage, price_include=False): return cls.env['account.tax'].create({ 'name': f'Tax {percentage}%', 'amount': percentage, 'price_include': price_include, 'include_base_amount': False, 'invoice_repartition_line_ids': [ (0, 0, { 'factor_percent': 100, 'repartition_type': 'base', 'tag_ids': [(6, 0, cls.tax_tag_invoice_base.ids)], }), (0, 0, { 'factor_percent': 100, 'repartition_type': 'tax', 'account_id': cls.tax_received_account.id, 'tag_ids': [(6, 0, cls.tax_tag_invoice_tax.ids)], }), ], 'refund_repartition_line_ids': [ (0, 0, { 'factor_percent': 100, 'repartition_type': 'base', 'tag_ids': [(6, 0, cls.tax_tag_refund_base.ids)], }), (0, 0, { 'factor_percent': 100, 'repartition_type': 'tax', 'account_id': cls.tax_received_account.id, 'tag_ids': [(6, 0, cls.tax_tag_refund_tax.ids)], }), ], }) tax7 = create_tax(7, price_include=False) tax10 = create_tax(10, price_include=True) tax21 = create_tax(21, price_include=True) tax_group_7_10 = tax7.copy() with Form(tax_group_7_10) as tax: tax.name = 'Tax 7+10%' tax.amount_type = 'group' tax.children_tax_ids.add(tax7) tax.children_tax_ids.add(tax10) return { 'tax7': tax7, 'tax10': tax10, 'tax21': tax21, 'tax_group_7_10': tax_group_7_10 }
def test_crm_lead_partner_sync_email_phone(self): """ Specifically test synchronize between a lead and its partner about phone and email fields. Phone especially has some corner cases due to automatic formatting (notably with onchange in form view). """ lead, partner = self.lead_1.with_user(self.env.user), self.contact_2 lead_form = Form(lead) # reset partner phone to a local number and prepare formatted / sanitized values partner_phone, partner_mobile = self.test_phone_data[ 2], self.test_phone_data[1] partner_phone_formatted = phone_format(partner_phone, 'US', '1') partner_phone_sanitized = phone_format(partner_phone, 'US', '1', force_format='E164') partner_mobile_formatted = phone_format(partner_mobile, 'US', '1') partner_mobile_sanitized = phone_format(partner_mobile, 'US', '1', force_format='E164') partner_email, partner_email_normalized = self.test_email_data[ 2], self.test_email_data_normalized[2] self.assertEqual(partner_phone_formatted, '+1 202-555-0888') self.assertEqual(partner_phone_sanitized, self.test_phone_data_sanitized[2]) self.assertEqual(partner_mobile_formatted, '+1 202-555-0999') self.assertEqual(partner_mobile_sanitized, self.test_phone_data_sanitized[1]) # ensure initial data self.assertEqual(partner.phone, partner_phone) self.assertEqual(partner.mobile, partner_mobile) self.assertEqual(partner.email, partner_email) # LEAD/PARTNER SYNC: email and phone are propagated to lead # as well as mobile (who does not trigger the reverse sync) lead_form.partner_id = partner self.assertEqual(lead_form.email_from, partner_email) self.assertEqual(lead_form.phone, partner_phone_formatted, 'Lead: form automatically formats numbers') self.assertEqual(lead_form.mobile, partner_mobile_formatted, 'Lead: form automatically formats numbers') self.assertFalse(lead_form.ribbon_message) lead_form.save() self.assertEqual(partner.phone, partner_phone, 'Lead / Partner: partner values sent to lead') self.assertEqual(lead.email_from, partner_email, 'Lead / Partner: partner values sent to lead') self.assertEqual( lead.email_normalized, partner_email_normalized, 'Lead / Partner: equal emails should lead to equal normalized emails' ) self.assertEqual( lead.phone, partner_phone_formatted, 'Lead / Partner: partner values (formatted) sent to lead') self.assertEqual( lead.mobile, partner_mobile_formatted, 'Lead / Partner: partner values (formatted) sent to lead') self.assertEqual(lead.phone_sanitized, partner_mobile_sanitized, 'Lead: phone_sanitized computed field on mobile') # for email_from, if only formatting differs, warning ribbon should # not appear and email on partner should not be updated lead_form.email_from = '"Hermes Conrad" <%s>' % partner_email_normalized self.assertFalse(lead_form.ribbon_message) lead_form.save() self.assertEqual(lead_form.partner_id.email, partner_email) # LEAD/PARTNER SYNC: lead updates partner new_email = '"John Zoidberg" <*****@*****.**>' new_email_normalized = '*****@*****.**' lead_form.email_from = new_email self.assertIn('the customer email will', lead_form.ribbon_message) new_phone = '+1 202 555 7799' new_phone_formatted = phone_format(new_phone, 'US', '1') lead_form.phone = new_phone self.assertEqual(lead_form.phone, new_phone_formatted) self.assertIn('the customer email and phone number will', lead_form.ribbon_message) lead_form.save() self.assertEqual(partner.email, new_email) self.assertEqual(partner.email_normalized, new_email_normalized) self.assertEqual(partner.phone, new_phone_formatted) # LEAD/PARTNER SYNC: mobile does not update partner new_mobile = '+1 202 555 6543' new_mobile_formatted = phone_format(new_mobile, 'US', '1') lead_form.mobile = new_mobile lead_form.save() self.assertEqual(lead.mobile, new_mobile_formatted) self.assertEqual(partner.mobile, partner_mobile) # LEAD/PARTNER SYNC: reseting lead values also resets partner for email # and phone, but not for mobile lead_form.email_from, lead_form.phone, lead.mobile = False, False, False self.assertIn('the customer email and phone number will', lead_form.ribbon_message) lead_form.save() self.assertFalse(partner.email) self.assertFalse(partner.email_normalized) self.assertFalse(partner.phone) self.assertFalse(lead.phone) self.assertFalse(lead.mobile) self.assertFalse(lead.phone_sanitized) self.assertEqual(partner.mobile, partner_mobile) self.assertEqual(partner.phone_sanitized, partner_mobile_sanitized, 'Partner sanitized should be computed on mobile')
def test_crm_lead_partner_sync_email_phone_corner_cases(self): """ Test corner cases of email and phone sync (False versus '', formatting differences, wrong input, ...) """ test_email = '*****@*****.**' lead = self.lead_1.with_user(self.env.user) contact = self.env['res.partner'].create({ 'name': 'NoContact Partner', 'phone': '', 'email': '', 'mobile': '', }) lead_form = Form(lead) self.assertEqual(lead_form.email_from, test_email) self.assertFalse(lead_form.ribbon_message) # email: False versus empty string lead_form.partner_id = contact self.assertIn('the customer email', lead_form.ribbon_message) lead_form.email_from = '' self.assertFalse(lead_form.ribbon_message) lead_form.email_from = False self.assertFalse(lead_form.ribbon_message) # phone: False versus empty string lead_form.phone = '+1 202-555-0888' self.assertIn('the customer phone', lead_form.ribbon_message) lead_form.phone = '' self.assertFalse(lead_form.ribbon_message) lead_form.phone = False self.assertFalse(lead_form.ribbon_message) # email/phone: formatting should not trigger ribbon lead.write({ 'email_from': '"My Name" <%s>' % test_email, 'phone': '+1 202-555-0888', }) contact.write({ 'email': '"My Name" <%s>' % test_email, 'phone': '+1 202-555-0888', }) lead_form = Form(lead) self.assertFalse(lead_form.ribbon_message) lead_form.partner_id = contact self.assertFalse(lead_form.ribbon_message) lead_form.email_from = '"Another Name" <%s>' % test_email # same email normalized self.assertFalse(lead_form.ribbon_message, 'Formatting-only change should not trigger write') lead_form.phone = '2025550888' # same number but another format self.assertFalse(lead_form.ribbon_message, 'Formatting-only change should not trigger write') # wrong value are also propagated lead_form.phone = '666 789456789456789456' self.assertIn('the customer phone', lead_form.ribbon_message)
def test_included_tax(self): ''' Test an account.move.line is created automatically when adding a tax. This test uses the following scenario: - Create manually a debit line of 1000 having an included tax. - Assume a line containing the tax amount is created automatically. - Create manually a credit line to balance the two previous lines. - Save the move. included tax = 20% Name | Debit | Credit | Tax_ids | Tax_line_id's name -----------------------|-----------|-----------|---------------|------------------- debit_line_1 | 1000 | | tax | included_tax_line | 200 | | | included_tax_line credit_line_1 | | 1200 | | ''' self.included_percent_tax = self.env['account.tax'].create({ 'name': 'included_tax_line', 'amount_type': 'percent', 'amount': 20, 'price_include': True, 'include_base_amount': False, }) self.account = self.company_data['default_account_revenue'] move_form = Form( self.env['account.move'].with_context(default_move_type='entry')) # Create a new account.move.line with debit amount. with move_form.line_ids.new() as debit_line: debit_line.name = 'debit_line_1' debit_line.account_id = self.account debit_line.debit = 1000 debit_line.tax_ids.clear() debit_line.tax_ids.add(self.included_percent_tax) self.assertTrue(debit_line.recompute_tax_line) # Create a third account.move.line with credit amount. with move_form.line_ids.new() as credit_line: credit_line.name = 'credit_line_1' credit_line.account_id = self.account credit_line.credit = 1200 move = move_form.save() self.assertRecordValues(move.line_ids, [ { 'name': 'debit_line_1', 'debit': 1000.0, 'credit': 0.0, 'tax_ids': [self.included_percent_tax.id], 'tax_line_id': False }, { 'name': 'included_tax_line', 'debit': 200.0, 'credit': 0.0, 'tax_ids': [], 'tax_line_id': self.included_percent_tax.id }, { 'name': 'credit_line_1', 'debit': 0.0, 'credit': 1200.0, 'tax_ids': [], 'tax_line_id': False }, ])
def test_misc_move_onchange(self): ''' Test the behavior on onchanges for account.move having 'entry' as type. ''' move_form = Form(self.env['account.move']) # Rate 1:3 move_form.date = fields.Date.from_string('2016-01-01') # New line that should get 400.0 as debit. with move_form.line_ids.new() as line_form: line_form.name = 'debit_line' line_form.account_id = self.company_data['default_account_revenue'] line_form.currency_id = self.currency_data['currency'] line_form.amount_currency = 1200.0 # New line that should get 400.0 as credit. with move_form.line_ids.new() as line_form: line_form.name = 'credit_line' line_form.account_id = self.company_data['default_account_revenue'] line_form.currency_id = self.currency_data['currency'] line_form.amount_currency = -1200.0 move = move_form.save() self.assertRecordValues( move.line_ids.sorted('debit'), [ { 'currency_id': self.currency_data['currency'].id, 'amount_currency': -1200.0, 'debit': 0.0, 'credit': 400.0, }, { 'currency_id': self.currency_data['currency'].id, 'amount_currency': 1200.0, 'debit': 400.0, 'credit': 0.0, }, ], ) # === Change the date to change the currency conversion's rate === with Form(move) as move_form: move_form.date = fields.Date.from_string('2017-01-01') self.assertRecordValues( move.line_ids.sorted('debit'), [ { 'currency_id': self.currency_data['currency'].id, 'amount_currency': -1200.0, 'debit': 0.0, 'credit': 600.0, }, { 'currency_id': self.currency_data['currency'].id, 'amount_currency': 1200.0, 'debit': 600.0, 'credit': 0.0, }, ], )
def test_recurrence_cron_repeat_after(self): domain = [('project_id', '=', self.project_recurring.id)] with freeze_time("2020-01-01"): form = Form(self.env['project.task']) form.name = 'test recurring task' form.description = 'my super recurring task bla bla bla' form.project_id = self.project_recurring form.date_deadline = datetime(2020, 2, 1) form.recurring_task = True form.repeat_interval = 1 form.repeat_unit = 'month' form.repeat_type = 'after' form.repeat_number = 2 form.repeat_on_month = 'date' form.repeat_day = '15' task = form.save() task.planned_hours = 2 self.assertEqual(task.recurrence_id.next_recurrence_date, date(2020, 1, 15)) self.assertEqual(self.env['project.task'].search_count(domain), 1) self.env['project.task.recurrence']._cron_create_recurring_tasks() self.assertEqual(self.env['project.task'].search_count(domain), 1, 'no extra task should be created') self.assertEqual(task.recurrence_id.recurrence_left, 2) with freeze_time("2020-01-15"): self.assertEqual(self.env['project.task'].search_count(domain), 1) self.env['project.task.recurrence']._cron_create_recurring_tasks() self.assertEqual(self.env['project.task'].search_count(domain), 2) self.assertEqual(task.recurrence_id.recurrence_left, 1) with freeze_time("2020-02-15"): self.env['project.task.recurrence']._cron_create_recurring_tasks() self.assertEqual(self.env['project.task'].search_count(domain), 3) self.assertEqual(task.recurrence_id.recurrence_left, 0) self.env['project.task.recurrence']._cron_create_recurring_tasks() self.assertEqual(self.env['project.task'].search_count(domain), 3) self.assertEqual(task.recurrence_id.recurrence_left, 0) tasks = self.env['project.task'].search(domain) self.assertEqual(len(tasks), 3) self.assertTrue(bool(tasks[2].date_deadline)) self.assertFalse(tasks[1].date_deadline, "Deadline should not be copied") for f in self.env['project.task.recurrence']._get_recurring_fields(): self.assertTrue(tasks[0][f] == tasks[1][f] == tasks[2][f], "Field %s should have been copied" % f)
def test_01_product_level_delay(self): """ To check schedule dates of multiple purchase order line of the same purchase order, we create two procurements for the two different product with same vendor and different Delivery Lead Time.""" company = self.env.ref('base.main_company') company.write({'po_lead': 0.00}) # Make procurement request from product_1's form view, create procurement and check it's state date_planned1 = fields.Datetime.to_string(fields.datetime.now() + timedelta(days=10)) self._create_make_procurement(self.product_1, 10.00, date_planned=date_planned1) purchase1 = self.env['purchase.order.line'].search( [('product_id', '=', self.product_1.id)], limit=1).order_id # Make procurement request from product_2's form view, create procurement and check it's state date_planned2 = fields.Datetime.to_string(fields.datetime.now() + timedelta(days=10)) self._create_make_procurement(self.product_2, 5.00, date_planned=date_planned2) purchase2 = self.env['purchase.order.line'].search( [('product_id', '=', self.product_2.id)], limit=1).order_id # Check purchase order is same or not self.assertEqual( purchase1, purchase2, 'Purchase orders should be same for the two different product with same vendor.' ) # Confirm purchase order purchase1.button_confirm() # Check order date of purchase order order_line_pro_1 = purchase2.order_line.filtered( lambda r: r.product_id == self.product_1) order_line_pro_2 = purchase2.order_line.filtered( lambda r: r.product_id == self.product_2) order_date = fields.Datetime.from_string(date_planned1) - timedelta( days=self.product_1.seller_ids.delay) self.assertEqual( purchase2.date_order, order_date, 'Order date should be equal to: Date of the procurement order - Delivery Lead Time.' ) # Check scheduled date of purchase order line for product_1 schedule_date_1 = datetime.combine( order_date + timedelta(days=self.product_1.seller_ids.delay), time.min).replace(tzinfo=None, hour=12) self.assertEqual( order_line_pro_1.date_planned, schedule_date_1, 'Schedule date of purchase order line for product_1 should be equal to: Order date of purchase order + Delivery Lead Time of product_1.' ) # Check scheduled date of purchase order line for product_2 schedule_date_2 = datetime.combine( order_date + timedelta(days=self.product_2.seller_ids.delay), time.min).replace(tzinfo=None, hour=12) self.assertEqual( order_line_pro_2.date_planned, schedule_date_2, 'Schedule date of purchase order line for product_2 should be equal to: Order date of purchase order + Delivery Lead Time of product_2.' ) # Check scheduled date of purchase order po_schedule_date = min(schedule_date_1, schedule_date_2) self.assertEqual( purchase2.order_line[1].date_planned, po_schedule_date, 'Schedule date of purchase order should be minimum of schedule dates of purchase order lines.' ) # Check the picking created or not self.assertTrue(purchase2.picking_ids, "Picking should be created.") # Check scheduled date of In Type shipment self.assertEqual( purchase2.picking_ids.scheduled_date, po_schedule_date, 'Schedule date of In type shipment should be same as schedule date of purchase order.' ) # Check deadline of pickings self.assertEqual( purchase2.picking_ids.date_deadline, purchase2.date_planned, "Deadline of pickings should be equals to the receipt date of purchase" ) purchase_form = Form(purchase2) purchase_form.date_planned = purchase2.date_planned + timedelta(days=2) purchase_form.save() self.assertEqual(purchase2.picking_ids.date_deadline, purchase2.date_planned, "Deadline of pickings should be propagate")
def test_01_sale_mrp_kit_qty_delivered(self): """ Test that the quantities delivered are correct when a kit with subkits is ordered with multiple backorders and returns """ # 'kit_parent' structure: # --------------------------- # # kit_parent --|- kit_2 x2 --|- component_d x1 # | |- kit_1 x2 -------|- component_a x2 # | |- component_b x1 # | |- component_c x3 # | # |- kit_3 x1 --|- component_f x1 # | |- component_g x2 # | # |- component_e x1 # Creation of a sale order for x7 kit_parent partner = self.env['res.partner'].create({'name': 'My Test Partner'}) f = Form(self.env['purchase.order']) f.partner_id = partner with f.order_line.new() as line: line.product_id = self.kit_parent line.product_qty = 7.0 line.price_unit = 10 po = f.save() po.button_confirm() # Check picking creation, its move lines should concern # only components. Also checks that the quantities are corresponding # to the PO self.assertEqual(len(po.picking_ids), 1) order_line = po.order_line[0] picking_original = po.picking_ids[0] move_lines = picking_original.move_lines products = move_lines.mapped('product_id') kits = [self.kit_parent, self.kit_3, self.kit_2, self.kit_1] components = [self.component_a, self.component_b, self.component_c, self.component_d, self.component_e, self.component_f, self.component_g] expected_quantities = { self.component_a: 56.0, self.component_b: 28.0, self.component_c: 84.0, self.component_d: 14.0, self.component_e: 7.0, self.component_f: 14.0, self.component_g: 28.0 } self.assertEqual(len(move_lines), 7) self.assertTrue(not any(kit in products for kit in kits)) self.assertTrue(all(component in products for component in components)) self._assert_quantities(move_lines, expected_quantities) # Process only 7 units of each component qty_to_process = 7 move_lines.write({'quantity_done': qty_to_process}) # Create a backorder for the missing componenents pick = po.picking_ids[0] res = pick.button_validate() Form(self.env[res['res_model']].with_context(res['context'])).save().process() # Check that a backorded is created self.assertEqual(len(po.picking_ids), 2) backorder_1 = po.picking_ids - picking_original self.assertEqual(backorder_1.backorder_id.id, picking_original.id) # Even if some components are received completely, # no KitParent should be received self.assertEqual(order_line.qty_received, 0) # Process just enough components to make 1 kit_parent qty_to_process = { self.component_a: 1, self.component_c: 5, } self._process_quantities(backorder_1.move_lines, qty_to_process) # Create a backorder for the missing componenents res = backorder_1.button_validate() Form(self.env[res['res_model']].with_context(res['context'])).save().process() # Only 1 kit_parent should be received at this point self.assertEqual(order_line.qty_received, 1) # Check that the second backorder is created self.assertEqual(len(po.picking_ids), 3) backorder_2 = po.picking_ids - picking_original - backorder_1 self.assertEqual(backorder_2.backorder_id.id, backorder_1.id) # Set the components quantities that backorder_2 should have expected_quantities = { self.component_a: 48, self.component_b: 21, self.component_c: 72, self.component_d: 7, self.component_f: 7, self.component_g: 21 } # Check that the computed quantities are matching the theorical ones. # Since component_e was totally processed, this componenent shouldn't be # present in backorder_2 self.assertEqual(len(backorder_2.move_lines), 6) move_comp_e = backorder_2.move_lines.filtered(lambda m: m.product_id.id == self.component_e.id) self.assertFalse(move_comp_e) self._assert_quantities(backorder_2.move_lines, expected_quantities) # Process enough components to make x3 kit_parents qty_to_process = { self.component_a: 16, self.component_b: 5, self.component_c: 24, self.component_g: 5 } self._process_quantities(backorder_2.move_lines, qty_to_process) # Create a backorder for the missing componenents res = backorder_2.button_validate() Form(self.env[res['res_model']].with_context(res['context'])).save().process() # Check that x3 kit_parents are indeed received self.assertEqual(order_line.qty_received, 3) # Check that the third backorder is created self.assertEqual(len(po.picking_ids), 4) backorder_3 = po.picking_ids - (picking_original + backorder_1 + backorder_2) self.assertEqual(backorder_3.backorder_id.id, backorder_2.id) # Check the components quantities that backorder_3 should have expected_quantities = { self.component_a: 32, self.component_b: 16, self.component_c: 48, self.component_d: 7, self.component_f: 7, self.component_g: 16 } self._assert_quantities(backorder_3.move_lines, expected_quantities) # Process all missing components self._process_quantities(backorder_3.move_lines, expected_quantities) # Validating the last backorder now it's complete. # All kits should be received backorder_3.button_validate() self.assertEqual(order_line.qty_received, 7.0) # Return all components processed by backorder_3 stock_return_picking_form = Form(self.env['stock.return.picking'] .with_context(active_ids=backorder_3.ids, active_id=backorder_3.ids[0], active_model='stock.picking')) return_wiz = stock_return_picking_form.save() for return_move in return_wiz.product_return_moves: return_move.write({ 'quantity': expected_quantities[return_move.product_id], 'to_refund': True }) res = return_wiz.create_returns() return_pick = self.env['stock.picking'].browse(res['res_id']) # Process all components and validate the picking wiz_act = return_pick.button_validate() wiz = Form(self.env[wiz_act['res_model']].with_context(wiz_act['context'])).save() wiz.process() # Now quantity received should be 3 again self.assertEqual(order_line.qty_received, 3) stock_return_picking_form = Form(self.env['stock.return.picking'] .with_context(active_ids=return_pick.ids, active_id=return_pick.ids[0], active_model='stock.picking')) return_wiz = stock_return_picking_form.save() for move in return_wiz.product_return_moves: move.quantity = expected_quantities[move.product_id] res = return_wiz.create_returns() return_of_return_pick = self.env['stock.picking'].browse(res['res_id']) # Process all components except one of each for move in return_of_return_pick.move_lines: move.write({ 'quantity_done': expected_quantities[move.product_id] - 1, 'to_refund': True }) wiz_act = return_of_return_pick.button_validate() wiz = Form(self.env[wiz_act['res_model']].with_context(wiz_act['context'])).save() wiz.process() # As one of each component is missing, only 6 kit_parents should be received self.assertEqual(order_line.qty_received, 6) # Check that the 4th backorder is created. self.assertEqual(len(po.picking_ids), 7) backorder_4 = po.picking_ids - ( picking_original + backorder_1 + backorder_2 + backorder_3 + return_of_return_pick + return_pick) self.assertEqual(backorder_4.backorder_id.id, return_of_return_pick.id) # Check the components quantities that backorder_4 should have for move in backorder_4.move_lines: self.assertEqual(move.product_qty, 1)
def test_product_1(self): """ As an user of Company A, checks we can or cannot create new product depending of its `company_id`.""" # Creates a new product with no company_id and set a responsible. # The product must be created as there is no company on the product. product_form = Form(self.env['product.template'].with_user( self.user_a)) product_form.name = 'Paramite Pie' product_form.responsible_id = self.user_b product = product_form.save() self.assertEqual(product.company_id.id, False) self.assertEqual(product.responsible_id.id, self.user_b.id) # Creates a new product belong to Company A and set a responsible belong # to Company B. The product mustn't be created as the product and the # user don't belong of the same company. self.user_b.company_ids = [(6, 0, [self.company_b.id])] product_form = Form(self.env['product.template'].with_user( self.user_a)) product_form.name = 'Meech Munchy' product_form.company_id = self.company_a product_form.responsible_id = self.user_b with self.assertRaises(UserError): # Raises an UserError for company incompatibility. product = product_form.save() # Creates a new product belong to Company A and set a responsible belong # to Company A & B (default B). The product must be created as the user # belongs to product's company. self.user_b.company_ids = [(6, 0, [self.company_a.id, self.company_b.id])] product_form = Form(self.env['product.template'].with_user( self.user_a)) product_form.name = 'Scrab Cake' product_form.company_id = self.company_a product_form.responsible_id = self.user_b product = product_form.save() self.assertEqual(product.company_id.id, self.company_a.id) self.assertEqual(product.responsible_id.id, self.user_b.id)
def _import_ubl(self, tree, invoice): """ Decodes an UBL invoice into an invoice. :param tree: the UBL tree to decode. :param invoice: the invoice to update or an empty recordset. :returns: the invoice where the UBL data was imported. """ def _get_ubl_namespaces(): ''' If the namespace is declared with xmlns='...', the namespaces map contains the 'None' key that causes an TypeError: empty namespace prefix is not supported in XPath Then, we need to remap arbitrarily this key. :param tree: An instance of etree. :return: The namespaces map without 'None' key. ''' namespaces = tree.nsmap namespaces['inv'] = namespaces.pop(None) return namespaces namespaces = _get_ubl_namespaces() with Form( invoice.with_context( account_predictive_bills_disable_prediction=True) ) as invoice_form: # Reference elements = tree.xpath('//cbc:ID', namespaces=namespaces) if elements: invoice_form.ref = elements[0].text elements = tree.xpath('//cbc:InstructionID', namespaces=namespaces) if elements: invoice_form.payment_reference = elements[0].text # Dates elements = tree.xpath('//cbc:IssueDate', namespaces=namespaces) if elements: invoice_form.invoice_date = elements[0].text elements = tree.xpath('//cbc:PaymentDueDate', namespaces=namespaces) if elements: invoice_form.invoice_date_due = elements[0].text # allow both cbc:PaymentDueDate and cbc:DueDate elements = tree.xpath('//cbc:DueDate', namespaces=namespaces) invoice_form.invoice_date_due = invoice_form.invoice_date_due or elements and elements[ 0].text # Currency elements = tree.xpath('//cbc:DocumentCurrencyCode', namespaces=namespaces) currency_code = elements and elements[0].text or '' currency = self.env['res.currency'].search( [('name', '=', currency_code.upper())], limit=1) if elements: invoice_form.currency_id = currency # Incoterm elements = tree.xpath( '//cbc:TransportExecutionTerms/cac:DeliveryTerms/cbc:ID', namespaces=namespaces) if elements: invoice_form.invoice_incoterm_id = self.env[ 'account.incoterms'].search( [('code', '=', elements[0].text)], limit=1) # Partner partner_element = tree.xpath( '//cac:AccountingSupplierParty/cac:Party', namespaces=namespaces) if partner_element: domains = [] partner_element = partner_element[0] elements = partner_element.xpath( '//cac:AccountingSupplierParty/cac:Party//cbc:Name', namespaces=namespaces) if elements: partner_name = elements[0].text domains.append([('name', 'ilike', partner_name)]) else: partner_name = '' elements = partner_element.xpath( '//cac:AccountingSupplierParty/cac:Party//cbc:Telephone', namespaces=namespaces) if elements: partner_telephone = elements[0].text domains.append([('phone', '=', partner_telephone), ('mobile', '=', partner_telephone)]) elements = partner_element.xpath( '//cac:AccountingSupplierParty/cac:Party//cbc:ElectronicMail', namespaces=namespaces) if elements: partner_mail = elements[0].text domains.append([('email', '=', partner_mail)]) elements = partner_element.xpath( '//cac:AccountingSupplierParty/cac:Party//cbc:CompanyID', namespaces=namespaces) if elements: partner_id = elements[0].text domains.append([('vat', 'like', partner_id)]) if domains: partner = self.env['res.partner'].search( expression.OR(domains), limit=1) if partner: invoice_form.partner_id = partner partner_name = partner.name else: invoice_form.partner_id = self.env['res.partner'] # Regenerate PDF attachments = self.env['ir.attachment'] elements = tree.xpath('//cac:AdditionalDocumentReference', namespaces=namespaces) for element in elements: attachment_name = element.xpath('cbc:ID', namespaces=namespaces) attachment_data = element.xpath( 'cac:Attachment//cbc:EmbeddedDocumentBinaryObject', namespaces=namespaces) if attachment_name and attachment_data: text = attachment_data[0].text # Normalize the name of the file : some e-fff emitters put the full path of the file # (Windows or Linux style) and/or the name of the xml instead of the pdf. # Get only the filename with a pdf extension. name = PureWindowsPath( attachment_name[0].text).stem + '.pdf' attachments |= self.env['ir.attachment'].create({ 'name': name, 'res_id': invoice.id, 'res_model': 'account.move', 'datas': text + '=' * (len(text) % 3), # Fix incorrect padding 'type': 'binary', 'mimetype': 'application/pdf', }) if attachments: invoice.with_context(no_new_invoice=True).message_post( attachment_ids=attachments.ids) # Lines lines_elements = tree.xpath('//cac:InvoiceLine', namespaces=namespaces) for eline in lines_elements: with invoice_form.invoice_line_ids.new() as invoice_line_form: # Product elements = eline.xpath( 'cac:Item/cac:SellersItemIdentification/cbc:ID', namespaces=namespaces) domains = [] if elements: product_code = elements[0].text domains.append([('default_code', '=', product_code)]) elements = eline.xpath( 'cac:Item/cac:StandardItemIdentification/cbc:ID[@schemeID=\'GTIN\']', namespaces=namespaces) if elements: product_ean13 = elements[0].text domains.append([('barcode', '=', product_ean13)]) if domains: product = self.env['product.product'].search( expression.OR(domains), limit=1) if product: invoice_line_form.product_id = product # Quantity elements = eline.xpath('cbc:InvoicedQuantity', namespaces=namespaces) quantity = elements and float(elements[0].text) or 1.0 invoice_line_form.quantity = quantity # Price Unit elements = eline.xpath('cac:Price/cbc:PriceAmount', namespaces=namespaces) price_unit = elements and float(elements[0].text) or 0.0 elements = eline.xpath('cbc:LineExtensionAmount', namespaces=namespaces) line_extension_amount = elements and float( elements[0].text) or 0.0 invoice_line_form.price_unit = price_unit or line_extension_amount / invoice_line_form.quantity or 0.0 # Name elements = eline.xpath('cac:Item/cbc:Description', namespaces=namespaces) if elements and elements[0].text: invoice_line_form.name = elements[0].text invoice_line_form.name = invoice_line_form.name.replace( '%month%', str( fields.Date.to_date(invoice_form.invoice_date). month)) # TODO: full name in locale invoice_line_form.name = invoice_line_form.name.replace( '%year%', str( fields.Date.to_date( invoice_form.invoice_date).year)) else: invoice_line_form.name = "%s (%s)" % ( partner_name or '', invoice_form.invoice_date) # Taxes taxes_elements = eline.xpath( 'cac:TaxTotal/cac:TaxSubtotal', namespaces=namespaces) invoice_line_form.tax_ids.clear() for etax in taxes_elements: elements = etax.xpath('cbc:Percent', namespaces=namespaces) if elements: tax = self.env['account.tax'].search( [ ('company_id', '=', self.env.company.id), ('amount', '=', float(elements[0].text)), ('type_tax_use', '=', invoice_form.journal_id.type), ], order='sequence ASC', limit=1) if tax: invoice_line_form.tax_ids.add(tax) return invoice_form.save()
def test_journal_sequence_ordering(self): """Entries are correctly sorted when posting multiple at once.""" self.test_move.name = 'XMISC/2016/00001' copies = reduce( (lambda x, y: x + y), [self.create_move(date=self.test_move.date) for i in range(6)]) copies[0].date = '2019-03-05' copies[1].date = '2019-03-06' copies[2].date = '2019-03-07' copies[3].date = '2019-03-04' copies[4].date = '2019-03-05' copies[5].date = '2019-03-05' # that entry is actualy the first one of the period, so it already has a name # set it to '/' so that it is recomputed at post to be ordered correctly. copies[0].name = '/' copies.action_post() # Ordered by date self.assertEqual(copies[0].name, 'XMISC/2019/00002') self.assertEqual(copies[1].name, 'XMISC/2019/00005') self.assertEqual(copies[2].name, 'XMISC/2019/00006') self.assertEqual(copies[3].name, 'XMISC/2019/00001') self.assertEqual(copies[4].name, 'XMISC/2019/00003') self.assertEqual(copies[5].name, 'XMISC/2019/00004') # Can't have twice the same name with self.assertRaises(ValidationError): copies[0].name = 'XMISC/2019/00001' # Lets remove the order by date copies[0].name = 'XMISC/2019/10001' copies[1].name = 'XMISC/2019/10002' copies[2].name = 'XMISC/2019/10003' copies[3].name = 'XMISC/2019/10004' copies[4].name = 'XMISC/2019/10005' copies[5].name = 'XMISC/2019/10006' copies[4].button_draft() copies[4].with_context(force_delete=True).unlink() copies[5].button_draft() wizard = Form( self.env['account.resequence.wizard'].with_context( active_ids=set(copies.ids) - set(copies[4].ids), active_model='account.move'), ) new_values = json.loads(wizard.new_values) self.assertEqual(new_values[str(copies[0].id)]['new_by_date'], 'XMISC/2019/10002') self.assertEqual(new_values[str(copies[0].id)]['new_by_name'], 'XMISC/2019/10001') self.assertEqual(new_values[str(copies[1].id)]['new_by_date'], 'XMISC/2019/10004') self.assertEqual(new_values[str(copies[1].id)]['new_by_name'], 'XMISC/2019/10002') self.assertEqual(new_values[str(copies[2].id)]['new_by_date'], 'XMISC/2019/10005') self.assertEqual(new_values[str(copies[2].id)]['new_by_name'], 'XMISC/2019/10003') self.assertEqual(new_values[str(copies[3].id)]['new_by_date'], 'XMISC/2019/10001') self.assertEqual(new_values[str(copies[3].id)]['new_by_name'], 'XMISC/2019/10004') self.assertEqual(new_values[str(copies[5].id)]['new_by_date'], 'XMISC/2019/10003') self.assertEqual(new_values[str(copies[5].id)]['new_by_name'], 'XMISC/2019/10005') wizard.save().resequence() self.assertEqual(copies[3].state, 'posted') self.assertEqual(copies[5].name, 'XMISC/2019/10005') self.assertEqual(copies[5].state, 'draft')
def test_replenishment_report_1(self): self.product_replenished = self.env['product.product'].create({ 'name': 'Security razor', 'type': 'product', 'categ_id': self.env.ref('product.product_category_all').id, }) # get auto-created pull rule from when warehouse is created self.wh.reception_route_id.rule_ids.unlink() self.env['stock.rule'].create({ 'name': 'Rule Supplier', 'route_id': self.wh.reception_route_id.id, 'location_id': self.wh.lot_stock_id.id, 'location_src_id': self.env.ref('stock.stock_location_suppliers').id, 'action': 'pull', 'delay': 1.0, 'procure_method': 'make_to_stock', 'picking_type_id': self.wh.in_type_id.id, }) delivery_picking = self.env['stock.picking'].create({ 'location_id': self.wh.lot_stock_id.id, 'location_dest_id': self.ref('stock.stock_location_customers'), 'picking_type_id': self.ref('stock.picking_type_out'), }) self.env['stock.move'].create({ 'name': 'Delivery', 'product_id': self.product_replenished.id, 'product_uom_qty': 500.0, 'product_uom': self.uom_unit.id, 'location_id': self.wh.lot_stock_id.id, 'location_dest_id': self.ref('stock.stock_location_customers'), 'picking_id': delivery_picking.id, }) delivery_picking.action_confirm() # Trigger the manual orderpoint creation for missing product self.env['stock.move'].flush() self.env['stock.warehouse.orderpoint'].action_open_orderpoints() orderpoint = self.env['stock.warehouse.orderpoint'].search([ ('product_id', '=', self.product_replenished.id) ]) self.assertTrue(orderpoint) self.assertEqual(orderpoint.location_id, self.wh.lot_stock_id) self.assertEqual(orderpoint.qty_to_order, 500.0) orderpoint.action_replenish() self.env['stock.warehouse.orderpoint'].action_open_orderpoints() move = self.env['stock.move'].search([ ('product_id', '=', self.product_replenished.id), ('location_dest_id', '=', self.wh.lot_stock_id.id) ]) # Simulate a supplier delay move.date = fields.datetime.now() + timedelta(days=1) orderpoint = self.env['stock.warehouse.orderpoint'].search([ ('product_id', '=', self.product_replenished.id) ]) self.assertFalse(orderpoint) orderpoint_form = Form(self.env['stock.warehouse.orderpoint']) orderpoint_form.product_id = self.product_replenished orderpoint_form.location_id = self.wh.lot_stock_id orderpoint = orderpoint_form.save() self.assertEqual(orderpoint.qty_to_order, 0.0) self.env['stock.warehouse.orderpoint'].action_open_orderpoints() self.assertEqual(orderpoint.qty_to_order, 0.0)
def test_subcontracting_account_flow_1(self): self.stock_location = self.env.ref('stock.stock_location_stock') self.customer_location = self.env.ref('stock.stock_location_customers') self.supplier_location = self.env.ref('stock.stock_location_suppliers') self.uom_unit = self.env.ref('uom.product_uom_unit') self.env.ref( 'product.product_category_all').property_cost_method = 'fifo' # IN 10@10 comp1 10@20 comp2 move1 = self.env['stock.move'].create({ 'name': 'IN 10 units @ 10.00 per unit', 'location_id': self.supplier_location.id, 'location_dest_id': self.env.company.subcontracting_location_id.id, 'product_id': self.comp1.id, 'product_uom': self.uom_unit.id, 'product_uom_qty': 10.0, 'price_unit': 10.0, }) move1._action_confirm() move1._action_assign() move1.move_line_ids.qty_done = 10.0 move1._action_done() move2 = self.env['stock.move'].create({ 'name': 'IN 10 units @ 20.00 per unit', 'location_id': self.supplier_location.id, 'location_dest_id': self.env.company.subcontracting_location_id.id, 'product_id': self.comp2.id, 'product_uom': self.uom_unit.id, 'product_uom_qty': 10.0, 'price_unit': 20.0, }) move2._action_confirm() move2._action_assign() move2.move_line_ids.qty_done = 10.0 move2._action_done() picking_form = Form(self.env['stock.picking']) picking_form.picking_type_id = self.env.ref('stock.picking_type_in') picking_form.partner_id = self.subcontractor_partner1 with picking_form.move_ids_without_package.new() as move: move.product_id = self.finished move.product_uom_qty = 1 picking_receipt = picking_form.save() picking_receipt.move_lines.price_unit = 30.0 picking_receipt.action_confirm() picking_receipt.move_lines.quantity_done = 1.0 picking_receipt._action_done() mo = picking_receipt._get_subcontracted_productions() # Finished is made of 1 comp1 and 1 comp2. # Cost of comp1 = 10 # Cost of comp2 = 20 # --> Cost of finished = 10 + 20 = 30 # Additionnal cost = 30 (from the purchase order line or directly set on the stock move here) # Total cost of subcontracting 1 unit of finished = 30 + 30 = 60 self.assertEqual(mo.move_finished_ids.stock_valuation_layer_ids.value, 60) self.assertEqual( picking_receipt.move_lines.stock_valuation_layer_ids.value, 0) self.assertEqual(picking_receipt.move_lines.product_id.value_svl, 60) # Do the same without any additionnal cost picking_receipt = picking_receipt.copy() picking_receipt.move_lines.price_unit = 0 picking_receipt.action_confirm() picking_receipt.move_lines.quantity_done = 1.0 picking_receipt._action_done() mo = picking_receipt._get_subcontracted_productions() # In this case, since there isn't any additionnal cost, the total cost of the subcontracting # is the sum of the components' costs: 10 + 20 = 30 self.assertEqual(mo.move_finished_ids.stock_valuation_layer_ids.value, 30) self.assertEqual(picking_receipt.move_lines.product_id.value_svl, 90)
def test_generate_04_generate_in_multiple_time(self): """ Generates a Serial Number for each move lines (except the last one) but with multiple assignments, and checks the generated Serial Numbers are what we expect. """ nbre_of_lines = 10 move = self.get_new_move(nbre_of_lines) form_wizard = Form(self.env['stock.assign.serial'].with_context( default_move_id=move.id, )) # First assignment form_wizard.next_serial_count = 3 form_wizard.next_serial_number = '001' wiz = form_wizard.save() wiz.generate_serial_numbers() # Second assignment form_wizard.next_serial_count = 2 form_wizard.next_serial_number = 'bilou-64' wiz = form_wizard.save() wiz.generate_serial_numbers() # Third assignment form_wizard.next_serial_count = 4 form_wizard.next_serial_number = 'ro-1337-bot' wiz = form_wizard.save() wiz.generate_serial_numbers() # Checks all move lines have the right SN generated_numbers = [ # Correspond to the first assignment '001', '002', '003', # Correspond to the second assignment 'bilou-64', 'bilou-65', # Correspond to the third assignment 'ro-1337-bot', 'ro-1338-bot', 'ro-1339-bot', 'ro-1340-bot', ] self.assertEqual(len(move.move_line_ids), nbre_of_lines + len(generated_numbers)) self.assertEqual(len(move.move_line_nosuggest_ids), len(generated_numbers)) for move_line in move.move_line_nosuggest_ids: self.assertEqual(move_line.qty_done, 1) self.assertEqual(move_line.lot_name, generated_numbers.pop(0)) for move_line in (move.move_line_ids - move.move_line_nosuggest_ids): self.assertEqual(move_line.qty_done, 0) self.assertEqual(move_line.lot_name, False)
def test_allow_rule_creation_for_route_without_company(self): self.env['res.config.settings'].write({ 'group_stock_adv_location': True, 'group_stock_multi_locations': True, }) warehouse = self.env['stock.warehouse'].search( [('company_id', '=', self.env.company.id)], limit=1) location_1 = self.env['stock.location'].create({ 'name': 'loc1', 'location_id': warehouse.id }) location_2 = self.env['stock.location'].create({ 'name': 'loc2', 'location_id': warehouse.id }) receipt_1 = self.env['stock.picking.type'].create({ 'name': 'Receipts from loc1', 'sequence_code': 'IN1', 'code': 'incoming', 'warehouse_id': warehouse.id, 'default_location_dest_id': location_1.id, }) receipt_2 = self.env['stock.picking.type'].create({ 'name': 'Receipts from loc2', 'sequence_code': 'IN2', 'code': 'incoming', 'warehouse_id': warehouse.id, 'default_location_dest_id': location_2.id, }) route = self.env['stock.location.route'].create({ 'name': 'Buy', 'company_id': False }) with Form(route) as r: with r.rule_ids.new() as line: line.name = 'first rule' line.action = 'buy' line.picking_type_id = receipt_1 with r.rule_ids.new() as line: line.name = 'second rule' line.action = 'buy' line.picking_type_id = receipt_2
def test_generate_02_prefix_suffix(self): """ Generates some Serial Numbers and checks the prefix and/or suffix are correctly used. """ nbre_of_lines = 10 # Case #1: Prefix, no suffix move = self.get_new_move(nbre_of_lines) form_wizard = Form(self.env['stock.assign.serial'].with_context( default_move_id=move.id, default_next_serial_number='bilou-87', default_next_serial_count=nbre_of_lines, )) wiz = form_wizard.save() wiz.generate_serial_numbers() # Checks all move lines have the right SN generated_numbers = [ 'bilou-87', 'bilou-88', 'bilou-89', 'bilou-90', 'bilou-91', 'bilou-92', 'bilou-93', 'bilou-94', 'bilou-95', 'bilou-96' ] for move_line in move.move_line_nosuggest_ids: # For a product tracked by SN, the `qty_done` is set on 1 when # `lot_name` is set. self.assertEqual(move_line.qty_done, 1) self.assertEqual(move_line.lot_name, generated_numbers.pop(0)) # Case #2: No prefix, suffix move = self.get_new_move(nbre_of_lines) form_wizard = Form(self.env['stock.assign.serial'].with_context( default_move_id=move.id, default_next_serial_number='005-ccc', default_next_serial_count=nbre_of_lines, )) wiz = form_wizard.save() wiz.generate_serial_numbers() # Checks all move lines have the right SN generated_numbers = [ '005-ccc', '006-ccc', '007-ccc', '008-ccc', '009-ccc', '010-ccc', '011-ccc', '012-ccc', '013-ccc', '014-ccc' ] for move_line in move.move_line_nosuggest_ids: # For a product tracked by SN, the `qty_done` is set on 1 when # `lot_name` is set. self.assertEqual(move_line.qty_done, 1) self.assertEqual(move_line.lot_name, generated_numbers.pop(0)) # Case #3: Prefix + suffix move = self.get_new_move(nbre_of_lines) form_wizard = Form(self.env['stock.assign.serial'].with_context( default_move_id=move.id, default_next_serial_number='alpha-012-345-beta', default_next_serial_count=nbre_of_lines, )) wiz = form_wizard.save() wiz.generate_serial_numbers() # Checks all move lines have the right SN generated_numbers = [ 'alpha-012-345-beta', 'alpha-012-346-beta', 'alpha-012-347-beta', 'alpha-012-348-beta', 'alpha-012-349-beta', 'alpha-012-350-beta', 'alpha-012-351-beta', 'alpha-012-352-beta', 'alpha-012-353-beta', 'alpha-012-354-beta' ] for move_line in move.move_line_nosuggest_ids: # For a product tracked by SN, the `qty_done` is set on 1 when # `lot_name` is set. self.assertEqual(move_line.qty_done, 1) self.assertEqual(move_line.lot_name, generated_numbers.pop(0)) # Case #4: Prefix + suffix, identical number pattern move = self.get_new_move(nbre_of_lines) form_wizard = Form(self.env['stock.assign.serial'].with_context( default_move_id=move.id, default_next_serial_number='BAV023B00001S00001', default_next_serial_count=nbre_of_lines, )) wiz = form_wizard.save() wiz.generate_serial_numbers() # Checks all move lines have the right SN generated_numbers = [ 'BAV023B00001S00001', 'BAV023B00001S00002', 'BAV023B00001S00003', 'BAV023B00001S00004', 'BAV023B00001S00005', 'BAV023B00001S00006', 'BAV023B00001S00007', 'BAV023B00001S00008', 'BAV023B00001S00009', 'BAV023B00001S00010' ] for move_line in move.move_line_nosuggest_ids: # For a product tracked by SN, the `qty_done` is set on 1 when # `lot_name` is set. self.assertEqual(move_line.qty_done, 1) self.assertEqual(move_line.lot_name, generated_numbers.pop(0))
def test_in_refund_line_onchange_accounting_fields_1(self): move_form = Form(self.invoice) with move_form.line_ids.edit(2) as line_form: # Custom credit on the first product line. line_form.credit = 3000 with move_form.line_ids.edit(3) as line_form: # Custom debit on the second product line. Credit should be reset by onchange. # /!\ It's a negative line. line_form.debit = 500 with move_form.line_ids.edit(0) as line_form: # Custom credit on the first tax line. line_form.credit = 800 with move_form.line_ids.edit(4) as line_form: # Custom credit on the second tax line. line_form.credit = 250 move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'price_unit': 3000.0, 'price_subtotal': 3000.0, 'price_total': 3450.0, 'amount_currency': -3000.0, 'credit': 3000.0, }, { **self.product_line_vals_2, 'price_unit': -500.0, 'price_subtotal': -500.0, 'price_total': -650.0, 'amount_currency': 500.0, 'debit': 500.0, 'credit': 0.0, }, { **self.tax_line_vals_1, 'price_unit': 800.0, 'price_subtotal': 800.0, 'price_total': 800.0, 'amount_currency': -800.0, 'credit': 800.0, }, { **self.tax_line_vals_2, 'price_unit': 250.0, 'price_subtotal': 250.0, 'price_total': 250.0, 'amount_currency': -250.0, 'credit': 250.0, }, { **self.term_line_vals_1, 'price_unit': -3550.0, 'price_subtotal': -3550.0, 'price_total': -3550.0, 'amount_currency': 3550.0, 'debit': 3550.0, }, ], { **self.move_vals, 'amount_untaxed': 2500.0, 'amount_tax': 1050.0, 'amount_total': 3550.0, })
def test_02_product_route_level_delays(self): """ In order to check dates, set product's Delivery Lead Time and warehouse route's delay.""" company = self.env.ref('base.main_company') company.write({'po_lead': 1.00}) # Update warehouse_1 with Incoming Shipments 3 steps self.warehouse_1.write({'reception_steps': 'three_steps'}) # Set delay on push rule for push_rule in self.warehouse_1.reception_route_id.rule_ids: push_rule.write({'delay': 2}) rule_delay = sum( self.warehouse_1.reception_route_id.rule_ids.mapped('delay')) date_planned = fields.Datetime.to_string(fields.datetime.now() + timedelta(days=10)) # Create procurement order of product_1 self.env['procurement.group'].run([ self.env['procurement.group'].Procurement( self.product_1, 5.000, self.uom_unit, self.warehouse_1.lot_stock_id, 'Test scheduler for RFQ', '/', self.env.company, { 'warehouse_id': self.warehouse_1, 'date_planned': date_planned, # 10 days added to current date of procurement to get future schedule date and order date of purchase order. 'date_deadline': date_planned, # 10 days added to current date of procurement to get future schedule date and order date of purchase order. 'rule_id': self.warehouse_1.buy_pull_id, 'group_id': False, 'route_ids': [], }) ]) # Confirm purchase order purchase = self.env['purchase.order.line'].search( [('product_id', '=', self.product_1.id)], limit=1).order_id purchase.button_confirm() # Check order date of purchase order order_date = fields.Datetime.from_string(date_planned) - timedelta( days=self.product_1.seller_ids.delay + rule_delay + company.po_lead) self.assertEqual( purchase.date_order, order_date, 'Order date should be equal to: Date of the procurement order - Delivery Lead Time(supplier and pull rules).' ) # Check scheduled date of purchase order schedule_date = order_date + timedelta( days=self.product_1.seller_ids.delay + rule_delay + company.po_lead) self.assertEqual( date_planned, str(schedule_date), 'Schedule date should be equal to: Order date of Purchase order + Delivery Lead Time(supplier and pull rules).' ) # Check the picking crated or not self.assertTrue(purchase.picking_ids, "Picking should be created.") # Check scheduled date of Internal Type shipment incoming_shipment1 = self.env['stock.picking'].search([ ('move_lines.product_id', 'in', (self.product_1.id, self.product_2.id)), ('picking_type_id', '=', self.warehouse_1.int_type_id.id), ('location_id', '=', self.warehouse_1.wh_input_stock_loc_id.id), ('location_dest_id', '=', self.warehouse_1.wh_qc_stock_loc_id.id) ]) incoming_shipment1_date = order_date + timedelta( days=self.product_1.seller_ids.delay + company.po_lead) self.assertEqual( incoming_shipment1.scheduled_date, incoming_shipment1_date, 'Schedule date of Internal Type shipment for input stock location should be equal to: schedule date of purchase order + push rule delay.' ) self.assertEqual(incoming_shipment1.date_deadline, incoming_shipment1_date) old_deadline1 = incoming_shipment1.date_deadline incoming_shipment2 = self.env['stock.picking'].search([ ('picking_type_id', '=', self.warehouse_1.int_type_id.id), ('location_id', '=', self.warehouse_1.wh_qc_stock_loc_id.id), ('location_dest_id', '=', self.warehouse_1.lot_stock_id.id) ]) incoming_shipment2_date = schedule_date - timedelta( days=incoming_shipment2.move_lines[0].rule_id.delay) self.assertEqual( incoming_shipment2.scheduled_date, incoming_shipment2_date, 'Schedule date of Internal Type shipment for quality control stock location should be equal to: schedule date of Internal type shipment for input stock location + push rule delay..' ) self.assertEqual(incoming_shipment2.date_deadline, incoming_shipment2_date) old_deadline2 = incoming_shipment2.date_deadline # Modify the date_planned of the purchase -> propagate the deadline purchase_form = Form(purchase) purchase_form.date_planned = purchase.date_planned + timedelta(days=1) purchase_form.save() self.assertEqual(incoming_shipment2.date_deadline, old_deadline2 + timedelta(days=1), 'Deadline should be propagate') self.assertEqual(incoming_shipment1.date_deadline, old_deadline1 + timedelta(days=1), 'Deadline should be propagate')
def test_in_refund_line_onchange_taxes_1(self): move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: line_form.price_unit = 960 line_form.tax_ids.add(self.tax_armageddon) move_form.save() child_tax_1 = self.tax_armageddon.children_tax_ids[0] child_tax_2 = self.tax_armageddon.children_tax_ids[1] self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'price_unit': 960.0, 'price_subtotal': 800.0, 'price_total': 1176.0, 'tax_ids': (self.tax_purchase_a + self.tax_armageddon).ids, 'tax_exigible': False, }, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, { 'name': child_tax_1.name, 'product_id': False, 'account_id': self.company_data['default_account_expense'].id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': 1.0, 'discount': 0.0, 'price_unit': 96.0, 'price_subtotal': 96.0, 'price_total': 105.6, 'tax_ids': child_tax_2.ids, 'tax_line_id': child_tax_1.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': -96.0, 'debit': 0.0, 'credit': 96.0, 'date_maturity': False, 'tax_exigible': False, }, { 'name': child_tax_1.name, 'product_id': False, 'account_id': self.company_data['default_account_tax_sale'].id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': 1.0, 'discount': 0.0, 'price_unit': 64.0, 'price_subtotal': 64.0, 'price_total': 70.4, 'tax_ids': child_tax_2.ids, 'tax_line_id': child_tax_1.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': -64.0, 'debit': 0.0, 'credit': 64.0, 'date_maturity': False, 'tax_exigible': False, }, { 'name': child_tax_2.name, 'product_id': False, 'account_id': child_tax_2.cash_basis_transition_account_id.id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': 1.0, 'discount': 0.0, 'price_unit': 96.0, 'price_subtotal': 96.0, 'price_total': 96.0, 'tax_ids': [], 'tax_line_id': child_tax_2.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': -96.0, 'debit': 0.0, 'credit': 96.0, 'date_maturity': False, 'tax_exigible': False, }, { **self.term_line_vals_1, 'price_unit': -1384.0, 'price_subtotal': -1384.0, 'price_total': -1384.0, 'amount_currency': 1384.0, 'debit': 1384.0, }, ], { **self.move_vals, 'amount_untaxed': 960.0, 'amount_tax': 424.0, 'amount_total': 1384.0, })
def test_reordering_days_to_purchase(self): company = self.env.ref('base.main_company') company2 = self.env['res.company'].create({ 'name': 'Second Company', }) company.write({'po_lead': 0.00}) self.patcher = patch( 'flectra.addons.stock.models.stock_orderpoint.fields.Date', wraps=fields.Date) self.mock_date = self.patcher.start() vendor = self.env['res.partner'].create({'name': 'Colruyt'}) vendor2 = self.env['res.partner'].create({'name': 'Delhaize'}) self.env.company.days_to_purchase = 2.0 product = self.env['product.product'].create({ 'name': 'Chicory', 'type': 'product', 'seller_ids': [(0, 0, { 'name': vendor2.id, 'delay': 15.0, 'company_id': company2.id }), (0, 0, { 'name': vendor.id, 'delay': 1.0, 'company_id': company.id })] }) orderpoint_form = Form(self.env['stock.warehouse.orderpoint']) orderpoint_form.product_id = product orderpoint_form.product_min_qty = 0.0 orderpoint = orderpoint_form.save() orderpoint_form = Form( self.env['stock.warehouse.orderpoint'].with_company(company2)) orderpoint_form.product_id = product orderpoint_form.product_min_qty = 0.0 orderpoint = orderpoint_form.save() warehouse = self.env['stock.warehouse'].search([], limit=1) delivery_moves = self.env['stock.move'] for i in range(0, 6): delivery_moves |= self.env['stock.move'].create({ 'name': 'Delivery', 'date': datetime.today() + timedelta(days=i), 'product_id': product.id, 'product_uom': product.uom_id.id, 'product_uom_qty': 5.0, 'location_id': warehouse.lot_stock_id.id, 'location_dest_id': self.ref('stock.stock_location_customers'), }) delivery_moves._action_confirm() self.env['procurement.group'].run_scheduler() po_line = self.env['purchase.order.line'].search([('product_id', '=', product.id)]) self.assertEqual(fields.Date.to_date(po_line.order_id.date_order), fields.Date.today() + timedelta(days=2)) self.assertEqual(len(po_line), 1) self.assertEqual(po_line.product_uom_qty, 20.0) self.assertEqual(len(po_line.order_id), 1) orderpoint_form = Form(orderpoint) orderpoint_form.save() self.mock_date.today.return_value = fields.Date.today() + timedelta( days=1) orderpoint._compute_qty() self.env['procurement.group'].run_scheduler() po_line = self.env['purchase.order.line'].search([('product_id', '=', product.id)]) self.assertEqual(len(po_line), 2) self.assertEqual(len(po_line.order_id), 2) new_order = po_line.order_id.sorted('date_order')[-1] self.assertEqual(fields.Date.to_date(new_order.date_order), fields.Date.today() + timedelta(days=2)) self.assertEqual(new_order.order_line.product_uom_qty, 5.0) self.patcher.stop()
def test_in_refund_line_onchange_currency_1(self): move_form = Form(self.invoice) move_form.currency_id = self.currency_data['currency'] move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -800.0, 'credit': 400.0, }, { **self.product_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -160.0, 'credit': 80.0, }, { **self.tax_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -144.0, 'credit': 72.0, }, { **self.tax_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -24.0, 'credit': 12.0, }, { **self.term_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 1128.0, 'debit': 564.0, }, ], { **self.move_vals, 'currency_id': self.currency_data['currency'].id, }) move_form = Form(self.invoice) # Change the date to get another rate: 1/3 instead of 1/2. move_form.date = fields.Date.from_string('2016-01-01') move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -800.0, 'credit': 266.67, }, { **self.product_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -160.0, 'credit': 53.33, }, { **self.tax_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -144.0, 'credit': 48.0, }, { **self.tax_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -24.0, 'credit': 8.0, }, { **self.term_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 1128.0, 'debit': 376.0, }, ], { **self.move_vals, 'currency_id': self.currency_data['currency'].id, 'date': fields.Date.from_string('2016-01-01'), }) move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: # 0.045 * 0.1 = 0.0045. As the foreign currency has a 0.001 rounding, # the result should be 0.005 after rounding. line_form.quantity = 0.1 line_form.price_unit = 0.045 move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'quantity': 0.1, 'price_unit': 0.05, 'price_subtotal': 0.005, 'price_total': 0.006, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -0.005, 'credit': 0.0, }, { **self.product_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -160.0, 'credit': 53.33, }, { **self.tax_line_vals_1, 'price_unit': 24.0, 'price_subtotal': 24.001, 'price_total': 24.001, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -24.001, 'credit': 8.0, }, { **self.tax_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -24.0, 'credit': 8.0, }, { **self.term_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'price_unit': -208.01, 'price_subtotal': -208.006, 'price_total': -208.006, 'amount_currency': 208.006, 'debit': 69.33, }, ], { **self.move_vals, 'currency_id': self.currency_data['currency'].id, 'date': fields.Date.from_string('2016-01-01'), 'amount_untaxed': 160.005, 'amount_tax': 48.001, 'amount_total': 208.006, }) # Exit the multi-currencies. move_form = Form(self.invoice) move_form.currency_id = self.company_data['currency'] move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'quantity': 0.1, 'price_unit': 0.05, 'price_subtotal': 0.01, 'price_total': 0.01, 'amount_currency': -0.01, 'credit': 0.01, }, self.product_line_vals_2, { **self.tax_line_vals_1, 'price_unit': 24.0, 'price_subtotal': 24.0, 'price_total': 24.0, 'amount_currency': -24.0, 'credit': 24.0, }, self.tax_line_vals_2, { **self.term_line_vals_1, 'price_unit': -208.01, 'price_subtotal': -208.01, 'price_total': -208.01, 'amount_currency': 208.01, 'debit': 208.01, }, ], { **self.move_vals, 'currency_id': self.company_data['currency'].id, 'date': fields.Date.from_string('2016-01-01'), 'amount_untaxed': 160.01, 'amount_tax': 48.0, 'amount_total': 208.01, })
def test_recurrence_fields_visibility(self): form = Form(self.env['project.task']) form.name = 'test recurring task' form.project_id = self.project_recurring form.recurring_task = True form.repeat_unit = 'week' self.assertTrue(form.repeat_show_dow) self.assertFalse(form.repeat_show_day) self.assertFalse(form.repeat_show_week) self.assertFalse(form.repeat_show_month) form.repeat_unit = 'month' form.repeat_on_month = 'date' self.assertFalse(form.repeat_show_dow) self.assertTrue(form.repeat_show_day) self.assertFalse(form.repeat_show_week) self.assertFalse(form.repeat_show_month) form.repeat_unit = 'month' form.repeat_on_month = 'day' self.assertFalse(form.repeat_show_dow) self.assertFalse(form.repeat_show_day) self.assertTrue(form.repeat_show_week) self.assertFalse(form.repeat_show_month) form.repeat_unit = 'year' form.repeat_on_year = 'date' self.assertFalse(form.repeat_show_dow) self.assertTrue(form.repeat_show_day) self.assertFalse(form.repeat_show_week) self.assertTrue(form.repeat_show_month) form.repeat_unit = 'year' form.repeat_on_year = 'day' self.assertFalse(form.repeat_show_dow) self.assertFalse(form.repeat_show_day) self.assertTrue(form.repeat_show_week) self.assertTrue(form.repeat_show_month) form.recurring_task = False self.assertFalse(form.repeat_show_dow) self.assertFalse(form.repeat_show_day) self.assertFalse(form.repeat_show_week) self.assertFalse(form.repeat_show_month)