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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ This codebase (Rails 8.1)
| `Communicable` | Gives a record the `notifications` association, nested attributes, a default `communications_scope`, and a `before_validation` addressing a hand-logged communication to the record's `communications_email`. Included by every model whose form renders `shared/_comments_and_communications` |
| `AuthorCreditable` | Author attribution. Credits are formatted by the credited **person's profile** (`Person#display_name_preference`), not by the record. The record's `author_credit_preference` is the consent snapshot taken at create time and is human-editable only on the author credit divergences page. Anonymity is a one-way latch: the profile or the record can set it, neither can strip it from the other. `credits_to_org` flips the unattributed label to "AWBW Staff"; `credits_creator` (idea models, workshop logs) credits the submitting account's person when no author is named |
| `Featureable` | `featured`, `publicly_featured` scopes |
| `FilemakerCoded` | Reads `filemaker_code` as the trimmed, comma-separated **list** it is (Person, Organization): `#filemaker_codes` for the set a record answers to, `.join_filemaker_codes` to combine any mix of strings/lists into one normalized column value (both dedupers union the codes on merge). `filemaker_archived_in` names the `Fm*` archive table(s) a model's codes point into (`.filemaker_archives`), and `.with_filemaker_code(code)` finds records carrying a code. Nothing should compare the whole column against a single code β€” `FilemakerHelper#filemaker_code_links` renders one archive link per code, and `fm_archives#show` links back to the portal records carrying its code |
| `FilemakerCoded` | Reads `filemaker_code` as the trimmed, comma-separated **list** it is (Person, Organization, Affiliation): `#filemaker_codes` for the set a record answers to, `.join_filemaker_codes` to combine any mix of strings/lists into one normalized column value (both dedupers union the codes on merge). `filemaker_archived_in` names the `Fm*` archive table(s) a model's codes point into (`.filemaker_archives`), and `.with_filemaker_code(code)` finds records carrying a code. Nothing should compare the whole column against a single code β€” `FilemakerHelper#filemaker_code_links` renders one archive link per code, and `fm_archives#show` links back to the portal records carrying its code |
| `Mentioner` | ActionText @mention extraction and grouping |
| `NameFilterable` | Name-based filtering |
| `PayChargeExtensions` | Included into `Pay::Charge` (via `config/initializers/pay.rb`): turns a Stripe charge into an AWBW `Payment` + `Allocation`, resolving the membership invoice it pays |
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/fm_archives_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class FmArchivesController < ApplicationController
{ label: "Other", icon: "πŸ“Ž", tables: %w[notes] }
]

PORTAL_MODELS = [ Person, Organization ].freeze
PORTAL_MODELS = [ Person, Organization, Affiliation ].freeze

def index
authorize! :fm_archive
Expand Down
1 change: 1 addition & 0 deletions app/helpers/filemaker_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def filemaker_portal_link(record)
text, path = case record
when Person then [ record.full_name, person_path(record) ]
when Organization then [ record.name, organization_path(record) ]
when Affiliation then [ "#{record.person&.full_name} at #{record.organization&.name}", edit_affiliation_path(record) ]
end

link_to text, path, class: button_classes(:primary_outline, size: :sm)
Expand Down
3 changes: 3 additions & 0 deletions app/models/affiliation.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
class Affiliation < ApplicationRecord
include Communicable
include FilemakerCoded
filemaker_archived_in FmProject, FmPersonnel

# Standing title given to the "facilitator affiliation" we create on registration
# and org linking. Matches the `facilitators` scope / `facilitator?` predicate
# (both treat exactly "Facilitator" as canonical).
Expand Down
1 change: 1 addition & 0 deletions app/views/affiliations/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
<div class="admin-only ml-auto w-40 rounded-md bg-blue-100 p-2">
<%= f.input :filemaker_code,
label: "FileMaker code",
hint: filemaker_code_hint(@affiliation),
label_html: { class: "block text-xs font-medium text-gray-700 mb-1" },
input_html: { class: "w-full rounded-md border-gray-300 bg-white shadow-sm focus:border-blue-500 focus:ring-blue-200 text-sm" } %>
</div>
Expand Down
11 changes: 11 additions & 0 deletions config/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4250,3 +4250,14 @@
- "Separate several codes with commas. Spaces, tabs, and line breaks are ignored, and the list saves sorted."
- "An organization's edit page also links the FileMaker organization its project belongs to."
- "An organization's code can be a FileMaker project ID (like P2775) or organization ID (like ORG07206). Type the whole code, letters included."

- name: "Open an affiliation's FileMaker record"
area: people
display_status: admin_facing
released_on: 2026-10-12
pr_number: 2649
summary: >-
An affiliation's edit page now links its FileMaker code to the archive, and
the archive record links back to the affiliation.
pro_tips:
- "An affiliation's code is usually its program's FileMaker project ID (like P2775), and occasionally a personnel ID (like PS9999)."
4 changes: 2 additions & 2 deletions docs/adr/0005-filemaker-code-archive-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ drive the archive pages' links.
|---|---|---|---|
| `people.filemaker_code` | `fm_rolodexes` | `ID`, e.g. `00006` | `filemaker_code_links` on person edit + admin details |
| `organizations.filemaker_code` | `fm_projects`, then `fm_organizations` | `ProjectID`, e.g. `P2775`; `OrgID`, e.g. `ORG07206` | `filemaker_code_links` on org form; `filemaker_project_links` / `filemaker_organization_links` on org edit |
| `affiliations.filemaker_code` | `fm_projects`, then `fm_personnels` | `ProjectID`, e.g. `P2775`; rarely `PrsnlRecID`, e.g. `PS9999` | no UI link yet |
| `affiliations.filemaker_code` | `fm_projects`, then `fm_personnels` | `ProjectID`, e.g. `P2775`; rarely `PrsnlRecID`, e.g. `PS9999` | `filemaker_code_links` on affiliation edit |
| `payments.filemaker_code` | `fm_payments` | `RecordID`, e.g. `D29186` | dev seed (no UI link yet) |
| `workshops.filemaker_code` | `fm_workshops` | `WorkshopID` | dev seed (no UI link yet) |
| `resources.filemaker_code` | `fm_quotations` | `RecordID`, e.g. `Q10215` | no UI link yet |
Expand Down Expand Up @@ -173,6 +173,6 @@ Production, 2026-10-12: 84 records (41 organizations, 16 affiliations, 14 worksh

## Consequences

A UI link for affiliations, payments, workshops, or resources includes `FilemakerCoded` on the
A UI link for payments, workshops, or resources includes `FilemakerCoded` on the
model and declares the archive table above (e.g. `filemaker_archived_in FmPersonnel`);
`filemaker_code_links` reads it from there.
16 changes: 16 additions & 0 deletions spec/helpers/filemaker_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@
expect(helper.filemaker_code_links(person)).to be_nil
end
end

context "with a record archived in more than one table" do
let(:affiliation) { build(:affiliation, filemaker_code: "P2775, PS9999") }

before do
FmProject.create!(fm_id: "P2775")
FmPersonnel.create!(fm_id: "PS9999")
end

it "links each code to whichever table holds it" do
html = helper.filemaker_code_links(affiliation)

expect(html).to include(fm_archive_path("P2775", table: "projects"))
expect(html).to include(fm_archive_path("PS9999", table: "personnels"))
end
end
end

describe "#filemaker_code_hint" do
Expand Down
1 change: 1 addition & 0 deletions spec/models/concerns/filemaker_coded_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
it "names the archive tables each coded model points into" do
expect(Person.filemaker_archives).to eq([ FmRolodex ])
expect(Organization.filemaker_archives).to eq([ FmProject, FmOrganization ])
expect(Affiliation.filemaker_archives).to eq([ FmProject, FmPersonnel ])
end
end
end
19 changes: 19 additions & 0 deletions spec/requests/fm_archives_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,25 @@
expect(response.body.scan(%r{href="/organizations/\d+"}).size).to eq(2)
end

it "links an archived project back to the affiliations carrying its code" do
FmProject.create!(fm_id: "P2775")
affiliation = create(:affiliation, filemaker_code: "P2775")

get fm_archive_path("P2775", table: "projects")

expect(response.body).to include(edit_affiliation_path(affiliation))
end

it "links an archived personnel row back to its portal affiliation" do
FmPersonnel.create!(fm_id: "PS9999")
affiliation = create(:affiliation, filemaker_code: "PS9999")

get fm_archive_path("PS9999", table: "personnels")

expect(response.body).to include(edit_affiliation_path(affiliation))
expect(response.body).to include(ERB::Util.html_escape("#{affiliation.person.full_name} at #{affiliation.organization.name}"))
end

it "omits the portal section when nothing carries the code" do
FmRolodex.create!(fm_id: "00006")

Expand Down
Loading