feat: comprehensive demo seed + paperless integration spec
Demo Seed (npm run seed:demo): - 5 demo staff members - 8 grow rooms - 7 batches across all stages - 292+ touch points with activity history - 30 days of walkthrough history (with reservoir/irrigation/health checks) - 9 SOPs and documents - 12 supply items - 7 tasks - IPM schedules for active batches - Weight logs - 3 announcements - 14 days time punch history Paperless Integration Spec: - API integration design for document archival - Sync workflow (manual + automatic) - Tagging conventions - Document types - Implementation phases
This commit is contained in:
parent
af3775c9b4
commit
5c7a4b83c3
2 changed files with 373 additions and 17 deletions
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
|
|
|||
355
specs/paperless-integration.md
Normal file
355
specs/paperless-integration.md
Normal file
|
|
@ -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<PaperlessDocument>;
|
||||
async getDocument(id: number): Promise<PaperlessDocument>;
|
||||
async searchDocuments(query: string, tags?: string[]): Promise<PaperlessDocument[]>;
|
||||
async downloadDocument(id: number): Promise<Buffer>;
|
||||
async deleteDocument(id: number): Promise<void>;
|
||||
|
||||
// Tag management
|
||||
async createTag(name: string, color?: string): Promise<Tag>;
|
||||
async getOrCreateTag(name: string): Promise<Tag>;
|
||||
|
||||
// Document type management
|
||||
async getOrCreateDocumentType(name: string): Promise<DocumentType>;
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
<DocumentCard>
|
||||
<DocumentTitle>{doc.title}</DocumentTitle>
|
||||
<DocumentMeta>
|
||||
<Badge>{doc.type}</Badge>
|
||||
{doc.paperlessId && (
|
||||
<Badge variant="success">
|
||||
<CloudArchive /> Archived
|
||||
</Badge>
|
||||
)}
|
||||
</DocumentMeta>
|
||||
<Actions>
|
||||
<Button onClick={viewDocument}>View</Button>
|
||||
{!doc.paperlessId && (
|
||||
<Button onClick={archiveToPaperless}>
|
||||
Archive to Paperless
|
||||
</Button>
|
||||
)}
|
||||
</Actions>
|
||||
</DocumentCard>
|
||||
```
|
||||
|
||||
### Archive Modal
|
||||
|
||||
```tsx
|
||||
<ArchiveModal>
|
||||
<Title>Archive to Paperless</Title>
|
||||
<TagSelector
|
||||
selected={selectedTags}
|
||||
onChange={setSelectedTags}
|
||||
suggestions={["batch:current", "type:sop"]}
|
||||
/>
|
||||
<DocumentTypeSelector
|
||||
value={documentType}
|
||||
onChange={setDocumentType}
|
||||
/>
|
||||
<Button onClick={handleArchive}>Archive</Button>
|
||||
</ArchiveModal>
|
||||
```
|
||||
|
||||
## 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)
|
||||
Loading…
Add table
Reference in a new issue