forked from Mapan/odoo17e
480 lines
21 KiB
Python
480 lines
21 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import datetime
|
|
|
|
from odoo import fields, Command
|
|
from odoo.tests import tagged
|
|
from odoo.addons.l10n_ke_edi_oscu.tests.common import TestKeEdiCommon
|
|
|
|
|
|
class TestKeEdiStock(TestKeEdiCommon):
|
|
@classmethod
|
|
def setUpClass(cls, chart_template_ref='ke'):
|
|
super().setUpClass(chart_template_ref=chart_template_ref)
|
|
cls.partner_import = cls.env['res.partner'].create([{
|
|
'name': 'OPW Fluid Transfer Group EU B.V',
|
|
'street': 'Roggestraat 38',
|
|
'city': 'Nieuw-Vennep',
|
|
'zip': '2153 GC',
|
|
'country_id': cls.env.ref('base.nl').id,
|
|
'vat': 'NL800672835B01',
|
|
}])
|
|
|
|
# Set up products
|
|
cls.product_a.write({
|
|
'name': 'Zaxxon machine',
|
|
'type': 'product',
|
|
'taxes_id': [Command.set(cls.standard_rate_sales_tax.ids)],
|
|
'supplier_taxes_id': [Command.set(cls.standard_rate_purchase_tax.ids)],
|
|
'standard_price': 30,
|
|
'l10n_ke_product_type_code': '2',
|
|
'l10n_ke_origin_country_id': cls.env.ref('base.be').id,
|
|
'unspsc_code_id': cls.env['product.unspsc.code'].search([
|
|
('code', '=', '52161557'),
|
|
], limit=1).id,
|
|
'l10n_ke_packaging_unit_id': cls.env.ref('l10n_ke_edi_oscu.code_17_SK').id,
|
|
'l10n_ke_packaging_quantity': 5,
|
|
})
|
|
cls.product_b.write({
|
|
'name': 'Windowpane',
|
|
'type': 'product',
|
|
'taxes_id': [Command.set(cls.reduced_rate_sales_tax.ids)],
|
|
'supplier_taxes_id': [Command.set(cls.reduced_rate_purchase_tax.ids)],
|
|
'standard_price': 30,
|
|
'l10n_ke_product_type_code': '1',
|
|
'l10n_ke_origin_country_id': cls.env.ref('base.be').id,
|
|
'unspsc_code_id': cls.env['product.unspsc.code'].search([
|
|
('code', '=', '30171613'),
|
|
], limit=1).id,
|
|
'l10n_ke_packaging_unit_id': cls.env.ref('l10n_ke_edi_oscu.code_17_CR').id,
|
|
'l10n_ke_packaging_quantity': 1,
|
|
})
|
|
cls.product_import = cls.env['product.product'].create([{
|
|
'name': 'Bottom loading adaptor AL handle',
|
|
'type': 'product',
|
|
'taxes_id': [Command.set(cls.standard_rate_sales_tax.ids)],
|
|
'supplier_taxes_id': [Command.set(cls.standard_rate_purchase_tax.ids)],
|
|
'standard_price': 500,
|
|
'l10n_ke_product_type_code': '1',
|
|
'l10n_ke_origin_country_id': cls.env.ref('base.nl').id,
|
|
'unspsc_code_id': cls.env['product.unspsc.code'].search([
|
|
('code', '=', '25181700'),
|
|
], limit=1).id,
|
|
'l10n_ke_packaging_unit_id': cls.env.ref('l10n_ke_edi_oscu.code_17_CR').id,
|
|
'l10n_ke_packaging_quantity': 1,
|
|
}])
|
|
|
|
# Create initial quants
|
|
cls.customer_location = cls.env.ref('stock.stock_location_customers')
|
|
cls.supplier_location = cls.env.ref('stock.stock_location_suppliers')
|
|
cls.warehouse = cls.env['stock.warehouse'].search([('company_id', '=', cls.company_data['company'].id)], limit=1)
|
|
cls.stock_location = cls.warehouse.lot_stock_id
|
|
|
|
cls.env['stock.quant'].create({
|
|
'product_id': cls.product_a.id,
|
|
'location_id': cls.stock_location.id,
|
|
'quantity': 20.0,
|
|
})
|
|
|
|
cls.env['stock.quant'].create({
|
|
'product_id': cls.product_b.id,
|
|
'location_id': cls.stock_location.id,
|
|
'quantity': 20.0,
|
|
})
|
|
|
|
def _test_send_invoice_and_credit_note(self):
|
|
""" Send an invoice and a credit note.
|
|
We do this sequentially (first the invoice, its stock IO and stock master,
|
|
then the credit note, its stock IO and stock master).
|
|
"""
|
|
# Step 1: create invoice
|
|
invoice = self.init_invoice(
|
|
'out_invoice',
|
|
partner=self.partner_a,
|
|
invoice_date='2024-01-28',
|
|
products=[self.product_a, self.product_b]
|
|
)
|
|
invoice.invoice_line_ids[0].discount = 10
|
|
|
|
# Step 2: create sale order from invoice
|
|
action = invoice.action_l10n_ke_create_sale_order()
|
|
so = self.env['sale.order'].browse(action['res_id'])
|
|
so.action_confirm()
|
|
|
|
# Step 3: validate picking
|
|
picking = so.picking_ids
|
|
picking.button_validate()
|
|
|
|
# Step 4: send invoice
|
|
self.assertFalse(invoice.l10n_ke_validation_message)
|
|
invoice.action_post()
|
|
send_and_print = self.create_send_and_print(invoice, l10n_ke_checkbox_oscu=True)
|
|
with self.set_invoice_number(invoice), self.patch_cron_trigger() as mocked_trigger:
|
|
send_and_print.action_send_and_print()
|
|
|
|
self.assertTrue(invoice.l10n_ke_oscu_invoice_number)
|
|
self.assertTrue(invoice.l10n_ke_oscu_receipt_number)
|
|
self.assertTrue(invoice.l10n_ke_oscu_internal_data)
|
|
|
|
# Step 5: picking cron should get called.
|
|
mocked_trigger.assert_called()
|
|
|
|
# Step 6: create and validate return pickings
|
|
wizard_return = self.env['stock.return.picking'].with_context(active_id=picking.id, active_model='stock.picking').create({})
|
|
action = wizard_return.create_returns()
|
|
return_pickings = self.env['stock.picking'].browse(action['res_id'])
|
|
return_pickings.button_validate()
|
|
|
|
# Step 7: create credit note
|
|
credit_note = self.create_reversal(invoice)
|
|
self.assertFalse(credit_note.l10n_ke_validation_message)
|
|
credit_note.action_post()
|
|
|
|
# Step 8: send credit note
|
|
send_and_print = self.create_send_and_print(credit_note, l10n_ke_checkbox_oscu=True)
|
|
with self.set_invoice_number(credit_note), self.patch_cron_trigger() as mocked_trigger:
|
|
send_and_print.action_send_and_print()
|
|
|
|
self.assertTrue(credit_note.l10n_ke_oscu_invoice_number)
|
|
self.assertTrue(credit_note.l10n_ke_oscu_receipt_number)
|
|
self.assertTrue(credit_note.l10n_ke_oscu_internal_data)
|
|
|
|
# Step 9: picking cron should get called.
|
|
mocked_trigger.assert_called()
|
|
|
|
def _test_send_invoiced_stock_moves(self):
|
|
""" This test ensures that the Stock IO and Stock Master reflect the invoices that have been sent.
|
|
1) Create Sale Order & Invoice n.1, with 2 of each product, receive the products but don't send the invoice.
|
|
2) Create Sale Order & Invoice n.2, with 1 of each product, receive the products and send the invoice.
|
|
3) Stock IO and Stock Master should be sent with the quantities in Sale Order n.2.
|
|
4) Now send Invoice n.1.
|
|
5) Stock IO and Stock Master should be sent with the quantities in Sale Order n.1.
|
|
"""
|
|
# Step 1: create invoice 1 and sale order, and validate picking.
|
|
invoice_1 = self.init_invoice(
|
|
'out_invoice',
|
|
partner=self.partner_a,
|
|
invoice_date='2024-01-28',
|
|
products=[self.product_a, self.product_b],
|
|
)
|
|
invoice_1.invoice_line_ids.write({
|
|
'quantity': 2,
|
|
'discount': 10,
|
|
})
|
|
action = invoice_1.action_l10n_ke_create_sale_order()
|
|
so_1 = self.env['sale.order'].browse(action['res_id'])
|
|
so_1.action_confirm()
|
|
picking_1 = so_1.picking_ids
|
|
picking_1.button_validate()
|
|
self.assertFalse(invoice_1.l10n_ke_validation_message)
|
|
|
|
# Step 2: create invoice 2 and sale order, and validate picking.
|
|
invoice_2 = self.init_invoice(
|
|
'out_invoice',
|
|
partner=self.partner_a,
|
|
invoice_date='2024-01-28',
|
|
products=[self.product_a, self.product_b],
|
|
)
|
|
invoice_2.invoice_line_ids[0].discount = 10
|
|
action = invoice_2.action_l10n_ke_create_sale_order()
|
|
so_2 = self.env['sale.order'].browse(action['res_id'])
|
|
so_2.action_confirm()
|
|
picking_2 = so_2.picking_ids
|
|
picking_2.button_validate()
|
|
self.assertFalse(invoice_2.l10n_ke_validation_message)
|
|
|
|
# Step 3: Send invoice 2.
|
|
invoice_2.action_post()
|
|
send_and_print = self.create_send_and_print(invoice_2, l10n_ke_checkbox_oscu=True)
|
|
with self.set_invoice_number(invoice_2), self.patch_cron_trigger() as mocked_trigger:
|
|
send_and_print.action_send_and_print()
|
|
|
|
self.assertTrue(invoice_2.l10n_ke_oscu_invoice_number)
|
|
self.assertTrue(invoice_2.l10n_ke_oscu_receipt_number)
|
|
self.assertTrue(invoice_2.l10n_ke_oscu_internal_data)
|
|
|
|
# Step 4: Picking cron should get called for (and only for) the stock move related to invoice 2.
|
|
mocked_trigger.assert_called()
|
|
|
|
# Step 5: Send invoice 1.
|
|
invoice_1.action_post()
|
|
send_and_print = self.create_send_and_print(invoice_1, l10n_ke_checkbox_oscu=True)
|
|
with self.set_invoice_number(invoice_1), self.patch_cron_trigger() as mocked_trigger:
|
|
send_and_print.action_send_and_print()
|
|
|
|
self.assertTrue(invoice_1.l10n_ke_oscu_invoice_number)
|
|
self.assertTrue(invoice_1.l10n_ke_oscu_receipt_number)
|
|
self.assertTrue(invoice_1.l10n_ke_oscu_internal_data)
|
|
|
|
# Step 6: Picking cron should get called for (and only for) the stock move related to invoice 1.
|
|
mocked_trigger.assert_called()
|
|
|
|
def _test_get_vendor_bill(self):
|
|
# Step 1: Retrieve vendor bill
|
|
vendor_bill = self.env['account.move']._l10n_ke_oscu_fetch_purchases(self.company_data['company'])
|
|
|
|
expected_vendor_bill = {
|
|
'partner_id': self.partner_a.id,
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': fields.Date.from_string('2023-12-12'),
|
|
}
|
|
expected_vendor_bill_lines = [
|
|
{
|
|
'name': 'Zaxxon machine',
|
|
'product_id': self.product_a.id,
|
|
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
|
|
'quantity': 1,
|
|
'tax_ids': [self.standard_rate_purchase_tax.id],
|
|
'balance': 17499,
|
|
}, {
|
|
'name': 'window pane',
|
|
'product_id': self.product_b.id,
|
|
# In this case, the UoM in the JSON is unrecognized, so we take the product's default UoM.
|
|
# In general, that should be a good guess. However, because in this test we deliberately configured product_b
|
|
# to use dozens (in order to test conversions in other tests) it gives this nonsensical '12 dozens' result here.
|
|
'product_uom_id': self.env.ref('uom.product_uom_dozen').id,
|
|
'quantity': 12,
|
|
'tax_ids': [self.reduced_rate_purchase_tax.id],
|
|
'balance': 54000,
|
|
}, {
|
|
'name': '16%',
|
|
'product_id': None,
|
|
'product_uom_id': False,
|
|
'quantity': 0,
|
|
'tax_ids': [],
|
|
'balance': 2799.84,
|
|
}, {
|
|
'name': '8%',
|
|
'product_id': None,
|
|
'product_uom_id': False,
|
|
'quantity': 0,
|
|
'tax_ids': [],
|
|
'balance': 4320.00,
|
|
}, {
|
|
'name': '',
|
|
'product_id': None,
|
|
'product_uom_id': False,
|
|
'quantity': 0,
|
|
'tax_ids': [],
|
|
'balance': -78618.84,
|
|
}
|
|
]
|
|
self.assertInvoiceValues(vendor_bill, expected_vendor_bill_lines, expected_vendor_bill)
|
|
|
|
# Manually adjust the quantity and UoM of the 'Window Pane' line to 12 units.
|
|
vendor_bill.invoice_line_ids.filtered(lambda l: l.product_id == self.product_b).write({
|
|
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
|
|
'quantity': 12,
|
|
})
|
|
|
|
return vendor_bill
|
|
|
|
def _test_confirm_vendor_bill(self, vendor_bill):
|
|
# Step 2: create purchase order from vendor bill
|
|
action = vendor_bill.action_l10n_ke_create_purchase_order()
|
|
po = self.env['purchase.order'].browse(action['res_id'])
|
|
po.button_confirm()
|
|
|
|
# Step 3: validate picking
|
|
po.picking_ids.button_validate()
|
|
|
|
# Step 4: send vendor bill confirmation
|
|
vendor_bill.l10n_ke_payment_method_id = self.env.ref('l10n_ke_edi_oscu.code_07_05')
|
|
self.assertFalse(vendor_bill.l10n_ke_validation_message)
|
|
vendor_bill.action_post()
|
|
with self.patch_cron_trigger() as mocked_trigger:
|
|
vendor_bill.action_l10n_ke_oscu_confirm_vendor_bill()
|
|
|
|
# Step 5: picking cron should get called
|
|
mocked_trigger.assert_called()
|
|
|
|
def _test_get_custom_import(self):
|
|
# Step 1: Retrieve custom import
|
|
self.env['l10n_ke_edi.customs.import'].sudo()._receive_customs_import(self.company_data['company'])
|
|
custom_import = self.env['l10n_ke_edi.customs.import'].search([('company_id', '=', self.company_data['company'].id)])
|
|
|
|
expected_import_values = [{
|
|
'declaration_date': datetime.date(2023, 2, 1),
|
|
'declaration_number': '23NBOIM401172243',
|
|
'task_code': '20230208667984',
|
|
'item_seq': 1,
|
|
'supplier_name': 'OPW FLUID TRANSFER GROUP EUROPE B.V',
|
|
'item_name': 'TANKER TRAILER PARTS & ACCESSORIES API BOTTOM LOADING ADAPTOR AL HANDLE',
|
|
'number_packages': 17,
|
|
'package_unit_code_id': self.env.ref('l10n_ke_edi_oscu.code_17_CR').id,
|
|
'quantity': 2,
|
|
'uom_code_id': self.env.ref('l10n_ke_edi_oscu.code_10_U').id,
|
|
'hs_code': '87169000',
|
|
'origin_country_id': self.env.ref('base.nl').id,
|
|
'export_country_id': self.env.ref('base.nl').id,
|
|
}]
|
|
|
|
self.assertRecordValues(custom_import, expected_import_values)
|
|
return custom_import
|
|
|
|
def _test_confirm_custom_import(self, custom_import):
|
|
# Step 2: Add partner and product on custom import
|
|
custom_import.write({
|
|
'product_id': self.product_import.id,
|
|
'partner_id': self.partner_import.id,
|
|
})
|
|
self.assertRecordValues(custom_import, [{'warning_msg': False}])
|
|
|
|
# Step 3: create purchase order from custom import
|
|
action = custom_import.action_create_purchase_order()
|
|
po = self.env['purchase.order'].browse(action['res_id'])
|
|
po.button_confirm()
|
|
|
|
# Step 4: validate picking
|
|
po.picking_ids.button_validate()
|
|
|
|
# Step 5: match and approve custom import
|
|
custom_import.button_approve()
|
|
|
|
# Step 6: create vendor bill and send confirmation
|
|
po.action_create_invoice()
|
|
vendor_bill = po.invoice_ids
|
|
vendor_bill.write({
|
|
'invoice_date': fields.Date.today(),
|
|
'l10n_ke_payment_method_id': self.env.ref('l10n_ke_edi_oscu.code_07_05'),
|
|
})
|
|
self.assertFalse(vendor_bill.l10n_ke_validation_message)
|
|
vendor_bill.action_post()
|
|
with self.patch_cron_trigger() as mocked_trigger:
|
|
vendor_bill.action_l10n_ke_oscu_confirm_vendor_bill()
|
|
|
|
# Step 7: picking cron should get called
|
|
mocked_trigger.assert_called()
|
|
|
|
def _test_send_picking_between_branches(self):
|
|
# Step 1: Create Kakamega branch
|
|
self._test_create_branches()
|
|
branch = self.env['res.company'].search([('parent_id', '=', self.company_data['company'].id), ('name', '=like', 'KAKAMEGA%')])
|
|
branch.write({
|
|
'l10n_ke_oscu_cmc_key': self.branch_cmc_key,
|
|
'l10n_ke_oscu_user_agreement': True,
|
|
})
|
|
|
|
# Step 2: Create transfer from HQ to Kakamega
|
|
parent_delivery_type = self.env['stock.picking.type'].search([('company_id', '=', self.company_data['company'].id), ('code', '=', 'outgoing')], limit=1)
|
|
out_picking = self.env['stock.picking'].create({
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'partner_id': branch.partner_id.id,
|
|
'picking_type_id': parent_delivery_type.id,
|
|
'move_ids': [
|
|
Command.create({
|
|
'name': self.product_a.name,
|
|
'location_id': self.stock_location.id,
|
|
'location_dest_id': self.customer_location.id,
|
|
'product_id': self.product_a.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom': self.product_a.uom_id.id,
|
|
'description_picking': self.product_a.name,
|
|
})
|
|
]
|
|
})
|
|
out_picking.button_validate()
|
|
|
|
# Step 3: Run picking cron as superuser
|
|
self.env.ref('l10n_ke_edi_oscu_stock.ir_cron_send_stock_moves').method_direct_trigger()
|
|
|
|
# Step 4: Create receipt in Kakamega
|
|
branch_receipt_type = self.env['stock.picking.type'].search([('company_id', '=', branch.id), ('code', '=', 'incoming')], limit=1)
|
|
branch_stock_location = self.env['stock.warehouse'].search([('company_id', '=', branch.id)], limit=1).lot_stock_id
|
|
in_picking = self.env['stock.picking'].with_company(branch).create({
|
|
'location_id': branch_stock_location.id,
|
|
'location_dest_id': self.supplier_location.id,
|
|
'partner_id': self.company_data['company'].partner_id.id,
|
|
'picking_type_id': branch_receipt_type.id,
|
|
'move_ids': [
|
|
Command.create({
|
|
'name': self.product_a.name,
|
|
'location_id': self.supplier_location.id,
|
|
'location_dest_id': branch_stock_location.id,
|
|
'product_id': self.product_a.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom': self.product_a.uom_id.id,
|
|
'description_picking': self.product_a.name,
|
|
})
|
|
]
|
|
})
|
|
in_picking.button_validate()
|
|
|
|
# Step 5: Run picking cron as superuser
|
|
self.env.ref('l10n_ke_edi_oscu_stock.ir_cron_send_stock_moves').method_direct_trigger()
|
|
|
|
def _test_send_inventory_adjustment(self):
|
|
self.user.write({'groups_id': [Command.link(self.env.ref('stock.group_stock_user').id)]})
|
|
self.product_a.action_l10n_ke_oscu_save_item()
|
|
|
|
# Step 1: Create inventory adjustment
|
|
with self.patch_cron_trigger() as mocked_trigger:
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.product_a.id,
|
|
'location_id': self.stock_location.id,
|
|
'inventory_quantity_auto_apply': 21.0,
|
|
})
|
|
|
|
# Step 2: Check values of the created stock move
|
|
stock_move = self.env['stock.move'].search([
|
|
('company_id', '=', self.env.company.id),
|
|
('is_inventory', '=', True),
|
|
])
|
|
inventory_adjustment_location = self.env['ir.property']._get('property_stock_inventory', 'product.template')
|
|
expected_stock_move_vals = [{
|
|
'product_id': self.product_a.id,
|
|
'product_uom': self.product_a.uom_id.id,
|
|
'product_uom_qty': 1.0,
|
|
'location_id': inventory_adjustment_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
}]
|
|
expected_stock_move_line_vals = [{
|
|
'product_id': self.product_a.id,
|
|
'product_uom_id': self.product_a.uom_id.id,
|
|
'quantity': 1.0,
|
|
'location_id': inventory_adjustment_location.id,
|
|
'location_dest_id': self.stock_location.id,
|
|
}]
|
|
self.assertRecordValues(stock_move, expected_stock_move_vals)
|
|
self.assertRecordValues(stock_move.move_line_ids, expected_stock_move_line_vals)
|
|
|
|
# Step 3: Check that the cron was called.
|
|
mocked_trigger.assert_called()
|
|
|
|
|
|
@tagged('external', 'external_l10n', 'post_install', '-post_install_l10n', '-at_install', '-standard')
|
|
class TestKeEdiStockLive(TestKeEdiStock):
|
|
@classmethod
|
|
def setUpClass(cls, chart_template_ref='ke'):
|
|
super().setUpClass(chart_template_ref=chart_template_ref)
|
|
cls.is_live_test = True
|
|
|
|
def test_send_invoice_and_credit_note(self):
|
|
self._test_send_invoice_and_credit_note()
|
|
|
|
def test_send_invoiced_stock_moves(self):
|
|
self._test_send_invoiced_stock_moves()
|
|
|
|
def test_confirm_vendor_bill(self):
|
|
# This is mocked because there are no purchases on the test server to retrieve.
|
|
with self.patch_session([
|
|
('selectTrnsPurchaseSalesList', 'get_purchases', 'get_purchases_2'),
|
|
]):
|
|
vendor_bill = self._test_get_vendor_bill()
|
|
self._test_confirm_vendor_bill(vendor_bill)
|
|
|
|
def test_confirm_custom_import(self):
|
|
# This is mocked because there are no custom imports on the test server to retrieve.
|
|
with self.patch_session([
|
|
('selectImportItemList', 'get_imports', 'get_imports_1'),
|
|
]):
|
|
custom_import = self._test_get_custom_import()
|
|
self._test_confirm_custom_import(custom_import)
|
|
|
|
def test_send_picking_between_branches(self):
|
|
self._test_send_picking_between_branches()
|
|
|
|
def test_send_inventory_adjustment(self):
|
|
self._test_send_inventory_adjustment()
|