"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = require("express"); const client_1 = require("@prisma/client"); const estimate_service_1 = require("../services/estimate.service"); const pdf_service_1 = require("../services/pdf.service"); const router = (0, express_1.Router)(); const prisma = new client_1.PrismaClient(); const estimateService = new estimate_service_1.EstimateService(prisma); const pdfService = new pdf_service_1.PdfService(); function getUserId(req) { if (!req.user?.userId) throw new Error('Unauthorized'); return req.user.userId; } async function canAccessEstimate(estimateId, userId) { const estimate = await prisma.estimate.findUnique({ where: { id: estimateId }, select: { ownerId: true }, }); if (!estimate) return false; if (estimate.ownerId === userId) return true; const share = await prisma.estimateShare.findUnique({ where: { estimateId_sharedWithId: { estimateId, sharedWithId: userId } }, }); return !!share; } async function isOwner(estimateId, userId) { const estimate = await prisma.estimate.findUnique({ where: { id: estimateId }, select: { ownerId: true }, }); return estimate?.ownerId === userId; } // Get all estimates (owned + shared with me), with sharedWithMe flag router.get('/', async (req, res) => { try { const userId = getUserId(req); const owned = await prisma.estimate.findMany({ where: { ownerId: userId }, include: { direction: true, items: { orderBy: { orderNumber: 'asc' } }, totals: { orderBy: { orderNumber: 'asc' } }, }, orderBy: { createdAt: 'desc' }, }); const shared = await prisma.estimateShare.findMany({ where: { sharedWithId: userId }, include: { estimate: { include: { direction: true, items: { orderBy: { orderNumber: 'asc' } }, totals: { orderBy: { orderNumber: 'asc' } }, }, }, }, }); const sharedEstimates = shared.map(s => ({ ...s.estimate, sharedWithMe: true, })); const ownedWithFlag = owned.map(e => ({ ...e, sharedWithMe: false })); const combined = [...ownedWithFlag, ...sharedEstimates].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); res.json(combined); } catch (error) { if (error.message === 'Unauthorized') { return res.status(401).json({ error: error.message }); } res.status(500).json({ error: 'Failed to fetch estimates' }); } }); // Get estimate by ID (owner or shared) router.get('/:id', async (req, res) => { try { const userId = getUserId(req); const ok = await canAccessEstimate(req.params.id, userId); if (!ok) { return res.status(404).json({ error: 'Estimate not found' }); } const estimate = await prisma.estimate.findUnique({ where: { id: req.params.id }, include: { direction: true, items: { orderBy: { orderNumber: 'asc' }, include: { priceItem: true }, }, totals: { orderBy: { orderNumber: 'asc' } }, }, }); if (!estimate) { return res.status(404).json({ error: 'Estimate not found' }); } const sharedWithMe = estimate.ownerId !== userId; res.json({ ...estimate, sharedWithMe }); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(500).json({ error: 'Failed to fetch estimate' }); } }); // Create new estimate router.post('/', async (req, res) => { try { const userId = getUserId(req); const estimate = await estimateService.createEstimate({ ...req.body, ownerId: userId, }); res.status(201).json(estimate); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: error.message || 'Failed to create estimate' }); } }); // Update estimate (owner only) router.put('/:id', async (req, res) => { try { const userId = getUserId(req); const ok = await isOwner(req.params.id, userId); if (!ok) { return res.status(403).json({ error: 'Только владелец может изменять смету' }); } const estimate = await estimateService.updateEstimate(req.params.id, req.body); res.json(estimate); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: error.message || 'Failed to update estimate' }); } }); // Add item (owner or shared) router.post('/:id/items', async (req, res) => { try { const userId = getUserId(req); const ok = await canAccessEstimate(req.params.id, userId); if (!ok) return res.status(404).json({ error: 'Estimate not found' }); const item = await estimateService.addEstimateItem(req.params.id, req.body); res.status(201).json(item); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: error.message || 'Failed to add item' }); } }); // Update estimate item (owner or shared) router.put('/:id/items/:itemId', async (req, res) => { try { const userId = getUserId(req); const ok = await canAccessEstimate(req.params.id, userId); if (!ok) return res.status(404).json({ error: 'Estimate not found' }); const item = await estimateService.updateEstimateItem(req.params.itemId, req.body); res.json(item); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: error.message || 'Failed to update item' }); } }); // Delete estimate item (owner or shared) router.delete('/:id/items/:itemId', async (req, res) => { try { const userId = getUserId(req); const ok = await canAccessEstimate(req.params.id, userId); if (!ok) return res.status(404).json({ error: 'Estimate not found' }); await prisma.estimateItem.delete({ where: { id: req.params.itemId } }); res.status(204).send(); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: 'Failed to delete item' }); } }); // Recalculate (owner or shared) router.post('/:id/recalculate', async (req, res) => { try { const userId = getUserId(req); const ok = await canAccessEstimate(req.params.id, userId); if (!ok) return res.status(404).json({ error: 'Estimate not found' }); const estimate = await estimateService.recalculateTotals(req.params.id); res.json(estimate); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: error.message || 'Failed to recalculate' }); } }); // Generate PDF (owner or shared) router.get('/:id/pdf', async (req, res) => { try { const userId = getUserId(req); const ok = await canAccessEstimate(req.params.id, userId); if (!ok) return res.status(404).json({ error: 'Estimate not found' }); const estimate = await prisma.estimate.findUnique({ where: { id: req.params.id }, include: { direction: true, items: { orderBy: { orderNumber: 'asc' } }, totals: { orderBy: { orderNumber: 'asc' } }, }, }); if (!estimate) { return res.status(404).json({ error: 'Estimate not found' }); } const pdfBuffer = await pdfService.generateEstimatePdf(estimate); res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', `attachment; filename="smeta-${estimate.number}.pdf"`); res.send(pdfBuffer); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(500).json({ error: 'Failed to generate PDF' }); } }); // Delete estimate (owner only) router.delete('/:id', async (req, res) => { try { const userId = getUserId(req); const ok = await isOwner(req.params.id, userId); if (!ok) { return res.status(403).json({ error: 'Только владелец может удалить смету' }); } await prisma.estimate.delete({ where: { id: req.params.id } }); res.status(204).send(); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: 'Failed to delete estimate' }); } }); // Share estimate with user by email router.post('/:id/share', async (req, res) => { try { const userId = getUserId(req); const ok = await isOwner(req.params.id, userId); if (!ok) { return res.status(403).json({ error: 'Только владелец может поделиться сметой' }); } const { email } = req.body; if (!email || typeof email !== 'string') { return res.status(400).json({ error: 'Укажите email пользователя' }); } const normalizedEmail = String(email).trim().toLowerCase(); const sharedWith = await prisma.user.findUnique({ where: { email: normalizedEmail }, }); if (!sharedWith) { return res.status(404).json({ error: 'Пользователь с таким email не найден' }); } if (sharedWith.id === userId) { return res.status(400).json({ error: 'Нельзя поделиться сметой с самим собой' }); } await prisma.estimateShare.upsert({ where: { estimateId_sharedWithId: { estimateId: req.params.id, sharedWithId: sharedWith.id }, }, create: { estimateId: req.params.id, ownerId: userId, sharedWithId: sharedWith.id, }, update: {}, }); res.status(201).json({ sharedWith: { id: sharedWith.id, email: sharedWith.email, name: sharedWith.name } }); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: error.message || 'Failed to share' }); } }); // Unshare: remove share for a user router.delete('/:id/share/:sharedWithUserId', async (req, res) => { try { const userId = getUserId(req); const ok = await isOwner(req.params.id, userId); if (!ok) { return res.status(403).json({ error: 'Только владелец может отменить доступ' }); } await prisma.estimateShare.deleteMany({ where: { estimateId: req.params.id, sharedWithId: req.params.sharedWithUserId, ownerId: userId, }, }); res.status(204).send(); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(400).json({ error: 'Failed to unshare' }); } }); // List users this estimate is shared with router.get('/:id/shares', async (req, res) => { try { const userId = getUserId(req); const ok = await isOwner(req.params.id, userId); if (!ok) return res.status(403).json({ error: 'Доступ запрещён' }); const shares = await prisma.estimateShare.findMany({ where: { estimateId: req.params.id, ownerId: userId }, include: { sharedWith: { select: { id: true, email: true, name: true } }, }, }); res.json(shares.map(s => ({ id: s.id, sharedWith: s.sharedWith, createdAt: s.createdAt }))); } catch (error) { if (error.message === 'Unauthorized') return res.status(401).json({ error: error.message }); res.status(500).json({ error: 'Failed to fetch shares' }); } }); exports.default = router; //# sourceMappingURL=estimates.js.map