Skip to main content

Order Trigger - Order Metadata Management

Overview

File: queue-manager/triggers/order.js
Function: updateAccountsWithOrderInfo()
Purpose: Comprehensive order metadata management and account relationship tracking

This trigger is the most complex in the triggers system, managing order metadata synchronization, account flags, became_customer_on timestamps, and team assignments across orders, tasks, and pulses.

Trigger Configuration

Watched Collection

  • Collection: Orders
  • Events: Insert, Update
  • Watched Fields:
    • seller_account
    • buyer_account
    • assigned_user_ids
    • manager
    • specialist
    • managers[] (array with start/end dates)
    • specialists[] (array with start/end dates)
    • metadata.content_specialist
    • trigger_metadata_sync (manual trigger flag)

Match Conditions

const matchConditions = {
$or: [
{ operationType: 'insert' },
{
$and: [
{ operationType: 'update' },
{
$or: [
{ 'updateDescription.updatedFields.seller_account': { $exists: true } },
{ 'updateDescription.updatedFields.buyer_account': { $exists: true } },
{ 'updateDescription.updatedFields.assigned_user_ids': { $exists: true } },
{ 'updateDescription.updatedFields.manager': { $exists: true } },
{ 'updateDescription.updatedFields.specialist': { $exists: true } },
{ 'updateDescription.updatedFields.managers': { $exists: true } },
{ 'updateDescription.updatedFields.specialists': { $exists: true } },
{ 'updateDescription.updatedFields.trigger_metadata_sync': { $exists: true } },
{
$expr: {
$ne: [
{
$type: {
$getField: {
field: 'metadata.content_specialist',
input: '$updateDescription.updatedFields',
},
},
},
'missing',
],
},
},
],
},
],
},
],
};

Data Flow

sequenceDiagram
participant API as Internal API
participant ORDER as Orders Collection
participant TRIGGER as order.js Trigger
participant ACCOUNT as Accounts Collection
participant USER as Users Collection
participant STRIPE as Stripe Subscriptions
participant TASK as Projects Tasks
participant PULSE as Projects Pulses

API->>ORDER: Create/Update Order
ORDER->>TRIGGER: Change stream event

TRIGGER->>ACCOUNT: Set has_orders: true
TRIGGER->>ACCOUNT: Set became_customer_on

TRIGGER->>USER: Query seller account
TRIGGER->>USER: Query buyer account
TRIGGER->>USER: Query assigned users
TRIGGER->>USER: Query manager
TRIGGER->>USER: Query specialist
TRIGGER->>USER: Query managers array
TRIGGER->>USER: Query specialists array

TRIGGER->>ORDER: Update metadata object

alt Manager on insert
TRIGGER->>STRIPE: Update manager field
end

TRIGGER->>TASK: Sync order_metadata
TRIGGER->>PULSE: Sync order_metadata

Processing Logic

Step 1: Early Exit for Timestamp-Only Updates

if (op === 'update' && Object.keys(updates).length === 1 && updates.updated_at) {
logger.log({
initiator: 'queue-manager/triggers/order',
message: 'Skipping update - only updated_at changed',
});
return;
}

Step 2: Manager Sync to Stripe (Insert Only)

On order insert, if manager exists, sync to Stripe subscription:

if (op == 'insert' && fullDocument?.manager) {
const subscription = await Subscription.findOne(
{ _id: fullDocument.subscription },
{ stripe_id: 1 },
).lean();

if (subscription && subscription.stripe_id) {
await stripeSubscription.updateOne(
{ stripe_id: subscription.stripe_id },
{ $set: { manager: fullDocument.manager } },
);
}
}

Step 3: Set Account Flags

Mark accounts as having orders:

const sellerAccountId = fullDocument.seller_account;
const buyerAccountId = fullDocument.buyer_account;

const isSameBuyer =
sellerAccountId && buyerAccountId && sellerAccountId.toString() === buyerAccountId.toString();

const updatePayload = { has_orders: true };

if (isSameBuyer) {
await Account.updateOne({ _id: sellerAccountId }, { $set: updatePayload });
} else {
if (sellerAccountId) {
await Account.updateOne({ _id: sellerAccountId }, { $set: updatePayload });
}
if (buyerAccountId) {
await Account.updateOne({ _id: buyerAccountId }, { $set: updatePayload });
}
}

