Skip to content

[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

Description

@rjonesbsink

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.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:

'member_type_id'   => !empty($input['member_type_id'])   ? (int) $input['member_type_id']   : null,
'member_status_id' => !empty($input['member_status_id']) ? (int) $input['member_status_id'] : null,

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:

ON mt.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:

One correction I owe this thread. api/personnel-config.php is 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions