Saltar al contenido principal

Low Stock Alert Communication System Integration

📋 Overview

The low stock alert system has been refactored to use the proper multi-channel communication system instead of hardcoded email logic. This provides a robust, configurable, and enterprise-grade notification system.


🎯 What Changed

Before (Old System) ❌

The email service had hardcoded logic:

// ❌ OLD: Hardcoded in email.service.ts
@OnEvent(OnCreateLowStockAlertEvent.eventName)
async handleOnCreateLowStockAlertEvent() {
const to = "luisrangelc@rpasolution.com"; // 🔴 Hardcoded email
await sgMail.send(msg); // 🔴 Direct SendGrid call
console.log("Email sent"); // 🔴 Console logging
}

Problems:

  • 🔴 Always sent to same hardcoded email
  • 🔴 Bypassed business communication configuration
  • 🔴 No rate limiting
  • 🔴 No multi-channel support (SMS/WhatsApp)
  • 🔴 Used console.log instead of proper logger
  • 🔴 Ignored template system
  • 🔴 No proper error handling

After (New System) ✅

Now uses the proper communication system:

// ✅ NEW: Proper handler in communications module
@OnEvent(OnCreateLowStockAlertEvent.eventName)
async handleOnCreateLowStockAlert(event) {
// 1. Get template from system
const template = await this.templatesService.findByCode('low_stock_alert_email');

// 2. Get business users who should receive alerts
const businessUsers = await this.businessUsersService.findByBusinessId(businessId);

// 3. Send via communication system (respects config, rate limits, etc.)
await this.communicationsService.send({
type: "low_stock_alert",
channel: "email", // Can be SMS/WhatsApp based on config
templateId: template.id,
recipientContact: user.email,
// ... proper configuration
});
}

✅ Benefits

1. Dynamic Recipients

  • Sends to all active business users (not hardcoded)
  • Each business's users get their own alerts
  • Easy to add role-based filtering (e.g., only managers)