Step 4: Set became_customer_on Timestamp

Calculate when the account became a customer (first subscription):

const setBecameCustomerOn = async accountId => {
const account = await Account.findById(accountId, { became_customer_on: 1 }).lean();

if (!account?.became_customer_on) {
const oldestSubscription = await Subscription.findOne({
$or: [
{ 'metadata.account_id': accountId },
{ 'metadata.account_id': new ObjectId(accountId) },
],
})
.sort({ created: 1 })
.select('created')
.lean();

if (oldestSubscription) {
const becameCustomerDate =
oldestSubscription.created instanceof Date
? oldestSubscription.created
: new Date(oldestSubscription.created * 1000);

await Account.updateOne(
{ _id: accountId },
{ $set: { became_customer_on: becameCustomerDate } },
);
}
}
};

Step 5: Inherit Manager from Account

If order has no manager, inherit from seller account:

if (!fullDocument.manager) {
const acc = await Account.findOne({ _id: fullDocument.seller_account }, { manager: 1 }).lean();

if (acc && acc.manager) {
fullDocument.manager = acc.manager;
}
}

Step 6: Build Metadata Object (Parallel Queries)

Query all related entities in parallel:

const findQueries = [];

// Seller account
if (sellerAccountId) {
findQueries.push(
Account.findOne({ _id: sellerAccountId }, accountProjection)
.lean()
.then(
account =>
account && {
type: 'seller',
data: { ...account, id: new ObjectId(account.id) },
},
),
);
}

// Buyer account (if different)
if (!isSameBuyer && buyerAccountId) {
findQueries.push(
Account.findOne({ _id: buyerAccountId }, accountProjection)
.lean()
.then(
account =>
account && {
type: 'buyer',
data: { ...account, id: new ObjectId(account.id) },
},
),
);
}

// Assigned users
if (fullDocument.assigned_user_ids?.length > 0) {
findQueries.push(
User.find({ _id: { $in: fullDocument.assigned_user_ids } }, userProjection)
.lean()
.then(
users =>
users &&
users.length > 0 && {
type: 'responsible',
data: users.map(u => ({ ...u, id: new ObjectId(u.id) })),
},
),
);
}

// Manager
if (fullDocument.manager) {
findQueries.push(
User.findOne({ _id: fullDocument.manager }, userProjection)
.lean()
.then(
user =>
user && {
type: 'dashclicks_team.manager',
data: { ...user, id: new ObjectId(user.id) },
},
),
);
}

// Managers array
if (fullDocument.managers && fullDocument.managers.length > 0) {
const managerIds = fullDocument.managers.map(m => m.user_id);
findQueries.push(
User.find({ _id: { $in: managerIds } }, userProjection)
.lean()
.then(users => {
if (users && users.length > 0) {
const managersWithUserData = fullDocument.managers
.map(manager => {
const userData = users.find(
user => user.id.toString() === manager.user_id.toString(),
);
if (userData) {
const managerData = {
type: manager.type,
user_id: manager.user_id,
user_data: { ...userData, id: new ObjectId(userData.id) },
};
if (manager.start_date) managerData.start_date = manager.start_date;
if (manager.end_date) managerData.end_date = manager.end_date;
return managerData;
}
return null;
})
.filter(Boolean);
return managersWithUserData.length > 0
? { type: 'dashclicks_team.managers', data: managersWithUserData }
: null;
}
return null;
}),
);
}

// Specialist, specialists array, content_specialist...
// Similar patterns for other team members

const results = await Promise.allSettled(findQueries);
const metadataUpdate = {};

results.forEach(result => {
if (result.status === 'fulfilled' && result.value) {
metadataUpdate[`metadata.${result.value.type}`] = result.value.data;
}
});

Step 7: Handle Same Buyer/Seller

If seller and buyer are the same account, duplicate seller metadata:

if (isSameBuyer && metadataUpdate['metadata.seller']) {
metadataUpdate['metadata.buyer'] = metadataUpdate['metadata.seller'];
}

