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_accountbuyer_accountassigned_user_idsmanagerspecialistmanagers[](array with start/end dates)specialists[](array with start/end dates)metadata.content_specialisttrigger_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
metadataobject - Stripe Subscriptions -
managerfield (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:
- Order created with seller, buyer, manager, specialist
- Trigger fires on insert
- Accounts marked
has_orders: true became_customer_onset to oldest subscription date- Metadata built from all team members
- Order metadata saved
- Future tasks/pulses automatically inherit metadata
Use Case 2: Manager Reassignment
Scenario: Order manager changes from Alice to Bob
Flow:
- Order.manager updated to Bob's ID
- Trigger fires on update
- Bob's user details queried
- Order metadata.dashclicks_team.manager updated
- All tasks for this order get updated order_metadata
- All pulses for this order get updated order_metadata
- Bob now appears as manager in all contexts
Use Case 3: Team Expansion
Scenario: Additional specialist added to order
Flow:
- Specialist added to order.specialists array
- Trigger fires
- All specialists queried
- metadata.dashclicks_team.specialists rebuilt with full user data
- Tasks/pulses updated with new team composition
Performance Considerations
Optimizations
- Early Exit: Skip if only
updated_atchanged - Parallel Queries:
Promise.allSettled()for all user/account queries - Lean Queries: All queries use
.lean()for raw JSON - Conditional Queries: Only query if data exists (e.g., skip buyer if same as seller)
- 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
- Many Team Members: Orders with large managers/specialists arrays
- Task/Pulse Volume: Orders with many tasks/pulses trigger bulk updates
- 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' });
}
Related Triggers
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