317 lines
13 KiB
JavaScript
317 lines
13 KiB
JavaScript
|
|
"use strict";
|
|||
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|||
|
|
exports.EstimateService = void 0;
|
|||
|
|
const client_1 = require("@prisma/client");
|
|||
|
|
class EstimateService {
|
|||
|
|
constructor(prisma) {
|
|||
|
|
this.prisma = prisma;
|
|||
|
|
}
|
|||
|
|
async createEstimate(data) {
|
|||
|
|
// Get direction
|
|||
|
|
const direction = await this.prisma.surveyDirection.findUnique({
|
|||
|
|
where: { code: data.directionCode },
|
|||
|
|
});
|
|||
|
|
if (!direction) {
|
|||
|
|
throw new Error(`Direction not found: ${data.directionCode}`);
|
|||
|
|
}
|
|||
|
|
// Get default executor from settings
|
|||
|
|
let executor = data.executor;
|
|||
|
|
if (!executor) {
|
|||
|
|
const defaultExecutor = await this.prisma.setting.findUnique({
|
|||
|
|
where: { key: 'default_executor' },
|
|||
|
|
});
|
|||
|
|
executor = defaultExecutor?.value || 'Не указан';
|
|||
|
|
}
|
|||
|
|
// Generate estimate number (per owner)
|
|||
|
|
const count = await this.prisma.estimate.count({
|
|||
|
|
where: { ownerId: data.ownerId },
|
|||
|
|
});
|
|||
|
|
const number = `${count + 1}`;
|
|||
|
|
const estimate = await this.prisma.estimate.create({
|
|||
|
|
data: {
|
|||
|
|
number,
|
|||
|
|
directionId: direction.id,
|
|||
|
|
ownerId: data.ownerId,
|
|||
|
|
objectName: data.objectName,
|
|||
|
|
customer: data.customer,
|
|||
|
|
executor,
|
|||
|
|
vatRate: data.vatRate ? new client_1.Prisma.Decimal(data.vatRate) : new client_1.Prisma.Decimal(20),
|
|||
|
|
status: 'draft',
|
|||
|
|
},
|
|||
|
|
include: {
|
|||
|
|
direction: true,
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
return estimate;
|
|||
|
|
}
|
|||
|
|
async updateEstimate(id, data) {
|
|||
|
|
const updateData = {};
|
|||
|
|
if (data.objectName)
|
|||
|
|
updateData.objectName = data.objectName;
|
|||
|
|
if (data.customer)
|
|||
|
|
updateData.customer = data.customer;
|
|||
|
|
if (data.executor)
|
|||
|
|
updateData.executor = data.executor;
|
|||
|
|
if (data.regionalCoef !== undefined)
|
|||
|
|
updateData.regionalCoef = new client_1.Prisma.Decimal(data.regionalCoef);
|
|||
|
|
if (data.inflationIndex !== undefined)
|
|||
|
|
updateData.inflationIndex = new client_1.Prisma.Decimal(data.inflationIndex);
|
|||
|
|
if (data.inflationDocRef)
|
|||
|
|
updateData.inflationDocRef = data.inflationDocRef;
|
|||
|
|
if (data.companyCoef !== undefined)
|
|||
|
|
updateData.companyCoef = new client_1.Prisma.Decimal(data.companyCoef);
|
|||
|
|
if (data.executorCoef !== undefined)
|
|||
|
|
updateData.executorCoef = new client_1.Prisma.Decimal(data.executorCoef);
|
|||
|
|
if (data.vatRate !== undefined)
|
|||
|
|
updateData.vatRate = new client_1.Prisma.Decimal(data.vatRate);
|
|||
|
|
if (data.status)
|
|||
|
|
updateData.status = data.status;
|
|||
|
|
const estimate = await this.prisma.estimate.update({
|
|||
|
|
where: { id },
|
|||
|
|
data: updateData,
|
|||
|
|
include: {
|
|||
|
|
direction: true,
|
|||
|
|
items: { orderBy: { orderNumber: 'asc' } },
|
|||
|
|
totals: { orderBy: { orderNumber: 'asc' } },
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
return estimate;
|
|||
|
|
}
|
|||
|
|
async addEstimateItem(estimateId, data) {
|
|||
|
|
// Get max order number
|
|||
|
|
const maxOrder = await this.prisma.estimateItem.aggregate({
|
|||
|
|
where: { estimateId },
|
|||
|
|
_max: { orderNumber: true },
|
|||
|
|
});
|
|||
|
|
const orderNumber = (maxOrder._max.orderNumber || 0) + 1;
|
|||
|
|
// Calculate total price
|
|||
|
|
const basePrice = new client_1.Prisma.Decimal(data.basePrice);
|
|||
|
|
const quantity = new client_1.Prisma.Decimal(data.quantity);
|
|||
|
|
let totalPrice = basePrice.mul(quantity);
|
|||
|
|
if (data.coef1)
|
|||
|
|
totalPrice = totalPrice.mul(new client_1.Prisma.Decimal(data.coef1));
|
|||
|
|
if (data.coef2)
|
|||
|
|
totalPrice = totalPrice.mul(new client_1.Prisma.Decimal(data.coef2));
|
|||
|
|
if (data.coef3)
|
|||
|
|
totalPrice = totalPrice.mul(new client_1.Prisma.Decimal(data.coef3));
|
|||
|
|
const item = await this.prisma.estimateItem.create({
|
|||
|
|
data: {
|
|||
|
|
estimateId,
|
|||
|
|
orderNumber,
|
|||
|
|
sectionType: data.sectionType,
|
|||
|
|
priceItemId: data.priceItemId || null,
|
|||
|
|
workName: data.workName,
|
|||
|
|
justification: data.justification || null,
|
|||
|
|
basePrice,
|
|||
|
|
quantity,
|
|||
|
|
unit: data.unit || null,
|
|||
|
|
coef1: data.coef1 ? new client_1.Prisma.Decimal(data.coef1) : null,
|
|||
|
|
coef1Desc: data.coef1Desc || null,
|
|||
|
|
coef2: data.coef2 ? new client_1.Prisma.Decimal(data.coef2) : null,
|
|||
|
|
coef2Desc: data.coef2Desc || null,
|
|||
|
|
coef3: data.coef3 ? new client_1.Prisma.Decimal(data.coef3) : null,
|
|||
|
|
coef3Desc: data.coef3Desc || null,
|
|||
|
|
totalPrice,
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
return item;
|
|||
|
|
}
|
|||
|
|
async updateEstimateItem(itemId, data) {
|
|||
|
|
const updateData = {};
|
|||
|
|
if (data.workName)
|
|||
|
|
updateData.workName = data.workName;
|
|||
|
|
if (data.justification)
|
|||
|
|
updateData.justification = data.justification;
|
|||
|
|
if (data.basePrice !== undefined)
|
|||
|
|
updateData.basePrice = new client_1.Prisma.Decimal(data.basePrice);
|
|||
|
|
if (data.quantity !== undefined)
|
|||
|
|
updateData.quantity = new client_1.Prisma.Decimal(data.quantity);
|
|||
|
|
if (data.unit)
|
|||
|
|
updateData.unit = data.unit;
|
|||
|
|
if (data.coef1 !== undefined)
|
|||
|
|
updateData.coef1 = data.coef1 ? new client_1.Prisma.Decimal(data.coef1) : null;
|
|||
|
|
if (data.coef1Desc !== undefined)
|
|||
|
|
updateData.coef1Desc = data.coef1Desc;
|
|||
|
|
if (data.coef2 !== undefined)
|
|||
|
|
updateData.coef2 = data.coef2 ? new client_1.Prisma.Decimal(data.coef2) : null;
|
|||
|
|
if (data.coef2Desc !== undefined)
|
|||
|
|
updateData.coef2Desc = data.coef2Desc;
|
|||
|
|
if (data.coef3 !== undefined)
|
|||
|
|
updateData.coef3 = data.coef3 ? new client_1.Prisma.Decimal(data.coef3) : null;
|
|||
|
|
if (data.coef3Desc !== undefined)
|
|||
|
|
updateData.coef3Desc = data.coef3Desc;
|
|||
|
|
// Recalculate total if price/quantity/coefs changed
|
|||
|
|
const currentItem = await this.prisma.estimateItem.findUnique({ where: { id: itemId } });
|
|||
|
|
if (currentItem) {
|
|||
|
|
const basePrice = updateData.basePrice || currentItem.basePrice;
|
|||
|
|
const quantity = updateData.quantity || currentItem.quantity;
|
|||
|
|
let totalPrice = basePrice.mul(quantity);
|
|||
|
|
const coef1 = updateData.coef1 !== undefined ? updateData.coef1 : currentItem.coef1;
|
|||
|
|
const coef2 = updateData.coef2 !== undefined ? updateData.coef2 : currentItem.coef2;
|
|||
|
|
const coef3 = updateData.coef3 !== undefined ? updateData.coef3 : currentItem.coef3;
|
|||
|
|
if (coef1)
|
|||
|
|
totalPrice = totalPrice.mul(coef1);
|
|||
|
|
if (coef2)
|
|||
|
|
totalPrice = totalPrice.mul(coef2);
|
|||
|
|
if (coef3)
|
|||
|
|
totalPrice = totalPrice.mul(coef3);
|
|||
|
|
updateData.totalPrice = totalPrice;
|
|||
|
|
}
|
|||
|
|
const item = await this.prisma.estimateItem.update({
|
|||
|
|
where: { id: itemId },
|
|||
|
|
data: updateData,
|
|||
|
|
});
|
|||
|
|
return item;
|
|||
|
|
}
|
|||
|
|
async recalculateTotals(estimateId) {
|
|||
|
|
const estimate = await this.prisma.estimate.findUnique({
|
|||
|
|
where: { id: estimateId },
|
|||
|
|
include: {
|
|||
|
|
items: true,
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
if (!estimate) {
|
|||
|
|
throw new Error('Estimate not found');
|
|||
|
|
}
|
|||
|
|
// Calculate section totals
|
|||
|
|
const fieldWorks = estimate.items
|
|||
|
|
.filter(i => i.sectionType === 'field')
|
|||
|
|
.reduce((sum, i) => sum.add(i.totalPrice), new client_1.Prisma.Decimal(0));
|
|||
|
|
const officeWorks = estimate.items
|
|||
|
|
.filter(i => i.sectionType === 'office')
|
|||
|
|
.reduce((sum, i) => sum.add(i.totalPrice), new client_1.Prisma.Decimal(0));
|
|||
|
|
const laboratory = estimate.items
|
|||
|
|
.filter(i => i.sectionType === 'laboratory')
|
|||
|
|
.reduce((sum, i) => sum.add(i.totalPrice), new client_1.Prisma.Decimal(0));
|
|||
|
|
const subtotal = estimate.items
|
|||
|
|
.reduce((sum, i) => sum.add(i.totalPrice), new client_1.Prisma.Decimal(0));
|
|||
|
|
// Apply coefficients
|
|||
|
|
const regionalCoef = estimate.regionalCoef || new client_1.Prisma.Decimal(1);
|
|||
|
|
const inflationIndex = estimate.inflationIndex || new client_1.Prisma.Decimal(1);
|
|||
|
|
const companyCoef = estimate.companyCoef || new client_1.Prisma.Decimal(1);
|
|||
|
|
const executorCoef = estimate.executorCoef || new client_1.Prisma.Decimal(1);
|
|||
|
|
let totalWithoutVat = subtotal
|
|||
|
|
.mul(regionalCoef)
|
|||
|
|
.mul(inflationIndex)
|
|||
|
|
.mul(companyCoef)
|
|||
|
|
.mul(executorCoef);
|
|||
|
|
const vatRate = estimate.vatRate || new client_1.Prisma.Decimal(20);
|
|||
|
|
const vatAmount = totalWithoutVat.mul(vatRate).div(100);
|
|||
|
|
const totalWithVat = totalWithoutVat.add(vatAmount);
|
|||
|
|
// Update estimate
|
|||
|
|
const updated = await this.prisma.estimate.update({
|
|||
|
|
where: { id: estimateId },
|
|||
|
|
data: {
|
|||
|
|
totalFieldWorks: fieldWorks,
|
|||
|
|
totalOfficeWorks: officeWorks,
|
|||
|
|
totalLaboratory: laboratory,
|
|||
|
|
subtotal,
|
|||
|
|
totalWithoutVat,
|
|||
|
|
vatAmount,
|
|||
|
|
totalWithVat,
|
|||
|
|
},
|
|||
|
|
include: {
|
|||
|
|
direction: true,
|
|||
|
|
items: { orderBy: { orderNumber: 'asc' } },
|
|||
|
|
totals: { orderBy: { orderNumber: 'asc' } },
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
// Update totals section
|
|||
|
|
await this.updateTotalsSection(estimateId, estimate, updated);
|
|||
|
|
return updated;
|
|||
|
|
}
|
|||
|
|
async updateTotalsSection(estimateId, estimate, updated) {
|
|||
|
|
// Delete existing totals
|
|||
|
|
await this.prisma.estimateTotal.deleteMany({ where: { estimateId } });
|
|||
|
|
const totals = [];
|
|||
|
|
let order = 1;
|
|||
|
|
// Add totals rows
|
|||
|
|
if (updated.regionalCoef && Number(updated.regionalCoef) !== 1) {
|
|||
|
|
totals.push({
|
|||
|
|
estimateId,
|
|||
|
|
orderNumber: order++,
|
|||
|
|
label: 'С районным коэффициентом',
|
|||
|
|
description: `К=${updated.regionalCoef}`,
|
|||
|
|
baseValue: updated.subtotal,
|
|||
|
|
coefficient: updated.regionalCoef,
|
|||
|
|
resultValue: updated.subtotal.mul(updated.regionalCoef),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
if (updated.inflationIndex && Number(updated.inflationIndex) !== 1) {
|
|||
|
|
const prevValue = totals.length > 0
|
|||
|
|
? totals[totals.length - 1].resultValue
|
|||
|
|
: updated.subtotal;
|
|||
|
|
totals.push({
|
|||
|
|
estimateId,
|
|||
|
|
orderNumber: order++,
|
|||
|
|
label: 'Перевод в текущие цены',
|
|||
|
|
description: updated.inflationDocRef || `Индекс: ${updated.inflationIndex}`,
|
|||
|
|
baseValue: prevValue,
|
|||
|
|
coefficient: updated.inflationIndex,
|
|||
|
|
resultValue: prevValue.mul(updated.inflationIndex),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
if (updated.companyCoef && Number(updated.companyCoef) !== 1) {
|
|||
|
|
const prevValue = totals.length > 0
|
|||
|
|
? totals[totals.length - 1].resultValue
|
|||
|
|
: updated.subtotal;
|
|||
|
|
totals.push({
|
|||
|
|
estimateId,
|
|||
|
|
orderNumber: order++,
|
|||
|
|
label: 'Коэффициент компании',
|
|||
|
|
description: null,
|
|||
|
|
baseValue: prevValue,
|
|||
|
|
coefficient: updated.companyCoef,
|
|||
|
|
resultValue: prevValue.mul(updated.companyCoef),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
if (updated.executorCoef && Number(updated.executorCoef) !== 1) {
|
|||
|
|
const prevValue = totals.length > 0
|
|||
|
|
? totals[totals.length - 1].resultValue
|
|||
|
|
: updated.subtotal;
|
|||
|
|
totals.push({
|
|||
|
|
estimateId,
|
|||
|
|
orderNumber: order++,
|
|||
|
|
label: `Коэффициент ${estimate.executor}`,
|
|||
|
|
description: null,
|
|||
|
|
baseValue: prevValue,
|
|||
|
|
coefficient: updated.executorCoef,
|
|||
|
|
resultValue: prevValue.mul(updated.executorCoef),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
// Final totals
|
|||
|
|
totals.push({
|
|||
|
|
estimateId,
|
|||
|
|
orderNumber: order++,
|
|||
|
|
label: 'Итого без НДС:',
|
|||
|
|
description: null,
|
|||
|
|
baseValue: null,
|
|||
|
|
coefficient: null,
|
|||
|
|
resultValue: updated.totalWithoutVat,
|
|||
|
|
});
|
|||
|
|
totals.push({
|
|||
|
|
estimateId,
|
|||
|
|
orderNumber: order++,
|
|||
|
|
label: `НДС ${updated.vatRate}%:`,
|
|||
|
|
description: null,
|
|||
|
|
baseValue: updated.totalWithoutVat,
|
|||
|
|
coefficient: updated.vatRate.div(100),
|
|||
|
|
resultValue: updated.vatAmount,
|
|||
|
|
});
|
|||
|
|
totals.push({
|
|||
|
|
estimateId,
|
|||
|
|
orderNumber: order++,
|
|||
|
|
label: 'Всего с НДС:',
|
|||
|
|
description: null,
|
|||
|
|
baseValue: null,
|
|||
|
|
coefficient: null,
|
|||
|
|
resultValue: updated.totalWithVat,
|
|||
|
|
});
|
|||
|
|
// Create totals
|
|||
|
|
for (const total of totals) {
|
|||
|
|
await this.prisma.estimateTotal.create({ data: total });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
exports.EstimateService = EstimateService;
|
|||
|
|
//# sourceMappingURL=estimate.service.js.map
|