Step 8: Update Order with Metadata

const updatedOrder = await Order.findOneAndUpdate(
{ _id: fullDocument._id },
{
$set: metadataUpdate,
$unset: { trigger_metadata_sync: '' },
},
{ new: true },
)
.lean()
.exec();

Step 9: Sync to Tasks and Pulses

if (updatedOrder && updatedOrder.metadata) {
await Promise.all([
ProjectTask.updateMany(
{ order_id: fullDocument._id },
{ $set: { order_metadata: updatedOrder.metadata } },
),
ProjectPulse.updateMany(
{ order_id: fullDocument._id },
{ $set: { order_metadata: updatedOrder.metadata } },
),
]);
}

Metadata Structure

Complete Metadata Object

{
seller: {
id: ObjectId,
name: String,
email: String,
phone: String,
image: String
},
buyer: {
id: ObjectId,
name: String,
email: String,
phone: String,
image: String
},
responsible: [
{
id: ObjectId,
name: String,
email: String,
phone: String,
image: String
}
],
dashclicks_team: {
manager: {
id: ObjectId,
name: String,
email: String,
phone: String,
image: String
},
specialist: {
id: ObjectId,
name: String,
email: String,
phone: String,
image: String
},
content_specialist: {
id: ObjectId,
name: String,
email: String,
phone: String,
image: String
},
managers: [
{
type: String,
user_id: ObjectId,
start_date: Date,
end_date: Date,
user_data: {
id: ObjectId,
name: String,
email: String,
phone: String,
image: String
}
}
],
specialists: [
{
type: String,
user_id: ObjectId,
start_date: Date,
end_date: Date,
user_data: {
id: ObjectId,
name: String,
email: String,
phone: String,
image: String
}
}
]
}
}

Collections Updated

Direct Updates

  • Accounts - has_orders: true, became_customer_on: Date
  • Orders - Complete metadata object
  • Stripe Subscriptions - manager field (insert only)

Cascaded Updates

  • Projects Tasks - order_metadata (full metadata copy)
  • Projects Pulses - order_metadata (full metadata copy)

Use Cases

Use Case 1: New Order Creation

Scenario: Client purchases SEO service

Flow:

  1. Order created with seller, buyer, manager, specialist
  2. Trigger fires on insert
  3. Accounts marked has_orders: true
  4. became_customer_on set to oldest subscription date
  5. Metadata built from all team members
  6. Order metadata saved
  7. Future tasks/pulses automatically inherit metadata

Use Case 2: Manager Reassignment

Scenario: Order manager changes from Alice to Bob

Flow:

  1. Order.manager updated to Bob's ID
  2. Trigger fires on update
  3. Bob's user details queried
  4. Order metadata.dashclicks_team.manager updated
  5. All tasks for this order get updated order_metadata
  6. All pulses for this order get updated order_metadata
  7. Bob now appears as manager in all contexts

Use Case 3: Team Expansion

Scenario: Additional specialist added to order

Flow:

  1. Specialist added to order.specialists array
  2. Trigger fires
  3. All specialists queried
  4. metadata.dashclicks_team.specialists rebuilt with full user data
  5. Tasks/pulses updated with new team composition

Performance Considerations

Optimizations

  1. Early Exit: Skip if only updated_at changed
  2. Parallel Queries: Promise.allSettled() for all user/account queries
  3. Lean Queries: All queries use .lean() for raw JSON
  4. Conditional Queries: Only query if data exists (e.g., skip buyer if same as seller)
  5. Batch Updates: Tasks and pulses updated in parallel

Query Volume

For an order with:

  • Seller + Buyer accounts (2 queries)
  • 3 assigned users (1 query)
  • 1 manager (1 query)
  • 2 managers in array (1 query)
  • 1 specialist (1 query)
  • 1 content_specialist (1 query)

Total: 8 parallel queries + 1 order update + 2 collection syncs = 11 database operations

Bottleneck Analysis

  1. Many Team Members: Orders with large managers/specialists arrays
  2. Task/Pulse Volume: Orders with many tasks/pulses trigger bulk updates
  3. Trigger Chains: This trigger can fire user/contacts triggers downstream

