Spaces:
Sleeping
Sleeping
| const { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType } = require('docx'); | |
| const Contract = require('../models/Contract'); | |
| const Clause = require('../models/Clause'); | |
| const ApiError = require('../utils/ApiError'); | |
| const asyncHandler = require('../utils/asyncHandler'); | |
| /** | |
| * Generates a Redlined .docx file containing high/critical risk clauses and their advocate rewrites. | |
| */ | |
| const exportRedlineToDocx = asyncHandler(async (req, res) => { | |
| // 1. Fetch Contract & Clauses | |
| const contract = await Contract.findOne({ _id: req.params.id, userId: req.user._id }); | |
| if (!contract) { | |
| throw new ApiError(404, 'Contract not found or access denied.'); | |
| } | |
| const clauses = await Clause.find({ contractId: contract._id }) | |
| .sort({ segmentIndex: 1 }) | |
| .lean(); | |
| if (!clauses || clauses.length === 0) { | |
| throw new ApiError(400, 'No clauses found for this contract.'); | |
| } | |
| // 2. Filter clauses that actually need negotiation (High/Critical) | |
| const riskyClauses = clauses.filter(c => | |
| (c.risk_level === 'high' || c.risk_level === 'critical') && | |
| c.suggested_rewrite | |
| ); | |
| // 3. Build DOCX Document | |
| const docChildren = [ | |
| new Paragraph({ | |
| text: "LexGuard Contract Negotiation Redlines", | |
| heading: HeadingLevel.TITLE, | |
| alignment: AlignmentType.CENTER, | |
| spacing: { after: 400 } | |
| }), | |
| new Paragraph({ | |
| text: `Contract: ${contract.originalFileName || 'Untitled Document'}`, | |
| heading: HeadingLevel.HEADING_2, | |
| spacing: { after: 200 } | |
| }), | |
| new Paragraph({ | |
| text: `Generated on: ${new Date().toLocaleDateString()}`, | |
| spacing: { after: 400 } | |
| }) | |
| ]; | |
| if (riskyClauses.length === 0) { | |
| docChildren.push(new Paragraph({ | |
| text: "Great news! LexGuard found no critical or high-risk clauses requiring immediate negotiation.", | |
| })); | |
| } else { | |
| docChildren.push(new Paragraph({ | |
| text: "The following clauses have been flagged as predatory or high-risk. We have provided legally-sound redline rewrites below to propose to the counterparty.", | |
| spacing: { after: 400 } | |
| })); | |
| riskyClauses.forEach((clause, index) => { | |
| // Clause Title / Type | |
| docChildren.push(new Paragraph({ | |
| text: `${index + 1}. ${clause.clause_type.replace(/_/g, ' ').toUpperCase()}`, | |
| heading: HeadingLevel.HEADING_3, | |
| spacing: { before: 400, after: 100 } | |
| })); | |
| // Risk Level & Reasoning | |
| docChildren.push(new Paragraph({ | |
| children: [ | |
| new TextRun({ text: "Risk Level: ", bold: true }), | |
| new TextRun({ | |
| text: clause.risk_level.toUpperCase(), | |
| color: clause.risk_level === 'critical' ? "FF0000" : "FFA500", | |
| bold: true | |
| }) | |
| ], | |
| spacing: { after: 100 } | |
| })); | |
| docChildren.push(new Paragraph({ | |
| children: [ | |
| new TextRun({ text: "Why it's dangerous: ", bold: true }), | |
| new TextRun({ text: clause.risk_reasons?.join(' ') || 'Standard review flagged.' }) | |
| ], | |
| spacing: { after: 200 } | |
| })); | |
| // Original Text | |
| docChildren.push(new Paragraph({ | |
| children: [ | |
| new TextRun({ text: "Original Text (To be replaced):", bold: true, strike: true }) | |
| ], | |
| spacing: { after: 100 } | |
| })); | |
| docChildren.push(new Paragraph({ | |
| text: clause.text || clause.rawText || "N/A", | |
| strike: true, | |
| color: "888888", | |
| spacing: { after: 200 } | |
| })); | |
| // Proposed Redline | |
| docChildren.push(new Paragraph({ | |
| children: [ | |
| new TextRun({ text: "Proposed Safe Rewrite (Insert):", bold: true, color: "008000" }) | |
| ], | |
| spacing: { after: 100 } | |
| })); | |
| docChildren.push(new Paragraph({ | |
| text: clause.suggested_rewrite, | |
| color: "008000", | |
| spacing: { after: 200 } | |
| })); | |
| }); | |
| } | |
| const doc = new Document({ | |
| sections: [{ | |
| properties: {}, | |
| children: docChildren | |
| }] | |
| }); | |
| // 4. Generate DOCX Buffer | |
| const buffer = await Packer.toBuffer(doc); | |
| // 5. Send as a File Download | |
| res.setHeader('Content-Disposition', `attachment; filename="LexGuard_Redlines_${contract._id}.docx"`); | |
| res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'); | |
| return res.status(200).send(buffer); | |
| }); | |
| const { exportCleanedContract } = require('../services/documentExportService'); | |
| const downloadCleanedContract = asyncHandler(async (req, res) => { | |
| // 1. Fetch Contract to ensure it exists and belongs to the user | |
| const contract = await Contract.findOne({ _id: req.params.id, userId: req.user._id }); | |
| if (!contract) { | |
| throw new ApiError(404, 'Contract not found or access denied.'); | |
| } | |
| // 2. Generate the full stitched DOCX Buffer from Agent 8 | |
| const buffer = await exportCleanedContract(contract._id); | |
| // 3. Send as a File Download | |
| res.setHeader('Content-Disposition', `attachment; filename="LexGuard_Cleaned_${contract._id}.docx"`); | |
| res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'); | |
| return res.status(200).send(buffer); | |
| }); | |
| module.exports = { | |
| exportRedlineToDocx, | |
| downloadCleanedContract | |
| }; | |