Skip to content

Commit 3cc25e8

Browse files
temo-odoodhha-odoo
authored andcommitted
[IMP] purchase_requisition: purchase alternative improvement
In this commit: ===================== - Make sure that the currency of the new vendor is applied when creating an alternative RFQ, improving multi-currency handling. - Make sure that copying of analytic distributions to alternative RFQs for better cost tracking. - Now user can create multiple alternatives by selecting multiple vendors, streamlining the multi-vendor selection process. - Update & add the test cases to align with recent changes and ensure compatibility. Task - [4184531](https://www.odoo.com/odoo/project/966/tasks/4184531) closes odoo#182085 Related: odoo/upgrade#6579 Signed-off-by: Steve Van Essche <[email protected]>
1 parent 796e879 commit 3cc25e8

File tree

7 files changed

+144
-70
lines changed

7 files changed

+144
-70
lines changed

addons/purchase_requisition/tests/test_purchase_requisition.py

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def test_07_alternative_purchases_wizards(self):
172172
# first flow: check that creating an alt PO correctly auto-links both POs to each other
173173
action = orig_po.action_create_alternative()
174174
alt_po_wiz = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
175-
alt_po_wiz.partner_id = self.res_partner_1
175+
alt_po_wiz.partner_ids = self.res_partner_1
176176
alt_po_wiz.copy_products = True
177177
alt_po_wiz = alt_po_wiz.save()
178178
alt_po_wiz.action_create_alternative()
@@ -203,7 +203,7 @@ def test_07_alternative_purchases_wizards(self):
203203
# second flow: create extra alt PO, check that all 3 POs are correctly auto-linked
204204
action = orig_po.action_create_alternative()
205205
alt_po_wiz = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
206-
alt_po_wiz.partner_id = self.res_partner_1
206+
alt_po_wiz.partner_ids = self.res_partner_1
207207
alt_po_wiz.copy_products = True
208208
alt_po_wiz = alt_po_wiz.save()
209209
alt_po_wiz.action_create_alternative()
@@ -276,7 +276,7 @@ def test_09_alternative_po_line_price_unit(self):
276276
# Creates an alternative PO.
277277
action = po_1.action_create_alternative()
278278
alt_po_wizard_form = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
279-
alt_po_wizard_form.partner_id = self.res_partner_1
279+
alt_po_wizard_form.partner_ids = self.res_partner_1
280280
alt_po_wizard_form.copy_products = True
281281
alt_po_wizard = alt_po_wizard_form.save()
282282
alt_po_wizard.action_create_alternative()
@@ -310,7 +310,7 @@ def test_10_alternative_po_line_price_unit_different_uom(self):
310310
# Creates an alternative PO.
311311
action = po_1.action_create_alternative()
312312
alt_po_wizard_form = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
313-
alt_po_wizard_form.partner_id = self.res_partner_1
313+
alt_po_wizard_form.partner_ids = self.res_partner_1
314314
alt_po_wizard_form.copy_products = True
315315
alt_po_wizard = alt_po_wizard_form.save()
316316
alt_po_wizard.action_create_alternative()
@@ -345,7 +345,7 @@ def test_11_alternative_po_from_po_with_requisition_id(self):
345345
# Creates an alternative PO.
346346
action = po_1.action_create_alternative()
347347
alt_po_wizard_form = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
348-
alt_po_wizard_form.partner_id = self.res_partner_1
348+
alt_po_wizard_form.partner_ids = self.res_partner_1
349349
alt_po_wizard_form.copy_products = True
350350
alt_po_wizard = alt_po_wizard_form.save()
351351
alt_po_wizard.action_create_alternative()
@@ -403,7 +403,7 @@ def test_12_alternative_po_line_different_currency(self):
403403
# Creates an alternative PO
404404
action = po_orig.action_create_alternative()
405405
alt_po_wizard_form = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
406-
alt_po_wizard_form.partner_id = vendor_usd
406+
alt_po_wizard_form.partner_ids = vendor_usd
407407
alt_po_wizard_form.copy_products = True
408408
alt_po_wizard = alt_po_wizard_form.save()
409409
alt_po_wizard.action_create_alternative()
@@ -461,7 +461,7 @@ def test_alternative_po_with_multiple_price_list(self):
461461
# Creates an alternative PO
462462
action = po_orig.action_create_alternative()
463463
alt_po_wizard_form = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
464-
alt_po_wizard_form.partner_id = vendor_b
464+
alt_po_wizard_form.partner_ids = vendor_b
465465
alt_po_wizard_form.copy_products = True
466466
alt_po_wizard = alt_po_wizard_form.save()
467467
alt_po_wizard.action_create_alternative()
@@ -544,7 +544,7 @@ def test_alternative_purchase_orders_with_vendor_specific_details(self):
544544
# 1. Create an alternative PO with Supplier B (should use 'Custom Name B')
545545
action = po_orig.action_create_alternative()
546546
alt_po_wizard_form = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
547-
alt_po_wizard_form.partner_id = vendor_b
547+
alt_po_wizard_form.partner_ids = vendor_b
548548
alt_po_wizard_form.copy_products = True
549549
alt_po_wizard = alt_po_wizard_form.save()
550550
alt_po_wizard.action_create_alternative()
@@ -554,7 +554,7 @@ def test_alternative_purchase_orders_with_vendor_specific_details(self):
554554
# 2. Create an alternative PO with Supplier C (should use '[Code C] Product')
555555
action = po_orig.action_create_alternative()
556556
alt_po_wizard_form = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
557-
alt_po_wizard_form.partner_id = vendor_c
557+
alt_po_wizard_form.partner_ids = vendor_c
558558
alt_po_wizard_form.copy_products = True
559559
alt_po_wizard = alt_po_wizard_form.save()
560560
alt_po_wizard.action_create_alternative()
@@ -573,7 +573,7 @@ def test_alternative_purchase_orders_with_vendor_specific_details(self):
573573
# 3. Create an alternative PO with Supplier C (should NOT copy description, uses default product name)
574574
action = po_single_qty.action_create_alternative()
575575
alt_po_wizard_form = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
576-
alt_po_wizard_form.partner_id = vendor_c
576+
alt_po_wizard_form.partner_ids = vendor_c
577577
alt_po_wizard_form.copy_products = True
578578
alt_po_wizard = alt_po_wizard_form.save()
579579
alt_po_wizard.action_create_alternative()
@@ -583,7 +583,7 @@ def test_alternative_purchase_orders_with_vendor_specific_details(self):
583583
# 4. Create an alternative PO with Supplier D (should copy description from Supplier A as no custom product_code/name exists)
584584
action = po_single_qty.action_create_alternative()
585585
alt_po_wizard_form = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
586-
alt_po_wizard_form.partner_id = vendor_d
586+
alt_po_wizard_form.partner_ids = vendor_d
587587
alt_po_wizard_form.copy_products = True
588588
alt_po_wizard = alt_po_wizard_form.save()
589589
alt_po_wizard.action_create_alternative()
@@ -672,7 +672,7 @@ def test_taxes_for_alternative_po(self):
672672
# Creates an alternative PO
673673
action = orig_po.action_create_alternative()
674674
alt_po_wizard_form = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
675-
alt_po_wizard_form.partner_id = vendor
675+
alt_po_wizard_form.partner_ids = vendor
676676
alt_po_wizard_form.copy_products = True
677677
alt_po_wizard = alt_po_wizard_form.save()
678678
alt_po_id = alt_po_wizard.action_create_alternative()['res_id']
@@ -694,7 +694,7 @@ def test_alternative_purchase_order_merge(self):
694694

695695
action = po_1.action_create_alternative()
696696
alt_po_wiz = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
697-
alt_po_wiz.partner_id = res_partner_2
697+
alt_po_wiz.partner_ids = res_partner_2
698698
alt_po_wiz.copy_products = True
699699
alt_po_wiz = alt_po_wiz.save()
700700
alt_po_wiz.action_create_alternative()
@@ -708,11 +708,74 @@ def test_alternative_purchase_order_merge(self):
708708

709709
action = po_2.action_create_alternative()
710710
alt_po_wiz = Form(self.env['purchase.requisition.create.alternative'].with_context(**action['context']))
711-
alt_po_wiz.partner_id = res_partner_2
711+
alt_po_wiz.partner_ids = res_partner_2
712712
alt_po_wiz.copy_products = True
713713
alt_po_wiz = alt_po_wiz.save()
714714
alt_po_wiz.action_create_alternative()
715715
po_orders = self.env['purchase.order'].search([('partner_id', '=', res_partner_2.id)])
716716
merger_alternative_orders = po_orders[0] | po_orders[1]
717717
merger_alternative_orders.action_merge()
718718
self.assertEqual(len(po_orders[0].alternative_po_ids), 4)
719+
720+
def test_create_alternatives_for_multiple_vendors(self):
721+
"""
722+
Ensure that adding multiple vendors in the Alternatives wizard
723+
creates a separate alternative purchase order for each vendor.
724+
"""
725+
res_partner_2 = self.env['res.partner'].create({'name': 'Vendor 2'})
726+
res_partner_3 = self.env['res.partner'].create({'name': 'Vendor 3'})
727+
res_partner_4 = self.env['res.partner'].create({'name': 'Vendor 4'})
728+
# Create original purchase order (PO) with a vendor.
729+
orig_po = self.env['purchase.order'].create({
730+
'partner_id': self.res_partner_1.id,
731+
})
732+
733+
po_form = Form(orig_po)
734+
with po_form.order_line.new() as line:
735+
line.product_id = self.product_09
736+
line.product_qty = 5.0
737+
po_form.save()
738+
739+
# Create alternatives PO's and check that all PO's (including the original) are auto-linked to each other.
740+
alt_po_wiz = Form.from_action(self.env, orig_po.action_create_alternative())
741+
alt_po_wiz.partner_ids = res_partner_2 | res_partner_3 | res_partner_4
742+
alt_po_wiz = alt_po_wiz.save()
743+
alt_po_wiz.action_create_alternative()
744+
self.assertEqual(len(orig_po.alternative_po_ids), 4, "There should be exactly 4 linked PO's: 1 original + 3 alternatives (one for each selected vendor.)")
745+
746+
def test_alternative_purchase_vendor_currency(self):
747+
"""
748+
Ensure that the currency on the alternative RFQ is correctly set
749+
based on the selected vendor purchase currency when creating
750+
an alternative RFQ from an existing purchase order.
751+
"""
752+
currency_eur = self.env.ref("base.EUR")
753+
currency_usd = self.env.ref("base.USD")
754+
755+
self.res_partner_1.write({'property_purchase_currency_id': currency_usd.id})
756+
res_partner_2 = self.env['res.partner'].create({'name': 'Vendor 2', 'property_purchase_currency_id': currency_eur.id})
757+
758+
orig_po = self.env['purchase.order'].create({
759+
'partner_id': self.res_partner_1.id,
760+
})
761+
po_form = Form(orig_po)
762+
with po_form.order_line.new() as line:
763+
line.product_id = self.product_09
764+
line.product_qty = 1.0
765+
po_form.save()
766+
767+
# Check that the currency is correctly set based on the selected vendor.
768+
self.assertEqual(orig_po.currency_id, self.res_partner_1.property_purchase_currency_id,
769+
"The original PO currency should match with the vendor purchase currency (USD)."
770+
)
771+
772+
alt_po_wiz = Form.from_action(self.env, orig_po.action_create_alternative())
773+
alt_po_wiz.partner_ids = res_partner_2
774+
alt_po_wizard = alt_po_wiz.save()
775+
alt_po_wizard.action_create_alternative()
776+
alt_po = orig_po.alternative_po_ids.filtered(lambda po: po.partner_id == res_partner_2)
777+
778+
# Check that the currency is correctly set on the alternative PO based on the selected vendor.
779+
self.assertEqual(alt_po.currency_id, res_partner_2.property_purchase_currency_id,
780+
"The alternative PO currency should match with vendor 2 purchase currency (EUR)."
781+
)

addons/purchase_requisition/views/purchase_views.xml

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,15 @@
1717
<xpath expr="//page[@name='purchase_delivery_invoice']" position="after">
1818
<page string="Alternatives" name="alternative_pos" groups="purchase_requisition.group_purchase_alternatives">
1919
<group>
20-
<group>
21-
<p colspan="2">Create a call for tender by adding alternative requests for quotation to different vendors.
22-
Make your choice by selecting the best combination of lead time, OTD and/or total amount.
23-
By comparing product lines you can also decide to order some products from one vendor and others from another vendor.</p>
24-
</group>
25-
<group>
26-
<p colspan="2">
27-
<button name="action_create_alternative" type="object" class="btn-link d-block" string="Create Alternative" icon="fa-copy"/>
28-
<button name="action_compare_alternative_lines" type="object" class="btn-link d-block" string="Compare Product Lines" icon="fa-bar-chart" invisible="not alternative_po_ids"/>
29-
</p>
30-
</group>
20+
<div class="d-flex gap-5">
21+
<button name="action_create_alternative" type="object" class="btn-link d-block" help="Create a call for tender by adding alternative requests for quotation to different vendors.
22+
Make your choice by selecting the best combination of lead time, OTD and/or total amount.
23+
By comparing product lines you can also decide to order some products from one vendor and others from another vendor." string="Create Alternative" icon="fa-copy"/>
24+
<button name="action_compare_alternative_lines" type="object" class="btn-link d-block" string="Compare Product Lines" icon="fa-bar-chart" invisible="not alternative_po_ids"/>
25+
</div>
3126
</group>
3227
<field name="alternative_po_ids" readonly="not id" widget="many2many_alt_pos" context="{'quotation_only': True}">
33-
<list string="Alternative Purchase Order" decoration-muted="state in ['cancel', 'purchase']" decoration-bf="id == parent.id">
28+
<list string="Alternative Purchase Order" decoration-muted="state in ['cancel', 'purchase']" default_order="amount_total_cc, date_planned, id">
3429
<control>
3530
<create string="Link to Existing RfQ"/>
3631
</control>

0 commit comments

Comments
 (0)