9
🛠

Conduct — Activity to Completion Report — Forms

Grants and reimbursements · board authorisation · ATR · expenses · annual report

Conduct — Activity to Completion Report — Forms & Input Specification

Reference technical blueprint: conduct.md 12 sub-modules · 13 forms · one register with two tracks — a grant carried from an outside request to its Action Taken Report, and the organisation's own monthly spending carried to the same account heads.

Conduct is not built on the generic forms engine. Every form here changes a stage, and a stage change has rules a plain CRUD post would not obey — assigning to the board, authorising exactly once, refusing an ATR without three photographs, splitting a beneficiary requirement across two tracks. Those rules live in conduct/service.py and every entry point goes through them.

Who fills what. Operators run the counter and the daily work; admins take the three decisions that change what the organisation owes and what its books say (assign, authorise, post). Board members hold no logins at all — the admin records the board's decision and names the approving trustee. External applicants hold no logins either: they prove who they are with Google and get an eight-hour applicant session, not an account.


Sub-module 1: Grant Applications

Form 1.1 — Apply for a grant (public, self-service)

Purpose: An outside applicant submits their own request. Route: POST /apply/{org_code}/submit Who fills: A member of the public, after signing in with Google. No staff account is involved.

Field Type Required Options / Validation Default Help
Google sign-in — Yes ID token verified against Google's keys; audience = GOOGLE_CLIENT_ID; email must be verified — Prerequisite, not a field. Establishes applicant_google_sub
Your full name Text (255) Yes non-blank Google display name Becomes beneficiary_name
Your address Textarea Yes non-blank — Full postal address
What you are asking for, and why Textarea Yes non-blank — Becomes purpose, and the opened activity's title
Phone Text (32) No — — —
Email Read-only — Taken from the verified Google account Google email Not editable — this is where the organisation writes
Photograph of yourself File No JPEG/PNG, magic-bytes sniffed, ≤ UPLOAD_MAX_SIZE_MB — Held privately; never published

Submit action: Creates conduct_applications with source='self_service', status='submitted', ref = GR-YYYY-nnn, and the verified applicant_google_sub / applicant_email / applicant_name. Any photograph goes to the private store with its SHA-256. Refusals: 404 if the organisation does not exist, is suspended, or is not entitled to conduct. Throttled at 10 applications per Google account per hour. After submitting: The applicant sees their reference number and can return to /apply/{org_code} at any time to see where it stands. While it is still submitted they may add further photographs — never to anyone else's application, which is matched on the Google subject id.

Form 1.2 — New grant application (counter)

Purpose: An operator takes an application from somebody who cannot or will not apply online. Route: POST /admin/conduct/apply Who fills: Operator (or admin).

Field Type Required Options / Validation Default Help
Beneficiary name Text (255) Yes non-blank — As it should appear in the annual report
Date applied Date No — today Drives the reference year
Address Textarea Yes non-blank — —
Purpose of the grant Textarea Yes non-blank — Becomes the activity title on authorisation
Phone Text (32) No — — —
Email Text (255) No — — Contact address; unverified, unlike 1.1
Photograph of the beneficiary File No JPEG/PNG — Private store

Submit action: Creates conduct_applications with source='counter', received_by_user_id = the operator, status='submitted'. Note: The review queue shows Verified · for 1.1 rows and Taken at the counter for 1.2 rows. An auditor can then tell a name somebody proved from a name somebody wrote down.


Sub-module 2: Admin Review

Form 2.1 — Assign to the board

Purpose: Put a submitted application to the board. Route: POST /admin/conduct/applications/{id}/assign Who fills: Admin.

Field Type Required Options / Validation Default Help
Note for the board Textarea No — — What the board should know before deciding

Submit action: status → assigned, assigned_on = today. Refused unless the current status is submitted.

Form 2.2 — Decline

Purpose: Close an application that will not go forward. Route: POST /admin/conduct/applications/{id}/decline Who fills: Admin.

Field Type Required Options / Validation Default Help
Reason for declining Textarea Yes non-blank; enforced by ck_conduct_appl_declined_has_reason — The applicant, and the trustee who asks why, are both owed an answer better than a blank status

Submit action: status → declined, decline_reason, declined_on = today. The reason is shown to the applicant on the public page.


