Skip to content

Main - #1836

Open
tarunnjoshi wants to merge 39 commits into
mainfrom
dev
Open

Main#1836
tarunnjoshi wants to merge 39 commits into
mainfrom
dev

Conversation

@tarunnjoshi

Copy link
Copy Markdown
Member

No description provided.

tarunnjoshi and others added 30 commits October 13, 2026 11:49
Contributions imported from CSV never reached the camp's totals and
counts. The import saves each contribution as Pending and completes it
with a separate Payment save; the post hooks marked the contribution as
processed on the first save and skipped the one that completed it.
Cancelled or refunded contributions were never subtracted either, and
material contributions entered from the back end were not counted.

All five fields are now recalculated from the database by one function,
recalculateContributionStats(), whenever a monetary contribution or a
Material Contribution activity linked to a collection source is created,
edited or deleted. The previous source is refreshed too when a record is
moved or deleted. Values are only saved when they change.

update-contribution-count.php now uses the same function, recalculates
all five fields, runs as a dry run unless APPLY=1, accepts IDS= to limit
the run, writes a CSV report, and never changes collection sources whose
values were typed in by hand on an outcome form.

Refs coloredcow-admin/goonj-crm#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the previous commit on this branch with a smaller fix inside
the existing functions: the old hook names, handlers and
update-contribution-count.php are kept as they are on dev.

The CSV import saves a contribution as Pending and completes it with a
separate Payment save. updateMonetaryContributionTotalAmount and
updateMonetaryContributorsCount remembered a contribution as processed on
its first save, so the save that completed it was skipped and the camp's
Footfall & Contribution Details never included imported contributions.
They also only ran for Completed contributions, so a cancellation never
brought the totals down.

Both hooks now remember each status of a contribution once instead of
each contribution once, and run whatever the status; the totals are
still summed from Completed contributions only. updateUniqueContributorsCount
gets an optional flag so a contribution that is not Completed is not
counted as an extra contributor.

Adds cli/backfill-collection-source-contribution-stats.php to correct the
affected records: dry run by default, APPLY=1 to save, IDS= to limit,
CSV report, and never changes values typed in by hand on an outcome form.

Refs coloredcow-admin/goonj-crm#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any Completed contribution whose payment method is not Razorpay (Credit
Card) or Cash/Cheque (UPI, Wire Transfer, and any method added later) now
goes into the new Core_Contribution_Details.Total_Other_Monetary_Contributions
field. updateMonetaryContributionTotalAmount adds it, the existing delete
hook subtracts it, and the backfill script recalculates it.

The custom field has to exist on an environment before this is deployed.

Refs coloredcow-admin/goonj-crm#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
updateEventMonetaryContributionTotalAmount added a contribution's amount
to the event's Online/Cash total each time the contribution was saved,
so any later save of the same contribution (an edit, a receipt or
settlement update) counted it again. Recalculate both totals from all
Completed contributions linked to the event instead, the same fix the
camp totals got in ddccbc6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any Completed contribution on an event whose payment method is not
Razorpay (Credit Card) or Cash/Cheque (UPI, Wire Transfer, and any
method added later) now goes into the new
Goonj_Events_Outcome.Total_Other_Monetary_Contributions field.
updateEventMonetaryContributionTotalAmount adds it and the existing
delete hook subtracts it, the same as the camp totals.

The custom field has to exist on an environment before this is deployed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same fix as the camp totals. updateEventMonetaryContributionTotalAmount
and updateEventMonetaryContributorsCount remembered a contribution as
processed on its first save and only ran for Completed contributions, so
a CSV-imported contribution (saved as Pending, then completed by a
separate Payment save) was never counted, and a cancellation never
brought the event's totals and counts down.

Both hooks now remember each status of a contribution once instead of
each contribution once, and run whatever the status; totals and counts
are still calculated from Completed contributions only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Afform saves the submission record before the activity, so
updateEventMatrialContributorsCount ran before the new Material
Contribution activity existed and the event's unique material and total
contributor counts always missed the latest donor. Count the submitting
contact explicitly, the same way the camp material hook does.
updateUniqueContributorsCount gets an optional $currentContactId; the
monetary hook's call is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…totals

