Files
geo/backend/dist/routes/estimates.js
2026-02-04 00:11:19 +05:00

337 lines
13 KiB
JavaScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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