You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Bug]: Personnel config "Members by Type"/"Members by Status" show every member as Unassigned — joins legacy member.field3/field21, the app writes member_type_id/member_status_id #112
Git clone on Windows (IIS 10 / PHP 8.4.22 / MySQL 8.0.46)
What page or screen were you on?
Personnel configuration — the "Members by Type" / "Members by Status" summary panel
What went wrong?
Every member shows as Unassigned in both summary panels, including members with a real, correctly-set type and status that display fine on the roster.
This is the same legacy-column pattern as #95 and #103, in a third file: api/personnel-config.php joins the legacy member.field3 / member.field21 columns, while the app writes member_type_id / member_status_id.
Cause
inc/member-write.php:53-54 writes the modern columns:
api/members.php reads those same columns, which is why the roster looks right (:278-279, :452-453, :485-486, :549-550).
api/personnel-config.php reads the legacy ones instead — six sites, no COALESCE:
:102, :118 (SELECT COUNT(*) FROM member m WHERE m.field3 = mt.id) AS member_count
:133 (SELECT COUNT(*) FROM member m WHERE m.field21 = ms.id) AS member_count
:203-204 LEFT JOIN member_types mt ON m.field3 = mt.id GROUP BY m.field3, mt.name, mt.color
:215-216 LEFT JOIN member_status ms ON m.field21 = ms.id GROUP BY m.field21, ms.status_val, ms.color
:393 SELECT COUNT(*) FROM member WHERE field3 = ?
:449 SELECT COUNT(*) FROM member WHERE field21 = ?
Nothing writes field3 / field21 any more, so the joins never match and every member falls into the null group — rendered as "Unassigned".
The two delete guards are the sharper edge
:393 and :449 are not display code. They are the "is this type/status still in use?" checks that guard deletion. Because they count on the legacy column, a member type with members genuinely assigned to it counts as zero, and the guard will not stop the delete.
I have not driven a delete to see what the caller does with that count, so I can't say whether it silently orphans members or is caught elsewhere — but it seemed worth separating from the cosmetic half rather than filing it all as a display bug.
Suggested direction
The codebase already contains the shape for this, one file over. api/teams.php:72 does exactly the right thing for the integer FK case:
ONmt.id= COALESCE(m.member_type_id, m.field3)
That is the plain integer COALESCE, not the NULLIF(named, '') string form #95 needed — the correct choice here, since these are FK ids rather than text. Applying the same to the six sites in personnel-config.php would line it up with a pattern already shipping.
Whether the fallback is wanted at all is your call — it only matters if some install still has unmigrated data in field3/field21. A straight switch to the modern column would be simpler if not.
Prior art checked
Searched openises/TicketsCAD, open and closed, for field3, field21, personnel-config, "Members by Type", "Members by Status", member_type_id unassigned, plus audit legacy columns, field1, phantom, dead_control_audit and remaining field usages. Summary of what is and is not already covered, so this doesn't get closed as a duplicate of work that didn't actually reach this file:
No open tracking issue exists for auditing the remaining field* reads.
One correction I owe this thread.api/personnel-config.phpis already named in #95's comments, in the dead_control_audit output:
[NEW] phantom:member.field3 — read by api/reports.php, api/teams.php,
api/compliance.php, api/personnel-config.php
[NEW] phantom:member.field21 — read by api/personnel-config.php
I characterised those findings as "a direct consequence of the #95 fix itself" and "the behaviour is intended: the two just disagree by construction." That was correct for api/reports.php, whose COALESCE fallback genuinely does read the legacy column on purpose — and wrong for api/personnel-config.php, which has no fallback and predates the fix. The audit tool was right and I waved it off. That is why the signal was already on the board and nobody looked further, and it is the reason I checked the git history for this one rather than trusting the earlier reading.
The same dismissal covered api/teams.php and api/compliance.php. I have now checked both, and they split:
api/teams.php — correct, no action needed.:72 uses COALESCE(m.member_type_id, m.field3) and :68/:116 use COALESCE(NULLIF(m.available, ''), m.field8). The dismissal genuinely did hold for this one.
api/compliance.php — same defect as this issue.:49 selects m.field3 AS type_id and :52 joins LEFT JOIN member_types mt ON m.field3 = mt.id, both bare. I have not exercised that screen to see how it surfaces, so I am flagging it rather than reporting a symptom for it.
Also worth considering
Counting #95 and #103, that is four files with the identical defect and one (teams.php) that got it right. The pattern is consistent enough that a targeted sweep of the remaining field* reads against their modern counterparts would probably be cheaper than another round of one-off reports — #91 closed without covering this class, so nothing is currently tracking it.
Happy to do that pass and report what it finds if useful. I would rather ask than file these one at a time.
Which version are you running?
4.2.25
How is it installed?
Git clone on Windows (IIS 10 / PHP 8.4.22 / MySQL 8.0.46)
What page or screen were you on?
Personnel configuration — the "Members by Type" / "Members by Status" summary panel
What went wrong?
Every member shows as Unassigned in both summary panels, including members with a real, correctly-set type and status that display fine on the roster.
This is the same legacy-column pattern as #95 and #103, in a third file:
api/personnel-config.phpjoins the legacymember.field3/member.field21columns, while the app writesmember_type_id/member_status_id.Cause
inc/member-write.php:53-54writes the modern columns:api/members.phpreads those same columns, which is why the roster looks right (:278-279,:452-453,:485-486,:549-550).api/personnel-config.phpreads the legacy ones instead — six sites, no COALESCE:Nothing writes
field3/field21any more, so the joins never match and every member falls into the null group — rendered as "Unassigned".The two delete guards are the sharper edge
:393and:449are not display code. They are the "is this type/status still in use?" checks that guard deletion. Because they count on the legacy column, a member type with members genuinely assigned to it counts as zero, and the guard will not stop the delete.I have not driven a delete to see what the caller does with that count, so I can't say whether it silently orphans members or is caught elsewhere — but it seemed worth separating from the cosmetic half rather than filing it all as a display bug.
Suggested direction
The codebase already contains the shape for this, one file over.
api/teams.php:72does exactly the right thing for the integer FK case:That is the plain integer
COALESCE, not theNULLIF(named, '')string form #95 needed — the correct choice here, since these are FK ids rather than text. Applying the same to the six sites inpersonnel-config.phpwould line it up with a pattern already shipping.Whether the fallback is wanted at all is your call — it only matters if some install still has unmigrated data in
field3/field21. A straight switch to the modern column would be simpler if not.Prior art checked
Searched openises/TicketsCAD, open and closed, for
field3,field21,personnel-config,"Members by Type","Members by Status",member_type_id unassigned, plusaudit legacy columns,field1,phantom,dead_control_auditandremaining field usages. Summary of what is and is not already covered, so this doesn't get closed as a duplicate of work that didn't actually reach this file:api/reports.phpand the shared member-name label. It never touchedapi/personnel-config.php.git logconfirms that file's last change was v4.2.8 (97b15cb), well before the [Bug]: Personnel reports show blank Last/First/Callsign/Type — they read legacy member.field1-8, the app writes the named columns #95 fix, and its reads are bare joins with no COALESCE.inc/import-export.php. Does not mention this file.field3/field21are the opposite case — actively read, never written — so Question: what should happen to controls and columns that exist but nothing reads? #91's resolution does not cover them.field*reads.One correction I owe this thread.
api/personnel-config.phpis already named in #95's comments, in thedead_control_auditoutput:I characterised those findings as "a direct consequence of the #95 fix itself" and "the behaviour is intended: the two just disagree by construction." That was correct for
api/reports.php, whose COALESCE fallback genuinely does read the legacy column on purpose — and wrong forapi/personnel-config.php, which has no fallback and predates the fix. The audit tool was right and I waved it off. That is why the signal was already on the board and nobody looked further, and it is the reason I checked the git history for this one rather than trusting the earlier reading.The same dismissal covered
api/teams.phpandapi/compliance.php. I have now checked both, and they split:api/teams.php— correct, no action needed.:72usesCOALESCE(m.member_type_id, m.field3)and:68/:116useCOALESCE(NULLIF(m.available, ''), m.field8). The dismissal genuinely did hold for this one.api/compliance.php— same defect as this issue.:49selectsm.field3 AS type_idand:52joinsLEFT JOIN member_types mt ON m.field3 = mt.id, both bare. I have not exercised that screen to see how it surfaces, so I am flagging it rather than reporting a symptom for it.Also worth considering
Counting #95 and #103, that is four files with the identical defect and one (
teams.php) that got it right. The pattern is consistent enough that a targeted sweep of the remainingfield*reads against their modern counterparts would probably be cheaper than another round of one-off reports — #91 closed without covering this class, so nothing is currently tracking it.Happy to do that pass and report what it finds if useful. I would rather ask than file these one at a time.