# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

```bash
npm start          # Start production server (port 3000 or $PORT)
npm run dev        # Start dev server with nodemon hot-reload
npm run debug:pm2  # Start with PM2 + Node inspector on port 9229
```

Database migrations via Sequelize CLI:
```bash
npx sequelize-cli db:migrate
npx sequelize-cli db:migrate:undo
npx sequelize-cli migration:generate --name <name>
```

No test or lint commands are configured.

---

## Architecture

**Nucleus** is a pharmaceutical manufacturing operations management REST API built with Express.js + PostgreSQL (Sequelize ORM). All routes are prefixed `/api/v1/`.

### Request Flow

```
server.js
  └── app/routes/index.js          (aggregates 110+ route files)
        └── [feature].route.js
              └── verify_auth.js   (JWT middleware, attaches req.user_data)
                    └── [feature].controller.js
                          └── app/models/  (Sequelize queries)
```

- `server.js` — bootstraps Express (150 MB body limit), Morgan logging, CORS, Swagger at `/api-docs`, registers all routes under `/api/v1`, serves static files from `/public`
- `app/routes/index.js` — single file registering all route modules; `verify_auth` applied to every authenticated group
- `app/middelwares/verify_auth.js` — JWT `Bearer` token validation; decoded payload in `req.user_data`; also exports `role_admin` (admin/qa_super_user only) and `department_role` (remember_token sessions)
- `app/models/index.js` — loads every model file and defines **all Sequelize associations** (hasMany / belongsTo); this is the single source of truth for relationships

---

## Models

### Location & Associations

- All model files: `app/models/[name].js`
- **All associations are defined in `app/models/index.js`** (not in individual model files)
- Individual model files only define the schema (fields, types, defaults)

### Domain Grouping (~377 model files)

| Domain | Key Models |
|---|---|
| **Users & Auth** | `users`, `active_session`, `user_password`, `user_matrix`, `designated_authorities`, `designation_log`, `LoginLogoutDetails`, `failed_logins` |
| **Materials & Procurement** | `materials`, `batch_details`, `approved_vendors`, `suppliers`, `mrn`, `mrn_dom_and_int`, `mrn_projects` |
| **Purchase Orders** | `purchase_order_domestic`, `purchase_order_international`, `purchase_order_projects`, `purchase_returns`, `purchase_return_projects` |
| **QC & Verification** | `qc_check`, `document_verification`, `qa_damaged_containers`, `qa_document_storage`, `qa_weighing_discrepancy`, `qa_line_clearance_batch` |
| **Sampling** | `sampling_history`, `product_sampling`, `removed_stock`, `removedstock` |
| **Stock & Inventory** | `stock_history`, `stock_card_issuance`, `stock_reconcililation`, `bins`, `containers`, `batch_bin_locations`, `prod_bin_locations` |
| **Dispensing** | `dispensing_requests`, `dispensing_material`, `dispensing_history`, `dispensing_material_batches`, `dispensing_material_parts`, `dispense_sub_batches`, `new_dispensary_containers`, `dispense_weighing` |
| **Line Clearance** | `line_clearance_batch`, `line_clearance_input`, `qa_line_clearance_batch`, `list_clerance_checklist` |
| **Blending** | `blending`, `blending_container`, `blending_leftover`, `blend_distrubition_record` |
| **Granulation & Milling** | `granulation`, `shifting_and_milling`, `shifting_and_milling_leftover`, `extra_granular_container`, `extra_granular_leftover` |
| **BMR / eBMR** | `bom_process_group_master`, `bom_process_master`, `bom_materials`, `bmr_product_materials`, `bmr_events`, `bmr_status_logs`, `execute_bmr_details`, `parameter_actual_values`, `acccessory_actual_values`, `bmr_requisition_slip` |
| **Product Master** | `product_details`, `product_master`, `product_type`, `stage_master`, `process_master`, `stage_group`, `stage_sub_group`, `instruction_config`, `accessory_master` |
| **Equipment Planner** | `equipment_planner`, `equipment_planner_versions`, `equipment_plan_history`, `physical_log_entry`, `equipment_master`, `equipment_model`, `equipment_process_mapping`, `process_equipment_config` |
| **Equipment Maintenance** | `equipment_maintainence`, `equipment_maintainence_tracker`, `equipment_maintainence_category_masters` |
| **Focus Pharma Integration** | `eq_blender_batch`, `eq_fbp_batchinfo`, `eq_coating_batchinfo`, `eq_rmg_batchinfo`, `eqp_integration_master`, `pre004_*`, `pre059_*`, `pre060_*`, `pre063_*`, `pre065_*`, `pre115_*`, `pre116_*` |
| **Cleaning & Environment** | `cleaning_record_first/sec`, `cleaning_wrapping_first/sec`, `trhmr_log_book`, `ipa_preparation_log_books`, `area_masters`, `area_name_master`, `temperature_limit_master`, `relative_humidity_master` |
| **Batch Traceability** | `batch_num_log`, `wip_batch_num`, `fg_batch_num`, `detailed_fg_batch`, `batch_container` |
| **Documents & DMS** | `dms_documents`, `dms_evaluation_prints`, `doc_verify_uploads`, `test_request_form`, `invoice_details`, `consignee_details` |
| **Dispatch & Finished Goods** | `mode_of_dispatch_of_finished_goods`, `dispatch_checklist_for_finished_goods_log_books`, `miscellaneous_labels` |
| **Admin & Licensing** | `license`, `ems`, `ems_room_number_masters`, `country_market_name_masters` |