Sub-module 3: Board Authorisation

Form 3.1 — Authorise payment

Purpose: Record the board's decision and open the activity it funds. Route: POST /admin/conduct/applications/{id}/authorise Who fills: Admin, on the board's behalf. Board members do not log in.

Field Type Required Options / Validation Default Help
Authorised amount (₹) Decimal(14,2) Yes > 0 — ck_conduct_auth_amount_positive
Instrument Dropdown Yes bank_transfer / cheque / demand_draft / upi / cash bank_transfer Coded, not free text — otherwise four spellings of "NEFT" in one year
Instrument number / reference Text (64) Yes non-blank — Cheque no., UTR, DD no.
Date of instrument Date No — today —
Bank / account used Text (255) No — — —
Authorised by (board member) Text (255) Yes non-blank — The trustee who approved, by name. Not an FK — the board is not in the user table
Minute / resolution reference Text (64) No — — Where the board recorded it
Cost centre Text (32) No — — Carried onto every Vitta journal line this activity produces
Board notes Textarea No — — —

Submit action: Creates conduct_authorisations (one per application, enforced by uq_conduct_auth_one_per_application), sets the application to authorised, and opens conduct_activities with kind='grant', status='open', ref = AC-YYYY-nnn — in the same transaction. An authorised grant with no activity is money out of the door with nobody accountable for reporting on it. Refusals: Not assigned; already authorised; missing amount, instrument number or approving trustee. Audit: recorded_by_user_id (who typed it) is kept alongside authorised_by (who approved it). Those being different people is the point.


Sub-module 4: Operational Expense Months

Form 4.1 — Open an expense month

Purpose: A container for the organisation's own recurring spending. Route: POST /admin/conduct/activities/new Who fills: Operator. Ruled by VKG, 2026-09-07 — "Operators could start the process."

Field Type Required Options / Validation Default Help
What this month covers Text (255) Yes non-blank — e.g. Vidyalaya — salaries, provisions and utilities
Month Dropdown No 1–12; ck_conduct_activity_month_range current month A school's March recurs and has to be findable next year
Year Number No — current year Defaults to the opening year when a month is given
Notes Textarea No — — For the accountant or auditor
Cost centre Text (32) No — — Carried onto every Vitta journal line

Submit action: Creates conduct_activities with kind='operational', application_id NULL (enforced), status='open'. No applicant, no board authorisation, no ATR. Demanding three photographs of an electricity bill would teach the clerk to photograph anything to make the form go away, and the ATR would stop meaning what it means on the grant track.

Form 4.2 — Close the month

Purpose: End the period. Route: POST /admin/conduct/activity/{id}/close · Who fills: Operator. No fields. Sets status='closed', closed_on = today. Refused on a grant activity — that closes by filing an ATR.


Sub-module 5: Activities & Action Taken Report

Form 5.1 — Save the description

Route: POST /admin/conduct/activity/{id}/description · Who fills: Operator.

Field Type Required Options / Validation Default Help
Description Textarea No (Yes before an ATR) — existing value On a grant: the work as it should read in the annual report

Refused once the activity is closed.

Form 5.2 — Attach photographs

Route: POST /admin/conduct/activity/{id}/photos · Who fills: Operator.

Field Type Required Options / Validation Default Help
Photographs File (multiple) Yes JPEG/PNG only, magic-bytes sniffed — A PDF is refused here — an ATR photograph must be a photograph
Caption Text (255) No — — Applied to each file in the batch

Each file is stored privately with a SHA-256 and a position. Refused once the activity is closed.

Form 5.3 — File the ATR

Route: POST /admin/conduct/activity/{id}/atr · Who fills: Operator.

Field Type Required Options / Validation Default Help
Description Textarea Yes non-blank at the moment of filing 5.1's value —
Closing note Text No — — atr_notes

Preconditions: kind='grant', status='open', description non-blank, ≥ 3 photographs. The button is disabled below the minimum and the service refuses it anyway — the browser is not where an import or an API caller arrives. Submit action: status → atr_filed, atr_filed_on = today. The activity is now closed and takes no further evidence.


Sub-module 6: Expenses

Form 6.1 — Record an expense

