Sep 15 - Reverse the check-in pages, set type of work Regular as default
This commit is contained in:
@@ -547,15 +547,12 @@ Toggle UI in `create_qr_code.html` and `edit_qr_code.html` — uses `addEventLis
|
|||||||
### Type of Work — Check-In Dropdown (Sept 2026)
|
### Type of Work — Check-In Dropdown (Sept 2026)
|
||||||
|
|
||||||
The check-in page carries a **Type of Work / Tipo de Trabajo** `<select>` directly after
|
The check-in page carries a **Type of Work / Tipo de Trabajo** `<select>` directly after
|
||||||
the Employee ID field. The employee enters a **numeric-only** ID and picks the type.
|
the Employee ID field. The employee enters a **numeric-only** ID and picks the type;
|
||||||
**There is no default (Sept 14, 2026)** — the dropdown opens on a disabled placeholder
|
`Regular` is selected by default.
|
||||||
and submit is blocked (red outline + bilingual message) until a type is chosen, because
|
|
||||||
employees were leaving it on Regular without looking.
|
|
||||||
|
|
||||||
| Option value | Label shown (bilingual, one plain string) |
|
| Option value | Label shown (bilingual, one plain string) |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `""` (disabled, selected) | -- Select type of work / Seleccione tipo de trabajo -- |
|
| `""` | Regular / Trabajo Regular |
|
||||||
| `R` | Regular / Trabajo Regular — stored as the plain numeric ID |
|
|
||||||
| `PW` | Periodic Work / Trabajo Periódico (PW) |
|
| `PW` | Periodic Work / Trabajo Periódico (PW) |
|
||||||
| `SP` | Special Project / Proyecto Especial (SP) |
|
| `SP` | Special Project / Proyecto Especial (SP) |
|
||||||
| `C` | Covering / Cobertura (C) |
|
| `C` | Covering / Cobertura (C) |
|
||||||
@@ -567,11 +564,7 @@ languages share one label separated by `/`.
|
|||||||
the format every calculator and export already parses:
|
the format every calculator and export already parses:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
work_type = request.form.get('work_type', '').strip().upper()
|
work_type = request.form.get('work_type', '').strip().upper() # '' = Regular
|
||||||
if not work_type:
|
|
||||||
return jsonify({...}), 400 # nothing chosen — rejected
|
|
||||||
if work_type == 'R':
|
|
||||||
work_type = '' # Regular — no code appended
|
|
||||||
if work_type and work_type not in VALID_CHECKIN_WORK_TYPES: # ('SP','PW','PT','C')
|
if work_type and work_type not in VALID_CHECKIN_WORK_TYPES: # ('SP','PW','PT','C')
|
||||||
return jsonify({...}), 400 # reject unknown codes
|
return jsonify({...}), 400 # reject unknown codes
|
||||||
if employee_id and work_type:
|
if employee_id and work_type:
|
||||||
@@ -677,7 +670,7 @@ load (i.e. after the fetch resolves) and caches plain GETs aggressively:
|
|||||||
|
|
||||||
### Check-In Flow
|
### Check-In Flow
|
||||||
1. Employee scans QR → `qr_destination.html`
|
1. Employee scans QR → `qr_destination.html`
|
||||||
2. Enters numeric ID and **must** pick Type of Work (no default); GPS captured by browser
|
2. Enters numeric ID and picks Type of Work (Regular by default); GPS captured by browser
|
||||||
3. On a Check Out scan the page pre-selects the open check-in's work type
|
3. On a Check Out scan the page pre-selects the open check-in's work type
|
||||||
4. Work-type code appended to the ID server-side (`1234` → `1234SP`)
|
4. Work-type code appended to the ID server-side (`1234` → `1234SP`)
|
||||||
5. 30-min interval guard (configurable via `TIME_INTERVAL`)
|
5. 30-min interval guard (configurable via `TIME_INTERVAL`)
|
||||||
@@ -1197,13 +1190,6 @@ exact-match test is what dropped every SP/PW/PT row the query had already return
|
|||||||
| `templates/time_attendance_records.html` | Tooltip on the Export by Building button |
|
| `templates/time_attendance_records.html` | Tooltip on the Export by Building button |
|
||||||
| — | Replaces the manual "Copilot" procedure (delete PM rows + SP rows, then build a weekly table). Decisions: PM IDs removed from both sheets; SP hours excluded from weekly hours |
|
| — | Replaces the manual "Copilot" procedure (delete PM rows + SP rows, then build a weekly table). Decisions: PM IDs removed from both sheets; SP hours excluded from weekly hours |
|
||||||
|
|
||||||
### Set 19 — Type of Work Must Be Selected (Sept 14, 2026)
|
|
||||||
| File | Change |
|
|
||||||
|---|---|
|
|
||||||
| `templates/qr_destination.html` | Disabled placeholder option is the default; Regular value `""` → `R`; `required`; submit guard + `.work-type-missing` red outline; check-out suggestion maps server `""` → `R` |
|
|
||||||
| `static/js/qr_destination.js` | Same guard in `submitCheckin()` |
|
|
||||||
| `routes/qr_codes.py` | `qr_checkin` rejects an empty `work_type` with HTTP 400; `R` normalised to `''` (Regular). `last-work-type` still returns `""` for Regular |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 21. Infrastructure & Deployment
|
## 21. Infrastructure & Deployment
|
||||||
|
|||||||
+1
-12
@@ -743,19 +743,8 @@ def qr_checkin(qr_url):
|
|||||||
# The employee enters a numeric ID and picks a work type; the code is
|
# The employee enters a numeric ID and picks a work type; the code is
|
||||||
# appended to the ID so the stored value keeps the existing storage
|
# appended to the ID so the stored value keeps the existing storage
|
||||||
# format ("1234SP") that every calculator and export already parses.
|
# format ("1234SP") that every calculator and export already parses.
|
||||||
# The dropdown has no default: 'R' = Regular (ID stored unchanged),
|
# Empty selection = Regular work — the ID is stored unchanged.
|
||||||
# and an empty value means the employee did not choose — rejected.
|
|
||||||
work_type = request.form.get('work_type', '').strip().upper()
|
work_type = request.form.get('work_type', '').strip().upper()
|
||||||
if not work_type:
|
|
||||||
logger_handler.logger.warning(
|
|
||||||
f"Check-in rejected: no type of work selected for employee {employee_id}"
|
|
||||||
)
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'message': 'Please select the Type of Work. / Por favor seleccione el Tipo de Trabajo.'
|
|
||||||
}), 400
|
|
||||||
if work_type == 'R':
|
|
||||||
work_type = '' # Regular — no code appended
|
|
||||||
if work_type and work_type not in VALID_CHECKIN_WORK_TYPES:
|
if work_type and work_type not in VALID_CHECKIN_WORK_TYPES:
|
||||||
logger_handler.logger.warning(
|
logger_handler.logger.warning(
|
||||||
f"Check-in rejected: invalid work type '{work_type}' for employee {employee_id}"
|
f"Check-in rejected: invalid work type '{work_type}' for employee {employee_id}"
|
||||||
|
|||||||
@@ -327,25 +327,10 @@ function submitCheckin() {
|
|||||||
const workTypeField = document.getElementById("work_type");
|
const workTypeField = document.getElementById("work_type");
|
||||||
const workType = workTypeField ? workTypeField.value.trim() : "";
|
const workType = workTypeField ? workTypeField.value.trim() : "";
|
||||||
|
|
||||||
// Type of Work has no default - the employee must choose one
|
|
||||||
if (!workType) {
|
|
||||||
if (workTypeField) {
|
|
||||||
workTypeField.classList.add("work-type-missing");
|
|
||||||
workTypeField.focus();
|
|
||||||
}
|
|
||||||
showCustomStatusMessage(
|
|
||||||
"Please select the Type of Work / Por favor seleccione el Tipo de Trabajo",
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
isSubmitting = false;
|
|
||||||
updateSubmitButton(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare form data
|
// Prepare form data
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("employee_id", employeeId);
|
formData.append("employee_id", employeeId);
|
||||||
// R = Regular (ID stored unchanged); PW / SP / C are appended to the ID server-side
|
// Empty string = Regular; PW / SP / C are appended to the ID server-side
|
||||||
formData.append("work_type", workType);
|
formData.append("work_type", workType);
|
||||||
formData.append(
|
formData.append(
|
||||||
"latitude",
|
"latitude",
|
||||||
|
|||||||
@@ -69,12 +69,6 @@
|
|||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Type of Work has no default - flagged when submit is tried without one */
|
|
||||||
select.work-type-missing {
|
|
||||||
border: 2px solid #ef4444 !important;
|
|
||||||
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bilingual-container {
|
.bilingual-container {
|
||||||
background: rgba(255, 255, 255, 0.98);
|
background: rgba(255, 255, 255, 0.98);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -1224,16 +1218,11 @@
|
|||||||
name="work_type"
|
name="work_type"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
required
|
|
||||||
>
|
>
|
||||||
<!-- <option> cannot hold the coloured english-text/spanish-text
|
<!-- <option> cannot hold the coloured english-text/spanish-text
|
||||||
spans used elsewhere, so both languages share one plain label,
|
spans used elsewhere, so both languages share one plain label,
|
||||||
separated by "/" like the Employee ID placeholder above.
|
separated by "/" like the Employee ID placeholder above. -->
|
||||||
No default: the employee must pick a type before submitting.
|
<option value="" selected>Regular / Trabajo Regular</option>
|
||||||
Regular is "R" so it is distinguishable from "nothing picked";
|
|
||||||
the server stores it as the plain numeric ID. -->
|
|
||||||
<option value="" selected disabled>-- Select type of work / Seleccione tipo de trabajo --</option>
|
|
||||||
<option value="R">Regular / Trabajo Regular</option>
|
|
||||||
<option value="PW">Periodic Work / Trabajo Periódico (PW)</option>
|
<option value="PW">Periodic Work / Trabajo Periódico (PW)</option>
|
||||||
<option value="SP">Special Project / Proyecto Especial (SP)</option>
|
<option value="SP">Special Project / Proyecto Especial (SP)</option>
|
||||||
<option value="C">Covering / Cobertura (C)</option>
|
<option value="C">Covering / Cobertura (C)</option>
|
||||||
@@ -1446,7 +1435,6 @@
|
|||||||
// Bilingual labels - mirror WORK_TYPE_LABELS in routes/qr_codes.py.
|
// Bilingual labels - mirror WORK_TYPE_LABELS in routes/qr_codes.py.
|
||||||
const WORK_TYPE_LABELS = {
|
const WORK_TYPE_LABELS = {
|
||||||
"": { en: "Regular", es: "Trabajo Regular" },
|
"": { en: "Regular", es: "Trabajo Regular" },
|
||||||
R: { en: "Regular", es: "Trabajo Regular" },
|
|
||||||
PW: { en: "Periodic Work", es: "Trabajo Periódico" },
|
PW: { en: "Periodic Work", es: "Trabajo Periódico" },
|
||||||
SP: { en: "Special Project", es: "Proyecto Especial" },
|
SP: { en: "Special Project", es: "Proyecto Especial" },
|
||||||
PT: { en: "Project Team", es: "Equipo de Proyecto" },
|
PT: { en: "Project Team", es: "Equipo de Proyecto" },
|
||||||
@@ -1514,14 +1502,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const field = document.getElementById("work_type");
|
const field = document.getElementById("work_type");
|
||||||
// The server reports an open Regular check-in as ""; the dropdown's
|
if (field && field.value !== suggestedWorkTypeCode) {
|
||||||
// Regular option is "R" ("" is the "nothing selected" placeholder).
|
field.value = suggestedWorkTypeCode;
|
||||||
const optionValue = suggestedWorkTypeCode === "" ? "R" : suggestedWorkTypeCode;
|
|
||||||
if (field && field.value !== optionValue) {
|
|
||||||
field.value = optionValue;
|
|
||||||
}
|
|
||||||
if (field && field.value) {
|
|
||||||
field.classList.remove("work-type-missing");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1645,9 +1627,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
field.addEventListener("change", function () {
|
field.addEventListener("change", function () {
|
||||||
if (field.value) {
|
|
||||||
field.classList.remove("work-type-missing");
|
|
||||||
}
|
|
||||||
if (!workTypeTouchedByEmployee) {
|
if (!workTypeTouchedByEmployee) {
|
||||||
// No interaction preceded this change: it is Safari restoring the
|
// No interaction preceded this change: it is Safari restoring the
|
||||||
// control, not the employee choosing. Re-assert the suggestion.
|
// control, not the employee choosing. Re-assert the suggestion.
|
||||||
@@ -1935,19 +1914,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type of Work has no default - the employee must choose one
|
|
||||||
if (!workType) {
|
|
||||||
if (workTypeField) {
|
|
||||||
workTypeField.classList.add("work-type-missing");
|
|
||||||
workTypeField.focus();
|
|
||||||
}
|
|
||||||
showStatusMessage(
|
|
||||||
"Please select the Type of Work / Por favor seleccione el Tipo de Trabajo",
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save Employee ID to localStorage for next time
|
// Save Employee ID to localStorage for next time
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("qr_last_employee_id", employeeId);
|
localStorage.setItem("qr_last_employee_id", employeeId);
|
||||||
@@ -1978,7 +1944,7 @@
|
|||||||
// Prepare form data with location information
|
// Prepare form data with location information
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("employee_id", employeeId);
|
formData.append("employee_id", employeeId);
|
||||||
// R = Regular (ID stored unchanged); PW / SP / C are appended to the ID server-side
|
// Empty string = Regular; PW / SP / C are appended to the ID server-side
|
||||||
formData.append("work_type", workType);
|
formData.append("work_type", workType);
|
||||||
|
|
||||||
// Read selected location — window globals (most reliable), then hidden field, then dropdown
|
// Read selected location — window globals (most reliable), then hidden field, then dropdown
|
||||||
|
|||||||
Reference in New Issue
Block a user