def _create_invoice_from_file(self, attachment): self = self.with_context(default_journal_id=self.journal_id.id) invoice_form = Form(self.env['account.invoice'], view='account.invoice_supplier_form') invoice = invoice_form.save() attachment.write({'res_model': 'account.invoice', 'res_id': invoice.id}) invoice.message_post(attachment_ids=[attachment.id]) return invoice
def test_defaults(self): """ Checks that we can load a default form view and perform trivial default_get & onchanges & computations """ f = Form(self.env['test_testing_utilities.a']) self.assertEqual(f.id, False, "check that our record is not in db (yet)") self.assertEqual(f.f2, 42) self.assertEqual(f.f3, 21) self.assertEqual(f.f4, 42) f.f1 = 4 self.assertEqual(f.f2, 42) self.assertEqual(f.f3, 21) self.assertEqual(f.f4, 10) f.f2 = 8 self.assertEqual(f.f3, 4) self.assertEqual(f.f4, 2) r = f.save() self.assertEqual( (r.f1, r.f2, r.f3, r.f4), (4, 8, 4, 2), )
def test_o2m_editable_list(self): """ Tests the o2m proxy when the list view is editable rather than delegating to a separate form view """ f = Form(self.env['test_testing_utilities.parent'], view='test_testing_utilities.o2m_parent_ed') custom_tree = self.env.ref('test_testing_utilities.editable_external').id subs_field = f._view['fields']['subs'] tree_view = subs_field['views']['tree'] self.assertEqual(tree_view['type'], 'tree') self.assertEqual( tree_view['view_id'], custom_tree, 'check that the tree view is the one referenced by tree_view_ref' ) self.assertIs(subs_field['views']['edition'], tree_view, "check that the edition view is the tree view") self.assertEqual( subs_field['views']['edition']['view_id'], custom_tree ) with f.subs.new() as s: s.value = 1 with f.subs.new() as s: s.value = 3 with f.subs.new() as s: s.value = 7 r = f.save() self.assertEqual(r.v, 12) self.assertEqual( [get(s) for s in r.subs], [('1', 1, 1), ('3', 3, 3), ('7', 7, 7)] )
def create_payment(self, invoices): payment_register = Form(self.env['account.payment'].with_context(active_model='account.invoice', active_ids=invoices.ids)) payment_register.payment_date = time.strftime('%Y') + '-07-15' payment_register.journal_id = self.bank_journal payment_register.payment_method_id = self.payment_method_check payment = payment_register.save() payment.post() return payment
def test_readonly(self): """ Checks that fields with readonly modifiers (marked as readonly or computed w/o set) raise an error when set. """ f = Form(self.env['test_testing_utilities.readonly']) with self.assertRaises(AssertionError): f.f1 = 5 with self.assertRaises(AssertionError): f.f2 = 42
def test_readonly_save(self): """ Should not save readonly fields unless they're force_save """ f = Form(self.env['test_testing_utilities.a'], view='test_testing_utilities.non_normalized_attrs') f.f1 = '1' f.f2 = 987 self.assertEqual(f.f5, 987) self.assertEqual(f.f6, 987) r = f.save() self.assertEqual(r.f5, 0) self.assertEqual(r.f6, 987)
def test_department_leave(self): """ Create a department leave """ self.employee_hrmanager.write({'department_id': self.hr_dept.id}) self.assertFalse(self.env['hr.leave'].search([('employee_id', 'in', self.hr_dept.member_ids.ids)])) leave_form = Form(self.env['hr.leave'].sudo(self.user_hrmanager), view='hr_holidays.hr_leave_view_form_manager') leave_form.holiday_status_id = self.holidays_type_1 leave_form.holiday_type = 'department' leave_form.department_id = self.hr_dept leave = leave_form.save() leave.action_approve() member_ids = self.hr_dept.member_ids.ids self.assertEqual(self.env['hr.leave'].search_count([('employee_id', 'in', member_ids)]), len(member_ids), "Leave should be created for members of department")
def test_attrs(self): """ Checks that attrs/modifiers with non-normalized domains work """ f = Form(self.env['test_testing_utilities.a'], view='test_testing_utilities.non_normalized_attrs') # not readonly yet, should work f.f2 = 5 # make f2 readonly f.f1 = 63 f.f3 = 5 with self.assertRaises(AssertionError): f.f2 = 6
def test_onchange_taxes_2(self): ''' Test the amount of tax account.move.line is adapted when editing the account.move.line amount. This test uses the following scenario: - Create manually a debit line of 1000 having a tax. - Assume a line containing the tax amount is created automatically. - Set the debit amount to 2000 in the first created line. - Assume the line containing the tax amount has been updated automatically. - Create manually a credit line to balance the two previous lines. - Save the move. tax = 10% Name | Debit | Credit | Tax_ids | Tax_line_id's name ----------------|-----------|-----------|---------------|------------------- debit_line_1 | 2000 | | tax | tax_line | 200 | | | tax_line debit_line_1 | | 2200 | | ''' move_form = Form(self.env['account.move'], view='account.view_move_form') move_form.ref = 'azerty' move_form.journal_id = self.journal # 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.percent_tax) self.assertTrue(debit_line.recompute_tax_line) debit_line.debit = 2000 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 = 2200 move = move_form.save() self.check_complete_move(move, [ ['debit_line_1', 2000, 0, [self.percent_tax.id], None], ['tax_line', 200, 0, None, self.percent_tax.id], ['credit_line_1', 0, 2200, None, None], ], fields_name=['name', 'debit', 'credit', 'tax_ids', 'tax_line_id'])
def test_o2m_default(self): """ Tests that default_get can return defaults for the o2m """ f = Form(self.env['test_testing_utilities.default']) with f.subs.edit(index=0) as s: self.assertEqual(s.v, 5) self.assertEqual(s.value, False) r = f.save() self.assertEqual( [get(s) for s in r.subs], [("5", 2, 5)] )
def test_m2m_changed(self): Sub = self.env['test_testing_utilities.sub2'] a = Sub.create({'name': 'a'}) b = Sub.create({'name': 'b'}) c = Sub.create({'name': 'c'}) d = Sub.create({'name': 'd'}) f = Form(self.env['test_testing_utilities.f']) # check default_get self.assertEqual(f.m2m[:], a | b) f.m2o = c self.assertEqual(f.m2m[:], a | b | c) f.m2o = d self.assertEqual(f.m2m[:], a | b | c | d)
def test_o2m_inline(self): """ Tests the o2m proxy when the list and form views are provided inline rather than fetched separately """ f = Form(self.env['test_testing_utilities.parent'], view='test_testing_utilities.o2m_parent_inline') with f.subs.new() as s: s.value = 42 r = f.save() self.assertEqual( [get(s) for s in r.subs], [("0", 42, 0)], "should not have set v (and thus not name)" )
def test_add(self): Sub = self.env['test_testing_utilities.sub2'] f = Form(self.env['test_testing_utilities.e']) r1 = Sub.create({'name': "Item"}) r2 = Sub.create({'name': "Item2"}) f.m2m.add(r1) f.m2m.add(r2) r = f.save() self.assertEqual( r.m2m, r1 | r2 )
def test_remove_by_index(self): Sub = self.env['test_testing_utilities.sub2'] f = Form(self.env['test_testing_utilities.e']) r1 = Sub.create({'name': "Item"}) r2 = Sub.create({'name': "Item2"}) f.m2m.add(r1) f.m2m.add(r2) f.m2m.remove(index=0) r = f.save() self.assertEqual( r.m2m, r2 )
def test_m2m_readonly(self): Sub = self.env['test_testing_utilities.sub3'] a = Sub.create({'name': 'a'}) b = Sub.create({'name': 'b'}) r = self.env['test_testing_utilities.g'].create({ 'm2m': [(6, 0, a.ids)] }) f = Form(r) with self.assertRaises(AssertionError): f.m2m.add(b) with self.assertRaises(AssertionError): f.m2m.remove(id=a.id) f.save() self.assertEqual(r.m2m, a)
def test_o2m_remove(self): def commands(): return [c[0] for c in f._values['line_ids']] f = Form(self.env['test_testing_utilities.onchange_count']) self.assertEqual(f.count, 0) self.assertEqual(len(f.line_ids), 0) f.count = 5 self.assertEqual(f.count, 5) self.assertEqual(len(f.line_ids), 5) f.count = 2 self.assertEqual(f.count, 2) self.assertEqual(len(f.line_ids), 2) f.count = 4 r = f.save() previous = r.line_ids self.assertEqual(len(previous), 4) with Form(r) as f: f.count = 2 self.assertEqual(commands(), [0, 0, 2, 2, 2, 2], "Should contain 2 creations and 4 deletions") self.assertEqual(len(r.line_ids), 2) with Form(r) as f: f.line_ids.remove(0) self.assertEqual(commands(), [2, 1]) f.count = 1 self.assertEqual(commands(), [0, 2, 2], "should contain 1 '0' command and 2 deletions") self.assertEqual(len(r.line_ids), 1)
def test_onchange_taxes_1(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 a 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. tax = 10% Name | Debit | Credit | Tax_ids | Tax_line_id's name ----------------|-----------|-----------|---------------|------------------- debit_line_1 | 1000 | | tax | tax_line | 100 | | | tax_line debit_line_1 | | 1100 | | ''' move_form = Form(self.env['account.move'], view='account.view_move_form') move_form.ref = 'azerty' move_form.journal_id = self.journal # 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.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 = 1100 move = move_form.save() self.assertRecordValues(move.line_ids, [ {'name': 'credit_line_1', 'debit': 0.0, 'credit': 1100.0, 'tax_ids': [], 'tax_line_id': False}, {'name': 'tax_line', 'debit': 100.0, 'credit': 0.0, 'tax_ids': [], 'tax_line_id': self.percent_tax.id}, {'name': 'debit_line_1', 'debit': 1000.0, 'credit': 0.0, 'tax_ids': [self.percent_tax.id], 'tax_line_id': False}, ])
def test_basic(self): env = self.env(context=dict(self.env.context, journal_type='bank')) # select the period and journal for the bank statement journal = env['account.bank.statement'].with_context( date=time.strftime("%Y/%m/%d"), # ??? )._default_journal() self.assertTrue(journal, 'Journal has not been selected') f = Form(env['account.bank.statement']) # necessary as there may be existing bank statements with a non-zero # closing balance which will be used to initialise this one. f.balance_start = 0.0 f.balance_end_real = 0.0 with f.line_ids.new() as line: line.name = 'EXT001' line.amount = 1000 line.partner_id = env.ref('base.res_partner_4') statement_id = f.save() # process the bank statement line account = env['account.account'].create({ 'name': 'toto', 'code': 'bidule', 'user_type_id': env.ref('account.data_account_type_fixed_assets').id }) statement_id.line_ids[0].process_reconciliation(new_aml_dicts=[{ 'credit': 1000, 'debit': 0, 'name': 'toto', 'account_id': account.id, }]) with Form(statement_id) as f: # modify the bank statement and set the Ending Balance. f.balance_end_real = 1000.0 # confirm the bank statement using Validate button statement_id.button_confirm_bank() self.assertEqual(statement_id.state, 'confirm')
def test_default_and_onchange(self): """ Checks defaults & onchanges impacting m2o fields """ Sub = self.env['test_testing_utilities.m2o'] a = Sub.create({'name': "A"}) b = Sub.create({'name': "B"}) f = Form(self.env['test_testing_utilities.d']) self.assertEqual( f.f, a, "The default value for the m2o should be the first Sub record" ) f.f2 = "B" self.assertEqual( f.f, b, "The new m2o value should match the second field by name" ) f.save()
def test_o2m_editable_list(self): """ Tests the o2m proxy when the list view is editable rather than delegating to a separate form view """ f = Form(self.env['test_testing_utilities.parent'], view='test_testing_utilities.o2m_parent_ed') with f.subs.new() as s: s.value = 1 with f.subs.new() as s: s.value = 3 with f.subs.new() as s: s.value = 7 r = f.save() self.assertEqual(r.v, 12) self.assertEqual( [get(s) for s in r.subs], [('1', 1, 1), ('3', 3, 3), ('7', 7, 7)] )
def test_default_and_onchange(self): """ Checks defaults & onchanges impacting m2o fields """ Sub = self.env['test_testing_utilities.m2o'] a = Sub.create({'name': "A"}) b = Sub.create({'name': "B"}) f = Form(self.env['test_testing_utilities.d']) self.assertFalse( f.f, "The default value gets overridden by the onchange" ) f.f2 = "B" self.assertEqual( f.f, b, "The new m2o value should match the second field by name" ) f.save()
def test_state(self): f = Form(self.env['account.invoice']) f.partner_id = self.env.ref('base.res_partner_12') with f.invoice_line_ids.new() as l: l.product_id = self.env.ref('product.product_product_3') invoice = f.save() # I check that Initially customer invoice state is "Draft" self.assertEqual(invoice.state, 'draft') # I called the "Confirm Draft Invoices" wizard w = Form(self.env['account.invoice.confirm']).save() # I clicked on Confirm Invoices Button w.with_context( active_model='account.invoice', active_id=invoice.id, active_ids=invoice.ids, type='out_invoice', ).invoice_confirm() # I check that customer invoice state is "Open" self.assertEqual(invoice.state, 'open') # Electronic invoice must be present and have the same name as l10n_it_einvoice_name self.assertEqual(invoice.l10n_it_einvoice_id.name, invoice.l10n_it_einvoice_name)
def test_set(self): """ Checks that we get/set recordsets for m2o & that set correctly triggers onchange """ r1 = self.env['test_testing_utilities.m2o'].create({'name': "A"}) r2 = self.env['test_testing_utilities.m2o'].create({'name': "B"}) f = Form(self.env['test_testing_utilities.c']) # check that basic manipulations work f.f2 = r1 self.assertEqual(f.f2, r1) self.assertEqual(f.name, 'A') f.f2 = r2 self.assertEqual(f.name, 'B') # can't set an int to an m2o field with self.assertRaises(AssertionError): f.f2 = r1.id self.assertEqual(f.f2, r2) self.assertEqual(f.name, 'B') # can't set a record of the wrong model temp = self.env['test_testing_utilities.readonly'].create({}) with self.assertRaises(AssertionError): f.f2 = temp self.assertEqual(f.f2, r2) self.assertEqual(f.name, 'B') r = f.save() self.assertEqual(r.f2, r2)
def test_basic_alterations(self): """ Tests that the o2m proxy allows adding, removing and editing o2m records """ f = Form(self.env['test_testing_utilities.parent'], view='test_testing_utilities.o2m_parent') f.subs.new().save() f.subs.new().save() f.subs.new().save() f.subs.remove(index=0) r = f.save() self.assertEqual( [get(s) for s in r.subs], [("2", 2, 2), ("2", 2, 2)] ) self.assertEqual(r.v, 5) with Form(r, view='test_testing_utilities.o2m_parent') as f: with f.subs.new() as sub: sub.value = 5 f.subs.new().save() with f.subs.edit(index=2) as sub: self.assertEqual(sub.v, 5) f.subs.remove(index=0) self.assertEqual( [get(s) for s in r.subs], [("2", 2, 2), ("5", 5, 5), ("2", 2, 2)] ) self.assertEqual(r.v, 10) with Form(r, view='test_testing_utilities.o2m_parent') as f, \ f.subs.edit(index=0) as sub,\ self.assertRaises(AssertionError): sub.name = "whop whop"
def _create_product(self, name, uom_id, routes=()): p = Form(self.env['product.product']) p.name = name p.type = 'product' p.uom_id = uom_id p.uom_po_id = uom_id p.route_ids.clear() for r in routes: p.route_ids.add(r) return p.save()
def test_required(self): f = Form(self.env['test_testing_utilities.a']) # f1 no default & no value => should fail with self.assertRaisesRegexp(AssertionError, 'f1 is a required field'): f.save() # set f1 and unset f2 => should work f.f1 = 1 f.f2 = False r = f.save() self.assertEqual( (r.f1, r.f2, r.f3, r.f4), (1, 0, 0, 0) )
def _create_move_quantities(self, qty_to_process, components, warehouse): """ Helper to creates moves in order to update the quantities of components on a specific warehouse. This ensure that all compute fields are triggered. The structure of qty_to_process should be the following : qty_to_process = { component: (qty, uom), ... } """ for comp in components: f = Form(self.env['stock.move']) f.name = 'Test Receipt Components' f.location_id = self.env.ref('stock.stock_location_suppliers') f.location_dest_id = warehouse.lot_stock_id f.product_id = comp f.product_uom = qty_to_process[comp][1] f.product_uom_qty = qty_to_process[comp][0] move = f.save() move._action_confirm() move._action_assign() move_line = move.move_line_ids[0] move_line.qty_done = qty_to_process[comp][0] move._action_done()
def test_state(self): # In order to test Confirm Draft Invoice wizard I create an invoice # and confirm it with this wizard f = Form(self.env['account.invoice']) f.partner_id = self.env.ref('base.res_partner_12') with f.invoice_line_ids.new() as l: l.product_id = self.env.ref('product.product_product_3') invoice = f.save() # I check that Initially customer invoice state is "Draft" self.assertEqual(invoice.state, 'draft') # I called the "Confirm Draft Invoices" wizard w = Form(self.env['account.invoice.confirm']).save() # I clicked on Confirm Invoices Button w.with_context( active_model='account.invoice', active_id=invoice.id, active_ids=invoice.ids, type='out_invoice', ).invoice_confirm() # I check that customer invoice state is "Open" self.assertEqual(invoice.state, 'open') # I check the journal associated and put this journal as not moves = self.env['account.move.line'].search([ ('invoice_id', '=', invoice.id) ]) self.assertGreater(len(moves), 0, 'You should have multiple moves') moves[0].journal_id.write({'update_posted': True}) # I cancelled this open invoice using the button on invoice invoice.action_invoice_cancel() # I check that customer invoice is in the cancel state self.assertEqual(invoice.state, 'cancel')
'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, }) <<<<<<< HEAD # The journal forces you to provide a secondary currency. with self.assertRaises(UserError), self.cr.savepoint(): move_form = Form(self.invoice) move_form.currency_id = self.company_data['currency'] move_form.save() # Exit the multi-currencies. journal.currency_id = False ======= # Exit the multi-currencies. >>>>>>> f0a66d05e70e432d35dc68c9fb1e1cc6e51b40b8 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,
def test_out_refund_line_onchange_accounting_fields_1(self): move_form = Form(self.invoice) with move_form.line_ids.edit(2) as line_form: # Custom debit on the first product line. line_form.debit = 3000 with move_form.line_ids.edit(3) as line_form: # Custom credit on the second product line. Credit should be reset by onchange. # /!\ It's a negative line. line_form.credit = 500 with move_form.line_ids.edit(0) as line_form: # Custom debit on the first tax line. line_form.debit = 800 with move_form.line_ids.edit(4) as line_form: # Custom debit on the second tax line. line_form.debit = 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, 'debit': 3000.0, }, { **self.product_line_vals_2, 'price_unit': -500.0, 'price_subtotal': -500.0, 'price_total': -650.0, 'amount_currency': -500.0, 'debit': 0.0, 'credit': 500.0, }, { **self.tax_line_vals_1, 'price_unit': 800.0, 'price_subtotal': 800.0, 'price_total': 800.0, 'amount_currency': 800.0, 'debit': 800.0, }, { **self.tax_line_vals_2, 'price_unit': 250.0, 'price_subtotal': 250.0, 'price_total': 250.0, 'amount_currency': 250.0, 'debit': 250.0, }, { **self.term_line_vals_1, 'price_unit': -3550.0, 'price_subtotal': -3550.0, 'price_total': -3550.0, 'amount_currency': -3550.0, 'credit': 3550.0, }, ], { **self.move_vals, 'amount_untaxed': 2500.0, 'amount_tax': 1050.0, 'amount_total': 3550.0, })
def _create_stock_location(self, name): stock_location_form = Form(self.env["stock.location"]) stock_location_form.name = name stock_location_form.usage = self.env.ref( "stock.stock_location_stock").usage return stock_location_form.save()
def test_reordering_rule_2(self): """Test when there is not enough product to assign a picking => automatically run reordering rule (RR). Add extra product to already confirmed picking => automatically run another RR """ self.productA = self.env['product.product'].create({ 'name': 'Desk Combination', 'type': 'product', }) self.productB = self.env['product.product'].create({ 'name': 'Desk Decoration', 'type': 'product', }) warehouse = self.env['stock.warehouse'].search([], limit=1) orderpoint_form = Form(self.env['stock.warehouse.orderpoint']) orderpoint_form.product_id = self.productA orderpoint_form.location_id = warehouse.lot_stock_id orderpoint_form.product_min_qty = 0.0 orderpoint_form.product_max_qty = 5.0 orderpoint = orderpoint_form.save() self.env['stock.warehouse.orderpoint'].create({ 'name': 'ProductB RR', 'location_id': warehouse.lot_stock_id.id, 'product_id': self.productB.id, 'product_min_qty': 0, 'product_max_qty': 5, }) self.env['stock.rule'].create({ 'name': 'Rule Supplier', 'route_id': warehouse.reception_route_id.id, 'location_id': warehouse.lot_stock_id.id, 'location_src_id': self.env.ref('stock.stock_location_suppliers').id, 'action': 'pull', 'delay': 9.0, 'procure_method': 'make_to_stock', 'picking_type_id': warehouse.in_type_id.id, }) delivery_picking = self.env['stock.picking'].create({ 'location_id': warehouse.lot_stock_id.id, 'location_dest_id': self.ref('stock.stock_location_customers'), 'picking_type_id': self.ref('stock.picking_type_out'), }) delivery_move = self.env['stock.move'].create({ 'name': 'Delivery', 'product_id': self.productA.id, 'product_uom': self.uom_unit.id, 'product_uom_qty': 12.0, 'location_id': warehouse.lot_stock_id.id, 'location_dest_id': self.ref('stock.stock_location_customers'), 'picking_id': delivery_picking.id, }) delivery_picking.action_confirm() delivery_picking.action_assign() receipt_move = self.env['stock.move'].search([ ('product_id', '=', self.productA.id), ('location_id', '=', self.env.ref('stock.stock_location_suppliers').id) ]) self.assertTrue(receipt_move) self.assertEqual(receipt_move.date.date(), date.today()) self.assertEqual(receipt_move.product_uom_qty, 17.0) delivery_picking.write({ 'move_lines': [(0, 0, { 'name': 'Extra Move', 'product_id': self.productB.id, 'product_uom': self.uom_unit.id, 'product_uom_qty': 5.0, 'location_id': warehouse.lot_stock_id.id, 'location_dest_id': self.ref('stock.stock_location_customers'), 'picking_id': delivery_picking.id, 'additional': True })] }) receipt_move2 = self.env['stock.move'].search([ ('product_id', '=', self.productB.id), ('location_id', '=', self.env.ref('stock.stock_location_suppliers').id) ]) self.assertTrue(receipt_move2) self.assertEqual(receipt_move2.date.date(), date.today()) self.assertEqual(receipt_move2.product_uom_qty, 10.0)
>>>>>>> f0a66d05e70e432d35dc68c9fb1e1cc6e51b40b8 'partner_id': cls.partner_2.id, 'amount': -1000, 'sequence': 1, }) cls.tax21 = cls.env['account.tax'].create({ 'name': '21%', 'type_tax_use': 'purchase', 'amount': 21, }) @classmethod def _create_invoice_line(cls, amount, partner, type): ''' Create an invoice on the fly.''' invoice_form = Form(cls.env['account.move'].with_context(default_move_type=type, default_invoice_date='2019-09-01', default_date='2019-09-01')) invoice_form.partner_id = partner with invoice_form.invoice_line_ids.new() as invoice_line_form: invoice_line_form.name = 'xxxx' invoice_line_form.quantity = 1 invoice_line_form.price_unit = amount invoice_line_form.tax_ids.clear() invoice = invoice_form.save() invoice.post() lines = invoice.line_ids return lines.filtered(lambda l: l.account_id.user_type_id.type in ('receivable', 'payable')) def _post_statements(self): self.bank_st.balance_end_real = self.bank_st.balance_end self.cash_st.balance_end_real = self.cash_st.balance_end (self.bank_st + self.cash_st).button_post()
def setUp(self): self.env.ref('base.main_company').currency_id = self.env.ref( 'base.USD') super(TestPayment, self).setUp() self.register_payments_model = self.env[ 'account.payment.register'].with_context( active_model='account.move') self.payment_model = self.env['account.payment'] self.acc_bank_stmt_model = self.env['account.bank.statement'] self.acc_bank_stmt_line_model = self.env['account.bank.statement.line'] self.partner_agrolait = self.env['res.partner'].create({ 'name': 'Agrolait', 'is_company': True }) self.partner_china_exp = self.env['res.partner'].create({ 'name': 'China Export', 'is_company': True }) self.currency_chf_id = self.env.ref("base.CHF").id self.currency_usd_id = self.env.ref("base.USD").id self.currency_eur_id = self.env.ref("base.EUR").id company = self.env.ref('base.main_company') self.cr.execute( "UPDATE res_company SET currency_id = %s WHERE id = %s", [self.currency_eur_id, company.id]) self.product = self.env['product.product'].create({ 'name': 'Product Product 4', 'standard_price': 500.0, 'list_price': 750.0, 'type': 'consu', 'categ_id': self.env.ref('product.product_category_all').id, }) self.payment_method_manual_in = self.env.ref( "account.account_payment_method_manual_in") self.payment_method_manual_out = self.env.ref( "account.account_payment_method_manual_out") self.account_receivable = self.env['account.account'].search( [('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1) self.account_payable = self.env['account.account'].search( [('user_type_id', '=', self.env.ref('account.data_account_type_payable').id)], limit=1) self.account_revenue = self.env['account.account'].search( [('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1) self.bank_journal_euro = self.env['account.journal'].create({ 'name': 'Bank', 'type': 'bank', 'code': 'BNK67' }) self.account_eur = self.bank_journal_euro.default_debit_account_id self.cash_journal_euro = self.env['account.journal'].create({ 'name': 'Cash', 'type': 'cash', 'code': 'CASH' }) self.bank_journal_usd = self.env['account.journal'].create({ 'name': 'Bank US', 'type': 'bank', 'code': 'BNK68', 'currency_id': self.currency_usd_id }) self.account_usd = self.bank_journal_usd.default_debit_account_id if not self.env.user.company_id.transfer_account_id: self.env.user.company_id.transfer_account_id = self.usd_bnk self.transfer_account = self.env.user.company_id.transfer_account_id self.diff_income_account = self.env.user.company_id.income_currency_exchange_account_id self.diff_expense_account = self.env.user.company_id.expense_currency_exchange_account_id self.form_payment = Form(self.env['account.payment']) self.env['res.currency.rate'].create([{ 'currency_id': self.env.ref('base.EUR').id, 'name': '2010-01-02', 'rate': 1.0, }, { 'currency_id': self.env.ref('base.USD').id, 'name': '2010-01-02', 'rate': 1.2834, }, { 'currency_id': self.env.ref('base.USD').id, 'name': time.strftime('%Y-06-05'), 'rate': 1.5289, }])
def test_report_forecast_2_production_backorder(self): """ Creates a manufacturing order and produces half the quantity. Then creates a backorder and checks the report. """ # Configures the warehouse. warehouse = self.env.ref('stock.warehouse0') warehouse.manufacture_steps = 'pbm_sam' # Configures a product. product_apple_pie = self.env['product.product'].create({ 'name': 'Apple Pie', 'type': 'product', }) product_apple = self.env['product.product'].create({ 'name': 'Apple', 'type': 'consu', }) bom = self.env['mrp.bom'].create({ 'product_id': product_apple_pie.id, 'product_tmpl_id': product_apple_pie.product_tmpl_id.id, 'product_uom_id': product_apple_pie.uom_id.id, 'product_qty': 1.0, 'type': 'normal', 'bom_line_ids': [ (0, 0, { 'product_id': product_apple.id, 'product_qty': 5 }), ], }) # Creates a MO and validates the pick components. mo_form = Form(self.env['mrp.production']) mo_form.product_id = product_apple_pie mo_form.bom_id = bom mo_form.product_qty = 4 mo_1 = mo_form.save() mo_1.action_confirm() pick = mo_1.move_raw_ids.move_orig_ids.picking_id pick_form = Form(pick) with pick_form.move_line_ids_without_package.edit(0) as move_line: move_line.qty_done = 20 pick = pick_form.save() pick.button_validate() # Produces 3 products then creates a backorder for the remaining product. mo_form = Form(mo_1) mo_form.qty_producing = 3 mo_1 = mo_form.save() action = mo_1.button_mark_done() backorder_form = Form( self.env['mrp.production.backorder'].with_context( **action['context'])) backorder = backorder_form.save() backorder.action_backorder() mo_2 = (mo_1.procurement_group_id.mrp_production_ids - mo_1) # Checks the forecast report. report_values, docs, lines = self.get_report_forecast( product_template_ids=product_apple_pie.product_tmpl_id.ids) self.assertEqual(len(lines), 1, "Must have only one line about the backorder") self.assertEqual(lines[0]['document_in'].id, mo_2.id) self.assertEqual(lines[0]['quantity'], 1) self.assertEqual(lines[0]['document_out'], False) # Produces the last unit. mo_form = Form(mo_2) mo_form.qty_producing = 1 mo_2 = mo_form.save() mo_2.button_mark_done() # Checks the forecast report. report_values, docs, lines = self.get_report_forecast( product_template_ids=product_apple_pie.product_tmpl_id.ids) self.assertEqual(len(lines), 0, "Must have no line")
def test_out_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': 1000.0, 'debit': 500.0, }, { **self.product_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 200.0, 'debit': 100.0, }, { **self.tax_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 180.0, 'debit': 90.0, }, { **self.tax_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 30.0, 'debit': 15.0, }, { **self.term_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -1410.0, 'credit': 705.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': 1000.0, 'debit': 333.33, }, { **self.product_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 200.0, 'debit': 66.67, }, { **self.tax_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 180.0, 'debit': 60.0, }, { **self.tax_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 30.0, 'debit': 10.0, }, { **self.term_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'amount_currency': -1410.0, 'credit': 470.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, 'debit': 0.0, }, { **self.product_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 200.0, 'debit': 66.67, }, { **self.tax_line_vals_1, 'price_unit': 30.0, 'price_subtotal': 30.001, 'price_total': 30.001, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 30.001, 'debit': 10.0, }, { **self.tax_line_vals_2, 'currency_id': self.currency_data['currency'].id, 'amount_currency': 30.0, 'debit': 10.0, }, { **self.term_line_vals_1, 'currency_id': self.currency_data['currency'].id, 'price_unit': -260.01, 'price_subtotal': -260.006, 'price_total': -260.006, 'amount_currency': -260.006, 'credit': 86.67, }, ], { **self.move_vals, 'currency_id': self.currency_data['currency'].id, 'date': fields.Date.from_string('2016-01-01'), 'amount_untaxed': 200.005, 'amount_tax': 60.001, 'amount_total': 260.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, 'debit': 0.01, }, self.product_line_vals_2, { **self.tax_line_vals_1, 'price_unit': 30.0, 'price_subtotal': 30.0, 'price_total': 30.0, 'amount_currency': 30.0, 'debit': 30.0, }, self.tax_line_vals_2, { **self.term_line_vals_1, 'price_unit': -260.01, 'price_subtotal': -260.01, 'price_total': -260.01, 'amount_currency': -260.01, 'credit': 260.01, }, ], { **self.move_vals, 'currency_id': self.company_data['currency'].id, 'date': fields.Date.from_string('2016-01-01'), 'amount_untaxed': 200.01, 'amount_tax': 60.0, 'amount_total': 260.01, })
def test_default_picking_type(self): with Form(self.purchase_request_obj) as f: f.name = "Test Purchase" f.requested_by = self.env.user f.save()
def test_out_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 = 999.99 move_form.save() self.assertInvoiceValues(self.invoice, [ { 'name': 'add_invoice_line', 'product_id': False, 'account_id': self.cash_rounding_a.loss_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.01, 'credit': 0.0, 'date_maturity': False, 'tax_exigible': True, }, { **self.product_line_vals_1, 'price_unit': 999.99, 'price_subtotal': 999.99, 'price_total': 1149.99, 'amount_currency': 999.99, 'debit': 999.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': 999.99, 'price_subtotal': 999.99, 'price_total': 1149.99, 'amount_currency': 999.99, 'debit': 999.99, }, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, { 'name': '%s (rounding)' % self.tax_sale_a.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': -0.04, 'price_subtotal': -0.04, 'price_total': -0.04, 'tax_ids': [], 'tax_line_id': self.tax_sale_a.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': -0.04, 'debit': 0.0, 'credit': 0.04, 'date_maturity': False, 'tax_exigible': True, }, { **self.term_line_vals_1, 'price_unit': -1409.95, 'price_subtotal': -1409.95, 'price_total': -1409.95, 'amount_currency': -1409.95, 'credit': 1409.95, }, ], { **self.move_vals, 'amount_untaxed': 1199.99, 'amount_tax': 209.96, 'amount_total': 1409.95, })
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_out_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_receivable_id.id, 'price_unit': -987.0, 'price_subtotal': -987.0, 'price_total': -987.0, 'amount_currency': -987.0, 'credit': 987.0, 'date_maturity': fields.Date.from_string('2019-02-28'), }, { **self.term_line_vals_1, 'name': 'turlututu', 'partner_id': self.partner_b.id, 'account_id': self.partner_b.property_account_receivable_id.id, 'price_unit': -423.0, 'price_subtotal': -423.0, 'price_total': -423.0, 'amount_currency': -423.0, 'credit': 423.0, }, ], { **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': 1200.0, 'amount_tax': 210.0, 'amount_total': 1410.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_income_id.id, 'partner_id': self.partner_b.id, 'tax_ids': self.tax_sale_b.ids, }, { **self.product_line_vals_2, 'partner_id': self.partner_b.id, 'price_total': 230.0, 'tax_ids': self.tax_sale_b.ids, }, { **self.tax_line_vals_1, 'name': self.tax_sale_b.name, 'partner_id': self.partner_b.id, 'tax_line_id': self.tax_sale_b.id, }, { **self.term_line_vals_1, 'name': 'turlututu', 'account_id': self.partner_b.property_account_receivable_id.id, 'partner_id': self.partner_b.id, 'price_unit': -966.0, 'price_subtotal': -966.0, 'price_total': -966.0, 'amount_currency': -966.0, 'credit': 966.0, 'date_maturity': fields.Date.from_string('2019-02-28'), }, { **self.term_line_vals_1, 'name': 'turlututu', 'account_id': self.partner_b.property_account_receivable_id.id, 'partner_id': self.partner_b.id, 'price_unit': -414.0, 'price_subtotal': -414.0, 'price_total': -414.0, 'amount_currency': -414.0, 'credit': 414.0, }, ], { **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': 1200.0, 'amount_tax': 180.0, 'amount_total': 1380.0, })
def _import_facturx_invoice(self, tree): ''' Extract invoice values from the Factur-x xml tree passed as parameter. :param tree: The tree of the Factur-x xml file. :return: A dictionary containing account.invoice values to create/update it. ''' amount_total_import = None # type must be present in the context to get the right behavior of the _default_journal method (account.invoice). # journal_id must be present in the context to get the right behavior of the _default_account method (account.invoice.line). self_ctx = self.with_context(type='in_invoice') journal_id = self_ctx._default_journal().id self_ctx = self_ctx.with_context(journal_id=journal_id) # self could be a single record (editing) or be empty (new). with Form(self_ctx, view='account.invoice_supplier_form') as invoice_form: # Partner (first step to avoid warning 'Warning! You must first select a partner.'). elements = tree.xpath( '//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID', namespaces=tree.nsmap) partner = elements and self.env['res.partner'].search( [('vat', '=', elements[0].text)], limit=1) if not partner: elements = tree.xpath('//ram:SellerTradeParty/ram:Name', namespaces=tree.nsmap) partner_name = elements and elements[0].text partner = elements and self.env['res.partner'].search( [('name', 'ilike', partner_name)], limit=1) if not partner: elements = tree.xpath( '//ram:SellerTradeParty//ram:URIID[@schemeID=\'SMTP\']', namespaces=tree.nsmap) partner = elements and self.env['res.partner'].search( [('email', '=', elements[0].text)], limit=1) if partner: invoice_form.partner_id = partner # Reference. elements = tree.xpath('//rsm:ExchangedDocument/ram:ID', namespaces=tree.nsmap) if elements: invoice_form.reference = elements[0].text # Name. elements = tree.xpath( '//ram:BuyerOrderReferencedDocument/ram:IssuerAssignedID', namespaces=tree.nsmap) if elements: invoice_form.name = elements[0].text # Comment. elements = tree.xpath('//ram:IncludedNote/ram:Content', namespaces=tree.nsmap) if elements: invoice_form.comment = elements[0].text # Refund type. # There is two modes to handle refund in Factur-X: # a) type_code == 380 for invoice, type_code == 381 for refund, all positive amounts. # b) type_code == 380, negative amounts in case of refund. # To handle both, we consider the 'a' mode and switch to 'b' if a negative amount is encountered. elements = tree.xpath('//rsm:ExchangedDocument/ram:TypeCode', namespaces=tree.nsmap) type_code = elements[0].text refund_sign = 1 # Total amount. elements = tree.xpath('//ram:GrandTotalAmount', namespaces=tree.nsmap) if elements: total_amount = float(elements[0].text) # Handle 'a & b' refund mode. if (total_amount < 0 and type_code == '380') or type_code == '381': refund_sign = -1 # Currency. if elements[0].attrib.get('currencyID'): currency_str = elements[0].attrib['currencyID'] currency = self.env.ref('base.%s' % currency_str.upper(), raise_if_not_found=False) if currency != self.env.user.company_id.currency_id and currency.active: invoice_form.currency_id = currency # Store xml total amount. amount_total_import = total_amount * refund_sign # Date. elements = tree.xpath( '//rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString', namespaces=tree.nsmap) if elements: date_str = elements[0].text date_obj = datetime.strptime(date_str, DEFAULT_FACTURX_DATE_FORMAT) invoice_form.date_invoice = date_obj.strftime( DEFAULT_SERVER_DATE_FORMAT) # Due date. elements = tree.xpath( '//ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime/udt:DateTimeString', namespaces=tree.nsmap) if elements: date_str = elements[0].text date_obj = datetime.strptime(date_str, DEFAULT_FACTURX_DATE_FORMAT) date_due = date_obj.strftime(DEFAULT_SERVER_DATE_FORMAT) if date_due: invoice_form.payment_term_id = self.env[ 'account.payment.term'] invoice_form.date_due = date_due # Invoice lines. elements = tree.xpath('//ram:IncludedSupplyChainTradeLineItem', namespaces=tree.nsmap) if elements: for element in elements: with invoice_form.invoice_line_ids.new( ) as invoice_line_form: # Sequence. line_elements = element.xpath( './/ram:AssociatedDocumentLineDocument/ram:LineID', namespaces=tree.nsmap) if line_elements: invoice_line_form.sequence = int( line_elements[0].text) # Product. line_elements = element.xpath( './/ram:SpecifiedTradeProduct/ram:Name', namespaces=tree.nsmap) if line_elements: invoice_line_form.name = line_elements[0].text line_elements = element.xpath( './/ram:SpecifiedTradeProduct/ram:SellerAssignedID', namespaces=tree.nsmap) if line_elements and line_elements[0].text: product = self.env['product.product'].search([ ('default_code', '=', line_elements[0].text) ]) if product: invoice_line_form.product_id = product if not invoice_line_form.product_id: line_elements = element.xpath( './/ram:SpecifiedTradeProduct/ram:GlobalID', namespaces=tree.nsmap) if line_elements and line_elements[0].text: product = self.env['product.product'].search([ ('barcode', '=', line_elements[0].text) ]) if product: invoice_line_form.product_id = product # Quantity. line_elements = element.xpath( './/ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', namespaces=tree.nsmap) if line_elements: invoice_line_form.quantity = float( line_elements[0].text) * refund_sign # Price Unit. line_elements = element.xpath( './/ram:GrossPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap) if line_elements: invoice_line_form.price_unit = float( line_elements[0].text ) / invoice_line_form.quantity else: line_elements = element.xpath( './/ram:NetPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap) if line_elements: invoice_line_form.price_unit = float( line_elements[0].text ) / invoice_line_form.quantity # Discount. line_elements = element.xpath( './/ram:AppliedTradeAllowanceCharge/ram:CalculationPercent', namespaces=tree.nsmap) if line_elements: invoice_line_form.discount = float( line_elements[0].text) # Taxes line_elements = element.xpath( './/ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', namespaces=tree.nsmap) invoice_line_form.invoice_line_tax_ids.clear() for tax_element in line_elements: percentage = float(tax_element.text) tax = self.env['account.tax'].search([ ('company_id', '=', invoice_form.company_id.id), ('amount_type', '=', 'percent'), ('type_tax_use', '=', 'purchase'), ('amount', '=', percentage), ], limit=1) if tax: invoice_line_form.invoice_line_tax_ids.add(tax) elif amount_total_import: # No lines in BASICWL. with invoice_form.invoice_line_ids.new() as invoice_line_form: invoice_line_form.name = invoice_form.comment or '/' invoice_line_form.quantity = 1 invoice_line_form.price_unit = amount_total_import # Refund. invoice_form.type = 'in_refund' if refund_sign == -1 else 'in_invoice' return invoice_form.save()
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, })
def test_subkit_in_delivery_slip(self): """ Suppose this structure: Super Kit --|- Compo 01 x1 |- Sub Kit x1 --|- Compo 02 x1 | |- Compo 03 x1 This test ensures that, when delivering one Super Kit, one Sub Kit, one Compo 01 and one Compo 02, and when putting in pack the third component of the Super Kit, the delivery report is correct. """ compo01, compo02, compo03, subkit, superkit = self.env[ 'product.product'].create( [{ 'name': n, 'type': 'consu', } for n in ['Compo 01', 'Compo 02', 'Compo 03', 'Sub Kit', 'Super Kit']]) self.env['mrp.bom'].create([{ 'product_tmpl_id': subkit.product_tmpl_id.id, 'product_qty': 1, 'type': 'phantom', 'bom_line_ids': [ (0, 0, { 'product_id': compo02.id, 'product_qty': 1 }), (0, 0, { 'product_id': compo03.id, 'product_qty': 1 }), ], }, { 'product_tmpl_id': superkit.product_tmpl_id.id, 'product_qty': 1, 'type': 'phantom', 'bom_line_ids': [ (0, 0, { 'product_id': compo01.id, 'product_qty': 1 }), (0, 0, { 'product_id': subkit.id, 'product_qty': 1 }), ], }]) picking_form = Form(self.env['stock.picking']) picking_form.picking_type_id = self.picking_type_out picking_form.partner_id = self.partner with picking_form.move_ids_without_package.new() as move: move.product_id = superkit move.product_uom_qty = 1 with picking_form.move_ids_without_package.new() as move: move.product_id = subkit move.product_uom_qty = 1 with picking_form.move_ids_without_package.new() as move: move.product_id = compo01 move.product_uom_qty = 1 with picking_form.move_ids_without_package.new() as move: move.product_id = compo02 move.product_uom_qty = 1 picking = picking_form.save() picking.action_confirm() picking.move_lines.quantity_done = 1 move = picking.move_lines.filtered( lambda m: m.name == "Super Kit" and m.product_id == compo03) move.move_line_ids.result_package_id = self.env[ 'stock.quant.package'].create({'name': 'Package0001'}) picking.button_validate() report = self.env['ir.actions.report']._get_report_from_name( 'stock.report_deliveryslip') html_report = report._render_qweb_html( picking.ids)[0].decode('utf-8').split('\n') keys = [ "Package0001", "Compo 03", "Products with no package assigned", "Compo 01", "Compo 02", "Super Kit", "Compo 01", "Compo 02", "Sub Kit", "Compo 02", "Compo 03", ] for line in html_report: if not keys: break if keys[0] in line: keys = keys[1:] self.assertFalse( keys, "All keys should be in the report with the defined order")
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, '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, '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, '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, '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_sale_mrp(self): warehouse0 = self.env.ref('stock.warehouse0') # In order to test the sale_mrp module in OpenERP, I start by creating a new product 'Slider Mobile' # I define product category Mobile Products Sellable. with mute_logger('odoo.tests.common.onchange'): # Suppress warning on "Changing your cost method" when creating a # product category pc = Form(self.env['product.category']) pc.name = 'Mobile Products Sellable' product_category_allproductssellable0 = pc.save() uom_unit = self.env.ref('uom.product_uom_unit') self.assertIn("seller_ids", self.env['product.template'].fields_get()) # I define product for Slider Mobile. product = Form(self.env['product.template']) product.categ_id = product_category_allproductssellable0 product.list_price = 200.0 product.name = 'Slider Mobile' product.standard_price = 189.0 product.type = 'product' product.uom_id = uom_unit product.uom_po_id = uom_unit product.route_ids.clear() product.route_ids.add(warehouse0.manufacture_pull_id.route_id) product.route_ids.add(warehouse0.mto_pull_id.route_id) product_template_slidermobile0 = product.save() with Form(self.env['mrp.bom']) as bom: bom.product_tmpl_id = product_template_slidermobile0 # I create a sale order for product Slider mobile so_form = Form(self.env['sale.order']) so_form.partner_id = self.env.ref('base.res_partner_4') with so_form.order_line.new() as line: line.product_id = product_template_slidermobile0.product_variant_ids line.price_unit = 200 line.product_uom_qty = 500.0 line.customer_lead = 7.0 sale_order_so0 = so_form.save() # I confirm the sale order sale_order_so0.action_confirm() # I verify that a manufacturing order has been generated, and that its name and reference are correct mo = self.env['mrp.production'].search( [('origin', 'like', sale_order_so0.name)], limit=1) self.assertTrue(mo, 'Manufacturing order has not been generated')
def test_onchange_taxes_3(self): ''' Test the amount of tax account.move.line is still editable manually. Test the amount of tax account.move.line is cumulative for the same tax. This test uses the following scenario: - Create manually a debit line of 1000 having a tax. - Assume a line containing the tax amount is created automatically. - Edit the tax line amount of the auto-generated line by adding 5. - Create manually a credit line to balance the two previous lines. - Save the move. - Edit the move. - Create manually a debit line of 2000 having the same tax. - Assume the line containing the tax amount has been updated (no new line created). - Create manually a credit line to balance the four previous lines. - Save the move. tax = 10% Name | Debit | Credit | Tax_ids | Tax_line_id's name ----------------|-----------|-----------|---------------|------------------- debit_line_1 | 1000 | | tax | tax_line | 300 | | | tax_line credit_line_1 | | 1105 | | debit_line_2 | 2000 | | tax | credit_line_2 | | 2195 | | ''' move_form = Form(self.env['account.move'], view='account.view_move_form') move_form.ref = 'azerty' move_form.journal_id = self.journal # 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.percent_tax) self.assertTrue(debit_line.recompute_tax_line) # Edit the tax account.move.line with move_form.line_ids.edit(index=1) as tax_line: tax_line.debit = 105 # Was 100 # 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 = 1105 move = move_form.save() move_form = Form(move, view='account.view_move_form') # Create a new account.move.line with debit amount. with move_form.line_ids.new() as debit_line2: debit_line2.name = 'debit_line_2' debit_line2.account_id = self.account debit_line2.debit = 2000 debit_line2.tax_ids.clear() debit_line2.tax_ids.add(self.percent_tax) self.assertTrue(debit_line2.recompute_tax_line) with move_form.line_ids.new() as credit_line2: credit_line2.name = 'credit_line_2' credit_line2.account_id = self.account credit_line2.credit = 2195 move = move_form.save() self.check_complete_move(move, [ ['debit_line_1', 1000, 0, [self.percent_tax.id], None], ['tax_line', 300, 0, None, self.percent_tax.id], ['credit_line_1', 0, 1105, None, None], ['debit_line_2', 2000, 0, [self.percent_tax.id], None], ['credit_line_2', 0, 2195, None, None], ], fields_name=['name', 'debit', 'credit', 'tax_ids', 'tax_line_id'])
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 _import_facturx(self, tree, invoice): """ Decodes a factur-x invoice into an invoice. :param tree: the factur-x tree to decode. :param invoice: the invoice to update or an empty recordset. :returns: the invoice where the factur-x data was imported. """ def _find_value(xpath, element=tree): return self._find_value(xpath, element, tree.nsmap) amount_total_import = None default_move_type = False if invoice._context.get('default_journal_id'): journal = self.env['account.journal'].browse(self.env.context['default_journal_id']) default_move_type = 'out_invoice' if journal.type == 'sale' else 'in_invoice' elif invoice._context.get('default_move_type'): default_move_type = self._context['default_move_type'] elif invoice.move_type in self.env['account.move'].get_invoice_types(include_receipts=True): # in case an attachment is saved on a draft invoice previously created, we might # have lost the default value in context but the type was already set default_move_type = invoice.move_type if not default_move_type: raise UserError(_("No information about the journal or the type of invoice is passed")) if default_move_type == 'entry': return # Total amount. elements = tree.xpath('//ram:GrandTotalAmount', namespaces=tree.nsmap) total_amount = elements and float(elements[0].text) or 0.0 # Refund type. # There is two modes to handle refund in Factur-X: # a) type_code == 380 for invoice, type_code == 381 for refund, all positive amounts. # b) type_code == 380, negative amounts in case of refund. # To handle both, we consider the 'a' mode and switch to 'b' if a negative amount is encountered. elements = tree.xpath('//rsm:ExchangedDocument/ram:TypeCode', namespaces=tree.nsmap) type_code = elements[0].text default_move_type.replace('_refund', '_invoice') if type_code == '381': default_move_type = 'out_refund' if default_move_type == 'out_invoice' else 'in_refund' refund_sign = -1 else: # Handle 'b' refund mode. if total_amount < 0: default_move_type = 'out_refund' if default_move_type == 'out_invoice' else 'in_refund' refund_sign = -1 if 'refund' in default_move_type else 1 # Write the type as the journal entry is already created. invoice.move_type = default_move_type # self could be a single record (editing) or be empty (new). with Form(invoice.with_context(default_move_type=default_move_type, account_predictive_bills_disable_prediction=True)) as invoice_form: partner_type = invoice_form.journal_id.type == 'purchase' and 'SellerTradeParty' or 'BuyerTradeParty' invoice_form.partner_id = self._retrieve_partner( name=_find_value(f"/ram:{partner_type}/ram:Name"), mail=_find_value(f"//ram:{partner_type}//ram:URIID[@schemeID='SMTP']"), vat=_find_value(f"//ram:{partner_type}/ram:SpecifiedTaxRegistration/ram:ID"), ) # Delivery partner if 'partner_shipping_id' in invoice._fields: invoice_form.partner_shipping_id = self._retrieve_partner( name=_find_value("//ram:ShipToTradeParty/ram:Name"), mail=_find_value("//ram:ShipToTradeParty//ram:URIID[@schemeID='SMTP']"), vat=_find_value("//ram:ShipToTradeParty/ram:SpecifiedTaxRegistration/ram:ID"), ) # Reference. elements = tree.xpath('//rsm:ExchangedDocument/ram:ID', namespaces=tree.nsmap) if elements: invoice_form.ref = elements[0].text # Name. elements = tree.xpath('//ram:BuyerOrderReferencedDocument/ram:IssuerAssignedID', namespaces=tree.nsmap) if elements: invoice_form.payment_reference = elements[0].text # Comment. elements = tree.xpath('//ram:IncludedNote/ram:Content', namespaces=tree.nsmap) if elements: invoice_form.narration = elements[0].text # Total amount. elements = tree.xpath('//ram:GrandTotalAmount', namespaces=tree.nsmap) if elements: # Currency. currency_str = elements[0].attrib.get('currencyID', None) if currency_str: invoice_form.currency_id = self._retrieve_currency(currency_str) # Store xml total amount. amount_total_import = total_amount * refund_sign # Date. elements = tree.xpath('//rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString', namespaces=tree.nsmap) if elements: date_str = elements[0].text date_obj = datetime.strptime(date_str, DEFAULT_FACTURX_DATE_FORMAT) invoice_form.invoice_date = date_obj.strftime(DEFAULT_SERVER_DATE_FORMAT) # Due date. elements = tree.xpath('//ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime/udt:DateTimeString', namespaces=tree.nsmap) if elements: date_str = elements[0].text date_obj = datetime.strptime(date_str, DEFAULT_FACTURX_DATE_FORMAT) invoice_form.invoice_date_due = date_obj.strftime(DEFAULT_SERVER_DATE_FORMAT) # Invoice lines. elements = tree.xpath('//ram:IncludedSupplyChainTradeLineItem', namespaces=tree.nsmap) if elements: for element in elements: with invoice_form.invoice_line_ids.new() as invoice_line_form: # Sequence. line_elements = element.xpath('.//ram:AssociatedDocumentLineDocument/ram:LineID', namespaces=tree.nsmap) if line_elements: invoice_line_form.sequence = int(line_elements[0].text) # Product. name = _find_value('.//ram:SpecifiedTradeProduct/ram:Name', element) if name: invoice_line_form.name = name invoice_line_form.product_id = self._retrieve_product( default_code=_find_value('.//ram:SpecifiedTradeProduct/ram:SellerAssignedID', element), name=_find_value('.//ram:SpecifiedTradeProduct/ram:Name', element), barcode=_find_value('.//ram:SpecifiedTradeProduct/ram:GlobalID', element) ) # Quantity. line_elements = element.xpath('.//ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', namespaces=tree.nsmap) if line_elements: invoice_line_form.quantity = float(line_elements[0].text) # Price Unit. line_elements = element.xpath('.//ram:GrossPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap) if line_elements: quantity_elements = element.xpath('.//ram:GrossPriceProductTradePrice/ram:BasisQuantity', namespaces=tree.nsmap) if quantity_elements: invoice_line_form.price_unit = float(line_elements[0].text) / float(quantity_elements[0].text) else: invoice_line_form.price_unit = float(line_elements[0].text) else: line_elements = element.xpath('.//ram:NetPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap) if line_elements: quantity_elements = element.xpath('.//ram:NetPriceProductTradePrice/ram:BasisQuantity', namespaces=tree.nsmap) if quantity_elements: invoice_line_form.price_unit = float(line_elements[0].text) / float(quantity_elements[0].text) else: invoice_line_form.price_unit = float(line_elements[0].text) # Discount. line_elements = element.xpath('.//ram:AppliedTradeAllowanceCharge/ram:CalculationPercent', namespaces=tree.nsmap) if line_elements: invoice_line_form.discount = float(line_elements[0].text) # Taxes tax_element = element.xpath('.//ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', namespaces=tree.nsmap) invoice_line_form.tax_ids.clear() for eline in tax_element: tax = self._retrieve_tax( amount=eline.text, type_tax_use=invoice_form.journal_id.type ) if tax: invoice_line_form.tax_ids.add(tax) elif amount_total_import: # No lines in BASICWL. with invoice_form.invoice_line_ids.new() as invoice_line_form: invoice_line_form.name = invoice_form.comment or '/' invoice_line_form.quantity = 1 invoice_line_form.price_unit = amount_total_import return invoice_form.save()
def test_in_refund_line_onchange_currency_1(self): # New journal having a foreign currency set. journal = self.company_data['default_journal_purchase'].copy() journal.currency_id = self.currency_data['currency'] move_form = Form(self.invoice) move_form.journal_id = journal move_form.save() self.assertInvoiceValues( self.invoice, [ { **self.product_line_vals_1, 'currency_id': journal.currency_id.id, 'amount_currency': -800.0, 'credit': 400.0, }, { **self.product_line_vals_2, 'currency_id': journal.currency_id.id, 'amount_currency': -160.0, 'credit': 80.0, }, { **self.tax_line_vals_1, 'currency_id': journal.currency_id.id, 'amount_currency': -144.0, 'credit': 72.0, }, { **self.tax_line_vals_2, 'currency_id': journal.currency_id.id, 'amount_currency': -24.0, 'credit': 12.0, }, { **self.term_line_vals_1, 'currency_id': journal.currency_id.id, 'amount_currency': 1128.0, 'debit': 564.0, }, ], { **self.move_vals, 'currency_id': journal.currency_id.id, 'journal_id': journal.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': journal.currency_id.id, 'amount_currency': -800.0, 'credit': 266.67, }, { **self.product_line_vals_2, 'currency_id': journal.currency_id.id, 'amount_currency': -160.0, 'credit': 53.33, }, { **self.tax_line_vals_1, 'currency_id': journal.currency_id.id, 'amount_currency': -144.0, 'credit': 48.0, }, { **self.tax_line_vals_2, 'currency_id': journal.currency_id.id, 'amount_currency': -24.0, 'credit': 8.0, }, { **self.term_line_vals_1, 'currency_id': journal.currency_id.id, 'amount_currency': 1128.0, 'debit': 376.0, }, ], { **self.move_vals, 'currency_id': journal.currency_id.id, 'journal_id': journal.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': journal.currency_id.id, 'amount_currency': -0.005, 'credit': 0.0, }, { **self.product_line_vals_2, 'currency_id': journal.currency_id.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': journal.currency_id.id, 'amount_currency': -24.001, 'credit': 8.0, }, { **self.tax_line_vals_2, 'currency_id': journal.currency_id.id, 'amount_currency': -24.0, 'credit': 8.0, }, { **self.term_line_vals_1, 'currency_id': journal.currency_id.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': journal.currency_id.id, 'journal_id': journal.id, 'date': fields.Date.from_string('2016-01-01'), 'amount_untaxed': 160.005, 'amount_tax': 48.001, 'amount_total': 208.006, }) # The journal forces you to provide a secondary currency. with self.assertRaises(UserError), self.cr.savepoint(): move_form = Form(self.invoice) move_form.currency_id = self.company_data['currency'] move_form.save() # Exit the multi-currencies. journal.currency_id = False 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, '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, '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, 'debit': 208.01, }, ], { **self.move_vals, 'currency_id': self.company_data['currency'].id, 'journal_id': journal.id, 'date': fields.Date.from_string('2016-01-01'), 'amount_untaxed': 160.01, 'amount_tax': 48.0, 'amount_total': 208.01, })
def test_invoice_shipment(self): """ Tests the case into which we make the invoice first, and then receive the goods. """ # Create a PO and an invoice for it test_product = self.test_product_order purchase_order = self._create_purchase(test_product, '2017-12-01') invoice = self._create_invoice_for_po(purchase_order, '2017-12-23') move_form = Form(invoice) with move_form.invoice_line_ids.edit(0) as line_form: line_form.quantity = 1 invoice = move_form.save() # The currency rate changes self.env['res.currency.rate'].create({ 'currency_id': self.currency_one.id, 'company_id': self.company.id, 'rate': 13.834739702, 'name': '2017-12-22', }) # Validate the invoice and refund the goods invoice.post() self._process_pickings(purchase_order.picking_ids, date='2017-12-24') picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)]) self.check_reconciliation(invoice, picking) # The currency rate changes again self.env['res.currency.rate'].create({ 'currency_id': self.currency_one.id, 'company_id': self.company.id, 'rate': 10.54739702, 'name': '2018-01-01', }) # Return the goods and refund the invoice stock_return_picking_form = Form( self.env['stock.return.picking'].with_context( active_ids=picking.ids, active_id=picking.ids[0], active_model='stock.picking')) stock_return_picking = stock_return_picking_form.save() stock_return_picking.product_return_moves.quantity = 1.0 stock_return_picking_action = stock_return_picking.create_returns() return_pick = self.env['stock.picking'].browse( stock_return_picking_action['res_id']) return_pick.action_assign() return_pick.move_lines.quantity_done = 1 return_pick._action_done() self._change_pickings_date(return_pick, '2018-01-13') # The currency rate changes again self.env['res.currency.rate'].create({ 'currency_id': self.currency_one.id, 'company_id': self.company.id, 'rate': 9.56564564, 'name': '2018-03-12', }) # Refund the invoice refund_invoice_wiz = self.env['account.move.reversal'].with_context( active_model="account.move", active_ids=[invoice.id]).create({ 'reason': 'test_invoice_shipment_refund', 'refund_method': 'cancel', 'date': '2018-03-15', }) refund_invoice = self.env['account.move'].browse( refund_invoice_wiz.reverse_moves()['res_id']) # Check the result self.assertTrue(invoice.invoice_payment_state == refund_invoice.invoice_payment_state == 'paid' ), "Invoice and refund should both be in 'Paid' state" self.check_reconciliation(refund_invoice, return_pick)
def _create_product(self, name): product_form = Form(self.env["product.product"]) product_form.name = name product_form.type = "product" return product_form.save()
def _decode_bis3(self, tree, invoice): """ Decodes an EN16931 invoice into an invoice. :param tree: the UBL (EN16931) tree to decode. :param invoice: the invoice to update or an empty recordset. :returns: the invoice where the UBL (EN16931) data was imported. """ def _find_value(path, root=tree): element = root.find(path) return element.text if element is not None else None element = tree.find('./{*}InvoiceTypeCode') if element is not None: type_code = element.text move_type = 'in_refund' if type_code == '381' else 'in_invoice' else: move_type = 'in_invoice' default_journal = invoice.with_context( default_move_type=move_type)._get_default_journal() with Form( invoice.with_context( default_move_type=move_type, default_journal_id=default_journal.id)) as invoice_form: # Reference element = tree.find('./{*}ID') if element is not None: invoice_form.ref = element.text # Dates element = tree.find('./{*}IssueDate') if element is not None: invoice_form.invoice_date = element.text element = tree.find('./{*}DueDate') if element is not None: invoice_form.invoice_date_due = element.text # Currency currency = self._retrieve_currency( _find_value('./{*}DocumentCurrencyCode')) if currency: invoice_form.currency_id = currency # Partner specific_domain = self._bis3_get_extra_partner_domains(tree) invoice_form.partner_id = self._retrieve_partner( name=_find_value( './{*}AccountingSupplierParty/{*}Party/*/{*}Name'), phone=_find_value( './{*}AccountingSupplierParty/{*}Party/*/{*}Telephone'), mail=_find_value( './{*}AccountingSupplierParty/{*}Party/*/{*}ElectronicMail' ), vat=_find_value( './{*}AccountingSupplierParty/{*}Party/{*}PartyTaxScheme/{*}CompanyID' ), domain=specific_domain, ) # Lines for eline in tree.findall('.//{*}InvoiceLine'): with invoice_form.invoice_line_ids.new() as invoice_line_form: # Product invoice_line_form.product_id = self._retrieve_product( default_code=_find_value( './{*}Item/{*}SellersItemIdentification/{*}ID', eline), name=_find_value('./{*}Item/{*}Name', eline), barcode=_find_value( './{*}Item/{*}StandardItemIdentification/{*}ID[@schemeID=\'0160\']', eline)) # Quantity element = eline.find('./{*}InvoicedQuantity') quantity = element is not None and float( element.text) or 1.0 invoice_line_form.quantity = quantity # Price Unit element = eline.find('./{*}Price/{*}PriceAmount') price_unit = element is not None and float( element.text) or 0.0 line_extension_amount = element is not None and float( element.text) or 0.0 invoice_line_form.price_unit = price_unit or line_extension_amount / invoice_line_form.quantity or 0.0 # Name element = eline.find('./{*}Item/{*}Description') invoice_line_form.name = element is not None and element.text or '' # Taxes tax_elements = eline.findall( './{*}Item/{*}ClassifiedTaxCategory') invoice_line_form.tax_ids.clear() for tax_element in tax_elements: invoice_line_form.tax_ids.add( self._retrieve_tax( amount=_find_value('./{*}Percent', tax_element), type_tax_use=invoice_form.journal_id.type)) return invoice_form.save()
def test_partial_payment(self): """ Create test to pay invoices (cust. inv + vendor bill) with partial payment """ # Test Customer Invoice inv_1 = self.create_invoice(amount=600) payment_register = Form(self.env['account.payment'].with_context( active_model='account.move', active_ids=inv_1.ids)) payment_register.payment_date = time.strftime('%Y') + '-07-15' payment_register.journal_id = self.bank_journal_euro payment_register.payment_method_id = self.payment_method_manual_in # Perform the partial payment by setting the amount at 550 instead of 600 payment_register.amount = 550 payment = payment_register.save() self.assertEqual(len(payment), 1) self.assertEqual(payment.invoice_ids[0].id, inv_1.id) self.assertAlmostEqual(payment.amount, 550) self.assertEqual(payment.payment_type, 'inbound') self.assertEqual(payment.partner_id, self.partner_agrolait) self.assertEqual(payment.partner_type, 'customer') # Test Vendor Bill inv_2 = self.create_invoice(amount=500, type='in_invoice', partner=self.partner_china_exp.id) payment_register = Form(self.env['account.payment'].with_context( active_model='account.move', active_ids=inv_2.ids)) payment_register.payment_date = time.strftime('%Y') + '-07-15' payment_register.journal_id = self.bank_journal_euro payment_register.payment_method_id = self.payment_method_manual_in # Perform the partial payment by setting the amount at 300 instead of 500 payment_register.amount = 300 payment = payment_register.save() self.assertEqual(len(payment), 1) self.assertEqual(payment.invoice_ids[0].id, inv_2.id) self.assertAlmostEqual(payment.amount, 300) self.assertEqual(payment.payment_type, 'outbound') self.assertEqual(payment.partner_id, self.partner_china_exp) self.assertEqual(payment.partner_type, 'supplier')
def test_out_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 1000. # We set quantity = 4, discount = 50%, price_unit = 400. The debit/credit fields don't change because (4 * 500) * 0.5 = 1000. line_form.quantity = 4 line_form.discount = 50 line_form.price_unit = 500 move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'quantity': 4, 'discount': 50.0, 'price_unit': 500.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 = 1000 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, 'debit': 0.0, }, self.product_line_vals_2, { **self.tax_line_vals_1, 'price_unit': 30.0, 'price_subtotal': 30.0, 'price_total': 30.0, 'amount_currency': 30.0, 'debit': 30.0, }, self.tax_line_vals_2, { **self.term_line_vals_1, 'price_unit': -260.0, 'price_subtotal': -260.0, 'price_total': -260.0, 'amount_currency': -260.0, 'credit': 260.0, }, ], { **self.move_vals, 'amount_untaxed': 200.0, 'amount_tax': 60.0, 'amount_total': 260.0, })
def _import_xml_invoice(self, content, attachment): ''' Extract invoice values from the E-Invoice xml tree passed as parameter. :param content: The tree of the xml file. :return: A dictionary containing account.invoice values to create/update it. ''' try: tree = etree.fromstring(content) except: _logger.info('Error during decoding XML file') return self.env['account.invoice'] invoices = self.env['account.invoice'] # 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', namespaces=tree.nsmap): elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento', namespaces=tree.nsmap) if elements and elements[0].text and elements[0].text == 'TD01': self_ctx = self.with_context(type='in_invoice') elif elements and elements[0].text and elements[0].text == 'TD04': self_ctx = self.with_context(type='in_refund') else: _logger.info( _('Document type not managed: %s.') % (elements[0].text)) # type must be present in the context to get the right behavior of the _default_journal method (account.invoice). # journal_id must be present in the context to get the right behavior of the _default_account method (account.invoice.line). elements = tree.xpath('//CessionarioCommittente//IdCodice', namespaces=tree.nsmap) company = elements and self.env['res.company'].search( [('vat', 'ilike', elements[0].text)], limit=1) if not company: elements = tree.xpath( '//CessionarioCommittente//CodiceFiscale', namespaces=tree.nsmap) company = elements and self.env['res.company'].search( [('l10n_it_codice_fiscale', 'ilike', elements[0].text)], limit=1) if company: self_ctx = self_ctx.with_context(company_id=company.id) else: company = self.env.user.company_id if elements: _logger.info( _('Company not found with codice fiscale: %s. The company\'s user is set by default.' ) % elements[0].text) else: _logger.info( _('Company not found. The company\'s user is set by default.' )) if not self.env.user._is_superuser(): if self.env.user.company_id != company: raise UserError( _("You can only import invoice concern your current company: %s" ) % self.env.user.company_id.display_name) journal_id = self_ctx._default_journal().id self_ctx = self_ctx.with_context(journal_id=journal_id) # self could be a single record (editing) or be empty (new). with Form(self_ctx, view='account.invoice_supplier_form') as invoice_form: message_to_log = [] invoice_form.company_id = company # Refund type. # TD01 == invoice # TD02 == advance/down payment on invoice # TD03 == advance/down payment on fee # TD04 == credit note # TD05 == debit note # TD06 == fee elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento', namespaces=tree.nsmap) if elements and elements[0].text and elements[0].text == 'TD01': invoice_form.type = 'in_invoice' elif elements and elements[0].text and elements[ 0].text == 'TD04': invoice_form.type = 'in_refund' # Partner (first step to avoid warning 'Warning! You must first select a partner.'). <1.2> elements = tree.xpath('//CedentePrestatore//IdCodice', namespaces=tree.nsmap) 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', namespaces=tree.nsmap) partner = elements and self.env['res.partner'].search( [ '&', ('l10n_it_codice_fiscale', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False) ], limit=1) if not partner: elements = tree.xpath('//DatiTrasmissione//Email', namespaces=tree.nsmap) 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:" ), self._compose_info_message(tree, './/CedentePrestatore'))) # Numbering attributed by the transmitter. <1.1.2> elements = tree.xpath('//ProgressivoInvio', namespaces=tree.nsmap) if elements: invoice_form.name = elements[0].text elements = body_tree.xpath('.//DatiGeneraliDocumento//Numero', namespaces=body_tree.nsmap) if elements: invoice_form.reference = elements[0].text # Currency. <2.1.1.2> elements = body_tree.xpath('.//DatiGeneraliDocumento/Divisa', namespaces=body_tree.nsmap) 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.user.company_id.currency_id and currency.active: invoice_form.currency_id = currency # Date. <2.1.1.3> elements = body_tree.xpath('.//DatiGeneraliDocumento/Data', namespaces=body_tree.nsmap) if elements: date_str = elements[0].text date_obj = datetime.strptime( date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT) invoice_form.date_invoice = date_obj.strftime( DEFAULT_FACTUR_ITALIAN_DATE_FORMAT) # Dati Bollo. <2.1.1.6> elements = body_tree.xpath( './/DatiGeneraliDocumento/DatiBollo/ImportoBollo', namespaces=body_tree.nsmap) 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', namespaces=body_tree.nsmap) total_discount_amount = 0.0 if discount_elements: for discount_element in discount_elements: discount_line = discount_element.xpath( './/Tipo', namespaces=body_tree.nsmap) discount_sign = -1 if discount_line and discount_line[0].text == 'SC': discount_sign = 1 discount_percentage = discount_element.xpath( './/Percentuale', namespaces=body_tree.nsmap) 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', namespaces=body_tree.nsmap) 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', namespaces=body_tree.nsmap) for element in elements: invoice_form.comment = '%s%s\n' % (invoice_form.comment 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, namespaces=body_tree.nsmap) if elements: for element in elements: message_to_log.append( "%s %s<br/>%s" % (document_type, _("from XML file:"), self._compose_info_message(element, '.'))) # Dati DDT. <2.1.8> elements = body_tree.xpath('.//DatiGenerali/DatiDDT', namespaces=body_tree.nsmap) if elements: message_to_log.append( "%s<br/>%s" % (_("Transport informations from XML file:"), self._compose_info_message( body_tree, './/DatiGenerali/DatiDDT'))) # Due date. <2.4.2.5> elements = body_tree.xpath( './/DatiPagamento/DettaglioPagamento/DataScadenzaPagamento', namespaces=body_tree.nsmap) if elements: date_str = elements[0].text date_obj = datetime.strptime( date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT) invoice_form.date_due = fields.Date.to_string(date_obj) # Total amount. <2.4.2.6> elements = body_tree.xpath('.//ImportoPagamento', namespaces=body_tree.nsmap) 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> elements = body_tree.xpath( './/DatiPagamento/DettaglioPagamento/IBAN', namespaces=body_tree.nsmap) 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:" ), self._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', namespaces=body_tree.nsmap) if elements: message_to_log.append("%s<br/>%s" % ( _("Bank account not found, useful informations from XML file:" ), self._compose_info_message(body_tree, './/DatiPagamento'))) # Invoice lines. <2.2.1> elements = body_tree.xpath('.//DettaglioLinee', namespaces=body_tree.nsmap) if elements: for element in elements: with invoice_form.invoice_line_ids.new( ) as invoice_line_form: # Sequence. line_elements = element.xpath( './/NumeroLinea', namespaces=body_tree.nsmap) if line_elements: invoice_line_form.sequence = int( line_elements[0].text) * 2 # Product. line_elements = element.xpath( './/Descrizione', namespaces=body_tree.nsmap) if line_elements: invoice_line_form.name = " ".join( line_elements[0].text.split()) elements_code = element.xpath( './/CodiceArticolo', namespaces=body_tree.nsmap) if elements_code: for element_code in elements_code: type_code = element_code.xpath( './/CodiceTipo', namespaces=body_tree.nsmap)[0] code = element_code.xpath( './/CodiceValore', namespaces=body_tree.nsmap)[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', namespaces=body_tree.nsmap)[0] product = self.env[ 'product.product'].search([ ('default_code', '=', code.text) ]) if product: invoice_line_form.product_id = product break # Price Unit. line_elements = element.xpath( './/PrezzoUnitario', namespaces=body_tree.nsmap) if line_elements: invoice_line_form.price_unit = float( line_elements[0].text) # Quantity. line_elements = element.xpath( './/Quantita', namespaces=body_tree.nsmap) if line_elements: invoice_line_form.quantity = float( line_elements[0].text) else: invoice_line_form.quantity = 1 # Taxes tax_element = element.xpath( './/AliquotaIVA', namespaces=body_tree.nsmap) natura_element = element.xpath( './/Natura', namespaces=body_tree.nsmap) invoice_line_form.invoice_line_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.invoice_line_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', namespaces=body_tree.nsmap) 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', namespaces=body_tree.nsmap) discount_sign = -1 if discount_line and discount_line[ 0].text == 'SC': discount_sign = 1 discount_percentage = line_element.xpath( './/Percentuale', namespaces=body_tree.nsmap) 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', namespaces=body_tree.nsmap) 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: ' ) + invoice_line_form.name else: discount["name"] = _( 'EXTRA CHARGE: ' ) + invoice_line_form.name discount["amount"] = total_discount_amount discount["tax"] = [] for tax in invoice_line_form.invoice_line_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.invoice_line_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', namespaces=body_tree.nsmap) if elements: for element in elements: name_attachment = element.xpath( './/NomeAttachment', namespaces=body_tree.nsmap)[0].text attachment_64 = str.encode( element.xpath('.//Attachment', namespaces=body_tree.nsmap)[0].text) attachment_64 = self.env['ir.attachment'].create({ 'name': name_attachment, 'datas': attachment_64, 'datas_fname': name_attachment, 'type': 'binary', }) # default_res_id is had to context to avoid facturx to import his content new_invoice.with_context( 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) if attachment: new_invoice.l10n_it_einvoice_name = attachment.name attachment.write({ 'res_model': 'account.invoice', 'res_id': new_invoice.id }) new_invoice.message_post(attachment_ids=[attachment.id]) invoices += new_invoice return invoices
def test_sale_mrp_kit_bom_cogs(self): """Check invoice COGS aml after selling and delivering a product with Kit BoM having another product with Kit BoM as component""" # ---------------------------------------------- # BoM of Kit A: # - BoM Type: Kit # - Quantity: 3 # - Components: # * 2 x Kit B # * 1 x Component A (Cost: $3, Storable) # # BoM of Kit B: # - BoM Type: Kit # - Quantity: 10 # - Components: # * 2 x Component B (Cost: $4, Storable) # * 3 x Component BB (Cost: $5, Consumable) # ---------------------------------------------- self.env.user.company_id.anglo_saxon_accounting = True self.env.ref('base.USD').active = True self.stock_input_account = self.env['account.account'].create({ 'name': 'Stock Input', 'code': 'StockIn', 'user_type_id': self.env.ref('account.data_account_type_current_assets').id, }) self.stock_output_account = self.env['account.account'].create({ 'name': 'Stock Output', 'code': 'StockOut', 'reconcile': True, 'user_type_id': self.env.ref('account.data_account_type_current_assets').id, }) self.stock_valuation_account = self.env['account.account'].create({ 'name': 'Stock Valuation', 'code': 'StockVal', 'user_type_id': self.env.ref('account.data_account_type_current_assets').id, }) self.expense_account = self.env['account.account'].create({ 'name': 'Expense Account', 'code': 'Exp', 'user_type_id': self.env.ref('account.data_account_type_expenses').id, }) self.income_account = self.env['account.account'].create({ 'name': 'Income Account', 'code': 'Inc', 'user_type_id': self.env.ref('account.data_account_type_expenses').id, }) self.stock_journal = self.env['account.journal'].create({ 'name': 'Stock Journal', 'code': 'STJTEST', 'type': 'general', }) self.recv_account = self.env['account.account'].create({ 'name': 'account receivable', 'code': 'RECV', 'user_type_id': self.env.ref('account.data_account_type_receivable').id, 'reconcile': True, }) self.pay_account = self.env['account.account'].create({ 'name': 'account payable', 'code': 'PAY', 'user_type_id': self.env.ref('account.data_account_type_payable').id, 'reconcile': True, }) self.customer = self.env['res.partner'].create({ 'name': 'customer', 'property_account_receivable_id': self.recv_account.id, 'property_account_payable_id': self.pay_account.id, }) self.journal_sale = self.env['account.journal'].create({ 'name': 'Sale Journal - Test', 'code': 'AJ-SALE', 'type': 'sale', 'company_id': self.env.user.company_id.id, }) self.component_a = self._create_product('Component A', 'product', 3.00) self.component_b = self._create_product('Component B', 'product', 4.00) self.component_bb = self._create_product('Component BB', 'consu', 5.00) self.kit_a = self._create_product('Kit A', 'product', 0.00) self.kit_b = self._create_product('Kit B', 'consu', 0.00) self.kit_a.write({ 'categ_id': self.env.ref('product.product_category_all').id, 'property_account_expense_id': self.expense_account.id, 'property_account_income_id': self.income_account.id, }) self.kit_a.categ_id.write({ 'property_stock_account_input_categ_id': self.stock_input_account.id, 'property_stock_account_output_categ_id': self.stock_output_account.id, 'property_stock_valuation_account_id': self.stock_valuation_account.id, 'property_stock_journal': self.stock_journal.id, 'property_valuation': 'real_time', }) # Create BoM for Kit A bom_product_form = Form(self.env['mrp.bom']) bom_product_form.product_id = self.kit_a bom_product_form.product_tmpl_id = self.kit_a.product_tmpl_id bom_product_form.product_qty = 3.0 bom_product_form.type = 'phantom' with bom_product_form.bom_line_ids.new() as bom_line: bom_line.product_id = self.kit_b bom_line.product_qty = 2.0 with bom_product_form.bom_line_ids.new() as bom_line: bom_line.product_id = self.component_a bom_line.product_qty = 1.0 self.bom_a = bom_product_form.save() # Create BoM for Kit B bom_product_form = Form(self.env['mrp.bom']) bom_product_form.product_id = self.kit_b bom_product_form.product_tmpl_id = self.kit_b.product_tmpl_id bom_product_form.product_qty = 10.0 bom_product_form.type = 'phantom' with bom_product_form.bom_line_ids.new() as bom_line: bom_line.product_id = self.component_b bom_line.product_qty = 2.0 with bom_product_form.bom_line_ids.new() as bom_line: bom_line.product_id = self.component_bb bom_line.product_qty = 3.0 self.bom_b = bom_product_form.save() so = self.env['sale.order'].create({ 'partner_id': self.customer.id, 'order_line': [ (0, 0, { 'name': self.kit_a.name, 'product_id': self.kit_a.id, 'product_uom_qty': 1.0, 'product_uom': self.kit_a.uom_id.id, 'price_unit': 1, 'tax_id': False, })], }) so.action_confirm() so.picking_ids.move_ids.quantity_done = 1 so.picking_ids.button_validate() invoice = so.with_context(default_journal_id=self.journal_sale.id)._create_invoices() invoice.action_post() # Check the resulting accounting entries amls = invoice.line_ids self.assertEqual(len(amls), 4) stock_out_aml = amls.filtered(lambda aml: aml.account_id == self.stock_output_account) self.assertEqual(stock_out_aml.debit, 0) self.assertAlmostEqual(stock_out_aml.credit, 1.53, "Should not include the value of consumable component") cogs_aml = amls.filtered(lambda aml: aml.account_id == self.expense_account) self.assertAlmostEqual(cogs_aml.debit, 1.53, "Should not include the value of consumable component") self.assertEqual(cogs_aml.credit, 0)
def test_invoice_shipment(self): """ Tests the case into which we make the invoice first, and then receive the goods. """ # Create a PO and an invoice for it test_product = self.test_product_order purchase_order = self._create_purchase(test_product, '2017-12-01') invoice = self._create_invoice_for_po(purchase_order, '2017-12-23') invoice_line = self.env['account.invoice.line'].search([('invoice_id', '=', invoice.id)]) invoice_line.quantity = 1 # The currency rate changes self.env['res.currency.rate'].create({ 'currency_id': self.currency_one.id, 'company_id': self.company.id, 'rate': 13.834739702, 'name': '2017-12-22', }) # Validate the invoice and refund the goods invoice.action_invoice_open() self._process_pickings(purchase_order.picking_ids, date='2017-12-24') picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)]) self.check_reconciliation(invoice, picking) # The currency rate changes again self.env['res.currency.rate'].create({ 'currency_id': self.currency_one.id, 'company_id': self.company.id, 'rate': 10.54739702, 'name': '2018-01-01', }) # Return the goods and refund the invoice stock_return_picking_form = Form(self.env['stock.return.picking'] .with_context(active_ids=picking.ids, active_id=picking.ids[0], active_model='stock.picking')) stock_return_picking = stock_return_picking_form.save() stock_return_picking.product_return_moves.quantity = 1.0 stock_return_picking_action = stock_return_picking.create_returns() return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id']) return_pick.action_assign() return_pick.move_lines.quantity_done = 1 return_pick.action_done() self._change_pickings_date(return_pick, '2018-01-13') # The currency rate changes again self.env['res.currency.rate'].create({ 'currency_id': self.currency_one.id, 'company_id': self.company.id, 'rate': 9.56564564, 'name': '2018-03-12', }) # Refund the invoice refund_invoice_wiz = self.env['account.invoice.refund'].with_context(active_ids=[invoice.id]).create({ 'description': 'test_invoice_shipment_refund', 'filter_refund': 'cancel', 'date': '2018-03-15', 'date_invoice': '2018-03-15', }) refund_invoice_wiz.invoice_refund() # Check the result refund_invoice = self.env['account.invoice'].search([('name', '=', 'test_invoice_shipment_refund')])[0] self.assertTrue(invoice.state == refund_invoice.state == 'paid'), "Invoice and refund should both be in 'Paid' state" self.check_reconciliation(refund_invoice, return_pick)
def test_reset_avco_kit(self): """ Test a specific use case : One product with 2 variant, each variant has its own BoM with either component_1 or component_2. Create a SO for one of the variant, confirm, cancel, reset to draft and then change the product of the SO -> There should be no traceback """ component_1 = self.env['product.product'].create({'name': 'compo 1'}) component_2 = self.env['product.product'].create({'name': 'compo 2'}) product_category = self.env['product.category'].create({ 'name': 'test avco kit', 'property_cost_method': 'average' }) attributes = self.env['product.attribute'].create({'name': 'Legs'}) steel_legs = self.env['product.attribute.value'].create({'attribute_id': attributes.id, 'name': 'Steel'}) aluminium_legs = self.env['product.attribute.value'].create( {'attribute_id': attributes.id, 'name': 'Aluminium'}) product = self.env['product.product'].create({ 'name': 'test product', 'categ_id': product_category.id, 'attribute_line_ids': [(0, 0, { 'attribute_id': attributes.id, 'value_ids': [(6, 0, [steel_legs.id, aluminium_legs.id])] })] }) product_variant_ids = product.product_variant_ids.search([('id', '!=', product.id)]) product_variant_ids[0].categ_id.property_cost_method = 'average' product_variant_ids[1].categ_id.property_cost_method = 'average' # BoM 1 with component_1 self.env['mrp.bom'].create({ 'product_id': product_variant_ids[0].id, 'product_tmpl_id': product_variant_ids[0].product_tmpl_id.id, 'product_qty': 1.0, 'consumption': 'flexible', 'type': 'phantom', 'bom_line_ids': [(0, 0, {'product_id': component_1.id, 'product_qty': 1})] }) # BoM 2 with component_2 self.env['mrp.bom'].create({ 'product_id': product_variant_ids[1].id, 'product_tmpl_id': product_variant_ids[1].product_tmpl_id.id, 'product_qty': 1.0, 'consumption': 'flexible', 'type': 'phantom', 'bom_line_ids': [(0, 0, {'product_id': component_2.id, 'product_qty': 1})] }) partner = self.env['res.partner'].create({'name': 'Testing Man'}) so = self.env['sale.order'].create({ 'partner_id': partner.id, }) # Create the order line self.env['sale.order.line'].create({ 'name': "Order line", 'product_id': product_variant_ids[0].id, 'order_id': so.id, }) so.action_confirm() so.action_cancel() so.action_draft() with Form(so) as so_form: with so_form.order_line.edit(0) as order_line_change: # The actual test, there should be no traceback here order_line_change.product_id = product_variant_ids[1]
def test_out_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 = 1200 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': 1200.0, 'price_subtotal': 1000.0, 'price_total': 1470.0, 'tax_ids': (self.tax_sale_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_tax_sale'].id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': 1.0, 'discount': 0.0, 'price_unit': 80.0, 'price_subtotal': 80.0, 'price_total': 88.0, 'tax_ids': child_tax_2.ids, 'tax_line_id': child_tax_1.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': 80.0, 'debit': 80.0, 'credit': 0.0, 'date_maturity': False, 'tax_exigible': False, }, { 'name': child_tax_1.name, 'product_id': False, 'account_id': self.company_data['default_account_revenue'].id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': 1.0, 'discount': 0.0, 'price_unit': 120.0, 'price_subtotal': 120.0, 'price_total': 132.0, 'tax_ids': child_tax_2.ids, 'tax_line_id': child_tax_1.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': 120.0, 'debit': 120.0, 'credit': 0.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': 120.0, 'price_subtotal': 120.0, 'price_total': 120.0, 'tax_ids': [], 'tax_line_id': child_tax_2.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': 120.0, 'debit': 120.0, 'credit': 0.0, 'date_maturity': False, 'tax_exigible': False, }, { **self.term_line_vals_1, 'price_unit': -1730.0, 'price_subtotal': -1730.0, 'price_total': -1730.0, 'amount_currency': -1730.0, 'credit': 1730.0, }, ], { **self.move_vals, 'amount_untaxed': 1200.0, 'amount_tax': 530.0, 'amount_total': 1730.0, })
def test_report_forecast_1_mo_count(self): """ Creates and configures a product who could be produce and could be a component. Plans some producing and consumming MO and check the report values. """ # Create a variant attribute. product_chocolate = self.env['product.product'].create({ 'name': 'Chocolate', 'type': 'consu', }) product_chococake = self.env['product.product'].create({ 'name': 'Choco Cake', 'type': 'product', }) product_double_chococake = self.env['product.product'].create({ 'name': 'Double Choco Cake', 'type': 'product', }) # Creates two BOM: one creating a regular slime, one using regular slimes. bom_chococake = self.env['mrp.bom'].create({ 'product_id': product_chococake.id, 'product_tmpl_id': product_chococake.product_tmpl_id.id, 'product_uom_id': product_chococake.uom_id.id, 'product_qty': 1.0, 'type': 'normal', 'bom_line_ids': [ (0, 0, { 'product_id': product_chocolate.id, 'product_qty': 4 }), ], }) bom_double_chococake = self.env['mrp.bom'].create({ 'product_id': product_double_chococake.id, 'product_tmpl_id': product_double_chococake.product_tmpl_id.id, 'product_uom_id': product_double_chococake.uom_id.id, 'product_qty': 1.0, 'type': 'normal', 'bom_line_ids': [ (0, 0, { 'product_id': product_chococake.id, 'product_qty': 2 }), ], }) # Creates two MO: one for each BOM. mo_form = Form(self.env['mrp.production']) mo_form.product_id = product_chococake mo_form.bom_id = bom_chococake mo_form.product_qty = 10 mo_1 = mo_form.save() mo_form = Form(self.env['mrp.production']) mo_form.product_id = product_double_chococake mo_form.bom_id = bom_double_chococake mo_form.product_qty = 2 mo_2 = mo_form.save() report_values, docs, lines = self.get_report_forecast( product_template_ids=product_chococake.product_tmpl_id.ids) draft_picking_qty = docs['draft_picking_qty'] draft_production_qty = docs['draft_production_qty'] self.assertEqual(len(lines), 0, "Must have 0 line.") self.assertEqual(draft_picking_qty['in'], 0) self.assertEqual(draft_picking_qty['out'], 0) self.assertEqual(draft_production_qty['in'], 10) self.assertEqual(draft_production_qty['out'], 4) # Confirms the MO and checks the report lines. mo_1.action_confirm() mo_2.action_confirm() report_values, docs, lines = self.get_report_forecast( product_template_ids=product_chococake.product_tmpl_id.ids) draft_picking_qty = docs['draft_picking_qty'] draft_production_qty = docs['draft_production_qty'] self.assertEqual(len(lines), 2, "Must have two line.") line_1 = lines[0] line_2 = lines[1] self.assertEqual(line_1['document_in'].id, mo_1.id) self.assertEqual(line_1['quantity'], 4) self.assertEqual(line_1['document_out'].id, mo_2.id) self.assertEqual(line_2['document_in'].id, mo_1.id) self.assertEqual(line_2['quantity'], 6) self.assertEqual(line_2['document_out'], False) self.assertEqual(draft_picking_qty['in'], 0) self.assertEqual(draft_picking_qty['out'], 0) self.assertEqual(draft_production_qty['in'], 0) self.assertEqual(draft_production_qty['out'], 0)