Route: POST /admin/conduct/expenses · Who fills: Operator.

Field Type Required Options / Validation Default Help
Activity FK (activities) Yes must belong to this tenant pre-selected from the activity page Closed activities stay in the list — a bill arriving after the ATR was filed is ordinary
Serial number Text (64) Yes unique per tenant — uq_conduct_expense_serial — Voucher / receipt serial. One bill cannot be claimed twice
Amount (₹) Decimal(14,2) Yes > 0; parsed as Decimal, paise preserved — ck_conduct_expense_amount_positive
Date Date No — today Drives the reference year and the posting date
Paid to / beneficiary Text (255) Yes on the grant track copied from the application when blank — Meaningless on an operational month — a bill has a payee, not a beneficiary
Grant instrument code Text (64) No — — Copied from the board authorisation, so an expense traces to the payment that funded it
Details Textarea Yes non-blank — What the money bought
Vitta expense head FK (vitta_accounts, type expense) No same tenant, active — May be left for the accountant
Paid from FK (vitta_accounts, type asset) No same tenant, active — Bank or cash
Account note Text (255) No — — What the paper register says
Voucher / photographs File (multiple) No JPEG/PNG/PDF — A scanned bill is often a PDF, so PDFs are accepted here and nowhere else

Submit action: Creates conduct_expenses, ref = EX-YYYY-nnn. Not posted to Vitta — see 7.1. Deliberately permissive: an expense records even when no chart of accounts exists yet. A clerk with a voucher in hand must not wait on an accountant's setup; the entry lands in the pending queue instead. Overspend is recorded, not refused. The variance against the authorisation shows on the activity page.


Sub-module 7: Vitta Categorisation

Form 7.1 — Post to Vitta

Route: POST /admin/conduct/expenses/{id}/post · Who fills: Admin only — this changes the books.

Field Type Required Options / Validation Default Help
Expense head FK (vitta_accounts, type expense) Yes same tenant, active existing value Debited
Paid from FK (vitta_accounts, type asset) Yes same tenant, active existing value Credited

Submit action: Creates a vitta_vouchers row (voucher_type='payment', source_type='conduct_expense') and calls vitta.service.post_journal_entry with two balanced lines, carrying the activity's cost_centre and branch_id. Stamps journal_entry_id, voucher_id, posted_at, posted_by_user_id back onto the expense. Refusals: already posted; either head missing; an account belonging to another tenant; an inactive account; two accounts in different funds — money does not cross a fund boundary without a transfer entry, which is what keeps FCRA money separate. Corrections: made in Vitta with a reversing entry, never by editing the expense here. The queue: /admin/conduct/expenses/pending lists everything recorded and not yet in the books, with its value. Clearing it is what a monthly close means.


Sub-module 8: Activity Pages

No form. GET /admin/conduct/activity/{id} compiles the application, the board authorisation, the ATR and its photographs, every expense with its vouchers, and the authorised-versus-spent variance. Print CSS drops the chrome for a PDF. JSON at GET /api/conduct/activity-pages/{id}.


Sub-module 9: Annual Report

Form 9.1 — Choose year and depth

Route: GET /admin/conduct/report · Who fills: Operator (read-only).

Field Type Required Options / Validation Default Help
Year Dropdown No years the register has rows for most recent Falls back to the current year on an empty register
Depth Dropdown No brief / detailed brief Detailed adds a per-grant annexe with photographs

The two tracks are reported separately, with their own totals. A donor should not have to subtract the school's electricity, and an auditor should not have to add it back. Declined applications get their own table, with reasons.


Sub-module 10: Private Evidence Store

Form 10.1 — View a photograph

Route: GET /admin/conduct/photo/{id} · Who fills: Operator (read-only).

No fields. The tenant is resolved from the caller's own membership, never from the URL. On GCS the caller is redirected to a 15-minute signed URL; in development the bytes are streamed. There is no public path to these files at all.


Sub-module 11: Embed on your own website

Form 11.1 — Allowed websites

Purpose: Say which sites may carry the application card and be told the reference number. Route: POST /admin/conduct/embed · Who fills: Admin only.

Field Type Required Options / Validation Default Help
Allowed origins Textarea No One per line; scheme + host only; https unless localhost; deduplicated empty e.g. https://sgsdatta.org and https://www.sgsdatta.org

