Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions spp_change_request_v2/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,40 @@ Before declaring a new CR type complete:
Changelog
=========

19.0.3.1.16
~~~~~~~~~~~

- fix(change_request): an Edit Individual change request can be opened
again on a registrant that already holds a future date of birth.
``spp.change.request.create`` prefills the detail from the registrant
and that prefill is a write, so the guard added in 19.0.3.1.15 refused
the copied value and the request could not be created at all — closing
the very path field staff use to correct the date. A birthdate the
guard would refuse is now dropped from the prefill mapping rather than
offered, so the field arrives empty and a valid date has to be
entered. The rule itself lives in one place (``_is_future_birthdate``
on the mixin), so what prefill declines to offer and what the
constraint refuses cannot drift apart.
- fix(change_request): the guard's message names the person when the
record carries one, matching the registry constraint's wording. A
Create Group member line reported only the date, which did not say
which of several lines to fix.

19.0.3.1.15
~~~~~~~~~~~

- fix(change_request): refuse a date of birth in the future while the
change request is being filled in, rather than at apply time. The Add
Member, Edit Individual and Create Group detail models and the Create
Group member wizard each store a proposed ``birthdate`` with no guard
of their own, so a future date survived submission and review and was
only refused when a strategy wrote it to ``res.partner`` on the final
approval — rolling back the whole approval with an error the approver
could not trace back to a field. A shared ``spp.cr.birthdate.mixin``
now applies the registry's rule at data entry, comparing against the
user's own today so a registrar east of UTC is not refused a birth
recorded earlier that local day (#362)

19.0.3.1.14
~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion spp_change_request_v2/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "OpenSPP Change Request V2",
"version": "19.0.3.1.14",
"version": "19.0.3.1.16",
"sequence": 50,
"category": "OpenSPP",
"summary": "Configuration-driven change request system with UX improvements, conflict detection and duplicate prevention",
Expand Down
2 changes: 1 addition & 1 deletion spp_change_request_v2/details/add_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class SPPCRDetailAddMember(models.Model):

_name = "spp.cr.detail.add_member"
_description = "CR Detail: Add Group Member"
_inherit = ["spp.cr.detail.base", "mail.thread"]
_inherit = ["spp.cr.detail.base", "mail.thread", "spp.cr.birthdate.mixin"]

# ══════════════════════════════════════════════════════════════════════════
# MEMBER INFORMATION - Real Odoo fields with full features
Expand Down
1 change: 1 addition & 0 deletions spp_change_request_v2/details/create_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ class SPPCRDetailCreateGroupMemberNew(models.Model):

_name = "spp.cr.detail.create_group.member_new"
_description = "CR Detail: Create Group — New Member"
_inherit = ["spp.cr.birthdate.mixin"]

detail_id = fields.Many2one(
"spp.cr.detail.create_group",
Expand Down
16 changes: 14 additions & 2 deletions spp_change_request_v2/details/edit_individual.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class SPPCRDetailEditIndividual(models.Model):

_name = "spp.cr.detail.edit_individual"
_description = "CR Detail: Edit Individual"
_inherit = ["spp.cr.detail.base", "mail.thread"]
_inherit = ["spp.cr.detail.base", "mail.thread", "spp.cr.birthdate.mixin"]

# ══════════════════════════════════════════════════════════════════════════
# PERSONAL INFORMATION
Expand Down Expand Up @@ -45,10 +45,13 @@ class SPPCRDetailEditIndividual(models.Model):
def _get_prefill_mapping(self):
"""Return the field mapping for pre-filling from registrant.

The mapping is per-record: a birthdate the guard would refuse is
dropped rather than offered.

Returns:
dict: Mapping of detail field names to registrant field names
"""
return {
mapping = {
"given_name": "given_name",
"family_name": "family_name",
"birthdate": "birthdate",
Expand All @@ -60,6 +63,15 @@ def _get_prefill_mapping(self):
"city": "city",
"postal_code": "zip",
}
# A registrant saved before the future-birthdate guard existed can
# still hold one, and prefilling is a write: copying it back raises
# the mixin's constraint while the change request is being created,
# so the request could not be opened at all — and an Edit Individual
# request is how field staff correct the date. Leave the field empty
# instead, so a valid one has to be entered.
if self._is_future_birthdate(self.registrant_id.birthdate):
del mapping["birthdate"]
return mapping

# ══════════════════════════════════════════════════════════════════════════
# ONCHANGE - Pre-fill from registrant
Expand Down
17 changes: 17 additions & 0 deletions spp_change_request_v2/i18n/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -1419,6 +1419,11 @@ msgstr "Demande de modification Appliquer Mapping"
msgid "Change Request Audit"
msgstr "Demande de modification Audit"

#. module: spp_change_request_v2
#: model:ir.model,name:spp_change_request_v2.model_spp_cr_birthdate_mixin
msgid "Change Request Birthdate Guard Mixin"
msgstr ""

#. module: spp_change_request_v2
#: model:spp.event.type,name:spp_change_request_v2.event_type_cr_conflict
msgid "Change Request Conflict"
Expand Down Expand Up @@ -2163,6 +2168,18 @@ msgstr "Date of Birth"
msgid "Date of Death"
msgstr "Date of Death"

#. module: spp_change_request_v2
#. odoo-python
#: code:addons/spp_change_request_v2/models/birthdate_mixin.py:0
msgid "Date of birth cannot be in the future: %(date)s."
msgstr ""

#. module: spp_change_request_v2
#. odoo-python
#: code:addons/spp_change_request_v2/models/birthdate_mixin.py:0
msgid "Date of birth cannot be in the future: %(name)s has %(date)s."
msgstr ""

#. module: spp_change_request_v2
#: model:ir.model.fields,field_description:spp_change_request_v2.field_spp_cr_detail_merge_registrants__deactivate_duplicates
msgid "Deactivate Duplicates"
Expand Down
17 changes: 17 additions & 0 deletions spp_change_request_v2/i18n/spp_change_request_v2.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,11 @@ msgstr ""
msgid "Change Request Audit"
msgstr ""

#. module: spp_change_request_v2
#: model:ir.model,name:spp_change_request_v2.model_spp_cr_birthdate_mixin
msgid "Change Request Birthdate Guard Mixin"
msgstr ""

#. module: spp_change_request_v2
#: model:spp.event.type,name:spp_change_request_v2.event_type_cr_conflict
msgid "Change Request Conflict"
Expand Down Expand Up @@ -2044,6 +2049,18 @@ msgstr ""
msgid "Date of Death"
msgstr ""

#. module: spp_change_request_v2
#. odoo-python
#: code:addons/spp_change_request_v2/models/birthdate_mixin.py:0
msgid "Date of birth cannot be in the future: %(date)s."
msgstr ""

#. module: spp_change_request_v2
#. odoo-python
#: code:addons/spp_change_request_v2/models/birthdate_mixin.py:0
msgid "Date of birth cannot be in the future: %(name)s has %(date)s."
msgstr ""

#. module: spp_change_request_v2
#: model:ir.model.fields,field_description:spp_change_request_v2.field_spp_cr_detail_merge_registrants__deactivate_duplicates
msgid "Deactivate Duplicates"
Expand Down
1 change: 1 addition & 0 deletions spp_change_request_v2/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from . import conflict_rule
from . import conflict_mixin
from . import birthdate_mixin
from . import change_request_type
from . import change_request_type_mapping
from . import change_request
Expand Down
60 changes: 60 additions & 0 deletions spp_change_request_v2/models/birthdate_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
"""Shared guard against a proposed date of birth in the future.

``res.partner`` refuses a future ``birthdate`` on every write path
(``spp_registry/models/individual.py::_check_birthdate_not_future``), but a
change request stores its proposed birthdate on its own detail and wizard
models first. Without the same guard there the value is only refused when a
strategy writes it to ``res.partner`` at apply time — which rolls back the
whole approval and reports an error the approver cannot trace back to a
field. Mixing this in catches it at data entry instead.

Inheriting models must define a ``birthdate`` date field; the mixin
deliberately does not, so it stays a pure behaviour mixin with no schema
footprint of its own.
"""

from odoo import _, api, fields, models
from odoo.exceptions import ValidationError


class SPPCRBirthdateMixin(models.AbstractModel):
_name = "spp.cr.birthdate.mixin"
_description = "Change Request Birthdate Guard Mixin"

def _is_future_birthdate(self, birthdate):
"""Return whether ``birthdate`` is later than this user's today.

Shared with the callers that must avoid *offering* a value the
constraint would refuse — prefilling a change request from a
registrant that predates the guard — so the two cannot disagree.
"""
return bool(birthdate) and birthdate > fields.Date.context_today(self)

def _birthdate_record_name(self):
"""Return the person's name for the error message, when there is one.

The inheriting models are all half-filled forms: a new group member
line has no database identity, so ``display_name`` would name the
model rather than the person. The given/family names all four of them
carry are what tells the user which line to fix.
"""
self.ensure_one()
parts = [self[field] for field in ("given_name", "family_name") if field in self._fields and self[field]]
return " ".join(parts)

@api.constrains("birthdate")
def _check_birthdate_not_future(self):
for record in self:
if not record._is_future_birthdate(record.birthdate):
continue
name = record._birthdate_record_name()
if name:
raise ValidationError(
_(
"Date of birth cannot be in the future: %(name)s has %(date)s.",
name=name,
date=record.birthdate,
)
)
raise ValidationError(_("Date of birth cannot be in the future: %(date)s.", date=record.birthdate))
9 changes: 9 additions & 0 deletions spp_change_request_v2/readme/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
### 19.0.3.1.16

- fix(change_request): an Edit Individual change request can be opened again on a registrant that already holds a future date of birth. `spp.change.request.create` prefills the detail from the registrant and that prefill is a write, so the guard added in 19.0.3.1.15 refused the copied value and the request could not be created at all — closing the very path field staff use to correct the date. A birthdate the guard would refuse is now dropped from the prefill mapping rather than offered, so the field arrives empty and a valid date has to be entered. The rule itself lives in one place (`_is_future_birthdate` on the mixin), so what prefill declines to offer and what the constraint refuses cannot drift apart.
- fix(change_request): the guard's message names the person when the record carries one, matching the registry constraint's wording. A Create Group member line reported only the date, which did not say which of several lines to fix.

### 19.0.3.1.15

- fix(change_request): refuse a date of birth in the future while the change request is being filled in, rather than at apply time. The Add Member, Edit Individual and Create Group detail models and the Create Group member wizard each store a proposed `birthdate` with no guard of their own, so a future date survived submission and review and was only refused when a strategy wrote it to `res.partner` on the final approval — rolling back the whole approval with an error the approver could not trace back to a field. A shared `spp.cr.birthdate.mixin` now applies the registry's rule at data entry, comparing against the user's own today so a registrar east of UTC is not refused a birth recorded earlier that local day (#362)

### 19.0.3.1.14

- fix(change_request): group-scope conflict rules work again. `_get_group_member_ids` traversed `spp.group.membership` records through `individual_id` and `group_id`, but that model names its many2ones `individual` and `group` — so resolving a household's members raised `KeyError`/`AttributeError` instead of returning them. A change request whose type carried an active group-scope conflict rule crashed on creation for any group registrant, and for any individual registrant with a live membership — exactly the registrants the rule exists to check. The one existing test called the method with a member-less individual, the single shape that happened to work; group-scope detection is now tested with real memberships in both directions, including that ended memberships are excluded.
Expand Down
Loading
Loading