### Key Model Field Patterns

- `is_active: BOOLEAN DEFAULT true` — soft delete flag used on most models
- `created_at / updated_at` — Sequelize timestamps
- `user_id` — FK to `users` table, tracks who created/modified
- `mrn_number` — links batch_details → mrn → dispensing chain
- `ar_number` — QC identifier linking qc_check → sampling_history → TRF
- `inhouse_batch_number` — internal batch tracking key across production modules

### Change Log / Audit Trail Models (~60+ models)

Every major entity has a corresponding `*_change_logs` or `*_logs` model. Schema pattern:

```js
{ change_field: TEXT, from: TEXT, to: TEXT, update_date: DATE,
  transaction_type: STRING, is_active: BOOLEAN }
```

| Domain | Log Models |
|---|---|
| Batch | `batch_detail_logs`, `batch_book_change_log`, `bin_update_logs` |
| Material | `material_change_log`, `material_indent_changelog`, `material_cost_change_log` |
| QC & Verification | `qc_update_logs`, `batch_approver_logs`, `edit_qa_approver_log`, `report_qa_document_log` |
| Dispensing & Stock | `dispensing_logs`, `dispensing_requests_logs`, `dispense_delete_logs`, `stock_edit_log`, `fifo_qty_log` |
| Cleaning | `cleaning_change_logs`, `cleaning_wrp_change_logs`, `prep_change_logs` |
| Equipment | `equipment_maintainece_change_logs`, `eqp_change_log_books`, `equipment_log_books` |
| Production | `prod_dispense_change_logs`, `bmr_audit_logs`, `bmr_event_log`, `bmr_material_log`, `lc_edit_logs` |
| Cron | `cron_logs`, `crons_logs`, `blend_batch_cronlog` |
| Other | `user_change_log`, `document_log`, `req_slips_change_logs`, `inv_tracker_change_logs` |

---

## Cross-Module Flows

### Flow 1 — Material Receipt → QC → Sampling → Stock

```
MRN Creation       (mrn_dom_and_int, mrn_projects)
  ↓ mrn_number
Document Verify    (document_verification)
  ↓
QC Check           (qc_check, qa_document_storage, qa_damaged_containers)
  ↓ ar_number
Sampling           (sampling_history, product_sampling)
  ↓
Stock Card         (stock_card_issuance → stock_history)
```

Controllers: `mrn_domestic.controller.js` → `document.controller.js` → `qc_check.controller.js` → `sampling.controller.js`
Logs written: `mrn_logs`, `qc_update_logs`, `sampling_log`, `batch_detail_logs`

### Flow 2 — Batch Creation → Dispensing → Production

```
Batch Details      (batch_details, batch_bin_locations)
  ↓ mrn_number
Dispensing Request (dispensing_requests)
  ↓
Dispense Materials (dispensing_material, dispensing_material_batches)
  ↓
Line Clearance     (line_clearance_batch, qa_line_clearance_batch)
  ↓
Dispensing History (dispensing_history, new_dispensary_containers)
  ↓
Stock Reconcile    (stock_reconcililation)
```

Controllers: `batch.controller.js` → `dispensing.controller.js` → `lc.controller.js`
Logs written: `batch_detail_logs`, `dispensing_logs`, `dispensing_requests_logs`, `lc_edit_logs`

### Flow 3 — Equipment Planner → BMR Execution → Log Book

```
Equipment Planner  (equipment_planner, equipment_planner_versions)
  ↓
Physical Log       (physical_log_entry)
  ↓
Execute BMR        (execute_bmr_details, parameter_actual_values)
  ↓
Equipment Log      (equipment_log_books, eqp_change_log_books)
```

### Flow 4 — Focus Pharma Equipment Integration

```
Equipment Data     (eq_blender_batch / eq_fbp_batchinfo / eq_rmg_batchinfo, pre0xx_* tables)
  ↓
Sampling Creation  (sampling_history)
  ↓
focus_sampling.js  helper → POST to external Focus Pharma API
  ↓
sampling_history.focus_sampling = true  (marks as synced)
```

External API: `http://103.211.37.189/Graviti_Pharmaceuticals_Pvt_Ltd/Sampling/PostSampling`
Also see: `focus_stockrecosiliation.js` for stock sync to Focus.

### Flow 5 — Requisition → BMR Materials → Dispensing

```
Requisition Slip   (bmr_requisition_slip)
  ↓
BMR Materials      (bmr_product_materials, dispensing_bmr_materials)
  ↓
Dispensing Request (dispensing_requests)
  ↓
Status Logs        (req_status_logs, req_slips_change_logs)
```

---

## Helpers (`app/helpers/`)

| File | Purpose |
|---|---|
| `pagination.js` | `pagination()`, `getPagination(page, size)` — standard limit/offset |
| `newPagination.js` | `getLimitAndOffset()`, `getNewPagination()` — returns `total_pages`, `next`, `prev` |
| `templateToHtml.js` | Compiles `.hbs` template + data → HTML string; registers Handlebars helpers (`dateFormat`, `inc`, `serialNo`, `alphabets`, `ifEqual`, `json`) and auto-loads partials from `app/html_files/partials/` |
| `azureStorage.js` | Exports `blobServiceClient` and `containerDocuments`; used for upload/download to Azure Blob |
| `focus_sampling.js` | `postData()` — POSTs sampling record to Focus Pharma external API |
| `focus_stockrecosiliation.js` | `postStockData()` — POSTs stock shortage/overstock adjustments to Focus API |
| `download.js` | `downloadFile()` — streams file downloads |
| `extractFbbData.js` | Extracts Fluid Bed Blender process data from equipment tables |
| `ordinalsuffix.js` | Formats numbers with ordinal suffix (1st, 2nd…) |
| `physical_log_entry.validator.js` | Validates physical log entries for equipment planner |
| `productionDb.js` | Separate Sequelize connection to production DB (used in specific cross-env queries) |
| `multer.js` | Multer config — saves to `/public/images` with UUID filenames |
| `initiateVerification.js` | Token-based verification flow for document/approval steps |
| `audit_report.js` | Audit report generation utilities |

---

## Templates

### PDF Templates (`app/html_files/`)

Handlebars `.hbs` files rendered to HTML via `templateToHtml.compile(name, data)`, then to PDF via `html-pdf` or Puppeteer, merged via `pdf-merger-js`, uploaded to Azure, URL returned to client.

Key templates:
- `batchlog.hbs`, `batch-line-clearance.hbs` — batch records
- `dispensing_sheet.hbs`, `dispensing_manual.hbs`, `dispense_details.hbs` — dispensing documents
- `bmr_dispensingsheet_template.hbs`, `bmr_lists_template.hbs` — BMR documents
- `e_bmr_dispensing_sheet.hbs`, `e_bmr_area_lc.hbs` — eBMR execution documents
- `audit_report.hbs`, `audit_sampling_creationlog.hbs` — compliance audit reports
- `area-label.hbs`, `bin-label.hbs` — physical labels
- `prod_label_*.hbs`, `miscellaneous_labels*.hbs` — product labels
- Partials in `app/html_files/partials/` (auto-registered)

### Email Templates (`app/email/`)

Handlebars `.hbs` files used by Nodemailer via `nodemailer-express-handlebars`:
- `account_created.hbs` — new user onboarding (variables: `reciever_name`, `password`)
- `generate_mrn.hbs` — MRN generation notification sent to QC team

Email config: `app/config/mail.config.js` — Office 365 SMTP (`smtp.office365.com:587`), TLS with SSLv3, credentials from `Email` and `Email_Pass` env vars.

---

## Database Configuration

**File:** `app/config/config.json`

| Environment | Host | Database |
|---|---|---|
| `production` | nucleus-prod-db-do-user-6745027-0.c.db.ondigitalocean.com:25060 | (managed DO PostgreSQL) |
| `development-staging` | 128.199.26.222 | nucleusstagingdb |
| `development` | 143.110.254.173 | nucleusdevdb |

All environments: PostgreSQL dialect, SSL required, logging disabled, pool `{ max:5, min:0, acquire:30000, idle:10000 }`.

Active environment controlled by `NODE_ENV` env var.

**Sequelize CLI config:** `.sequelizerc` maps paths for models, migrations, seeders, and config.

**Migrations:** `app/migrations/` — named `YYYYMMDDHHmmss-description.js`. Always create a migration for schema changes; do not rely on `sequelize.sync()` (it is not enabled).

---

## Scheduled Jobs (Cron)

`node-cron` is used in several controllers for periodic tasks:

| Controller | Purpose |
|---|---|
| `batch.controller.js` | Batch status & expiry date updates |
| `batch_retest.controller.js` | Retest date scheduling |
| `equipment_maintainence.controller.js` | Maintenance reminder alerts |
| `license.controller.js` | License expiry tracking |
| `prod_dispense_controller.js` | Production dispensing workflows |

Cron executions are logged in `cron_logs`, `crons_logs`, `blend_batch_cronlog`.

---

## Patterns to Follow

- **Change logging (compliance required):** Every update to a critical entity must also insert a record into its `*_change_logs` table with `change_field`, `from`, `to`, `update_date`, and user reference. This is non-negotiable for regulatory compliance.
- **Sequelize transactions:** Use `sequelize.transaction()` for any operation touching multiple tables.
- **Audit identity:** Always capture `req.user_data` (user id/name) on create and update operations.
- **Pagination:** Use `newPagination.js` helpers (`getLimitAndOffset` + `getNewPagination`) for list endpoints — they return `total_pages`, `next`, `prev` metadata.
- **PDF generation:** Compile template with `templateToHtml.compile()` → render to PDF → upload blob to Azure → return blob URL.
- **Soft deletes:** Set `is_active = false` rather than deleting rows.
- **Swagger:** `app/swagger/swagger.js` is very large (~376 KB) — edit carefully and test that `/api-docs` loads after changes.
- **Focus API sync:** After creating/updating sampling or stock records, check whether the Focus Pharma sync flag needs to be set and call the appropriate helper.

---

## Environment Variables (`.env`)

| Variable | Purpose |
|---|---|
| `NODE_ENV` | Selects DB config: `development`, `development-staging`, `production` |
| `JWT_ENCRYPTION` | JWT signing secret |
| `AZURE_STORAGE_NAME` | Azure storage account name |
| `AZURE_STORAGE_CONTAINER` | Blob container name |
| `AZURE_STORAGE_URL` | Public base URL for blob access |
| `Email_Host` | SMTP host (Office 365) |
| `Email_SMTP_Port` | SMTP port (587) |
| `Email` | SMTP sender address |
| `Email_Pass` | SMTP password |
| `FETCH_URL` | Backend base URL (used in email links and PDF generation) |
| `MATERIAL_URL` | Frontend material deep-link (used in emails) |
| `DISPENSE_URL` | Frontend dispense deep-link (used in emails) |

---

## Working Style

> **New code adopts the patterns below. Existing code is not to be converted — leave it
> as-is. When editing existing code, match the surrounding style rather than imposing
> the new patterns.**

---

### Response Shape

All controller functions respond with one of two shapes — no exceptions:

```js
// Success
res.status(200).json({ success: true, message: "Record created successfully.", data: result });

// Client error
res.status(400).json({ success: false, message: "Descriptive validation message." });

// Server error
res.status(500).json({ success: false, message: "We're experiencing some technical difficulties. Please contact the Nucleus Team for assistance." });
```

Rules:
- **Never** forward `err.message` or stack traces to the client.
- Use specific `message` values on 4xx (validation context is safe to expose).
- Use the fixed generic message on all 5xx.
- HTTP status codes: `200` success, `400` bad request, `401` unauthenticated, `403` forbidden, `404` not found, `500` server error.

---

### Async Pattern (New Code)

New functions use **pure `async/await` with `try/catch`** — no mixing with `.then()/.catch()`:

```js
exports.create_record = async (req, res) => {
  try {
    const { field_one, field_two } = req.body;

    const result = await Model.create({
      field_one,
      field_two,
      user_id: req.user_data.id,
    });

    return res.status(200).json({ success: true, message: "Created successfully.", data: result });
  } catch (err) {
    console.log(err);
    return res.status(500).json({ success: false, message: "We're experiencing some technical difficulties. Please contact the Nucleus Team for assistance." });
  }
};
```

> **Existing code** uses a mixed `try { await .then().catch() }` pattern — leave it untouched. Do not refactor or "clean up" existing async code while making unrelated changes.

---

### Transactions (Multi-table Writes)

Any operation that writes to more than one table must use a Sequelize transaction:

```js
const { sequelize } = require('../models');

const t = await sequelize.transaction();
try {
  const record = await ModelA.create({ ...fields, user_id: req.user_data.id }, { transaction: t });
  await ModelB.create({ parent_id: record.id, ...logFields }, { transaction: t });
  await t.commit();
  return res.status(200).json({ success: true, message: "Done.", data: record });
} catch (err) {
  await t.rollback();
  console.log(err);
  return res.status(500).json({ success: false, message: "We're experiencing some technical difficulties. Please contact the Nucleus Team for assistance." });
}
```

> `sequelize` is imported from `../models` — the same instance used by all models.

---

### Error Handling

- No centralized error middleware — every controller handles its own errors.
- All catch blocks: `console.log(err)` then respond. Do not add structured logging.
- Never return `err.message` in the response body.

---

### Naming Conventions

| Scope | Convention | Example |
|---|---|---|
| Variables, functions | `snake_case` | `mfg_batch_lot_no`, `get_all_users` |
| Route paths | `kebab-case` | `/send-email`, `/re-generate-qr-labels` |
| Boolean flags | `is_` prefix | `is_active`, `is_verified` |
| Foreign keys | `{table}_id` | `user_id`, `batch_details_id` |
| Domain abbreviations | lowercase | `qty`, `mrn`, `qc`, `qa`, `trf`, `bmr`, `uom` |

---

### Sequelize Query Style

```js
// Single record read
const record = await Model.findOne({ where: { id, is_active: true }, raw: true });

// Paginated list
const { count, rows } = await Model.findAndCountAll({
  where: { is_active: true },
  include: [{ model: RelatedModel, required: false }],
  order: [['createdAt', 'DESC']],
  attributes: ['col1', 'col2'],
  limit,
  offset,
});

// Create
await Model.create({ field, user_id: req.user_data.id });

// Update
await Model.update({ field: value }, { where: { id } });
```

- Always add `raw: true` on reads that don't need Sequelize instance methods.
- Use `Op.iLike` for case-insensitive search (PostgreSQL).
- Use `attributes: [...]` to limit selected columns on heavy reads.
- Always filter by `is_active: true` on reads unless intentionally including soft-deleted records.

---

### Pagination (New List Endpoints)

Use `pagination.js` for all new list endpoints:

```js
const { getPagination, pagination } = require('../helpers/pagination');

const { limit, offset } = getPagination(req.query.page, req.query.per_page);
const { count, rows } = await Model.findAndCountAll({ limit, offset, where: { is_active: true } });

return res.status(200).json(
  pagination({ data: rows, count, page: req.query.page, per_page: req.query.per_page })
);
```

Response shape includes: `total_records`, `total_page`, `current_page`, `next_page`, `previous_page`. Default: 10 records per page.

> `newPagination.js` exists only for legacy endpoints. Do not use it in new code.

---

### Change Log Pattern (Compliance-required)

Every field-level update on a critical entity must write a change log in the same transaction:

```js
await SomeChangeLog.create({
  change_field: 'field_name',
  from:         String(existingRecord.field_name ?? ''),
  to:           String(newValue ?? ''),
  user_id:      req.user_data.id,
  parent_id:    recordId,
  update_date:  new Date(),
}, { transaction: t });
```

- `from` and `to` are always stored as strings (cast explicitly).
- `update_date` is a domain field — it is not the same as Sequelize's auto-managed `createdAt`.
- This is **non-negotiable** for regulatory compliance.

---

### User Identity

`req.user_data` is set by `verify_auth` middleware and contains:

| Field | Type | Usage |
|---|---|---|
| `id` | integer | Always pass as `user_id` on create/update |
| `fullname` | string | Display in audit trails |
| `email` | string | Notification targets |
| `role` | string | Permission checks |
| `department` | object | Dept-level access control |
| `remember_token` | string | `department_role` session auth |

Always pass `user_id: req.user_data.id` when creating or updating any record.

---

### Input Sanitization

Apply before saving to DB:

```js
// Empty string → null (loose equality is intentional — catches null and undefined too)
if (field == "") field = null;

// Numeric strings → float for arithmetic
const qty = parseFloat(req.body.qty);

// Date fields — empty string causes DB type errors
if (mfg_date == "") mfg_date = null;
```

> The `==` (loose equality) on empty-string checks is **intentional** — it catches both `""` and values coerced from `null`/`undefined`. Do not change to `===`.

---

### Date Handling

- `new Date()` — current timestamp for `created_at`, `update_date`, log fields.
- `moment` — formatting and display only; never store moment objects in the DB.
- No explicit timezone config — server timezone is assumed throughout.

---

### File Uploads

- Route-level middleware: `upload.single('fieldname')` (from `app/helpers/multer.js`).
- Multer disk storage saves to `/public/images/` with `${uuidv4()}-${original_name}` filename.
- Use `multer.memoryStorage()` only when buffer processing is needed before storage.
- Azure blob upload: use `blobServiceClient` from `app/helpers/azureStorage.js`; return the public blob URL in the response.

---

### Commented-Out Code

The codebase has heavy commented-out blocks (legacy URLs, alternate environment configs, old logic). This is expected and intentional — do not remove them. Only comment out new code when there is a concrete reason documented inline.


## Core Principles

- *Simplicity First*: Make every change as simple as possible. Impact minimal code.
- *No Laziness*: Find root causes. No temporary fixes. Senior developer standards.
- *Minimal Impact*: Changes should only touch what's necessary. Avoid introducing bugs.