Submit action: Replaces organisations.embed_origins with the cleaned list. One bad line refuses the whole save and leaves the previous list untouched — a half-applied allow-list is worse than an unchanged one. Refusals: no scheme; a path, query or fragment; plain http for anything but localhost. Each of these would compare unequal to a real browser origin and so would silently never match. Empty is safe and is the default: the card still opens the form and applications still arrive; the host page is simply not told the reference number.

The page above the form shows the paste-ready snippet, the custom-element form with its optional heading / blurb / label attributes, and a plain-English account of what the visitor sees. Nothing here is a secret — the snippet is public by nature.


Sub-module 12: Reimbursement sub-type

Applies to Forms 1.1 (public) and 1.2 (counter) when kind = reimbursement. Selecting it reveals the fields below; a plain grant never sees them and is unaffected.

Form 12.1 — Reimbursement fields

Field Type Required Options / Validation Default Help
What is being asked for Dropdown Yes grant / reimbursement grant Reimbursement = paying back money already spent
Category Text (80) No free text, datalist suggests Education / Medical / Skill Training — The memo calls the list "not exhaustive"
MoA object clause Text (120) No free text — Carried into the annual report. The clause list belongs to each trust's own deed
Recipient institution Text (255) Yes non-blank; ck_conduct_appl_reimbursement_has_institution — Full registered name of the school, hospital or institute actually paid
Amount claimed Decimal(14,2) Yes > 0 — What was spent. The board may authorise a different figure
Month of the expense Dropdown No 1–12; a month needs its year current month Of the original expense, not the reimbursement
Year of the expense Number Yes 1900–2200 current year The year on the receipt
Prior approval mode Dropdown No oral / email / blank blank See 12.2
Photograph of the institution File No JPEG/PNG — Role institution
Original receipt File Yes JPEG, PNG or PDF; role receipt — The institution's own fee receipt, bill or invoice

Submit action: as Form 1.1/1.2, plus kind='reimbursement' and the fields above. The receipt is attached with role='receipt', which the schema pins to the application. Refusals: no institution; no expense year; no amount claimed; a month without its year; no original receipt (refused in the route, and again at closure).

Form 12.2 — Prior approval

Recorded as one of two mutually exclusive modes, or left blank.

Mode Required data Enforced by
Oral Authorised person's name ck_conduct_appl_oral_approval_names_person
Email Date, From, To — all three ck_conduct_appl_email_approval_is_complete
(blank) nothing —

A blank is not a rejection. Memo §3.1 left this open; it ships as board discretion. The review and board pages render "not recorded — the board decides at its discretion" in amber so the gap is visible to the person deciding. One condition in service.submit_application flips it to a hard refusal if VKG rules the other way.

Form 12.3 — Attach evidence after the fact

Route: POST /admin/conduct/applications/{id}/evidence (operator) · POST /apply/{org_code}/applications/{id}/photo (the applicant, own rows only)

Field Type Required Options / Validation Default Help
Role Dropdown Yes applicant / institution / receipt applicant A role that does not belong to its subject is refused
Document File Yes JPEG/PNG; PDF for a receipt — —

A receipt that arrives a week late must not mean re-entering the application. The activity stays blocked until it lands, so this cannot be used to skip the rule.

The rule these forms exist to enforce

An original receipt is not an ATR photograph. It hangs off the application, not the activity; photo_count counts role = 'atr' alone; and file_atr refuses to close a reimbursement until a receipt is on file. Three photographs of a cheque changing hands cannot close a reimbursement.


Cross-cutting notes

Audit. Every stage change writes an audit_events row through audit.service.record_audit_event with provider aayojana.conduct — application submitted (with its source), assigned, declined with reason, authorised with amount and instrument, ATR filed with the photograph count, expense recorded, expense posted with both account codes and the entry number.

Two-key cosign is not applied anywhere in this module. The stage gates already require two different people for the sequence that matters — an operator records, an admin authorises, an admin posts — and the board's approver is named separately from the person who typed it.

Numbering. GR-/AC-/EX-YYYY-nnn, per tenant per year, unique in the database rather than only in the code that issues them.