1. Overview
The API-SKUs microservice is a critical component of the Nimbly inventory management system. It provides comprehensive functionality for managing SKUs, scheduling inventory checks, tracking stock movements, and generating detailed reports. The service implements a clean architecture pattern with clear separation between controllers, use cases, and repositories, supporting both MongoDB and Firebase as data stores.
System Integrations:
- Inventory - Main inventory management feature
- Schedule - Inventory check scheduling
- Reports - Inventory reporting and analytics
- Sites - Location-based inventory tracking
- Authentication - Access control and security
- Users - User assignment and permissions
Key Features:
- SKU Management: Create, update, delete, and bulk import SKUs
- Schedule Management: Configure recurring and ad-hoc inventory check schedules
- Report Generation: Track inventory counts, movements, and generate PDF/XLSX reports
- Multi-Database Architecture: Hybrid approach using MongoDB and Firebase
- Authentication: JWT-based authentication with role-based access control
- Asynchronous Processing: Pub/sub for report generation and email delivery
2. Libraries and Dependencies
Core Framework Dependencies:
- Express.js (^4.18.1): Web application framework
- TypeScript (4.7.4): Type-safe JavaScript development
- Node.js: Runtime environment (Engine: Node 20)
Database and Storage:
- mongoose (5.11.10): MongoDB ODM for data modeling
- firebase-admin (^11.5.0): Firebase Admin SDK for Realtime Database and Storage
- firebase-functions (^3.24.0): Firebase Functions support
Authentication and Security:
- JWT: JSON Web Token authentication (via custom middleware)
- cors (^2.8.5): Cross-Origin Resource Sharing support
- dotenv (^8.6.0): Environment variable management
Data Processing and Validation:
- @hapi/joi (^17.1.0) & joi (^17.6.0): Schema validation
- ramda (^0.27.2): Functional programming utilities
- moment (^2.29.4) & moment-timezone (^0.5.34): Date/time manipulation
- papaparse (^5.3.0): CSV parsing for [bulk operationss](../Bulk Operation/BulkOperationOverview.md)
- emoji-regex (^10.1.0): Emoji validation support
File Processing:
- pdfmake (^0.2.5): PDF generation for reports
- @sheet/image: Excel file manipulation
- jimp (^0.16.2): Image processing
- image-size (^1.0.2): Image dimension detection
- fluent-ffmpeg (^2.1.2): Video processing for thumbnails
- @ffmpeg-installer/ffmpeg (^1.0.13): FFmpeg binaries
- @ffprobe-installer/ffprobe (^2.1.2): FFprobe binaries
Communication and Notifications:
- @sendgrid/mail (^7.7.0): Email delivery service
- axios (^0.19.2): HTTP client for API calls
Utilities:
- busboy (^1.6.0): File upload parsing
- base64-stream (^1.0.0): Base64 encoding streams
- mkdirp (^1.0.4): Directory creation
- uuidv4 (^6.2.13): UUID generation
Monitoring and Logging:
- @google-cloud/trace-agent (^5.1.3): Google Cloud tracing
- morgan (^1.9.1): HTTP request logging
- express-http-context (^1.2.4): Request context management
Nimbly Internal Packages:
- @nimbly-technologies/entity-node (1.66.81): Entity definitions
- @nimbly-technologies/nimbly-access (^1.0.0): [Access control](../Settings/Access control/AccessControlOverview.md)
- @nimbly-technologies/nimbly-backend-utils (^1.1.2): Common utilities
- @nimbly-technologies/nimbly-common (1.92.86): Shared types
Development Dependencies:
- jest (^27.0.0): Testing framework
- eslint (^8.19.0): Code linting
- prettier (^2.7.1): Code formatting
- nodemon (^2.0.19): Development server
3. API Endpoints
SKU Management Endpoints
| Method | Endpoint | Description | Handler |
|---|---|---|---|
| GET | /search | Search SKUs with pagination and filters | SKUController.findByQueryPaginate |
| GET | /bulk-upload | Download SKU bulk upload template | SKUController.getSKUBulkUploadTemplate |
| POST | /bulk-upload | Bulk upload SKUs via Excel | SKUController.bulkUploadSKUs |
| GET | / | Find SKUs by organization | SKUController.findByOrganizationID |
| POST | / | Create new SKU | SKUController.create |
| GET | /compact | Get SKUs grouped by category | SKUController.findSKUPerCategory |
| GET | /skuID/:skuID | Find SKU by SKU ID | SKUController.findBySKUID |
| GET | /:id | Find SKU by MongoDB ID | SKUController.findByID |
| PATCH | /:id | Update SKU | SKUController.update |
| DELETE | /:id | Soft delete SKU | SKUController.delete |
Organization SKU Endpoints
| Method | Endpoint | Description | Handler |
|---|---|---|---|
| GET | /organization-skus/ | Get all organization SKUs | OrganizationSKUController.findAllByOrganizationID |
Site SKU Endpoints
| Method | Endpoint | Description | Handler |
|---|---|---|---|
| GET | /site-skus/ | Get all site SKUs | SiteSKUController.findAllByOrganizationID |
| GET | /site-skus/fetchSites/:userID | Get user’s assigned sites | SiteSKUController.fetchUserSites |
| GET | /site-skus/analytics/inventory/:siteID | Get inventory analytics | SiteSKUController.getSiteInventory |
SKU Settings Endpoints
| Method | Endpoint | Description | Handler |
|---|---|---|---|
| GET | /sku-settings/categories | Get SKU categories | SKUSettingsController.findCategories |
| POST | /sku-settings/categories | Add new category | SKUSettingsController.addCategory |
[Schedule](../Schedule/Schedule Listing/ScheduleListingOverview.md) Management Endpoints
| Method | Endpoint | Description | Handler |
|---|---|---|---|
| POST | /schedules/ | Create [schedule](../Schedule/Schedule Listing/ScheduleListingOverview.md) | SKUScheduleController.create |
| DELETE | /schedules/:id | Delete [schedule](../Schedule/Schedule Listing/ScheduleListingOverview.md) | SKUScheduleController.delete |
| PUT | /schedules/site/:siteID | Disable site [schedules](../Schedule/Schedule Listing/ScheduleListingOverview.md) | SKUScheduleController.findAndDisableSchedulesBysiteID |
| PATCH | /schedules/:id | Update [schedule](../Schedule/Schedule Listing/ScheduleListingOverview.md) | SKUScheduleController.update |
| GET | /schedules/search | Search [schedules](../Schedule/Schedule Listing/ScheduleListingOverview.md) | SKUScheduleController.findByQueryPaginate |
| GET | /schedules/active | Get active [schedules](../Schedule/Schedule Listing/ScheduleListingOverview.md) by date | SKUScheduleController.findActiveUserScheduleByDates |
| GET | /schedules/active/site | Get active site [schedules](../Schedule/Schedule Listing/ScheduleListingOverview.md) | SKUScheduleController.findActiveScheduleBySites |
| GET | /schedules/active/summary | Get [schedule](../Schedule/Schedule Listing/ScheduleListingOverview.md) summary | SKUScheduleController.findActiveUserScheduleSummaryByDates |
| GET | /schedules/user | Get user [schedules](../Schedule/Schedule Listing/ScheduleListingOverview.md) | SKUScheduleController.findActiveAtDates |
| GET | /schedules/get-timezone/:scheduleID | Get [schedule](../Schedule/Schedule Listing/ScheduleListingOverview.md) timezone | SKUScheduleController.getTimezone |
| GET | /schedules/:scheduleID | Get [schedule](../Schedule/Schedule Listing/ScheduleListingOverview.md) by ID | SKUScheduleController.findByScheduleID |
| GET | /schedules/ | Get organization [schedules](../Schedule/Schedule Listing/ScheduleListingOverview.md) | SKUScheduleController.findByOrganizationID |
Report Management Endpoints
| Method | Endpoint | Description | Handler |
|---|---|---|---|
| GET | /reports/summary | Get reports summary | SKUReportController.findSKUReports |
| GET | /reports/:skuReportID | Get report by ID | SKUReportController.findByReportID |
| PATCH | /reports/:skuReportID | Update report | SKUReportController.update |
| DELETE | /reports/:skuReportID | Delete report | SKUReportController.delete |
| POST | /reports/ | Create report | SKUReportController.create |
| GET | /reports/ | Find reports | SKUReportController.findByQuery |
| POST | /reports/submit/generate | Generate report file | SKUReportController.postSKUReportSubmission |
| POST | /reports/submit/:skuReportID | Submit report | SKUReportController.submit |
| GET | /reports/site/:siteID | Get site reports | SKUReportController.skuReportsBySite |
| POST | /reports/get-progress | Get report progress | SKUReportController.getProgress |
System Endpoints
| Method | Endpoint | Description | Handler |
|---|---|---|---|
| GET | /ping | Health check | Returns { message: 'CONNECTED' } |
4. Architecture Overview
Clean Architecture Layers
graph TB subgraph "Presentation Layer" A[Express Routes<br/>SKURouter.ts<br/>ScheduleRouter.ts<br/>ReportRouter.ts] B[Middleware<br/>AuthMiddleware<br/>validatorHandler<br/>parseXlsx] C[Validators<br/>sku.validator.ts<br/>schedule.validator.ts<br/>report.validator.ts] end subgraph "Application Layer" D[Controllers<br/>SKUController<br/>ScheduleController<br/>ReportController] E[Use Cases<br/>SKUUsecase<br/>ScheduleUsecase<br/>ReportUsecase] end subgraph "Domain Layer" F[Entities<br/>SKU.schema<br/>Schedule.schema<br/>Report.schema] G[Repository Interfaces<br/>ISKURepository<br/>IScheduleRepository<br/>IReportRepository] H[Business Rules<br/>validateCheckinTime<br/>hasSameSchedule<br/>mapScheduleByDate] end subgraph "Infrastructure Layer" I[MongoDB Repositories<br/>SKUMongoRepository<br/>ScheduleMongoRepository<br/>ReportMongoRepository] J[Firebase Repositories<br/>OrganizationSKUFirebaseRepository<br/>SiteSKUFirebaseRepository<br/>InventoryFirebaseRepository] K[External API Clients<br/>SiteApiRepository<br/>UOMRepository<br/>PubsubApi] L[Email Service<br/>SendGridMail<br/>createPdfReport<br/>createXlsxReport] end A --> D B --> D C --> D D --> E E --> F E --> G G --> I G --> J G --> K E --> L
Database Architecture
graph LR subgraph "MongoDB" M1[skus] M2[skuschedules] M3[skureports] M4[skusettings] end subgraph "Firebase Realtime DB" F1[organizationSKU] F2[siteSKU] F3[inventory] end subgraph "External Services" E1[Sites API] E2[UOM API] E3[User API] end M1 -.-> F1 M1 -.-> F2 M3 --> F3
5. Core Domain Flows
5.1 SKU Management Flow
SKU Creation Flow
sequenceDiagram participant Client participant Router participant AuthMiddleware participant Validator participant Controller participant UseCase participant Repository participant Firebase participant MongoDB Client->>Router: POST / Router->>AuthMiddleware: AuthMiddleware.validate() AuthMiddleware->>Validator: validatorHandler.validate() Validator->>Controller: SKUController.create() Controller->>UseCase: SKUUsecase.create() UseCase->>UseCase: validateSKUAtDBLevel() UseCase->>UseCase: validateSites() UseCase->>UseCase: findOrCreateCategories() UseCase->>UseCase: validateUOM() UseCase->>Repository: SKUMongoRepository.create() Repository->>MongoDB: mongoose.create() MongoDB-->>Repository: Created SKU document Repository-->>UseCase: Return SKU entity UseCase-->>Controller: Return result Controller-->>Client: response(data, null)
SKU Search Implementation
The SKU search functionality implements a sophisticated query system:
// Query Building in SKUUsecase.findByQueryPaginate
const query = {
organizationID,
disabled: withDisabled ? { $in: [true, false] } : false,
...(categories?.length && { categories: { $in: categories } }),
...(sites?.length && { sites: { $in: sites } }),
...(unitOfMeasurements?.length && { unitOfMeasurement: { $in: unitOfMeasurements } }),
...(search && {
$or: [
{ skuID: { $regex: search, $options: 'i' } },
{ name: { $regex: search, $options: 'i' } },
{ 'sites.name': { $regex: search, $options: 'i' } },
{ categories: { $in: [new RegExp(search, 'i')] } },
],
}),
};The search supports:
- Case-insensitive text search across multiple fields
- Multi-select filters for categories, sites, and UOMs
- Pagination with customizable page size
- Sorting by site names
- Optional inclusion of disabled SKUs
Bulk Upload Process
flowchart TD A[Upload Excel File] --> B[parseXlsx middleware] B --> C[Extract Rows<br/>req.body.data] C --> D{bulkUploadValidator validate} D -->|Valid| E[mapSiteNamesToIDs] D -->|Invalid| F[collectValidationErrors] E --> G[SKURepository findBySkuIDs] G --> H[SKUSettingsUsecase findOrCreateCategories] H --> I[SKURepository create<br/>Batch Operation] I --> J[Return Success Count] F --> K[Return Error Details] J --> L[response results, null] K --> L
5.2 Schedule Management Flow
Schedule Creation with Validation
// Schedule validation in SKUScheduleUsecase.create
1. Validate unique schedule name
2. Validate date ranges based on frequency
3. Validate department/site relationships
4. Validate auditor assignment
5. Validate SKUs exist at site (Firebase check)
6. Set timezone from site configurationSchedule Frequency Types
graph TD A[Schedule Type<br/>schedule.type] --> B[Scheduled<br/>recurring] A --> C[Adhoc<br/>one-time] B --> D[Daily<br/>frequencyType] B --> E[Weekly<br/>frequencyType] B --> F[Monthly<br/>frequencyType] B --> G[Custom<br/>frequencyType] D --> H[Every Day<br/>frequencyData.daily] D --> I[Specific Days<br/>frequencyData.specificDays] E --> J[Specific Weekdays<br/>frequencyData.weekdays] F --> K[Specific Dates<br/>frequencyData.dates] G --> L[Custom Pattern<br/>frequencyData.custom]
Active Schedule Calculation
The system uses complex date mapping to determine active schedules:
// mapScheduleByDate function logic
1. Get current date in site timezone
2. Check if date falls within schedule range
3. Apply frequency rules:
- Daily: Check if current day matches pattern
- Weekly: Check if weekday matches
- Monthly: Check if date matches
- Custom: Apply custom logic
4. Check total occurrences limit
5. Return schedule if active5.3 Report Generation Flow
Report Submission Process
sequenceDiagram participant App participant Controller participant UseCase participant Validator participant Repository participant PubSub participant EmailService App->>Controller: POST /reports/submit/:id Controller->>UseCase: SKUReportUsecase.submit() UseCase->>Validator: validateCheckinTime() Validator-->>UseCase: Time valid UseCase->>UseCase: calculateInventoryMovements() UseCase->>UseCase: checkNegativeStock() UseCase->>UseCase: identifyLowStockItems() UseCase->>Repository: SKUReportMongoRepository.update() UseCase->>Repository: updateSKUCounts() UseCase->>PubSub: pubsubApi.publish() PubSub->>UseCase: postSKUReportSubmission() UseCase->>UseCase: createPdfReport() / createXlsxReport() UseCase->>EmailService: sendgridMail.send()
Time Window Validation
// validateCheckinTime implementation
1. Get schedule configuration
2. Get site timezone
3. Calculate allowed time window:
- startHour: schedule time - beforeTime
- endHour: schedule time + afterTime
4. Convert current time to site timezone
5. Check if current time is within window
6. Special handling for midnight crossingReport Progress Calculation
flowchart LR A["Report Type<br/>report.type"] --> B{"isMovement()"} B -->|Yes| C["checkMovementAnswered()<br/>stockIn > 0 OR stockOut > 0"] B -->|No| D{"isOpname()"} D -->|Yes| E["checkOpnameAnswered()<br/>finalCount != initialCount"] D -->|No| F["checkQuestionsAnswered()"] C --> G["getAnsweredSKUProgress()"] E --> G F --> G G --> H["calculatePercentage()<br/>(answered/total)*100"] H --> I["Return progress%"]
5.4 Firebase Integration Patterns
Organization SKU Structure
// Firebase path: /organizationSKU/{organizationID}/{skuKey}
interface OrganizationSKU {
skuID: string;
name: string;
unitOfMeasurement: string;
imageURL?: string;
categories: string[];
disabled: boolean;
}Site SKU Structure
// Firebase path: /siteSKU/{organizationID}/{siteID}/{skuKey}
interface SiteSKU {
skuID: string;
name: string;
onHand: number;
unitOfMeasurement: string;
minLevel?: number;
maxLevel?: number;
}Inventory Tracking
// Firebase path: /inventory/{organizationID}/{siteID}/{dateKey}
interface InventoryQuestion {
skuID: string;
timestamp: number;
count: number;
stockIn?: number;
stockOut?: number;
auditor: string;
signature?: string;
}5.5 MongoDB Schema Definitions
SKU Schema
const skuSchema = {
skuID: { type: String, required: true },
name: { type: String, required: true },
organizationID: { type: String, required: true },
sites: [{ type: String }],
categories: [{ type: String }],
unitOfMeasurement: { type: String, required: true },
imageURL: String,
disabled: { type: Boolean, default: false },
createdBy: String,
updatedBy: String,
createdAt: Date,
updatedAt: Date,
};Schedule Schema
const scheduleSchema = {
name: { type: String, required: true },
organizationID: { type: String, required: true },
departmentID: String,
sites: [String],
auditors: [String],
supervisors: [String],
skus: [String],
type: { enum: ['scheduled', 'adhoc', 'movement', 'opname'] },
frequencyType: { enum: ['daily', 'weekly', 'monthly', 'custom'] },
frequencyData: Mixed,
scheduleTime: String,
timeConfig: {
beforeTime: Number,
afterTime: Number,
},
startDate: Date,
endDate: Date,
totalOccurrences: Number,
timezone: String,
disabled: Boolean,
requireSignature: Boolean,
emailTargets: [String],
};Report Schema
const reportSchema = {
scheduleID: ObjectId,
organizationID: String,
siteID: String,
auditorID: String,
supervisorID: String,
skus: [{
skuID: String,
name: String,
initialCount: Number,
finalCount: Number,
stockIn: Number,
stockOut: Number,
variance: Number,
note: String,
}],
status: { enum: ['draft', 'completed'] },
checkinAt: Date,
submitAt: Date,
signature: String,
emailSent: Boolean,
reportURL: String,
};6. Advanced Implementation Details
6.1 Authentication and Authorization
JWT Token Validation
// AuthMiddleware implementation pattern
1. Extract token from Authorization header
2. Verify token with JWT_SECRET
3. Extract user context:
- userID
- organizationID
- roleID
- email
4. Attach context to request
5. Pass to next middlewareDepartment-Based Access Control
// injectDepartmentIndex middleware
1. Get user's department assignments
2. Extract site access permissions
3. Filter data based on department
4. Inject department index into context6.2 Report Generation Engine
PDF Generation Process
// createPdfReport implementation
1. Set up document definition:
- Page size and margins
- Custom fonts (multi-language support)
- Header with logo
- Report metadata
2. Build content sections:
- Summary statistics
- SKU details table
- Stock movements
- Signature if required
3. Generate PDF buffer
4. Upload to storage
5. Return download URLExcel Generation Process
// createXlsxReport implementation
1. Create workbook with sheets:
- Summary sheet
- Detailed SKU data
- Stock movements
- Audit trail
2. Apply formatting:
- Headers and borders
- Conditional formatting
- Data validation
3. Generate buffer
4. Upload to storage6.3 Email Notification System
Email Target Resolution
flowchart TD A["getAllEmailTargets()"] --> B["Primary:<br/>getSupervisor()"] A --> C["schedule.emailTargets[]"] A --> D["report.adhocEmailTargets[]"] A --> E["getOrganizationAdmins()"] B --> F["isUnsubscribed()<br/>Check blacklist"] C --> F D --> F E --> F F --> G["filterActiveEmails()"] G --> H["sendgridMail.send()"]
Recap Email System
// Daily recap email logic
1. Query all completed reports for the day
2. Group by supervisor
3. Generate summary statistics:
- Total reports
- Sites covered
- SKUs checked
- Issues found
4. Create consolidated email
5. Send at configured time7. Conclusion
The API-SKUs microservice demonstrates a well-architected solution for inventory management, combining the flexibility of MongoDB for complex queries with the real-time capabilities of Firebase. The clean architecture pattern ensures maintainability, while the comprehensive validation and error handling provide reliability. The service successfully handles the complexity of multi-site, multi-timezone operations while maintaining data consistency and providing detailed audit trails.
Key strengths include:
- Robust validation at multiple levels
- Flexible scheduling system
- Comprehensive reporting capabilities
- Strong security implementation
- Scalable architecture design
The hybrid database approach, while adding complexity, provides the best of both worlds: MongoDB’s powerful querying for business logic and Firebase’s real-time updates for inventory tracking. This architecture positions the system well for future growth and feature additions while maintaining current operational efficiency.