del reports before import

This commit is contained in:
Sergey Yarkov 2026-03-18 14:52:57 +03:00
parent 1f73377e33
commit 44fd57ef2c
6 changed files with 88 additions and 46 deletions

View File

@ -10,10 +10,10 @@ import { reportsSchema } from "#schemas/report.schema.js";
* @type {import("#root/http-server/types/http-server.js").HttpRouteHandler} * @type {import("#root/http-server/types/http-server.js").HttpRouteHandler}
*/ */
export default async ({ res, data }) => { export default async ({ res, data }) => {
const { reports } = await reportsSchema.validate(data).catch((err) => { const payload = await reportsSchema.validate(data).catch((err) => {
throw new BadRequestError(err.message); throw new BadRequestError(err.message);
}); });
const rows = await reportsService.syncReportsWithDatabase(reports); const rows = await reportsService.syncReportsWithDatabase(payload);
res.sendJson({ data: { rows } }); res.sendJson({ data: { rows } });
}; };

View File

@ -1 +1,2 @@
export * from "./uow.js";
export * from "./report.repository.js"; export * from "./report.repository.js";

View File

@ -1,13 +1,12 @@
import sql from "mssql";
import db from "#root/db.js";
import { ReportModel } from "#models/index.js"; import { ReportModel } from "#models/index.js";
import { ReportExecutorTable, ReportTable } from "#tables/index.js"; import { ReportExecutorTable, ReportTable } from "#tables/index.js";
import { UOW } from "./index.js";
/** @import { ReportColumns } from "#tables/report.table.js" */ /** @import { ReportColumns } from "#tables/report.table.js" */
export const reportRepository = { export const reportRepository = {
async getAll() { async getAll() {
const request = new sql.Request(db); const request = UOW.request;
const result = await request.query(`select * from dbo.${ReportTable.tableName};`); const result = await request.query(`select * from dbo.${ReportTable.tableName};`);
const reports = ReportModel.fromRecordset(result.recordset); const reports = ReportModel.fromRecordset(result.recordset);
@ -15,18 +14,29 @@ export const reportRepository = {
}, },
async getLast() { async getLast() {
const request = new sql.Request(db); const request = UOW.request;
const result = await request.query(`select top 1 * from ${ReportTable.tableName} order by id desc`); const result = await request.query(`select top 1 * from ${ReportTable.tableName} order by id desc`);
const report = ReportModel.fromRecordset(result.recordset); const report = ReportModel.fromRecordset(result.recordset);
return report.at(0); return report.at(0);
}, },
async delAll() {
await UOW.run(async () => {
const request = UOW.request;
await request.query(`delete from ${ReportExecutorTable.tableName};`);
await request.query(`delete from ${ReportTable.tableName};`);
await request.query(`DBCC CHECKIDENT ('${ReportExecutorTable.tableName}', RESEED, 0);`);
await request.query(`DBCC CHECKIDENT ('${ReportTable.tableName}', RESEED, 0);`);
});
},
/** /**
* @param {ReportColumns} entry * @param {ReportColumns} entry
*/ */
async create(entry) { async create(entry) {
const request = new sql.Request(db); const request = UOW.request;
const report = new ReportModel(entry); const report = new ReportModel(entry);
request.input("date", ReportTable.getColumn("date"), report.date); request.input("date", ReportTable.getColumn("date"), report.date);
@ -51,10 +61,9 @@ export const reportRepository = {
/** /**
* @param {Record<string, Array<string | number>>[]} links * @param {Record<string, Array<string | number>>[]} links
* @param {sql.Transaction} [trx]
*/ */
async attachExecutors(links, trx) { async attachExecutors(links) {
const request = trx ? new sql.Request(trx) : new sql.Request(db); const request = UOW.request;
const table = ReportExecutorTable.createInstance(); const table = ReportExecutorTable.createInstance();
for (const report of Object.values(links)) { for (const report of Object.values(links)) {
@ -69,10 +78,9 @@ export const reportRepository = {
/** /**
* @param {ReportColumns[]} entries * @param {ReportColumns[]} entries
* @param {sql.Transaction} [trx]
*/ */
async bulk(entries, trx) { async bulk(entries) {
const request = trx ? new sql.Request(trx) : new sql.Request(db); const request = UOW.request;
const table = ReportTable.createInstance(); const table = ReportTable.createInstance();
const lastReportId = (await this.getLast())?.id || 0; const lastReportId = (await this.getLast())?.id || 0;
let nextAddId = typeof lastReportId === "string" ? Number.parseInt(lastReportId, 10) : lastReportId; let nextAddId = typeof lastReportId === "string" ? Number.parseInt(lastReportId, 10) : lastReportId;

27
repositories/uow.js Normal file
View File

@ -0,0 +1,27 @@
import { AsyncLocalStorage } from "node:async_hooks";
import sql from "mssql";
import db from "#root/db.js";
export class UOW {
/** @type {AsyncLocalStorage<sql.Transaction>} */
static #als = new AsyncLocalStorage();
/** @param {() => Promise<unknown>} callback */
static async run(callback) {
const trx = new sql.Transaction(db);
try {
await trx.begin();
const ret = await this.#als.run(trx, callback);
await trx.commit();
return ret;
} catch (error) {
await trx.rollback();
throw error;
}
}
static get request() {
const trx = this.#als.getStore();
return trx ? new sql.Request(trx) : new sql.Request(db);
}
}

View File

@ -23,4 +23,6 @@ export const reportItemSchema = y.object({
executors: y.array(y.object({ id: y.number().required(), fullname: y.string().required() })).required() executors: y.array(y.object({ id: y.number().required(), fullname: y.string().required() })).required()
}); });
export const reportsSchema = y.object({ reports: y.array(reportItemSchema).min(1).required() }).required(); export const reportsSchema = y
.object({ reports: y.array(reportItemSchema).min(1).required(), deleteAllBeforeImport: y.boolean().required() })
.required();

View File

@ -1,8 +1,7 @@
import xlsx from "xlsx"; import xlsx from "xlsx";
import pLimit from "p-limit"; import pLimit from "p-limit";
import db from "#root/db.js"; import db from "#root/db.js";
import mssql from "mssql"; import { reportRepository, UOW } from "#repositories/index.js";
import { reportRepository } from "#repositories/index.js";
import * as utilsDate from "#utils/date.js"; import * as utilsDate from "#utils/date.js";
/** @import { reportsSchema } from "#schemas/report.schema.js" */ /** @import { reportsSchema } from "#schemas/report.schema.js" */
@ -172,34 +171,42 @@ export const reportsService = {
}, },
/** /**
* @param {InferType<typeof reportsSchema>['reports']} inReports * @param {InferType<typeof reportsSchema>} payload
*/ */
async syncReportsWithDatabase(inReports) { async syncReportsWithDatabase(payload) {
const INVALID_DATE_FALLBACK = new Date("2016-10-24"); // дата для постановки вместо неверной. (колонка `date` в таблице имеет ограничение not null) const INVALID_DATE_FALLBACK = new Date("2016-10-24"); // дата для постановки вместо неверной. (колонка `date` в таблице имеет ограничение not null)
const trx = new mssql.Transaction(db); const { reports: inReports, deleteAllBeforeImport } = payload;
const reports = await reportRepository.getAll();
/** Отфильтровывание дубликатов по полям. */ /** @type {typeof payload['reports']} */
const filteredInReports = inReports.filter((inputR) => { let unImportedReports = [];
const isDub = reports.some((dbR) => {
return ( if (!deleteAllBeforeImport) {
inputR.reason_call === dbR.reason_call && const reports = await reportRepository.getAll();
inputR.job_description === dbR.job_description &&
inputR.root_cause === dbR.root_cause && /** Отфильтровывание дубликатов по полям. */
inputR.equipment?.id == dbR.equipment_id && unImportedReports = inReports.filter((inputR) => {
inputR.applicant?.id == dbR.applicant_id const isDub = reports.some((dbR) => {
); return (
inputR.reason_call === dbR.reason_call &&
inputR.job_description === dbR.job_description &&
inputR.root_cause === dbR.root_cause &&
inputR.equipment?.id == dbR.equipment_id &&
inputR.applicant?.id == dbR.applicant_id
);
});
return !isDub;
}); });
return !isDub;
});
if (filteredInReports.length === 0) return 0; if (unImportedReports.length === 0) return 0;
} else {
await reportRepository.delAll();
unImportedReports = inReports;
}
/** Добавление отчетов и связей с исполнителями к ним */ /** Добавление отчетов и связей с исполнителями к ним */
try { const ret = await UOW.run(async () => {
await trx.begin();
const bulkResult = await reportRepository.bulk( const bulkResult = await reportRepository.bulk(
filteredInReports.map((r) => { unImportedReports.map((r) => {
const reportDate = new Date(r.date || ""); const reportDate = new Date(r.date || "");
return { return {
applicant_id: r.applicant?.id || null, applicant_id: r.applicant?.id || null,
@ -209,22 +216,19 @@ export const reportsService = {
reason_call: r.reason_call || "", reason_call: r.reason_call || "",
root_cause: r.root_cause || "" root_cause: r.root_cause || ""
}; };
}), })
trx
); );
const executorsLinks = filteredInReports.map((r, i) => { const executorsLinks = unImportedReports.map((r, i) => {
return Object.fromEntries([[`${bulkResult.addedRowIds[i]}`, r.executors.map((e) => e.id)]]); return Object.fromEntries([[`${bulkResult.addedRowIds[i]}`, r.executors.map((e) => e.id)]]);
}); });
await reportRepository.attachExecutors(executorsLinks, trx); await reportRepository.attachExecutors(executorsLinks);
await trx.commit();
return bulkResult.rowsAffected; return bulkResult.rowsAffected;
} catch (error) { });
console.error(error);
await trx.rollback(); return ret;
throw error;
}
} }
}; };