Skip to content
Closed
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, Resource): `#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, Resource ].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 Resource then [ record.title, resource_path(record) ]
end

link_to text, path, class: button_classes(:primary_outline, size: :sm)
Expand Down
2 changes: 2 additions & 0 deletions app/models/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ class Resource < ApplicationRecord
include Rails.application.routes.url_helpers
include ActionText::Attachable
include Mentionable
include FilemakerCoded
filemaker_archived_in FmQuotation

# Define rich text fields for mentions functionality
def self.mentionable_rich_text_fields
Expand Down
5 changes: 5 additions & 0 deletions app/views/resources/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
<%= visibility_flag_input f, :publicly_featured %>
<%= visibility_flag_input f, :hidden_from_search %>
</div>
<%= f.input :filemaker_code,
label: "FileMaker quotation ID",
hint: filemaker_code_hint(f.object),
wrapper_html: { class: "mt-4" },
input_html: { class: "w-full rounded border-gray-300 bg-blue-100" } %>
</div>
<% end %>
</div>
Expand Down
12 changes: 12 additions & 0 deletions config/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4236,3 +4236,15 @@
- "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 a Leader spotlight's original FileMaker quote"
area: stories
display_status: admin_facing
released_on: 2026-10-12
pr_number: 2663
summary: >-
A resource's edit page now shows its FileMaker quotation ID and links it to the
quote in the FileMaker archive, and each archived quote links back to the
resource that carries it.
pro_tips:
- "The ID sits in the admin box beside the visibility flags. A struck-through ID means the archive has no such quote — check it for a typo."
2 changes: 2 additions & 0 deletions db/seeds/dev/fm_archive.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def seed_fm(model, fm_ids)
payments = (Payment.where(person: people).or(Payment.where(organization: organizations)).order(:id).first(3) +
Payment.order(:id).limit(3)).uniq.first(3)
workshops = Workshop.order(:title).first(3)
resources = (Resource.where(kind: "LeaderSpotlight").order(:title).first(3) + Resource.order(:title).first(3)).uniq.first(3)

workshop_titles = [ workshops[0]&.title || "Comfort Journals", workshops[1]&.title || "Watercolor Windows", "Story Dolls" ]
payment_amounts = [ payments[0], payments[1] ].map { |payment| payment && format("%.2f", payment.amount_cents / 100.0) }
Expand Down Expand Up @@ -729,3 +730,4 @@ def seed_fm(model, fm_ids)
link_codes.call(affiliations, fm_ids[:personnel])
link_codes.call(payments, fm_ids[:payment])
link_codes.call(workshops, fm_ids[:workshop])
link_codes.call(resources, fm_ids[:quotation])
8 changes: 4 additions & 4 deletions docs/adr/0005-filemaker-code-archive-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ drive the archive pages' links.
| `affiliations.filemaker_code` | `fm_projects`, then `fm_personnels` | `ProjectID`, e.g. `P2775`; rarely `PrsnlRecID`, e.g. `PS9999` | no UI link yet |
| `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 |
| `resources.filemaker_code` | `fm_quotations` | `RecordID`, e.g. `Q10215` | `filemaker_code_hint` on the resource form (admin box) |

- **Organization → Project, or → Organization.** An app `Organization` is usually one
art program site, which is a FileMaker *Project*. FileMaker's own Organization row is
Expand All @@ -50,8 +50,8 @@ drive the archive pages' links.
- **Resource → Quotation.** In production, 116 of 128 resource codes are quotation ids
(`Q…`), 115 of them on Leader spotlight resources and one on a Story; 115 of the 116
exist in `fm_quotations` (quotes and testimonials, with a `WorkShopLeader` field).
Form Letters are mail-merge templates, not resources. The dev seed still leaves
`resources.filemaker_code` blank.
Form Letters are mail-merge templates, not resources. The dev seed gives Leader
spotlights quotation codes first.

## Archive id patterns

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 affiliations, payments, or workshops includes `FilemakerCoded` on the
model and declares the archive table above (e.g. `filemaker_archived_in FmPersonnel`);
`filemaker_code_links` reads it from there.
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(Resource.filemaker_archives).to eq([ FmQuotation ])
end
end
end
9 changes: 9 additions & 0 deletions spec/requests/fm_archives_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@
expect(response.body.scan(%r{href="/organizations/\d+"}).size).to eq(2)
end

it "links an archived quotation back to the resource carrying its code" do
FmQuotation.create!(fm_id: "Q10215")
resource = create(:resource, kind: "LeaderSpotlight", title: "Spotlight on a leader", filemaker_code: "Q10215")

get fm_archive_path("Q10215", table: "quotations")

expect(response.body).to include(resource_path(resource), "Spotlight on a leader")
end

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

Expand Down
10 changes: 10 additions & 0 deletions spec/requests/resources_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@
expect(response.body).to include('name="resource[publicly_visible]"')
expect(response.body).to include(VisibilityFlagsHelper::FLAG_DEFINITIONS[:hidden_from_search][:description])
end

it "links the FileMaker code to its quotation in the archive" do
FmQuotation.create!(fm_id: "Q10215")
resource = Resource.create! valid_attributes.merge(filemaker_code: "Q10215, 21")
get edit_resource_url(resource)

expect(response.body).to include('name="resource[filemaker_code]"')
expect(response.body).to include(fm_archive_path("Q10215", table: "quotations"))
expect(response.body).to include(%(title="No FileMaker archive record for 21"))
end
end

describe "POST /create" do
Expand Down
10 changes: 10 additions & 0 deletions spec/seeds/fm_archive_seed_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@
expect(FmRolodex.find_by!(fm_id: "00901").data.values_at("FirstName", "LastName")).to eq(%w[Abe Archer])
end

it "gives Leader spotlights quotation codes ahead of other resources" do
other = create(:resource, kind: "Toolkit", title: "Aaa toolkit")
spotlight = create(:resource, kind: "LeaderSpotlight", title: "Zed spotlight")

load seed

expect(spotlight.reload.filemaker_code).to eq("Q90001")
expect(other.reload.filemaker_code).to eq("Q90002, Q90008")
end

it "is idempotent" do
load seed
expect { load seed }.not_to change(FmPayment, :count)
Expand Down
Loading