Back-office web application for the volunteer who manages the ranking of the families applying to the Il Sole social supermarket. Each application is scored against the supermarket's evaluation grid (docs/griglia-valutazione.pdf); the ranking decides who receives a shopping card and how many monthly points it carries.
It started in 2021 as a high-school final exam project, written in vanilla PHP, JavaScript and CSS without any framework, and is being completed in the same spirit. All the data in the repository is invented.
- Ranking of the applicants with the score broken down by criterion
- One form for new, updated and repeated applications, with automatic scoring
- Shopping cards: assign, withdraw, manage, history
- Applicant details, family members' skills, applicant removal
- E-mail to applicants, ranking export, scoring configuration
PHP 8.5 on Apache, MySQL 8.0, plain HTML/CSS/JavaScript. Docker Compose for local development.
The only libraries the pages load, jQuery and jQuery UI (for the date picker of the form), are copied in FileInclude/third-party/ and served by the application itself. No page loads anything from another host, so a visit sends no data to third parties and the site's privacy policy has no external service to declare. A unit test fails if a page starts loading a file from a CDN again.
Requires Docker and just.
| Command | What it does |
|---|---|
just up |
Build and start the app on http://localhost:4330 |
just down |
Stop it, keeping the database |
just reset-db |
Drop the database and reload schema and demo data |
just logs |
Follow the web and database logs |
just install |
Install the development dependencies (first time only) |
just test |
Unit tests of the scoring rules |
just check |
Syntax check, unit tests and smoke test |
just demo-data |
Regenerate the demo data from its script |
just package |
Build dist/: what to upload to a host |
The demo account is emporiosolidale / IlSole (capital i, lower-case L). The
login page shows it, with a button that fills it in. The stack runs in demo
mode: what you change is put back within the hour; just reset-db does it at
once.
| Path | Content |
|---|---|
index.php |
Login |
FileToBeExecuted/ |
One PHP page per feature |
FileInclude/ |
Shared PHP includes, stylesheets, scripts and images |
src/ |
Scoring, rules, form reading and storage |
tests/ |
Unit tests (PHPUnit) |
db/ |
Schema, configuration rows and demo data, loaded in order |
docs/ |
Deploy guide, evaluation grid, E/R diagrams |
scripts/ |
Smoke test and generator of the demo data |
ModuloDomanda.php is the only page for
new, updated and repeated applications. It does all its work before printing
anything, through the modules in src/:
| Module | Role |
|---|---|
Scoring.php |
The rules of the grid, one pure function per criterion |
Rules.php |
The configurable points: reading, validation, labels |
ApplicationForm.php |
Submitted form → structure to score |
ApplicationRecord.php |
Submitted form → record to store, its checks, and back |
AssessmentFields.php |
Markup of the "Griglia di valutazione" section |
ApplicationStore.php |
Reads and writes a record (prepared statements, transaction) |
Auth.php, Html.php |
Login, form tokens, HTML escaping |
Input.php, Export.php |
Request values, export cells |
Config.php, Demo.php, Mail.php |
Settings, demo reset, sending mail |
Everything is pure and covered by the unit tests in tests/, except
ApplicationStore.php and the session and database half of Auth.php, which
are exercised by the smoke test. The only runtime dependency is PHP itself: Composer is used for
PHPUnit alone.
A form with mistakes is not saved: it comes back filled in, with the list of what to fix. An update keeps the shopping card and the skills linked to the members.
Every page starts by loading src/bootstrap.php and calling
IlSole\Auth\requireLogin(), before printing anything. The login is a PHP
session (HTTP-only cookie, expiring after 30 minutes without requests);
passwords are stored with password_hash. Every form carries a token of the
session and a POST without it is refused.
Five wrong passwords from the same address within fifteen minutes lock the login for that address until the window has passed.
What comes from a request is reduced to the expected shape by
src/Input.php (tax codes, card codes, e-mail addresses, numbers) and reaches
the database only as a parameter; what comes from the database is escaped
before it is printed (IlSole\Html\escapeRow). Cells of the export that a
spreadsheet would run as formulas are made inert (src/Export.php).
Where the grid can be read in more than one way, this is the reading taken:
- Minors: every adult of the household counts, partner or not; 15 points with one adult, 10 with more, plus 4 for each minor after the first.
- Unemployment: 10 points for every unemployed adult (5 if semi-employed), plus at most one of the other lines, the highest that applies. A single parent counts as such only if no other adult of the household works. "Other adults" are the adults other than the head of the family and the spouse, and the line is added once.
- Disability: "alone" means a household of one person.
- Other debts (2–10) and other contributions (2–5): the operator picks the value inside the range.
From the form, debts are read from the events of the social and health table that have a year (seizure, arrears, eviction notice or imminent eviction) and from emergency housing; benefits from public housing and from the income rows for care allowances and income support. The lines that cannot be worked out from the rest of the form are asked in its "Griglia di valutazione" section: how many adults have a total or a severe disability, how many minors a severe one, the points for other debts and for other contributions.
The points of every line are stored in the RegolePunteggio table and edited
from the configuration page (src/Rules.php checks what is
typed there). A change applies to the applications entered from then on; the
"Ricalcola" button of the same page scores every stored application again
with the current points, ISEE brackets and ages.
With DEMO_MODE on, as in docker-compose.yml, the application is safe to
leave open to anyone who has the demo account: no mail is really sent, the
credentials cannot be changed, and data and configuration go back to what is
in db/ when the login page is opened more than an hour after the last
reset. Settings come from the environment or, on a host without it, from a
config.php file (src/Config.php). .htaccess keeps code,
database files and settings from being served.
docs/deploy.md has the steps for a host with PHP and MySQL.
db/03-demo-data.sql is generated by
scripts/generate-demo-data.php: twenty
invented families pass through the same code as a submitted form, so what is
stored and how it is scored is what the application itself would produce.
Six of them hold a shopping card and one has just given it back. To change
the data, edit the script and run just demo-data, then just reset-db.
Known gaps:
- The members outside the ISEE household, which the paper form lists, are not collected (issue #6).
- Apart from the application form, the pages still mix queries, logic and markup, as they did in 2021; their queries are parameterised and what they print is escaped, but they are not covered by unit tests, only by the smoke test.
- The export is a tab-separated file served with an
.xlsname.
Table and view names are written with the same casing everywhere, so the
application also runs on a database that tells Tessera from tessera, as
Linux hosts do.