feat: add Manager Info document generation in Managers Info folder
- Create Word document template with job information pre-populated - Auto-populate: Job Name, Address, Division, Client, Type, Status, Contact info - Include fillable fields: Scope, Vendors, Estimate Cost, Dates, Team Contacts, Notes - Document named [JobNumber] - Manager Info.docx for easy identification - Uses docx library to generate professional Word documents - Document uploaded automatically during job folder creation
This commit is contained in:
@@ -3,15 +3,199 @@
|
||||
// Uses the same Graph API token from the server to avoid re-authentication
|
||||
|
||||
const { DRIVE_ID, PARENT_ITEM_ID } = require('./config');
|
||||
const { Document, Packer, Paragraph, HeadingLevel, convertInchesToTwip } = require('docx');
|
||||
|
||||
/**
|
||||
* Creates a manager info document template
|
||||
* @param {string} jobNumber - Job number
|
||||
* @param {string} jobName - Job name
|
||||
* @param {Object} record - Full job record with all data
|
||||
* @returns {Promise<Buffer>} Word document buffer
|
||||
*/
|
||||
async function createManagerInfoDocument(jobNumber, jobName, record) {
|
||||
// Extract fields from record, with fallbacks
|
||||
const jobAddress = record.Job_Address || record.job_address || '';
|
||||
const companyClient = record.Company_Client || record.company_client || '';
|
||||
const jobType = record.Job_Type || record.job_type || '';
|
||||
const jobDivision = record.Job_Division || record.job_division || '';
|
||||
const contactPerson = record.Contact_Person || record.contact_person || '';
|
||||
const phoneNumber = record.Phone_Number || record.phone_number || '';
|
||||
const jobStatus = record.Job_Status || record.job_status || '';
|
||||
|
||||
const doc = new Document({
|
||||
sections: [{
|
||||
properties: {},
|
||||
children: [
|
||||
// Header: Job # and Name
|
||||
new Paragraph({
|
||||
text: `Job # ${jobNumber}`,
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
spacing: { after: 0 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: `Name: ${jobName}`,
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
spacing: { after: 200 }
|
||||
}),
|
||||
|
||||
// Project Info section
|
||||
new Paragraph({
|
||||
text: 'Project Info',
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
spacing: { before: 200, after: 100 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: `Job Name: ${jobName}`,
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: `Job Address: ${jobAddress}`,
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: `Job Division: ${jobDivision}`,
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: `Customer/Client Name: ${companyClient}`,
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: `Job Type: ${jobType}`,
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: `Job Status: ${jobStatus}`,
|
||||
spacing: { after: 200 }
|
||||
}),
|
||||
|
||||
// Estimates Information section
|
||||
new Paragraph({
|
||||
text: 'Estimates Information',
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
spacing: { before: 200, after: 100 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: 'Scope: _________________________________',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: 'Main Vendors: _________________________________',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: 'Estimate Cost: _________________________________',
|
||||
spacing: { after: 200 }
|
||||
}),
|
||||
|
||||
// Schedule section
|
||||
new Paragraph({
|
||||
text: 'Schedule',
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
spacing: { before: 200, after: 100 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: 'Expected Start Date: _________________________________',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: 'Actual Start Date: _________________________________',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: 'Estimated End Date: _________________________________',
|
||||
spacing: { after: 200 }
|
||||
}),
|
||||
|
||||
// Main Contacts section
|
||||
new Paragraph({
|
||||
text: 'Main Contacts',
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
spacing: { before: 200, after: 100 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: 'Project Manager (Name / Phone / Email):',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: `${contactPerson}${phoneNumber ? ' / ' + phoneNumber : ''}`,
|
||||
spacing: { after: 100 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: 'Superintendent (Name / Phone / Email):',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: '_________________________________________________________________',
|
||||
spacing: { after: 100 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: 'Assistant/Coordinator (Name / Phone / Email):',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: '_________________________________________________________________',
|
||||
spacing: { after: 100 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: 'Billing Manager (Name / Phone / Email):',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: '_________________________________________________________________',
|
||||
spacing: { after: 200 }
|
||||
}),
|
||||
|
||||
// Job Notes & Key Info section
|
||||
new Paragraph({
|
||||
text: 'Job Notes & Key Info',
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
spacing: { before: 200, after: 100 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: '_________________________________________________________________',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: '_________________________________________________________________',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: '_________________________________________________________________',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: '_________________________________________________________________',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: '_________________________________________________________________',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: '_________________________________________________________________',
|
||||
spacing: { after: 50 }
|
||||
}),
|
||||
new Paragraph({
|
||||
text: '_________________________________________________________________',
|
||||
spacing: { after: 50 }
|
||||
})
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
return await Packer.toBuffer(doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a job folder structure in OneDrive/SharePoint and returns the share link
|
||||
* @param {string} accessToken - Graph API access token (from server's token flow)
|
||||
* @param {string} jobNumber - Job number (e.g., "12345")
|
||||
* @param {string} jobFullName - Full job name calculated from form data
|
||||
* @param {Object} record - Full job record with all data
|
||||
* @returns {Promise<Object>} Result with folder IDs and share link
|
||||
*/
|
||||
async function createJobFolderAndGetLink(accessToken, jobNumber, jobFullName) {
|
||||
async function createJobFolderAndGetLink(accessToken, jobNumber, jobFullName, record) {
|
||||
try {
|
||||
// 1) Create main folder under configured parent
|
||||
const mainFolderName = `${jobNumber} - ${jobFullName}`;
|
||||
@@ -74,7 +258,36 @@ async function createJobFolderAndGetLink(accessToken, jobNumber, jobFullName) {
|
||||
|
||||
const subFolderId = subFolderData.id;
|
||||
|
||||
// 3) Create share link for the main folder
|
||||
// 3) Create and upload Manager Info document to Managers Info subfolder
|
||||
console.log('[Job Folder Integration] Creating and uploading Manager Info document');
|
||||
try {
|
||||
const docBuffer = await createManagerInfoDocument(jobNumber, jobFullName, record);
|
||||
const uploadResp = await fetch(
|
||||
`https://graph.microsoft.com/v1.0/drives/${DRIVE_ID}/items/${subFolderId}:/${jobNumber} - Manager Info.docx:/content`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
},
|
||||
body: docBuffer
|
||||
}
|
||||
);
|
||||
|
||||
if (!uploadResp.ok) {
|
||||
const errorData = await uploadResp.text();
|
||||
console.warn('[Job Folder Integration] Failed to upload Manager Info document:', {
|
||||
status: uploadResp.status,
|
||||
error: errorData
|
||||
});
|
||||
} else {
|
||||
console.log('[Job Folder Integration] ✓ Manager Info document uploaded successfully');
|
||||
}
|
||||
} catch (docError) {
|
||||
console.warn('[Job Folder Integration] Error creating/uploading Manager Info document:', docError);
|
||||
}
|
||||
|
||||
// 4) Create share link for the main folder
|
||||
console.log('[Job Folder Integration] Creating share link for main folder');
|
||||
const shareResp = await fetch(
|
||||
`https://graph.microsoft.com/v1.0/drives/${DRIVE_ID}/items/${mainFolderId}/createLink`,
|
||||
@@ -122,5 +335,6 @@ async function createJobFolderAndGetLink(accessToken, jobNumber, jobFullName) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createJobFolderAndGetLink
|
||||
createJobFolderAndGetLink,
|
||||
createManagerInfoDocument
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user