became_customer_on Logic

Why This Matters

The became_customer_on timestamp tracks when an account first became a paying customer. This is critical for:

  • Customer lifetime value (CLV) calculations
  • Churn risk analysis
  • Loyalty milestone tracking
  • Retention analytics

Implementation

const setBecameCustomerOn = async accountId => {
// Only set if not already set
const account = await Account.findById(accountId, { became_customer_on: 1 }).lean();

if (!account?.became_customer_on) {
// Find oldest subscription
const oldestSubscription = await Subscription.findOne({
$or: [
{ 'metadata.account_id': accountId },
{ 'metadata.account_id': new ObjectId(accountId) },
],
})
.sort({ created: 1 }) // Ascending = oldest first
.select('created')
.lean();

if (oldestSubscription) {
// Handle Unix timestamp vs Date object
const becameCustomerDate =
oldestSubscription.created instanceof Date
? oldestSubscription.created
: new Date(oldestSubscription.created * 1000);

await Account.updateOne(
{ _id: accountId },
{ $set: { became_customer_on: becameCustomerDate } },
);
}
}
};

When It's Called

  • On order insert (seller + buyer)
  • On order update (seller + buyer)
  • After setting has_orders: true

Manual Trigger Flag

trigger_metadata_sync

Orders can be flagged for metadata rebuild:

await Order.updateOne({ _id: orderId }, { $set: { trigger_metadata_sync: true } });

This causes the trigger to fire and rebuild metadata, even if no watched fields changed.

After processing, the flag is cleared:

{
$unset: {
trigger_metadata_sync: '';
}
}

Error Handling

Account Update Errors

try {
await Account.updateOne({ _id: sellerAccountId }, { $set: updatePayload });
} catch (err) {
console.error('Error updating accounts:', err);
}

Manager Inheritance Errors

try {
const acc = await Account.findOne({ _id: seller_account }, { manager: 1 }).lean();
if (acc && acc.manager) {
fullDocument.manager = acc.manager;
}
} catch (err) {
console.error('Error getting manager from account:', err);
}

Metadata Sync Errors

if (updatedOrder && updatedOrder.metadata) {
await Promise.all([
/* tasks/pulses updates */
]);
} else {
logger.log({ message: 'Cannot sync metadata: Order not updated properly' });
}

Downstream Triggers

  • projects.js: Tasks created after this trigger get metadata
  • projects.js: Pulses created after this trigger get metadata

Upstream Triggers

  • users.js: User changes trigger order metadata updates
  • contacts.js: Contact changes trigger order metadata updates

Monitoring

Key Metrics

  • Trigger Frequency: Orders created/updated per minute
  • Query Volume: Number of parallel queries per trigger
  • Update Volume: Tasks/pulses updated per order
  • Error Rate: Failed metadata builds or syncs

Logging

logger.log({
initiator: 'queue-manager/triggers/order',
message: 'Processing order change event',
changeEvent,
});

logger.error({
initiator: 'queue-manager/triggers/order',
message: 'Error setting became_customer_on field',
error,
});

Best Practices

1. Early Exit for Irrelevant Updates

if (op === 'update' && Object.keys(updates).length === 1 && updates.updated_at) {
return; // Don't process timestamp-only updates
}

2. Parallel Queries

const results = await Promise.allSettled(findQueries);
// Process all results together

3. Null Safety

if (fullDocument.assigned_user_ids?.length > 0) {
// Query assigned users
}

4. ObjectId Conversion

data: { ...account, id: new ObjectId(account.id) }

Summary

The order trigger is the backbone of order metadata management, building comprehensive metadata objects that include seller/buyer accounts, responsible users, and DashClicks team members. It manages account flags, customer timestamps, Stripe subscription syncs, and cascades metadata to tasks and pulses, ensuring consistent order information across the entire platform.

Key Characteristics:

  • 🏗️ Most complex trigger with extensive metadata building
  • ⚡ Parallel query execution for performance
  • 📊 Syncs to 5+ collections
  • 🎯 Handles 10+ different team member types
  • 🔄 Cascades to all order-related entities
  • 📈 Powers customer lifecycle analytics via became_customer_on
💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM