# Handoff: EHS Operations Module — VPS Deployment & Production Readiness

**Date:** 2026-05-19
**From:** Auditor session (post Phase 1 build + audit)
**To:** Next Claude session (deployer)
**Priority:** HIGH — first EHS submission due Monday May 26

---

## What Was Built

A complete Django app `apps/operations/` in the ERP (`/home/borbolla/django-ui-local/`) that:
- Generates HTS-format Excel reports (worker list + weekly plan) from database
- Emails them to HTS EHS team via Gmail API
- Has a web UI dashboard at `/operations/`
- Has a management command for CLI/cron use

### Files Created (new)
```
apps/operations/__init__.py
apps/operations/apps.py
apps/operations/models.py          # 8 models (SubcontractorCompany, EHSProject, BorbollaWorker, SubcontractorWorker, ProjectWorker, EHSInduction, EHSWeeklyPlan, EHSActivity)
apps/operations/admin.py           # All models registered
apps/operations/views.py           # 7 views (dashboard, project detail, worker list, plan list, generate, download)
apps/operations/urls.py
apps/operations/forms.py
apps/operations/templatetags/      # (if any custom tags)
apps/operations/services/worker_list_generator.py
apps/operations/services/weekly_plan_generator.py
apps/operations/services/ehs_email_service.py
apps/operations/management/commands/generate_ehs_report.py
apps/operations/migrations/0001_initial.py
apps/operations/excel_templates/List of workers - example.xlsx
apps/operations/excel_templates/Weekly work plan Sample.xlsx
templates/operations/dashboard.html
templates/operations/project_detail.html
templates/operations/worker_list.html
templates/operations/plan_list.html
```

### Files Modified (existing)
```
config/settings.py     — Added 'apps.operations' to INSTALLED_APPS + EHS_REPORTS_DIR + EHS_TEMPLATES_DIR
config/urls.py         — Added path('operations/', include('apps.operations.urls'))
requirements.txt       — Added openpyxl + google-api-python-client + google-auth packages
templates/base.html    — Added "EHS / Operaciones" sidebar nav link
```

### Verified Locally
- Migration 0001_initial applied
- 3 test workers seeded (Felipe, Samuel, Carlos)
- Both Excel generators produce correct output
- Management command `generate_ehs_report` works end-to-end
- Sunday auto-fills "No work scheduled"

---

## Production Environment

| Item | Value |
|------|-------|
| VPS | `root@100.117.59.75` |
| App path | `/opt/django-ui/current` |
| Shared dir | `/opt/django-ui/shared/` |
| Env file | `/opt/django-ui/shared/env/.env` |
| Database | `/opt/django-ui/shared/db/db.sqlite3` (or similar) |
| Static | WhiteNoise serves from app |
| Process manager | gunicorn (systemd) |
| Gmail token (dev) | `/home/borbolla/gmail/token.json` |

---

## TODO: Production Readiness Checklist

### A. Pre-Deploy — Local (do before pushing)

- [ ] **A1. Commit all changes.** The operations app is untracked (`??`). Modified files include accounting, settings, urls, requirements, base.html. Review diff carefully — accounting changes may be from a separate feature. Consider separate commits:
  - Commit 1: `apps/operations/` + `templates/operations/` + settings/urls/requirements/base.html changes (EHS module)
  - Commit 2: accounting changes (if separate feature)

- [ ] **A2. Fix: Email CC recipients missing.** `ehs_email_service.py` line 64 sets `msg['To']` but has NO CC field. The plan specified CC to `rene.rodriguez@hyundai-transys.com` and `javier.lazaro@hyundai-transys.com`. Add:
  ```python
  cc_addrs = 'rene.rodriguez@hyundai-transys.com, javier.lazaro@hyundai-transys.com'
  msg['Cc'] = cc_addrs
  ```
  Verify these email addresses are correct before sending (query business.db or ask Luis).

- [ ] **A3. Fix: EHSActivity missing TenantModel.** `models.py` line 208: `class EHSActivity(models.Model)` — this is the ONLY model that doesn't extend TenantModel. It works because it's accessed via `weekly_plan` FK (tenant-scoped), but it breaks the convention and prevents direct `EHSActivity.objects.all()` from being tenant-filtered. Change to `class EHSActivity(TenantModel)` and add tenant FK. Will require a new migration.

- [ ] **A4. Fix: Admin tenant auto-assign.** All models extend TenantModel, meaning Django admin forms require a `tenant` field. But admin users shouldn't pick a tenant manually. Override `save_model` in each admin class:
  ```python
  def save_model(self, request, obj, form, change):
      if not change:
          obj.tenant = request.tenant_membership.tenant if hasattr(request, 'tenant_membership') else Tenant.objects.first()
      super().save_model(request, obj, form, change)
  ```
  Or create a `TenantAdminMixin` to DRY this across all admin classes.

