-
Notifications
You must be signed in to change notification settings - Fork 3
feat(spp_programs): program config UX polish, payment zero-state, cancelable manager wizard (#941 #952 #953) #198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 19.0
Are you sure you want to change the base?
Changes from all commits
7cd3872
6496692
dfa7b2b
4fe075b
7310522
4a846f1
f8c0a2e
aee395d
5c962ba
f5569b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # Part of OpenSPP. See LICENSE file for full copyright and licensing details. | ||
| """Replace placeholder `name = "Default"` on default-manager records with | ||
| their method-specific labels — see #941 round 2 / item 3. | ||
|
|
||
| Existing programs upgraded from a prior version still carry rows whose | ||
| `name` literally reads "Default" (the value used by | ||
| `SPPProgram.create_default_managers` before the cleanup). New rows are | ||
| fine because each concrete model now seeds its own meaningful name via | ||
| `default_get`. This migration backfills the historical rows so the Edit | ||
| form shows e.g. "CEL Eligibility Criteria" instead of "Default". | ||
| """ | ||
|
|
||
| import logging | ||
|
|
||
| from psycopg2 import sql | ||
|
|
||
| _logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| # (table_name, label) pairs. Tables are derived from the concrete-manager | ||
| # `_name` (dots replaced by underscores). | ||
| _DEFAULT_NAME_RENAMES = ( | ||
| ("spp_program_membership_manager_default", "CEL Eligibility Criteria"), | ||
| ("spp_program_entitlement_manager_default", "Basic Cash"), | ||
| ("spp_program_entitlement_manager_cash", "Cash Entitlement"), | ||
| ("spp_program_entitlement_manager_inkind", "In-kind Entitlement"), | ||
| ("spp_cycle_manager_default", "Default Cycle Schedule"), | ||
| ("spp_compliance_manager_default", "CEL Compliance Criteria"), | ||
| ("spp_program_payment_manager_default", "Default Payment"), | ||
| ("spp_program_manager_default", "Default Program Manager"), | ||
| ("spp_deduplication_manager_default", "Default Deduplication"), | ||
| ) | ||
|
|
||
|
|
||
| def migrate(cr, version): | ||
| if not version: | ||
| return | ||
| for table, label in _DEFAULT_NAME_RENAMES: | ||
| # Skip tables that don't exist (modules not installed in this DB). | ||
| cr.execute( | ||
| "SELECT 1 FROM information_schema.tables WHERE table_name = %s", | ||
| (table,), | ||
| ) | ||
| if not cr.fetchone(): | ||
| continue | ||
| cr.execute( | ||
| sql.SQL("UPDATE {tbl} SET name = %s WHERE name = 'Default'").format( | ||
| tbl=sql.Identifier(table), | ||
| ), | ||
| (label,), | ||
| ) | ||
| if cr.rowcount: | ||
| _logger.info( | ||
| "Renamed %d %s rows from 'Default' to %r", | ||
| cr.rowcount, | ||
| table, | ||
| label, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,7 +84,7 @@ def _generate_code(self): | |
|
|
||
| is_cash_entitlement = fields.Boolean("Cash Entitlement", default=False) | ||
| currency_id = fields.Many2one("res.currency", readonly=True, related="journal_id.currency_id") | ||
| initial_amount = fields.Monetary(required=True, currency_field="currency_id") | ||
| initial_amount = fields.Monetary(string="Amount", required=True, currency_field="currency_id") | ||
| transfer_fee = fields.Monetary(currency_field="currency_id", default=0.0) | ||
| balance = fields.Monetary(compute="_compute_balance") # in company currency | ||
| # TODO: implement transactions against this entitlement | ||
|
|
@@ -390,6 +390,29 @@ def _get_approval_definition(self): | |
| self.ensure_one() | ||
| return self.entitlement_approval_definition_id | ||
|
|
||
| def _notify_thread_by_email(self, message, recipients_data, **kwargs): | ||
| """Suppress outgoing email when the parent program has email | ||
| notifications disabled. Chatter logging and in-app notifications are | ||
| unaffected — only the email dispatch is short-circuited.""" | ||
| self.ensure_one() | ||
| program = self.cycle_id.program_id | ||
| if program and not program._should_send_email_notifications(): | ||
| return | ||
| return super()._notify_thread_by_email(message, recipients_data, **kwargs) | ||
|
|
||
| def _create_approval_activity(self, definition, review): | ||
| """Gate the approver-email path on the parent program's toggle. | ||
|
|
||
| spp.approval.mixin schedules a mail.activity for each approver on | ||
| submit; the activity dispatch sends email through the assignee's | ||
| notification preferences. Skip the scheduling entirely when the | ||
| program has email notifications turned off.""" | ||
| self.ensure_one() | ||
| program = self.cycle_id.program_id | ||
| if program and not program._should_send_email_notifications(): | ||
| return | ||
| return super()._create_approval_activity(definition, review) | ||
|
Comment on lines
+403
to
+414
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overriding |
||
|
|
||
| def action_submit_for_approval(self): | ||
| """Submit entitlement for approval using standardized workflow.""" | ||
| for record in self: | ||
|
|
@@ -885,6 +908,24 @@ def _resolve_approval_definition(self): | |
| self.ensure_one() | ||
| return self._get_approval_definition() | ||
|
|
||
| def _notify_thread_by_email(self, message, recipients_data, **kwargs): | ||
| """Suppress outgoing email when the parent program has email | ||
| notifications disabled. Chatter logging and in-app notifications are | ||
| unaffected — only the email dispatch is short-circuited.""" | ||
| self.ensure_one() | ||
| program = self.cycle_id.program_id | ||
| if program and not program._should_send_email_notifications(): | ||
| return | ||
| return super()._notify_thread_by_email(message, recipients_data, **kwargs) | ||
|
|
||
| def _create_approval_activity(self, definition, review): | ||
| """Gate the approver-email path on the parent program's toggle.""" | ||
| self.ensure_one() | ||
| program = self.cycle_id.program_id | ||
| if program and not program._should_send_email_notifications(): | ||
| return | ||
| return super()._create_approval_activity(definition, review) | ||
|
Comment on lines
+921
to
+927
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overriding |
||
|
|
||
| def action_submit_for_approval(self): | ||
| """Submit in-kind entitlement for approval using standardized workflow.""" | ||
| for record in self: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,6 +84,57 @@ class DefaultFilePaymentManager(models.Model): | |
| MAX_PAYMENTS_FOR_SYNC_PREPARE = 200 | ||
| MAX_BATCHES_FOR_SYNC_SEND = 50 | ||
|
|
||
| @api.model | ||
| def default_get(self, fields_list): | ||
| """Default the manager name to its method-specific label.""" | ||
| res = super().default_get(fields_list) | ||
| if "name" in fields_list: | ||
| res.setdefault("name", _("Default Payment")) | ||
| return res | ||
|
|
||
| @api.model_create_multi | ||
| def create(self, vals_list): | ||
| """Auto-create the default batch tag when needed. | ||
|
|
||
| The `batch_tag_ids` constraint requires at least one tag when | ||
| `create_batch=True` (the default). The legacy flow relied on | ||
| the user toggling `create_batch` in the form to fire the | ||
| onchange that creates the tag — that doesn't fire on the form's | ||
| initial open, so the program form's `+ Add` button used to | ||
| pre-create the tag itself (#952). Pre-creation leaves an | ||
| orphan batch tag in the DB if the user dismisses the dialog | ||
| with `X` (#953). Doing it here means the tag is only created | ||
| atomically with the manager record on Save. | ||
| """ | ||
| for vals in vals_list: | ||
| if not vals.get("create_batch", True) or vals.get("batch_tag_ids"): | ||
| continue | ||
| program_id = vals.get("program_id") or self.env.context.get("default_program_id") | ||
| if not program_id: | ||
| continue | ||
| program = self.env["spp.program"].browse(program_id) | ||
| tag_name = f"Default {program.name}" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| BatchTag = self.env["spp.payment.batch.tag"].sudo() # nosemgrep: odoo-sudo-without-context | ||
| tag = BatchTag.search( | ||
| [ | ||
| ("name", "=", tag_name), | ||
| ("order", "=", 1), | ||
| ("max_batch_size", "=", 500), | ||
| ], | ||
| limit=1, | ||
| ) | ||
| if not tag: | ||
| tag = BatchTag.create( | ||
| { | ||
| "name": tag_name, | ||
| "order": 1, | ||
| "domain": [], | ||
| "max_batch_size": 500, | ||
| } | ||
| ) | ||
| vals["batch_tag_ids"] = [(4, tag.id)] | ||
| return super().create(vals_list) | ||
|
Comment on lines
+109
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In |
||
|
|
||
| currency_id = fields.Many2one("res.currency", related="program_id.journal_id.currency_id", readonly=True) | ||
|
|
||
| create_batch = fields.Boolean("Automatically Create Batch", default=True) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overriding
_create_approval_activityto returnNonewhen email notifications are disabled suppresses the creation of the Odoo Activity entirely. This prevents approvers from seeing the task in their Odoo systray or on the record's chatter, which might break the in-app workflow. If the intent is only to suppress the email notification, overriding_notify_thread_by_email(as done above) is sufficient, as it is called during activity creation to send the mail. Furthermore, returningNoneinstead of a recordset might cause tracebacks in callers expecting an activity object.