Count CSV-imported and cancelled contributions in camp totals
…ents

A Goonj Engagement Intent (JSM) submission that picks "Organise activities
with Goonj" now creates the matching intents, so the team no longer
re-enters them by hand (coloredcow-admin/goonj-crm#390).

- New EngagementIntentTransferService runs after the JSM submission is
  saved, once per engagement intent, and never blocks the submission.
- Decision rules for every Register as / Activities combination: Collection
  Drive folds Fundraising, Awareness Session, Upcycled Product Stall and Book
  Fair into the camp; Disaster Photo Exhibition and "Provide space" create
  their own intents; individual dropping center and Goonj Activities need a
  Completed induction.
- Individual Collection Camp: the contact becomes a volunteer the way the
  camp + volunteer form does it, so the existing induction and slot email
  fire once; camp gets In Review, 10 AM - 1 PM on the JSM date, open to
  public, Other Activities, Intent Source and the JSM link.
- Individual Dropping Center: created with the doc defaults, 10:00 AM to
  1:00 PM timing, name and number like the check-user page fills them.
- The JSM record gets "Intent Transferred" (or "...to collection camp, rest
  activities mentioned in Notes/Remarks") and a remark for anything skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Activities picked on the engagement form now create one combined
Individual Goonj Activities intent for inducted volunteers
(coloredcow-admin/goonj-crm#390).

- Fundraising, Awareness Session, Upcycled Product Stall, Book Fair and
  Disaster Photo Exhibition map to the form's engagement values; Awareness
  Session maps to Knowing Goonj Session, the closest option.
- Register as Individual, 10 AM - 1 PM on the JSM date, open to public,
  name and number the way the check-user page prefills them.
- Every intent with a start and end now shares the 10 AM - 1 PM timing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engagement form matches a returning person to their existing contact
without updating it, so the DPDP consent they ticked was dropped. Save it on
the contact when they have none yet; the consent date is then stamped by
DpdpConsentService as for every other form. A contact who already gave
consent keeps their original record, and nothing is saved on the intents
(coloredcow-admin/goonj-crm#390).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When an institution picks Collection Drive on the engagement form, create the
Institution Collection Camp intent the public institution form would create
(coloredcow-admin/goonj-crm#390).

- The person who filled the form is the Institution POC; the organisation is
  kept as the typed name, and the team picks the Organization contact on the
  verification form as for any public submission.
- "Register as" maps to the shared institution option list by value (NGO and
  Foundation share the name "Foundation" on production); "Other" keeps the
  typed institution type.
- Initiated By External, not open to public, no creative activity, India,
  10 AM - 1 PM on the JSM date; folded activities go to Other Activities.
- Office, coordinator and IHC/PCC come from the existing create hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The requirement maps the engagement form's Address to the contact's Street
Address (camp + volunteer form and the three institution forms), next to the
state and city already copied for chapter mapping. Write it on the contact's
primary address too; a blank address never clears the street already there
(coloredcow-admin/goonj-crm#390).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An address already on the contact is never replaced by the one typed on the
engagement form; the form's address is only added where the contact has no
street address yet (coloredcow-admin/goonj-crm#390).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When an institution picks "Provide space for material collection or storage"
on the engagement form, create the Institution Dropping Center intent
(coloredcow-admin/goonj-crm#390).

- Same split as the institution camp: the engagement form's person is the
  Institution POC, the organisation stays the typed name until the team picks
  the Organization contact on the verification form.
- Doc defaults: open to public Yes, donation box No, permission letter No,
  timing "10:00 AM to 1:00 PM", opening date from the engagement form,
  Initiated By External.
- Office and coordinator come from the existing create hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Activities an institution picks on the engagement form now create one
combined Institution Goonj Activities intent, and every Register as /
Activities combination is covered (coloredcow-admin/goonj-crm#390).

- Engagement values from the institution option list (Awareness Session as
  Knowing Goonj Session); 10 AM - 1 PM on the JSM date.
- Same split as the other institution intents: the engagement form's person
  is the POC, the organisation stays a typed name until the team picks the
  Organization on the review form; "Other" keeps the typed type.
- Office and coordinator come from the existing create hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every intent now shows where it came from (coloredcow-admin/goonj-crm#390):
intents the engagement form creates already say "Engagement Form", and any
intent created from another form now gets "Public Form".

- Set once, when the intent's Core Details are first saved; later edits and
  existing intents are left as they are.
- Covers the 6 public intent forms, the back-end/admin forms and API creates.
- Does nothing until the Intent Source field exists, so the code can go out
  before the field is added on the server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two new Engagement Intent statuses and the Intent Source options are
added by hand on staging and production (coloredcow-admin/goonj-crm#390).
CiviCRM's option screen lets us type the value but not the machine name:
"Intent Transferred" would be saved with the name "Intent Transferred", so a
lookup by name would never match. Write them by value instead (status 8/9,
source 1/2), which is what the runbook asks for on every site.

Tested on local with the two statuses re-added through Administer → Option
Groups: both statuses and both sources are set as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Option values can differ between sites and change when an option is
deleted and added again, so the auto-transfer now finds every option it
writes by its name, read off production (coloredcow-admin/goonj-crm#390):
statuses, Intent Source, "An individual", the Yes / No answers, External,
the Goonj Activities options (same names on the individual and institution
forms) and the volunteer defaults. The two new statuses and the two Intent
Source options are created by hand with these names.

Exceptions:
- Institution "You wish to register as" is matched by label: NGO and
  Foundation both carry the name "Foundation" on production, and the
  existing coordinator mapping relies on that name.
- The institution camp's Country is a text field that keeps the country id,
  so the id of India is looked up by the country's name.

Tested on local: individual camp + volunteer defaults, inducted dropping
center + Goonj Activities, institution NGO / Foundation / Other / College
and a public form give the same stored values as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found while testing every case of the requirement end to end on local
(coloredcow-admin/goonj-crm#390):

- Existing contacts: the engagement form's address now only fills the parts
  of the contact's address that are empty. Before, the street was kept but
  the city and state were overwritten, which mixed two addresses
  (e.g. "B-7, Koregaon Park" ended up in Mumbai instead of Pune).
- Saving the same submission again no longer repeats a remark such as
  "...was not created because the induction status is not Completed."
- A failure while sending the camp initiator's induction email is logged on
  its own and no longer reported as "could not create the camp intent",
  since the camp already exists at that point.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two new Engagement Intent statuses are added by hand through the Status
field's Multiple Choice Options (Add Option) screen, which makes the name
from the label and cuts it at 31 characters (coloredcow-admin/goonj-crm#390):
"Intent Transferred to collection camp, rest activities mentioned in
Notes/Remarks" becomes "Intent_Transferred_to_collectio". Look the status
up by that name so no API Explorer step is needed. The existing production
options were named the same way (e.g. Provide_space_for_material_coll).

Tested on local with both choices added through that screen: the folded
camp case gets status 9, the plain case status 8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Source only has to tell the team which intents came from the engagement
form (coloredcow-admin/goonj-crm#390). Those intents are already set to
"Engagement Form" when the transfer creates them. Remove the hook that also
wrote "Public Form" on every intent saved from the other public and admin
forms: those forms are back to exactly how they were, and the intent lists
show an empty Source as "Public Form" (SearchKit column, as estimated).

Tested on local: public camp form → Source empty, office set as before;
engagement form → institution camp with Source "Engagement Form" and the
engagement intent at "Intent Transferred".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review suggestion on #1826: the activity option name "Awarness_Session" is
the name stored in CiviCRM (production and staging), and the code matches
options by name, so correcting the spelling would break the match.
Comment only, no behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JSM: auto-create camp, dropping center and Goonj Activities intents from the engagement form
The monetary buttons shown after a QR scan use a root-relative
/contribute/ path, so they resolve against whichever host served the
page. Those pages are served from the CRM domain, which kept donors on
crm.goonj.org even though goonj.org/contribute/ is now proxied.

Add goonj_public_url() and use it at the two monetary button call
sites. It prefixes GOONJ_PUBLIC_BASE_URL when that constant is defined
and returns the path untouched otherwise, so local and staging keep
their current behaviour and only an environment that sets the constant
sends donors to the public domain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The renewal reminder email sent the donor to crm.goonj.org, so they
landed on the CRM domain even though the same page is now served at
goonj.org/contribute/website-user/. Verified both resolve to the same
contribution page and the onward redirect is root-relative, so the
donor now stays on goonj.org.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The donation buttons on the action pages a QR scan lands on are built
from home_url(), which is the CRM domain, so the donor stayed on
crm.goonj.org. These are the buttons actually rendered on those pages.

Add gb_goonj_donation_base_url() and use it at the three call sites. It
returns GOONJ_PUBLIC_BASE_URL when that constant is defined and falls
back to home_url() otherwise, so local and staging keep their current
absolute CRM links. build/render.php is updated alongside src, which is
what the plugin build step copies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The public domain serves /contribute/ only; the slash-less form falls
through to the website's own 404. The CRM domain hid this because it
redirects to add the slash, so the links worked there either way.

Only the public-domain branch gains the slash. The home_url() fallback
is left as it was, so local and staging are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Send monetary buttons to the public donation domain
tarunnjoshi and others added 6 commits October 16, 2026 11:45
The public domain serves /contribute/ only and answers the slash-less
form with the website's own 404. The CRM domain hid this because it
redirects to add the slash, so the links worked there either way.

Both branches of the helper now carry the slash, so the CRM domain also
gets the canonical URL and saves a redirect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keep the trailing slash on the contribution URL
Making a contact a volunteer (volunteer forms, and the Engagement form's
individual Collection Drive) took several seconds inside the submit:

- fetchTypeOfInduction looked up the Goonj office for the city with no
  select, limit or contact type, so it scanned every contact and loaded
  their groups and tags (about 2 s per volunteer on MySQL 8).
- The "induction already exists" checks filtered on target_contact_id,
  which scans every induction activity; they now join on the target row.

Results are unchanged (checked on 450 contacts and 64 cities). Locally,
individual to volunteer goes from 4.4 s to 0.8 s and an Engagement form
individual Collection Drive submission from 6.1 s to 2.2 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Speed up the induction checks run for every new volunteer
The "View Directions on Google Maps" link in the urban visit emails to the
external POC (on authorisation and the day-of reminder) put the office
address into the URL as-is. The Bangalore address has "25&26", so the &
cut the search to "GOONJ, Survey No. 25" and Maps listed every Goonj
location. Mumbai worked because its address has no &.

- Encode the address before building the Maps link.
- Read the office's primary address. Bangalore and Dhalwala Rishikesh
  also have an old address, and the email could pick that one.

Also ran phpcbf --standard=Drupal on the file (whitespace, doc comments,
an unused import).

Refs coloredcow-admin/goonj-crm#170

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the full office address in the visit directions link
@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 432a5e2e-1c47-4083-87c7-51f10dcd49a9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

tarunnjoshi and others added 3 commits October 19, 2026 12:39
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client asked (19 Oct 2026) that intents created from the engagement
form answer the permission letter question with No:

- Individual Collection Camp: "Do you require permission letters from
  Goonj to get permission from authorities for organizing camps?"
- Individual Dropping Center: "Some volunteers require permission letters
  from Goonj to get permission from authorities for Dropping Center. Do
  you need one too?"

Both fields and their Yes/No options are the same on local, staging and
production. They are set by option name, as the institution dropping
center already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JSM: answer the permission letter questions with No
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant