Files
geo/backend/dist/routes/estimates.js

337 lines
13 KiB
JavaScript
Raw Normal View History

2026-02-04 00:11:19 +05:00
"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