summaryrefslogtreecommitdiff
path: root/addons/sale_purchase/tests/test_sale_purchase.py
blob: 4f85ea2542b17b85110e07cf076ec9eb2a94efe6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo.exceptions import UserError, AccessError
from odoo.tests import tagged
from odoo.addons.sale_purchase.tests.common import TestCommonSalePurchaseNoChart


@tagged('-at_install', 'post_install')
class TestSalePurchase(TestCommonSalePurchaseNoChart):

    @classmethod
    def setUpClass(cls, chart_template_ref=None):
        super().setUpClass(chart_template_ref=chart_template_ref)

        # create a generic Sale Order with 2 classical products and a purchase service
        SaleOrder = cls.env['sale.order'].with_context(tracking_disable=True)
        cls.sale_order_1 = SaleOrder.create({
            'partner_id': cls.partner_a.id,
            'partner_invoice_id': cls.partner_a.id,
            'partner_shipping_id': cls.partner_a.id,
            'pricelist_id': cls.company_data['default_pricelist'].id,
        })
        cls.sol1_service_deliver = cls.env['sale.order.line'].create({
            'name': cls.company_data['product_service_delivery'].name,
            'product_id': cls.company_data['product_service_delivery'].id,
            'product_uom_qty': 1,
            'product_uom': cls.company_data['product_service_delivery'].uom_id.id,
            'price_unit': cls.company_data['product_service_delivery'].list_price,
            'order_id': cls.sale_order_1.id,
            'tax_id': False,
        })
        cls.sol1_product_order = cls.env['sale.order.line'].create({
            'name': cls.company_data['product_order_no'].name,
            'product_id': cls.company_data['product_order_no'].id,
            'product_uom_qty': 2,
            'product_uom': cls.company_data['product_order_no'].uom_id.id,
            'price_unit': cls.company_data['product_order_no'].list_price,
            'order_id': cls.sale_order_1.id,
            'tax_id': False,
        })
        cls.sol1_service_purchase_1 = cls.env['sale.order.line'].create({
            'name': cls.service_purchase_1.name,
            'product_id': cls.service_purchase_1.id,
            'product_uom_qty': 4,
            'product_uom': cls.service_purchase_1.uom_id.id,
            'price_unit': cls.service_purchase_1.list_price,
            'order_id': cls.sale_order_1.id,
            'tax_id': False,
        })

        cls.sale_order_2 = SaleOrder.create({
            'partner_id': cls.partner_a.id,
            'partner_invoice_id': cls.partner_a.id,
            'partner_shipping_id': cls.partner_a.id,
            'pricelist_id': cls.company_data['default_pricelist'].id,
        })
        cls.sol2_product_deliver = cls.env['sale.order.line'].create({
            'name': cls.company_data['product_delivery_no'].name,
            'product_id': cls.company_data['product_delivery_no'].id,
            'product_uom_qty': 5,
            'product_uom': cls.company_data['product_delivery_no'].uom_id.id,
            'price_unit': cls.company_data['product_delivery_no'].list_price,
            'order_id': cls.sale_order_2.id,
            'tax_id': False,
        })
        cls.sol2_service_order = cls.env['sale.order.line'].create({
            'name': cls.company_data['product_service_order'].name,
            'product_id': cls.company_data['product_service_order'].id,
            'product_uom_qty': 6,
            'product_uom': cls.company_data['product_service_order'].uom_id.id,
            'price_unit': cls.company_data['product_service_order'].list_price,
            'order_id': cls.sale_order_2.id,
            'tax_id': False,
        })
        cls.sol2_service_purchase_2 = cls.env['sale.order.line'].create({
            'name': cls.service_purchase_2.name,
            'product_id': cls.service_purchase_2.id,
            'product_uom_qty': 7,
            'product_uom': cls.service_purchase_2.uom_id.id,
            'price_unit': cls.service_purchase_2.list_price,
            'order_id': cls.sale_order_2.id,
            'tax_id': False,
        })

    def test_sale_create_purchase(self):
        """ Confirming 2 sales orders with a service that should create a PO, then cancelling the PO should shedule 1 next activity per SO """
        self.sale_order_1.action_confirm()
        self.sale_order_2.action_confirm()

        purchase_order = self.env['purchase.order'].search([('partner_id', '=', self.supplierinfo1.name.id), ('state', '=', 'draft')])
        purchase_lines_so1 = self.env['purchase.order.line'].search([('sale_line_id', 'in', self.sale_order_1.order_line.ids)])
        purchase_line1 = purchase_lines_so1[0]

        purchase_lines_so2 = self.env['purchase.order.line'].search([('sale_line_id', 'in', self.sale_order_2.order_line.ids)])
        purchase_line2 = purchase_lines_so2[0]

        self.assertEqual(len(purchase_order), 1, "Only one PO should have been created, from the 2 Sales orders")
        self.assertEqual(len(purchase_order.order_line), 2, "The purchase order should have 2 lines")
        self.assertEqual(len(purchase_lines_so1), 1, "Only one SO line from SO 1 should have create a PO line")
        self.assertEqual(len(purchase_lines_so2), 1, "Only one SO line from SO 2 should have create a PO line")
        self.assertEqual(len(purchase_order.activity_ids), 0, "No activity should be scheduled on the PO")
        self.assertEqual(purchase_order.state, 'draft', "The created PO should be in draft state")

        self.assertNotEqual(purchase_line1.product_id, purchase_line2.product_id, "The 2 PO line should have different products")
        self.assertEqual(purchase_line1.product_id, self.sol1_service_purchase_1.product_id, "The create PO line must have the same product as its mother SO line")
        self.assertEqual(purchase_line2.product_id, self.sol2_service_purchase_2.product_id, "The create PO line must have the same product as its mother SO line")

        purchase_order.button_cancel()

        self.assertEqual(len(self.sale_order_1.activity_ids), 1, "One activity should be scheduled on the SO 1 since the PO has been cancelled")
        self.assertEqual(self.sale_order_1.user_id, self.sale_order_1.activity_ids[0].user_id, "The activity should be assigned to the SO responsible")

        self.assertEqual(len(self.sale_order_2.activity_ids), 1, "One activity should be scheduled on the SO 2 since the PO has been cancelled")
        self.assertEqual(self.sale_order_2.user_id, self.sale_order_2.activity_ids[0].user_id, "The activity should be assigned to the SO responsible")

    def test_uom_conversion(self):
        """ Test generated PO use the right UoM according to product configuration """
        self.sale_order_2.action_confirm()
        purchase_line = self.env['purchase.order.line'].search([('sale_line_id', '=', self.sol2_service_purchase_2.id)])  # only one line

        self.assertTrue(purchase_line, "The SO line should generate a PO line")
        self.assertEqual(purchase_line.product_uom, self.service_purchase_2.uom_po_id, "The UoM on the purchase line should be the one from the product configuration")
        self.assertNotEqual(purchase_line.product_uom, self.sol2_service_purchase_2.product_uom, "As the product configuration, the UoM on the SO line should still be different from the one on the PO line")
        self.assertEqual(purchase_line.product_qty, self.sol2_service_purchase_2.product_uom_qty * 12, "The quantity from the SO should be converted with th UoM factor on the PO line")

    def test_no_supplier(self):
        """ Test confirming SO with product with no supplier raise Error """
        # delete the suppliers
        self.supplierinfo1.unlink()
        # confirm the SO should raise UserError
        with self.assertRaises(UserError):
            self.sale_order_1.action_confirm()

    def test_reconfirm_sale_order(self):
        """ Confirm SO, cancel it, then re-confirm it should not regenerate a purchase line """
        self.sale_order_1.action_confirm()

        purchase_order = self.env['purchase.order'].search([('partner_id', '=', self.supplierinfo1.name.id), ('state', '=', 'draft')])
        purchase_lines = self.env['purchase.order.line'].search([('sale_line_id', 'in', self.sale_order_1.order_line.ids)])
        purchase_line = purchase_lines[0]

        self.assertEqual(len(purchase_lines), 1, "Only one purchase line should be created on SO confirmation")
        self.assertEqual(len(purchase_order), 1, "One purchase order should have been created on SO confirmation")
        self.assertEqual(len(purchase_order.order_line), 1, "Only one line on PO, after SO confirmation")
        self.assertEqual(purchase_order, purchase_lines.order_id, "The generated purchase line should be in the generated purchase order")
        self.assertEqual(purchase_order.state, 'draft', "Generated purchase should be in draft state")
        self.assertEqual(purchase_line.price_unit, self.supplierinfo1.price, "Purchase line price is the one from the supplier")
        self.assertEqual(purchase_line.product_qty, self.sol1_service_purchase_1.product_uom_qty, "Quantity on SO line is not the same on the purchase line (same UoM)")

        self.sale_order_1.action_cancel()

        self.assertEqual(len(purchase_order.activity_ids), 1, "One activity should be scheduled on the PO since a SO has been cancelled")

        purchase_order = self.env['purchase.order'].search([('partner_id', '=', self.supplierinfo1.name.id), ('state', '=', 'draft')])
        purchase_lines = self.env['purchase.order.line'].search([('sale_line_id', 'in', self.sale_order_1.order_line.ids)])
        purchase_line = purchase_lines[0]

        self.assertEqual(len(purchase_lines), 1, "Always one purchase line even after SO cancellation")
        self.assertTrue(purchase_order, "Always one purchase order even after SO cancellation")
        self.assertEqual(len(purchase_order.order_line), 1, "Still one line on PO, even after SO cancellation")
        self.assertEqual(purchase_order, purchase_lines.order_id, "The generated purchase line should still be in the generated purchase order")
        self.assertEqual(purchase_order.state, 'draft', "Generated purchase should still be in draft state")
        self.assertEqual(purchase_line.price_unit, self.supplierinfo1.price, "Purchase line price is still the one from the supplier")
        self.assertEqual(purchase_line.product_qty, self.sol1_service_purchase_1.product_uom_qty, "Quantity on SO line should still be the same on the purchase line (same UoM)")

        self.sale_order_1.action_draft()
        self.sale_order_1.action_confirm()

        purchase_order = self.env['purchase.order'].search([('partner_id', '=', self.supplierinfo1.name.id), ('state', '=', 'draft')])
        purchase_lines = self.env['purchase.order.line'].search([('sale_line_id', 'in', self.sale_order_1.order_line.ids)])
        purchase_line = purchase_lines[0]

        self.assertEqual(len(purchase_lines), 1, "Still only one purchase line should be created even after SO reconfirmation")
        self.assertEqual(len(purchase_order), 1, "Still one purchase order should be after SO reconfirmation")
        self.assertEqual(len(purchase_order.order_line), 1, "Only one line on PO, even after SO reconfirmation")
        self.assertEqual(purchase_order, purchase_lines.order_id, "The generated purchase line should be in the generated purchase order")
        self.assertEqual(purchase_order.state, 'draft', "Generated purchase should be in draft state")
        self.assertEqual(purchase_line.price_unit, self.supplierinfo1.price, "Purchase line price is the one from the supplier")
        self.assertEqual(purchase_line.product_qty, self.sol1_service_purchase_1.product_uom_qty, "Quantity on SO line is not the same on the purchase line (same UoM)")

    def test_update_ordered_sale_quantity(self):
        """ Test the purchase order behovior when changing the ordered quantity on the sale order line.
            Increase of qty on the SO
            - If PO is draft ['draft', 'sent', 'to approve'] : increase the quantity on the PO
            - If PO is confirmed ['purchase', 'done', 'cancel'] : create a new PO

            Decrease of qty on the SO
            - If PO is draft  ['draft', 'sent', 'to approve'] : next activity on the PO
            - If PO is confirmed ['purchase', 'done', 'cancel'] : next activity on the PO
        """
        self.sale_order_1.action_confirm()

        purchase_order = self.env['purchase.order'].search([('partner_id', '=', self.supplierinfo1.name.id), ('state', '=', 'draft')])
        purchase_lines = self.env['purchase.order.line'].search([('sale_line_id', 'in', self.sale_order_1.order_line.ids)])
        purchase_line = purchase_lines[0]

        self.assertEqual(purchase_order.state, 'draft', "The created purchase should be in draft state")
        self.assertFalse(purchase_order.activity_ids, "There is no activities on the PO")
        self.assertEqual(purchase_line.product_qty, self.sol1_service_purchase_1.product_uom_qty, "Quantity on SO line is not the same on the purchase line (same UoM)")

        # increase the ordered quantity on sale line
        self.sol1_service_purchase_1.write({'product_uom_qty': self.sol1_service_purchase_1.product_uom_qty + 12})  # product_uom_qty = 16
        self.assertEqual(purchase_line.product_qty, self.sol1_service_purchase_1.product_uom_qty, "The quantity of draft PO line should be increased as the one from the sale line changed")

        sale_line_old_quantity = self.sol1_service_purchase_1.product_uom_qty

        # decrease the ordered quantity on sale line
        self.sol1_service_purchase_1.write({'product_uom_qty': self.sol1_service_purchase_1.product_uom_qty - 3})  # product_uom_qty = 13
        self.assertEqual(len(purchase_order.activity_ids), 1, "One activity should have been created on the PO")
        self.assertEqual(purchase_order.activity_ids.user_id, purchase_order.user_id, "Activity assigned to PO responsible")
        self.assertEqual(purchase_order.activity_ids.state, 'today', "Activity is for today, as it is urgent")

        # confirm the PO
        purchase_order.button_confirm()

        # decrease the ordered quantity on sale line
        self.sol1_service_purchase_1.write({'product_uom_qty': self.sol1_service_purchase_1.product_uom_qty - 5})  # product_uom_qty = 8

        purchase_order.invalidate_cache()  # Note: creating a second activity will not refresh the cache

        self.assertEqual(purchase_line.product_qty, sale_line_old_quantity, "The quantity on the PO line should not have changed.")
        self.assertEqual(len(purchase_order.activity_ids), 2, "a second activity should have been created on the PO")
        self.assertEqual(purchase_order.activity_ids.mapped('user_id'), purchase_order.user_id, "Activities assigned to PO responsible")
        self.assertEqual(purchase_order.activity_ids.mapped('state'), ['today', 'today'], "Activities are for today, as it is urgent")

        # increase the ordered quantity on sale line
        delta = 8
        self.sol1_service_purchase_1.write({'product_uom_qty': self.sol1_service_purchase_1.product_uom_qty + delta})  # product_uom_qty = 16

        self.assertEqual(purchase_line.product_qty, sale_line_old_quantity, "The quantity on the PO line should not have changed.")
        self.assertEqual(len(purchase_order.activity_ids), 2, "Always 2 activity on confirmed the PO")

        purchase_order2 = self.env['purchase.order'].search([('partner_id', '=', self.supplierinfo1.name.id), ('state', '=', 'draft')])
        purchase_lines = self.env['purchase.order.line'].search([('sale_line_id', 'in', self.sale_order_1.order_line.ids)])
        purchase_lines2 = purchase_lines.filtered(lambda pol: pol.order_id == purchase_order2)
        purchase_line2 = purchase_lines2[0]

        self.assertTrue(purchase_order2, "A second PO is created by increasing sale quantity when first PO is confirmed")
        self.assertEqual(purchase_order2.state, 'draft', "The second PO is in draft state")
        self.assertNotEqual(purchase_order, purchase_order2, "The 2 PO are different")
        self.assertEqual(len(purchase_lines), 2, "The same Sale Line has created 2 purchase lines")
        self.assertEqual(len(purchase_order2.order_line), 1, "The 2nd PO has only one line")
        self.assertEqual(purchase_line2.sale_line_id, self.sol1_service_purchase_1, "The 2nd PO line came from the SO line sol1_service_purchase_1")
        self.assertEqual(purchase_line2.product_qty, delta, "The quantity of the new PO line is the quantity added on the Sale Line, after first PO confirmation")