2. Business Configuration

  • Respects business_communication_config table
  • Can enable/disable per business
  • Can set as automatic or manual
  • Honors time windows (don't send at 3am!)

3. Multi-Channel Support

  • Email (primary)
  • SMS (can be enabled)
  • WhatsApp (can be enabled)
  • Future: Messenger, Slack, etc.

4. Rate Limiting

  • Won't spam users with too many alerts
  • Configurable: max sends per day, minimum interval
  • Professional behavior

5. Professional Templates

  • Uses low_stock_alert_email template from database
  • Brandable and customizable per business
  • Consistent formatting

6. Reliability

  • Queued delivery (won't lose messages if server restarts)
  • Retry logic built-in
  • Dead letter queue for failed messages
  • Proper error handling and logging

7. Observability

  • All communications logged to communication table
  • Can track: sent, delivered, opened, clicked
  • Easy to debug issues

🏗️ Architecture

Event Flow

graph TD
A[LowStockAlertsScheduler] -->|Creates Alert| B[LowStockAlert Created]
B -->|Emits Event| C[OnCreateLowStockAlertEvent]
C -->|Handled By| D[OnCreateLowStockAlertHandler]
D -->|Gets Users| E[BusinessUsersService]
D -->|Gets Template| F[CommunicationTemplatesService]
D -->|Sends| G[CommunicationsService]
G -->|Checks Config| H[BusinessCommunicationConfigService]
G -->|Queues| I[CommunicationQueueService]
I -->|Processes| J[Email/SMS/WhatsApp Adapter]
J -->|Delivers| K[Recipient]

Key Components

1. LowStockAlertsScheduler (Existing - Enhanced)

  • File: apps/backend/src/low-stock-alerts/jobs/low-stock-alerts.scheduler.ts
  • Enhancements:
    • ✅ Idempotency (checks for existing alerts)
    • ✅ Bulk operations (faster processing)
    • ✅ Granular error handling
    • ✅ Type-safe constants
    • ✅ Better logging with metrics

2. OnCreateLowStockAlertHandler (New)

  • File: apps/backend/src/communications/application/events/on-create-low-stock-alert.handler.ts
  • Purpose: Proper event handler for low stock alerts
  • Features:
    • Fetches business users dynamically
    • Uses template system
    • Sends via communication system
    • Handles errors gracefully

3. BusinessUsersService (Enhanced)

  • File: apps/backend/src/business-users/application/business-users.service.ts
  • New Method: findByBusinessId(businessId)
    • Returns all active users for a business
    • Includes user email and full name
    • Used to determine alert recipients

4. Low Stock Alert Constants (New)

  • File: apps/backend/src/low-stock-alerts/domain/low-stock-alerts.constants.ts

  • Purpose: Type-safe constants for status and identifiers

  • Constants:

    LowStockAlertStatus = { ACTIVE, RESOLVED, DISMISSED }
    SystemIdentifiers = { LOW_STOCK_CHECK, MANUAL_CREATION }

🔧 Configuration

1. Enable/Disable for a Business

Low stock alerts respect the business_communication_config table:

-- Enable email alerts for a business
UPDATE business_communication_config
SET is_enabled = true,
is_automatic = true
WHERE business_id = 'your-business-id'
AND communication_type = 'low_stock_alert'
AND channel = 'email';

2. Configure Time Windows

Only send alerts during business hours:

UPDATE business_communication_config
SET send_time_window_start = '08:00:00',
send_time_window_end = '20:00:00',
timezone = 'America/Mexico_City'
WHERE business_id = 'your-business-id'
AND communication_type = 'low_stock_alert';

3. Rate Limiting

Prevent alert fatigue:

UPDATE business_communication_config
SET max_sends_per_recipient_per_day = 3, -- Max 3 alerts per day
min_interval_minutes = 240 -- At least 4 hours between alerts
WHERE business_id = 'your-business-id'
AND communication_type = 'low_stock_alert';

4. Add SMS/WhatsApp Support

-- Enable WhatsApp alerts (requires Twilio setup)
UPDATE business_communication_config
SET is_enabled = true,
is_automatic = true
WHERE business_id = 'your-business-id'
AND communication_type = 'low_stock_alert'
AND channel = 'whatsapp';

📧 Template Variables

The low_stock_alert_email template has access to:

VariableTypeExampleDescription
locationNamestring"Main Store"Name of the location with low stock
alertDatestring"October 25, 2025, 10:30 AM"When the alert was created
itemCountnumber15Number of low stock items
itemsarray[{name, sku, currentStock, minStock, category}]Array of low stock items
recipientNamestring"John Doe"Name of the person receiving alert
businessIdstring"uuid"Business identifier
locationIdstring"uuid"Location identifier

Example Template Usage

<h1>🔴 Low Stock Alert - {{locationName}}</h1>

<p>Dear {{recipientName}},</p>

<p>We detected {{itemCount}} items that are running low on stock at {{locationName}} as of {{alertDate}}.</p>

<table>
<thead>
<tr>
<th>Item</th>
<th>SKU</th>
<th>Current</th>
<th>Minimum</th>
<th>Category</th>
</tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{name}}</td>
<td>{{sku}}</td>
<td>{{currentStock}}</td>
<td>{{minStock}}</td>
<td>{{category}}</td>
</tr>
{{/each}}
</tbody>
</table>

🧪 Testing

Manual Test

To test the system without waiting for the scheduler:

// In your test file or temp controller
async testLowStockAlert() {
const testAlert = {
businessId: 'test-business-id',
locationId: 'test-location-id',
locationName: 'Test Location',
alertDate: new Date(),
detail: JSON.stringify({
items: [
{
itemName: 'Test Product',
itemSku: 'TEST-001',
currentStock: 5,
minStock: 10,
categoryName: 'Test Category'
}
]
}),
status: 'ACTIVE',
createdBy: 'test-user'
};

// This will trigger the event and send notifications
await this.lowStockAlertsService.addLowStockAlert(testAlert);
}

Verify

  1. Check communication table for created records
  2. Check communication_queue for queued items
  3. Check recipient's email inbox
  4. Check logs for success/error messages

📊 Monitoring

Query Recent Alerts

SELECT 
c.id,
c.type,
c.channel,
c.status,
c.recipient_contact,
c.created_at,
c.sent_at,
c.delivered_at
FROM communication c
WHERE c.type = 'low_stock_alert'
AND c.created_at > NOW() - INTERVAL '24 hours'
ORDER BY c.created_at DESC;

Check Failed Deliveries

SELECT *
FROM communication
WHERE type = 'low_stock_alert'
AND status IN ('failed', 'bounced')
AND created_at > NOW() - INTERVAL '7 days';

User Alert Frequency

SELECT 
recipient_contact,
COUNT(*) as alert_count,
MAX(sent_at) as last_sent
FROM communication
WHERE type = 'low_stock_alert'
AND created_at > NOW() - INTERVAL '24 hours'
GROUP BY recipient_contact
ORDER BY alert_count DESC;

🚀 Future Enhancements

1. Role-Based Recipients

Filter recipients by role (only send to managers/admins):

const businessUsers = await this.businessUsersService.findByBusinessId(businessId);
const managers = businessUsers.filter(u =>
['admin', 'manager', 'inventory_manager'].includes(u.uniqueRoleName)
);

2. Location-Specific Recipients

Send to users assigned to specific locations:

// Add location filtering
const locationUsers = await this.businessUsersService.findByLocation(locationId);

3. Alert Aggregation

Instead of sending one alert per item, aggregate by day:

// Send daily digest at 8am with all low stock items
const dailyDigest = await this.aggregateLowStockAlerts(businessId);

4. Priority Levels

Different handling for critical vs warning levels:

if (currentStock === 0) {
priority = 'critical'; // Send immediately
} else if (currentStock < minStock * 0.5) {
priority = 'high';
} else {
priority = 'normal';
}

5. Custom Thresholds

Per-user preferences for when to receive alerts:

// Only notify me if stock is below 25% of minimum
const userPrefs = await getUserAlertPreferences(userId);

🐛 Troubleshooting

Not Receiving Alerts?

  1. Check if enabled for business:

    SELECT * FROM business_communication_config
    WHERE business_id = 'your-id'
    AND communication_type = 'low_stock_alert'
    AND channel = 'email';
  2. Check if users exist:

    SELECT bu.*, u.email, u.full_name
    FROM business_user bu
    JOIN "user" u ON u.id = bu.user_id
    WHERE bu.business_id = 'your-id'
    AND bu.is_active = true;
  3. Check communication queue:

    SELECT * FROM communication_queue
    WHERE status = 'failed'
    ORDER BY created_at DESC
    LIMIT 10;
  4. Check application logs:

    # Look for OnCreateLowStockAlertHandler logs
    grep "OnCreateLowStockAlertHandler" logs/app.log

Receiving Too Many Alerts?

Adjust rate limiting in business_communication_config:

UPDATE business_communication_config
SET max_sends_per_recipient_per_day = 2,
min_interval_minutes = 360 -- 6 hours
WHERE communication_type = 'low_stock_alert';


✅ Migration Checklist

  • Created OnCreateLowStockAlertHandler in communications module
  • Added findByBusinessId() method to BusinessUsersService
  • Registered handler in CommunicationsModule
  • Removed hardcoded email logic from EmailService
  • Added type-safe constants for alert status
  • Enhanced scheduler with idempotency and bulk operations
  • Documented the new system
  • Test in staging environment
  • Verify email delivery
  • Monitor for 24 hours
  • Deploy to production

Last Updated: October 25, 2025 Status: ✅ Complete Version: 2.0