/**
* Data Tools for NocoBase MCP Server
* Handles bulk insert/update operations efficiently
*/
import { NocoBaseClient } from '../client.js';
export interface BulkCreateArgs {
collectionName: string;
records: any[];
batchSize?: number;
}
export interface BulkUpdateArgs {
collectionName: string;
updates: Array<{
id: number;
data: any;
}>;
batchSize?: number;
}
/**
* Bulk create records with batching
*/
export async function bulkCreateRecords(
client: NocoBaseClient,
args: BulkCreateArgs
): Promise<any> {
const { collectionName, records, batchSize = 10 } = args;
console.error(`[Bulk Create] Creating ${records.length} records in ${collectionName}`);
const results: any[] = [];
const errors: any[] = [];
let processed = 0;
// Process in batches
for (let i = 0; i < records.length; i += batchSize) {
const batch = records.slice(i, i + batchSize);
console.error(`[Bulk Create] Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(records.length / batchSize)}`);
// Create records in parallel within batch
const batchPromises = batch.map(async (record, index) => {
try {
const response = await client.post(`/${collectionName}:create`, record);
processed++;
return {
success: true,
index: i + index,
data: response.data.data
};
} catch (error: any) {
console.error(`[Bulk Create] Error creating record ${i + index}:`, error.message);
return {
success: false,
index: i + index,
error: error.message,
record
};
}
});
const batchResults = await Promise.all(batchPromises);
batchResults.forEach(result => {
if (result.success) {
results.push(result);
} else {
errors.push(result);
}
});
// Small delay between batches to avoid rate limiting
if (i + batchSize < records.length) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
return {
success: errors.length === 0,
total: records.length,
created: results.length,
failed: errors.length,
results,
errors,
message: `Created ${results.length}/${records.length} records in ${collectionName}`
};
}
/**
* Bulk update records with batching
*/
export async function bulkUpdateRecords(
client: NocoBaseClient,
args: BulkUpdateArgs
): Promise<any> {
const { collectionName, updates, batchSize = 10 } = args;
console.error(`[Bulk Update] Updating ${updates.length} records in ${collectionName}`);
const results: any[] = [];
const errors: any[] = [];
// Process in batches
for (let i = 0; i < updates.length; i += batchSize) {
const batch = updates.slice(i, i + batchSize);
console.error(`[Bulk Update] Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(updates.length / batchSize)}`);
const batchPromises = batch.map(async (update, index) => {
try {
const response = await client.post(
`/${collectionName}:update?filterByTk=${update.id}`,
update.data
);
return {
success: true,
index: i + index,
id: update.id,
data: response.data.data
};
} catch (error: any) {
console.error(`[Bulk Update] Error updating record ${update.id}:`, error.message);
return {
success: false,
index: i + index,
id: update.id,
error: error.message
};
}
});
const batchResults = await Promise.all(batchPromises);
batchResults.forEach(result => {
if (result.success) {
results.push(result);
} else {
errors.push(result);
}
});
// Small delay between batches
if (i + batchSize < updates.length) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
return {
success: errors.length === 0,
total: updates.length,
updated: results.length,
failed: errors.length,
results,
errors,
message: `Updated ${results.length}/${updates.length} records in ${collectionName}`
};
}
/**
* Bulk delete records
*/
export async function bulkDeleteRecords(
client: NocoBaseClient,
args: { collectionName: string; ids: number[]; batchSize?: number }
): Promise<any> {
const { collectionName, ids, batchSize = 10 } = args;
console.error(`[Bulk Delete] Deleting ${ids.length} records from ${collectionName}`);
const results: any[] = [];
const errors: any[] = [];
for (let i = 0; i < ids.length; i += batchSize) {
const batch = ids.slice(i, i + batchSize);
const batchPromises = batch.map(async (id, index) => {
try {
await client.post(`/${collectionName}:destroy?filterByTk=${id}`);
return {
success: true,
index: i + index,
id
};
} catch (error: any) {
return {
success: false,
index: i + index,
id,
error: error.message
};
}
});
const batchResults = await Promise.all(batchPromises);
batchResults.forEach(result => {
if (result.success) {
results.push(result);
} else {
errors.push(result);
}
});
if (i + batchSize < ids.length) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
return {
success: errors.length === 0,
total: ids.length,
deleted: results.length,
failed: errors.length,
results,
errors,
message: `Deleted ${results.length}/${ids.length} records from ${collectionName}`
};
}
/**
* Load sample CRM data
*/
export async function loadSampleCRMData(client: NocoBaseClient): Promise<any> {
console.error('[Sample Data] Loading CRM sample data...');
const results: any = {
leads: undefined,
accounts: undefined,
contacts: undefined,
opportunities: undefined,
activities: undefined
};
// Sample Leads (40)
const sampleLeads = generateSampleLeads(40);
results.leads = await bulkCreateRecords(client, {
collectionName: 'leads',
records: sampleLeads,
batchSize: 10
});
// Sample Accounts (25)
const sampleAccounts = generateSampleAccounts(25);
results.accounts = await bulkCreateRecords(client, {
collectionName: 'accounts',
records: sampleAccounts,
batchSize: 10
});
// Sample Contacts (30)
const sampleContacts = generateSampleContacts(30, results.accounts.results);
results.contacts = await bulkCreateRecords(client, {
collectionName: 'contacts',
records: sampleContacts,
batchSize: 10
});
// Sample Opportunities (20)
const sampleOpportunities = generateSampleOpportunities(20, results.accounts.results);
results.opportunities = await bulkCreateRecords(client, {
collectionName: 'opportunities',
records: sampleOpportunities,
batchSize: 10
});
// Sample Activities (30)
const sampleActivities = generateSampleActivities(30, results.leads.results, results.opportunities.results);
results.activities = await bulkCreateRecords(client, {
collectionName: 'activities',
records: sampleActivities,
batchSize: 10
});
return {
success: true,
summary: {
leads: `${results.leads.created}/${results.leads.total}`,
accounts: `${results.accounts.created}/${results.accounts.total}`,
contacts: `${results.contacts.created}/${results.contacts.total}`,
opportunities: `${results.opportunities.created}/${results.opportunities.total}`,
activities: `${results.activities.created}/${results.activities.total}`
},
details: results
};
}
// Helper functions to generate sample data
function generateSampleLeads(count: number): any[] {
const companies = ['Tech Corp', 'Global Solutions', 'Innovation Hub', 'Digital Ventures', 'Smart Systems'];
const industries = ['Technology', 'Manufacturing', 'Retail', 'Healthcare', 'Finance'];
const sources = ['website', 'referral', 'event', 'cold_call', 'social_media'];
const statuses = ['new', 'contacted', 'qualified', 'unqualified'];
const ratings = ['hot', 'warm', 'cold'];
const businessLines = ['exhibition', 'agency'];
return Array.from({ length: count }, (_, i) => ({
tenant_id: 1,
owner_id: 1,
company_name: `${companies[i % companies.length]} ${i + 1}`,
contact_name: `Contact Person ${i + 1}`,
job_title: 'Marketing Director',
industry: industries[i % industries.length],
email: `contact${i + 1}@company${i + 1}.com`,
phone: `+84${String(i + 1).padStart(9, '0')}`,
lead_source: sources[i % sources.length],
rating: ratings[i % ratings.length],
status: statuses[i % statuses.length],
business_line: businessLines[i % businessLines.length],
description: `Sample lead ${i + 1} for testing purposes`
}));
}
function generateSampleAccounts(count: number): any[] {
const industries = ['Technology', 'Manufacturing', 'Retail', 'Healthcare', 'Finance'];
return Array.from({ length: count }, (_, i) => ({
tenant_id: 1,
name: `Account Company ${i + 1}`,
account_type: i % 3 === 0 ? 'customer' : 'prospect',
industry: industries[i % industries.length],
phone: `+84${String(i + 1).padStart(9, '0')}`,
email: `info@account${i + 1}.com`,
website: `https://account${i + 1}.com`,
annual_revenue: (i + 1) * 100000,
employee_count: (i + 1) * 10,
description: `Sample account ${i + 1}`
}));
}
function generateSampleContacts(count: number, accounts: any[]): any[] {
return Array.from({ length: count }, (_, i) => ({
tenant_id: 1,
account_id: accounts[i % accounts.length]?.data?.id || undefined,
first_name: `First${i + 1}`,
last_name: `Last${i + 1}`,
email: `contact${i + 1}@email.com`,
phone: `+84${String(i + 1).padStart(9, '0')}`,
job_title: 'Manager',
is_primary: i % 5 === 0,
is_decision_maker: i % 3 === 0
}));
}
function generateSampleOpportunities(count: number, accounts: any[]): any[] {
const stages = ['new_lead', 'prospect', 'negotiation', 'contracted', 'closed_won', 'closed_lost'];
const pipelines = ['exhibition', 'agency'];
return Array.from({ length: count }, (_, i) => ({
tenant_id: 1,
owner_id: 1,
account_id: accounts[i % accounts.length]?.data?.id || undefined,
opportunity_name: `Deal ${i + 1}`,
pipeline_type: pipelines[i % pipelines.length],
stage: stages[i % stages.length],
probability: (i % 10) * 10,
amount: (i + 1) * 10000,
currency: 'USD',
expected_close_date: new Date(Date.now() + (i + 1) * 86400000).toISOString().split('T')[0]
}));
}
function generateSampleActivities(count: number, leads: any[], opportunities: any[]): any[] {
const types = ['call', 'email', 'meeting', 'task', 'note'];
const statuses = ['planned', 'in_progress', 'completed'];
const priorities = ['low', 'normal', 'high', 'urgent'];
return Array.from({ length: count }, (_, i) => ({
tenant_id: 1,
owner_id: 1,
activity_type: types[i % types.length],
subject: `Activity ${i + 1}`,
description: `Sample activity ${i + 1}`,
status: statuses[i % statuses.length],
priority: priorities[i % priorities.length],
related_to_type: i % 2 === 0 ? 'lead' : 'opportunity',
related_to_id: i % 2 === 0
? leads[i % leads.length]?.data?.id
: opportunities[i % opportunities.length]?.data?.id,
due_date: new Date(Date.now() + (i + 1) * 86400000).toISOString().split('T')[0]
}));
}