Skip to content

perf(spp_programs): batch create entitlements and payments#128

Merged
gonzalesedwin1123 merged 3 commits into19.0from
ken/optimize_spp_program_phase_5
Mar 26, 2026
Merged

perf(spp_programs): batch create entitlements and payments#128
gonzalesedwin1123 merged 3 commits into19.0from
ken/optimize_spp_program_phase_5

Conversation

@kneckinator
Copy link
Copy Markdown
Contributor

@kneckinator kneckinator commented Mar 20, 2026

Summary

  • Batch-create entitlements in SppCashEntitlementManager.prepare_entitlements() (single create(vals_list) instead of per-beneficiary loop)
  • Batch-create payments in DefaultFilePaymentManager._prepare_payments() (single create(vals_list) per batch tag instead of per-entitlement loop)
  • Add bank_ids prefetch to avoid N+1 queries during payment preparation

Changes

File Before After
entitlement_manager_cash.py:159-165 for ent: self.env["spp.entitlement"].create(dict) — one INSERT per beneficiary Collect all dicts, filter zero amounts, single create(vals_list)
payment_manager.py:214-249 for entitlement: self.env["spp.payment"].create(dict) — one INSERT per entitlement, plus per-record payment.account_number = write Build vals list with account_number included, single create(vals_list), then batch_payments.write({"batch_id": ...}) for batch assignment

Note: DefaultCashEntitlementManager.prepare_entitlements() in entitlement_manager_base.py already used batch create() — no change needed there.


Context

Phase 5 of 9 in the spp_programs performance optimization effort. Independent of other phases.


Test Plan

  • ./scripts/test_single_module.sh spp_programs passes
  • test_cash_manager_batch_creates_entitlements — verifies single create() call for 5 beneficiaries
  • test_payment_manager_batch_creates_payments — verifies single create() call for 5 entitlements

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves performance within the spp_programs module by optimizing database interactions during entitlement and payment generation. By converting several individual record creation operations into single batch operations and implementing strategic data prefetching, the changes aim to reduce the number of database queries, thereby enhancing the efficiency and scalability of these core processes. This is part of a broader effort to optimize the performance of the spp_programs module.

Highlights

  • Batch Entitlement Creation: Entitlement creation in SppCashEntitlementManager.prepare_entitlements() has been optimized to use a single batch create(vals_list) call, replacing the previous per-beneficiary loop.
  • Batch Payment Creation: Payment creation in DefaultFilePaymentManager._prepare_payments() now utilizes a single batch create(vals_list) per batch tag, moving away from the per-entitlement loop.
  • N+1 Query Optimization: Added bank_ids prefetching to DefaultFilePaymentManager._prepare_payments() to eliminate N+1 queries during the payment preparation process.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces significant performance improvements by batching the creation of entitlements and payments, replacing loops of single create calls with batch create(vals_list) operations. The changes are well-implemented and include new tests to verify the batching behavior. My review includes a few suggestions to further improve code readability by using more idiomatic Python constructs and to enhance the new tests for better coverage.

Comment on lines 161 to +166
for ent in new_entitlements_to_create:
initial_amount = new_entitlements_to_create[ent]["initial_amount"]
new_entitlements_to_create[ent]["initial_amount"] = self._check_subsidy(initial_amount)
# Create non-zero entitlements only
if new_entitlements_to_create[ent]["initial_amount"] > 0.0:
self.env["spp.entitlement"].create(new_entitlements_to_create[ent])
vals_list.append(new_entitlements_to_create[ent])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the logic is correct, the loop can be made more direct and Pythonic by iterating over the dictionary's values instead of its keys. This improves readability by removing the extra dictionary lookup new_entitlements_to_create[ent] in each iteration.

Suggested change
for ent in new_entitlements_to_create:
initial_amount = new_entitlements_to_create[ent]["initial_amount"]
new_entitlements_to_create[ent]["initial_amount"] = self._check_subsidy(initial_amount)
# Create non-zero entitlements only
if new_entitlements_to_create[ent]["initial_amount"] > 0.0:
self.env["spp.entitlement"].create(new_entitlements_to_create[ent])
vals_list.append(new_entitlements_to_create[ent])
for vals in new_entitlements_to_create.values():
initial_amount = vals["initial_amount"]
vals["initial_amount"] = self._check_subsidy(initial_amount)
# Create non-zero entitlements only
if vals["initial_amount"] > 0.0:
vals_list.append(vals)

Comment on lines +221 to 236
payment_vals_list = []
for entitlement_id in tag_entitlements:
account_number = None
if entitlement_id.partner_id.bank_ids:
account_number = entitlement_id.partner_id.bank_ids[0].acc_number
payment_vals_list.append(
{
"name": str(uuid4()),
"entitlement_id": entitlement_id.id,
"cycle_id": entitlement_id.cycle_id.id,
"amount_issued": entitlement_id.initial_amount,
"payment_fee": entitlement_id.transfer_fee,
"state": "issued",
"account_number": account_number,
}
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for building payment_vals_list can be made more concise and Pythonic by using a list comprehension. This improves readability and reduces boilerplate code.

            payment_vals_list = [
                {
                    "name": str(uuid4()),
                    "entitlement_id": ent.id,
                    "cycle_id": ent.cycle_id.id,
                    "amount_issued": ent.initial_amount,
                    "payment_fee": ent.transfer_fee,
                    "state": "issued",
                    "account_number": ent.partner_id.bank_ids[0].acc_number if ent.partner_id.bank_ids else None,
                }
                for ent in tag_entitlements
            ]

Comment on lines +143 to +172
call_count = 0

def counting_create(self_model, vals_list):
nonlocal call_count
call_count += 1
return original_create(self_model, vals_list)

# Add a batch tag so we enter the loop
batch_tag = self.env["spp.payment.batch.tag"].create(
{
"name": "Test Tag",
"order": 1,
"domain": "[]",
"max_batch_size": 500,
}
)
self.payment_manager.batch_tag_ids = [(4, batch_tag.id)]

with patch.object(
type(self.env["spp.payment"]),
"create",
counting_create,
):
self.payment_manager._prepare_payments(self.cycle, self.entitlements)

self.assertEqual(
call_count,
1,
f"create() should be called once (batch), was called {call_count} times",
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better test coverage and consistency with test_cash_manager_batch_creates_entitlements, this test should also verify the total number of records created, not just the number of create() calls. This ensures that the batch operation not only runs once but also processes the correct number of items.

        call_count = 0
        total_created = 0

        def counting_create(self_model, vals_list):
            nonlocal call_count, total_created
            call_count += 1
            if isinstance(vals_list, list):
                total_created += len(vals_list)
            else:
                total_created += 1
            return original_create(self_model, vals_list)

        # Add a batch tag so we enter the loop
        batch_tag = self.env["spp.payment.batch.tag"].create(
            {
                "name": "Test Tag",
                "order": 1,
                "domain": "[]",
                "max_batch_size": 500,
            }
        )
        self.payment_manager.batch_tag_ids = [(4, batch_tag.id)]

        with patch.object(
            type(self.env["spp.payment"]),
            "create",
            counting_create,
        ):
            self.payment_manager._prepare_payments(self.cycle, self.entitlements)

        self.assertEqual(
            call_count,
            1,
            f"create() should be called once (batch), was called {call_count} times",
        )
        self.assertEqual(total_created, 5)

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 20, 2026

Codecov Report

❌ Patch coverage is 44.00000% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.98%. Comparing base (5916d10) to head (2bce58e).
⚠️ Report is 4 commits behind head on 19.0.

Files with missing lines Patch % Lines
spp_programs/models/managers/payment_manager.py 52.38% 10 Missing ⚠️
...ograms/models/managers/entitlement_manager_cash.py 0.00% 4 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #128      +/-   ##
==========================================
+ Coverage   70.95%   70.98%   +0.03%     
==========================================
  Files         899      899              
  Lines       52783    52791       +8     
==========================================
+ Hits        37451    37476      +25     
+ Misses      15332    15315      -17     
Flag Coverage Δ
spp_api_v2 79.96% <ø> (ø)
spp_api_v2_change_request 66.85% <ø> (ø)
spp_api_v2_cycles 71.12% <ø> (ø)
spp_api_v2_data 64.41% <ø> (ø)
spp_api_v2_entitlements 70.19% <ø> (ø)
spp_api_v2_gis 71.52% <ø> (ø)
spp_api_v2_products 66.27% <ø> (ø)
spp_api_v2_service_points 70.94% <ø> (ø)
spp_api_v2_simulation 71.12% <ø> (ø)
spp_api_v2_vocabulary 57.26% <ø> (ø)
spp_audit 64.19% <ø> (ø)
spp_base_common 90.26% <ø> (ø)
spp_case_entitlements 97.61% <ø> (ø)
spp_case_programs 97.14% <ø> (ø)
spp_cel_event 85.11% <ø> (ø)
spp_claim_169 58.11% <ø> (ø)
spp_dci_client_dr 55.87% <ø> (ø)
spp_dci_client_ibr 60.17% <ø> (ø)
spp_programs 52.84% <44.00%> (+0.29%) ⬆️
spp_security 66.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_programs/__manifest__.py 0.00% <ø> (ø)
...ograms/models/managers/entitlement_manager_cash.py 58.79% <0.00%> (-0.90%) ⬇️
spp_programs/models/managers/payment_manager.py 44.30% <52.38%> (+14.89%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

kneckinator and others added 2 commits March 26, 2026 14:38
Cash entitlement manager now collects all entitlement dicts and calls
create() once instead of per-beneficiary. Payment manager collects all
payment dicts per batch tag and calls create() once, then batch-assigns
via write(). Also prefetches bank_ids to avoid N+1 queries.
…atch test

Fix entitlement batch creation test to verify functional result instead
of counting create() calls, which is fragile due to ORM framework
internals.
@gonzalesedwin1123 gonzalesedwin1123 force-pushed the ken/optimize_spp_program_phase_5 branch from 4c281ae to e69b316 Compare March 26, 2026 06:43
@gonzalesedwin1123 gonzalesedwin1123 merged commit 5b26ad7 into 19.0 Mar 26, 2026
34 of 35 checks passed
@gonzalesedwin1123 gonzalesedwin1123 deleted the ken/optimize_spp_program_phase_5 branch March 26, 2026 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants