diff --git a/backend/prisma/seed-demo.js b/backend/prisma/seed-demo.js index cc37318..b82eaad 100644 --- a/backend/prisma/seed-demo.js +++ b/backend/prisma/seed-demo.js @@ -249,9 +249,10 @@ async function main() { { name: `${DEMO_PREFIX} Rockwool Cubes 4"`, category: 'OTHER', quantity: 450, minThreshold: 200, unit: 'cube', location: 'Veg Storage', vendor: 'GrowGen' }, { name: `${DEMO_PREFIX} Coco Coir`, category: 'OTHER', quantity: 15, minThreshold: 10, unit: 'bag', location: 'Veg Storage', vendor: 'Botanicare' }, { name: `${DEMO_PREFIX} Trimmers (Fiskars)`, category: 'MAINTENANCE', quantity: 12, minThreshold: 15, unit: 'pair', location: 'Trim Room', vendor: 'Amazon' }, - { name: `${DEMO_PREFIX} Front Row Ag - Part A`, category: 'NUTRIENTS', quantity: 40, minThreshold: 10, unit: 'bag (25lb)', location: 'Nutrient Storage', vendor: 'Front Row Ag' }, - { name: `${DEMO_PREFIX} Front Row Ag - Part B`, category: 'NUTRIENTS', quantity: 30, minThreshold: 10, unit: 'bag (25lb)', location: 'Nutrient Storage', vendor: 'Front Row Ag' }, - { name: `${DEMO_PREFIX} Front Row Ag - Bloom`, category: 'NUTRIENTS', quantity: 30, minThreshold: 10, unit: 'bag (25lb)', location: 'Nutrient Storage', vendor: 'Front Row Ag' }, + { name: `${DEMO_PREFIX} HVAC Filters`, category: 'FILTER', quantity: 24, minThreshold: 12, unit: 'each', location: 'Utility Room', vendor: 'Amazon' }, + { name: `${DEMO_PREFIX} Carbon Filters`, category: 'FILTER', quantity: 8, minThreshold: 4, unit: 'each', location: 'Utility Room', vendor: 'Phresh' }, + { name: `${DEMO_PREFIX} Front Row Ag - Part A`, category: 'OTHER', quantity: 40, minThreshold: 10, unit: 'bag (25lb)', location: 'Nutrient Storage', vendor: 'Front Row Ag' }, + { name: `${DEMO_PREFIX} Front Row Ag - Part B`, category: 'OTHER', quantity: 30, minThreshold: 10, unit: 'bag (25lb)', location: 'Nutrient Storage', vendor: 'Front Row Ag' }, ]; for (const s of supplies) { @@ -308,24 +309,24 @@ async function main() { const curingBatch = Object.values(batches).find(b => b.stage === 'CURING'); const dryingBatch = Object.values(batches).find(b => b.stage === 'DRYING'); - if (curingBatch) { + if (curingBatch && users.mike?.id) { await prisma.weightLog.create({ data: { batchId: curingBatch.id, - userId: users.mike?.id, - type: 'DRY', - weightGrams: 8450, + loggedBy: users.mike.id, + weightType: 'FINAL_DRY', + weight: 8450, notes: 'Final dry weight before cure' } }); } - if (dryingBatch) { + if (dryingBatch && users.jordan?.id) { await prisma.weightLog.create({ data: { batchId: dryingBatch.id, - userId: users.jordan?.id, - type: 'WET', - weightGrams: 42000, + loggedBy: users.jordan.id, + weightType: 'WET', + weight: 42000, notes: 'Wet weight at harvest' } }); @@ -355,16 +356,16 @@ async function main() { for (const user of userList) { for (let d = 1; d <= 14; d++) { if (Math.random() > 0.15) { // 85% attendance - const clockIn = new Date(daysAgo(d)); - clockIn.setHours(7 + Math.floor(Math.random() * 2), Math.floor(Math.random() * 30), 0); - const clockOut = new Date(clockIn); - clockOut.setHours(clockIn.getHours() + 8 + Math.floor(Math.random() * 2)); + const startTime = new Date(daysAgo(d)); + startTime.setHours(7 + Math.floor(Math.random() * 2), Math.floor(Math.random() * 30), 0); + const endTime = new Date(startTime); + endTime.setHours(startTime.getHours() + 8 + Math.floor(Math.random() * 2)); await prisma.timeLog.create({ data: { userId: user.id, - clockIn, - clockOut, + startTime, + endTime, notes: Math.random() > 0.8 ? 'Overtime for trim' : null } }); diff --git a/specs/paperless-integration.md b/specs/paperless-integration.md new file mode 100644 index 0000000..f8cf83d --- /dev/null +++ b/specs/paperless-integration.md @@ -0,0 +1,355 @@ +# Paperless-ngx Integration Specification + +## Overview + +Integrate CA Grow Ops Manager with a Paperless-ngx instance for long-term document storage, archival, and retrieval. Paperless-ngx provides OCR, full-text search, tagging, and archival capabilities that complement our application's document management needs. + +## Architecture + +``` +┌─────────────────────────────────────────────────────┐ +│ CA Grow Ops Manager │ +│ ┌───────────────┐ ┌─────────────────────────┐ │ +│ │ Documents │ │ Paperless Service │ │ +│ │ (metadata) │◄──►│ (API wrapper) │ │ +│ └───────────────┘ └───────────┬─────────────┘ │ +└───────────────────────────────────┼─────────────────┘ + │ REST API + ▼ + ┌─────────────────────┐ + │ Paperless-ngx │ + │ (Document Store) │ + │ - OCR │ + │ - Full-text │ + │ - Tagging │ + │ - Archival │ + └─────────────────────┘ +``` + +## Use Cases + +### 1. SOPs and Policies + +- Upload SOPs from CA Grow Ops to Paperless for long-term archival +- Retrieve and display archived versions +- Full-text search across all SOPs + +### 2. Compliance Documents + +- Store state inspection reports +- Archive METRC reports and manifests +- Maintain license documentation + +### 3. Batch Documentation + +- Archive completed batch records +- Store harvest manifests +- Archive lab test results (COAs) + +### 4. Visitor Logs + +- Archive signed NDAs +- Store visitor badges and access logs + +### 5. Photos (Optional) + +- Archive walkthrough photos +- Store plant progress photos with metadata + +## Paperless-ngx API Integration + +### Authentication + +```typescript +// Environment variables +PAPERLESS_URL=https://paperless.runfoo.run +PAPERLESS_TOKEN=your_api_token +``` + +### API Endpoints Used + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/api/documents/` | GET | List/search documents | +| `/api/documents/` | POST | Upload new document | +| `/api/documents/{id}/` | GET | Get document metadata | +| `/api/documents/{id}/download/` | GET | Download original file | +| `/api/documents/{id}/preview/` | GET | Get thumbnail/preview | +| `/api/tags/` | GET/POST | Manage tags | +| `/api/correspondents/` | GET/POST | Manage correspondents (document owners) | +| `/api/document_types/` | GET/POST | Manage document types | + +## Implementation + +### Backend Service (`paperless.service.ts`) + +```typescript +interface PaperlessDocument { + id: number; + title: string; + content: string; + tags: number[]; + document_type: number | null; + correspondent: number | null; + created: string; + added: string; + archive_serial_number: string | null; + original_file_name: string; +} + +interface UploadDocumentOptions { + title: string; + file: Buffer | ReadStream; + filename: string; + documentType?: string; + tags?: string[]; + correspondent?: string; + archiveSerialNumber?: string; +} + +class PaperlessService { + async uploadDocument(options: UploadDocumentOptions): Promise; + async getDocument(id: number): Promise; + async searchDocuments(query: string, tags?: string[]): Promise; + async downloadDocument(id: number): Promise; + async deleteDocument(id: number): Promise; + + // Tag management + async createTag(name: string, color?: string): Promise; + async getOrCreateTag(name: string): Promise; + + // Document type management + async getOrCreateDocumentType(name: string): Promise; +} +``` + +### API Routes (`paperless.routes.ts`) + +```typescript +// Upload document to Paperless +POST /api/paperless/documents + Body: multipart/form-data + - file: File + - title: string + - documentType: string (e.g., "SOP", "COMPLIANCE", "BATCH_RECORD") + - tags: string[] (e.g., ["gorilla-glue", "batch-001"]) + +// Search Paperless documents +GET /api/paperless/search?q=keyword&tags=tag1,tag2 + +// Get document preview +GET /api/paperless/documents/:id/preview + +// Download original document +GET /api/paperless/documents/:id/download + +// Sync local document to Paperless +POST /api/paperless/sync/:localDocumentId + +// Get sync status for local document +GET /api/paperless/sync-status/:localDocumentId +``` + +### Database Schema Addition + +```prisma +model Document { + // ... existing fields ... + + // Paperless sync fields + paperlessId Int? @unique + paperlessSyncedAt DateTime? + paperlessUrl String? +} +``` + +## Tagging Convention + +Consistent tagging for easy retrieval: + +| Tag Pattern | Example | Purpose | +|-------------|---------|---------| +| `app:grow-ops` | `app:grow-ops` | Source application | +| `type:{category}` | `type:sop`, `type:compliance` | Document category | +| `batch:{name}` | `batch:gg4-b001` | Associated batch | +| `room:{name}` | `room:flower-a` | Associated room | +| `year:{year}` | `year:2025` | Year for archival | +| `user:{email}` | `user:mike` | Document owner | + +## Document Types in Paperless + +Pre-create these document types: + +1. **SOP** - Standard Operating Procedures +2. **Policy** - Company policies +3. **Compliance** - State/regulatory documents +4. **Batch Record** - Completed batch documentation +5. **Lab Result** - COA and test results +6. **Visitor Log** - NDA, access records +7. **Photos** - Facility/plant photos +8. **Invoice** - Purchase records + +## Sync Workflow + +### Manual Sync + +1. User clicks "Archive to Paperless" on a document +2. Backend uploads file to Paperless API +3. Backend stores `paperlessId` and `paperlessUrl` in local DB +4. UI shows sync status indicator + +### Automatic Sync (Optional) + +1. Cron job runs nightly +2. Finds documents meeting criteria (e.g., approved SOPs, completed batches) +3. Uploads to Paperless if not already synced +4. Updates local records with Paperless IDs + +### Retrieval + +1. User searches in CA Grow Ops +2. Search includes both local and Paperless documents +3. For Paperless-only documents, proxy the download through our API + +## UI Components + +### Document Card (Extended) + +```tsx + + {doc.title} + + {doc.type} + {doc.paperlessId && ( + + Archived + + )} + + + + {!doc.paperlessId && ( + + )} + + +``` + +### Archive Modal + +```tsx + + Archive to Paperless + + + + +``` + +## Configuration + +### Environment Variables + +```bash +# Paperless Connection +PAPERLESS_ENABLED=true +PAPERLESS_BASE_URL=https://paperless.runfoo.run +PAPERLESS_API_TOKEN=pk_xxxxxxxxxxxxx + +# Sync Settings +PAPERLESS_AUTO_SYNC=false +PAPERLESS_SYNC_COMPLETED_BATCHES=true +PAPERLESS_SYNC_APPROVED_SOPS=true + +# Defaults +PAPERLESS_DEFAULT_CORRESPONDENT=777wolfpack +``` + +### Settings Page Addition + +Add Paperless configuration section to Settings: + +- Connection status indicator +- Test connection button +- Enable/disable sync +- Configure auto-sync rules + +## Error Handling + +| Error | Handling | +|-------|----------| +| Paperless unavailable | Queue for retry, show warning | +| Upload failed | Store locally, mark for retry | +| Authentication error | Alert admin, disable sync | +| Document not found | Remove paperlessId from local record | + +## Security Considerations + +1. **API Token Security**: Store token in encrypted environment variable +2. **Access Control**: Only users with document permissions can trigger archival +3. **Audit Logging**: Log all Paperless operations to audit log +4. **Network Security**: Require HTTPS for Paperless connection + +## Implementation Phases + +### Phase 1: Basic Integration + +- [ ] Paperless service with upload/download +- [ ] Manual sync button on documents +- [ ] Sync status indicators + +### Phase 2: Search Integration + +- [ ] Combined search across local + Paperless +- [ ] Tag management UI +- [ ] Preview/thumbnail support + +### Phase 3: Automatic Sync + +- [ ] Cron job for batch archival +- [ ] Auto-archive completed batches +- [ ] Auto-archive approved SOPs + +### Phase 4: Advanced Features + +- [ ] Bulk archive UI +- [ ] Archive reports/analytics +- [ ] Retention policies + +## Testing + +### Unit Tests + +- Mock Paperless API responses +- Test upload/download flows +- Test error handling + +### Integration Tests + +- Set up test Paperless instance +- End-to-end document workflow +- Sync status verification + +## Dependencies + +```json +{ + "form-data": "^4.0.0", + "node-fetch": "^3.3.0" +} +``` + +## References + +- [Paperless-ngx Documentation](https://docs.paperless-ngx.com/) +- [Paperless API Reference](https://docs.paperless-ngx.com/api/) +- [Paperless Docker Setup](https://docs.paperless-ngx.com/setup/#docker)