- [ ] **A5. Fix: DownloadReportView file handle.** `views.py` line 127: `open(file_path, 'rb')` without context manager. Django's `FileResponse` handles closing, but best practice is to pass the path directly:
  ```python
  return FileResponse(file_path.open('rb'), as_attachment=True, filename=filename)
  ```
  This is minor but clean.

- [ ] **A6. Pin Google API package versions.** Check `requirements.txt` — the handoff specified exact versions (`google-api-python-client==2.147.0`, `google-auth==2.35.0`, etc.). Verify they're pinned, not floating.

### B. Deploy to VPS

- [ ] **B1. Push code to VPS.** Method depends on deploy workflow:
  - If git-based: `git push origin main` then `ssh root@100.117.59.75` and pull
  - If rsync: `rsync -avz --exclude=__pycache__ /home/borbolla/django-ui-local/ root@100.117.59.75:/opt/django-ui/current/`
  - Verify the deploy method from previous deploys (check `.claude/skills/` or deploy scripts)

- [ ] **B2. Install Python packages on VPS.**
  ```bash
  ssh root@100.117.59.75
  cd /opt/django-ui/current
  pip install -r requirements.txt
  ```
  Verify openpyxl and google-api-python-client installed correctly.

- [ ] **B3. Run migrations on VPS.**
  ```bash
  cd /opt/django-ui/current
  DOTENV_PATH=/opt/django-ui/shared/env/.env python manage.py migrate
  ```
  Should apply `operations.0001_initial` (and A3 fix migration if made).

- [ ] **B4. Create EHS reports output directory.**
  ```bash
  mkdir -p /opt/django-ui/shared/ehs_reports
  chown www-data:www-data /opt/django-ui/shared/ehs_reports
  ```

- [ ] **B5. Copy Gmail token to production.**
  ```bash
  # From dev machine:
  scp /home/borbolla/gmail/token.json root@100.117.59.75:/opt/django-ui/shared/secrets/gmail_token.json
  ```
  Then set env var in `/opt/django-ui/shared/env/.env`:
  ```
  GMAIL_TOKEN_PATH=/opt/django-ui/shared/secrets/gmail_token.json
  ```
  **IMPORTANT:** Verify the OAuth token scopes include `gmail.send`. If the token was created for read-only, it will fail silently or throw 403.

- [ ] **B6. Collect static files.**
  ```bash
  DOTENV_PATH=/opt/django-ui/shared/env/.env python manage.py collectstatic --noinput
  ```

- [ ] **B7. Restart gunicorn.**
  ```bash
  systemctl restart django-ui  # or whatever the service name is
  ```

### C. Seed Production Data

- [ ] **C1. Create EHSProject via Django admin** (`/django-admin/`):
  - Name: `TMED-II Phase III`
  - Client: `Hyundai Transys Mexico`
  - Client contact name: `Elvin Jimenez`
  - Client contact email: `elvin.jimenez@hyundai-transys.com`
  - Location: `Pesqueria, Ramos Arizpe`
  - Start: `2026-05-20`
  - End: `2026-07-12`

- [ ] **C2. Create BorbollaWorker entries.** Known crew (get IMSS/CURP from Luis or hr_database.db):
  - Felipe Rodriguez — Supervisor
  - Samuel Cenicero — Rigger
  - Carlos Medellin — Rigger (verify this person exists)
  - (Add more as crew confirms)

- [ ] **C3. Create SubcontractorCompany: Belean Industrial**
  - Contact: Alex Aguilar
  - Email: ah.beleanindustrial@gmail.com

- [ ] **C4. Create SubcontractorWorker entries** when Alex provides the roster with IMSS/CURP.

- [ ] **C5. Create ProjectWorker assignments** linking workers to the TMED-II project with correct roles and crew numbers.

### D. Test on Production

- [ ] **D1. Test Excel generation (no email).**
  ```bash
  cd /opt/django-ui/current
  DOTENV_PATH=/opt/django-ui/shared/env/.env python manage.py generate_ehs_report --project-id=1 --week=2026-05-19
  ```
  Verify output files at `/opt/django-ui/shared/ehs_reports/`.
  Open both .xlsx files and confirm worker data, dates, formatting.

- [ ] **D2. Test email send (dry run to Luis first).**
  Temporarily change `client_contact_email` on the EHSProject to `direccion@borbollagroup.com` (Luis). Then:
  ```bash
  DOTENV_PATH=/opt/django-ui/shared/env/.env python manage.py generate_ehs_report --project-id=1 --send
  ```
  Verify Luis receives the email with both Excel attachments.
  Check: Subject line, body formatting, CC recipients, attachment names.

