Conduct — Activity to Completion Report — Forms
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 | — | — | — |
| 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 | — | — | — |
| 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 ·
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 |
| 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.