337 lines
13 KiB
JavaScript
Executable File
337 lines
13 KiB
JavaScript
Executable File
"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
|