- [ ] **D3. Test web UI.** Navigate to `https://erp.borbollagroup.com/operations/`. Verify:
  - Dashboard shows the TMED-II project with worker counts
  - Project detail shows worker list + weekly plan history
  - "Generate Reports" button works
  - "Download" links serve the Excel files

- [ ] **D4. Restore correct recipient.** Change `client_contact_email` back to `elvin.jimenez@hyundai-transys.com`.

### E. Cron Setup

- [ ] **E1. Install Monday cron on VPS.**
  ```bash
  crontab -e
  # Add:
  0 7 * * 1 cd /opt/django-ui/current && DOTENV_PATH=/opt/django-ui/shared/env/.env python manage.py generate_ehs_report --project-id=1 --send >> /opt/django-ui/shared/logs/ehs_report.log 2>&1
  ```
  **NOTE:** Verify the path is `/opt/django-ui/current` not `/opt/django-ui/app` (the plan had `/opt/django-ui/app` which may be wrong).

- [ ] **E2. Create log directory.**
  ```bash
  mkdir -p /opt/django-ui/shared/logs
  ```

- [ ] **E3. Verify cron timezone.** Production server must be set to `America/Monterrey` (or equivalent CST/CDT). Check with `timedatectl`. If UTC, adjust cron to `0 13 * * 1` (7am CDT = 13:00 UTC).

### F. Post-Deploy Verification

- [ ] **F1. First real send: Monday May 26.** Monitor `/opt/django-ui/shared/logs/ehs_report.log` for success/failure. Check Elvin's inbox.

- [ ] **F2. Verify Gmail token doesn't expire.** Google OAuth refresh tokens can expire if unused for 6 months, or if the app is in "testing" mode (7-day expiry). If the token was created in a testing project, it will fail after 7 days. Check Google Cloud Console project status.

- [ ] **F3. Add Belean workers** as Alex Aguilar provides the IMSS/CURP roster.

- [ ] **F4. Week 2+: Photo embedding** (Phase 3) if HTS rejects worker list without photos.

---

## Known Issues (Non-Blocking)

| Issue | Severity | Notes |
|-------|----------|-------|
| No CC on emails | LOW | Fix in A2 before deploy |
| EHSActivity not TenantModel | LOW | Fix in A3 — functional but breaks convention |
| Admin requires manual tenant selection | LOW | Fix in A4 — annoyance for data entry |
| Summary table limited to 4 rows | LOW | Worker list generator caps summary at rows 7-10. If >4 workers, rows 11+ overflow into "ATTACHED" divider. For now OK (crew starts small), expand later. |
| Manager name hardcoded | LOW | `weekly_plan_generator.py` line 49: `'Felipe Rodriguez'` hardcoded. Should pull from ProjectWorker with role=supervisor. Low priority — correct for TMED-II. |
| Cron path uncertain | LOW | `/opt/django-ui/app` vs `/opt/django-ui/current` — verify on VPS |

---

## File Paths Summary

| What | Dev Path | Production Path |
|------|----------|-----------------|
| App code | `/home/borbolla/django-ui-local/apps/operations/` | `/opt/django-ui/current/apps/operations/` |
| Templates | `/home/borbolla/django-ui-local/templates/operations/` | `/opt/django-ui/current/templates/operations/` |
| Excel templates | `apps/operations/excel_templates/` (bundled) | Same (bundled with code) |
| Generated reports | `BASE_DIR/shared/ehs_reports/` | `/opt/django-ui/shared/ehs_reports/` |
| Gmail token | `/home/borbolla/gmail/token.json` | `/opt/django-ui/shared/secrets/gmail_token.json` |
| Env file | local `.env` | `/opt/django-ui/shared/env/.env` |
| Cron log | — | `/opt/django-ui/shared/logs/ehs_report.log` |

---

## Key Contacts for EHS Submissions

| Name | Email | Role |
|------|-------|------|
| Elvin Jimenez | elvin.jimenez@hyundai-transys.com | HTS EHS Team (primary recipient) |
| Rene Rodriguez | rene.rodriguez@hyundai-transys.com | CC (verify before first send) |
| Javier Lazaro | javier.lazaro@hyundai-transys.com | CC (verify before first send) |

**IMPORTANT:** Verify CC emails with Luis before first production send. These came from "original email CC" but were never independently confirmed in business.db.

---

## Timeline

| Date | Milestone |
|------|-----------|
| May 19 (today) | Handoff written |
| May 20-23 | Deploy to VPS, seed data, test |
| May 24-25 | Dry run email to Luis |
| **May 26 (Mon)** | **First real EHS submission to Elvin** |
| May 26+ | Add Belean workers as roster arrives |
| Week 2+ | Phase 3 (photos, bulk import, compliance dashboard) |
