mirror of
https://github.com/sergeyyarkov/asu_tools_service.git
synced 2026-10-11 02:19:09 +03:00
upd
This commit is contained in:
parent
9342a9cf5e
commit
c0e1e2f655
@ -45,7 +45,7 @@ export default async (ctx) => {
|
|||||||
throw new ServiceUnavailableError("Your request on parsing is processing");
|
throw new ServiceUnavailableError("Your request on parsing is processing");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {ReportModel} report */
|
/** @param {ParsedReport} report */
|
||||||
const onParseProgress = async (report) => sseClientCtx.res.sendSSEJson(report, "progress");
|
const onParseProgress = async (report) => sseClientCtx.res.sendSSEJson(report, "progress");
|
||||||
|
|
||||||
const cancelAnalyzeCond = () => Boolean(sseClientCtx.local?.isClosed);
|
const cancelAnalyzeCond = () => Boolean(sseClientCtx.local?.isClosed);
|
||||||
|
|||||||
@ -14,6 +14,6 @@ export default async ({ res, data }) => {
|
|||||||
throw new BadRequestError(err.message);
|
throw new BadRequestError(err.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
await reportsService.syncReportsWithDatabase(reports);
|
const rows = await reportsService.syncReportsWithDatabase(reports);
|
||||||
res.sendJson({ data: { message: "OK" } });
|
res.sendJson({ data: { rows } });
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,7 +11,11 @@
|
|||||||
"#root/*": ["./*"],
|
"#root/*": ["./*"],
|
||||||
"#handlers/*": ["./handlers/*"],
|
"#handlers/*": ["./handlers/*"],
|
||||||
"#services/*": ["./services/*"],
|
"#services/*": ["./services/*"],
|
||||||
"#schemas/*": ["./schemas/*"]
|
"#schemas/*": ["./schemas/*"],
|
||||||
|
"#repositories/*": ["./repositories/*"],
|
||||||
|
"#tables/*": ["./tables/*"],
|
||||||
|
"#models/*": ["./models/*"],
|
||||||
|
"#utils/*": ["./utils/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["./http-server/types/*.d.ts"]
|
"exclude": ["./http-server/types/*.d.ts"]
|
||||||
|
|||||||
2
models.d.ts
vendored
2
models.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
type ReportModel = {
|
type ParsedReport = {
|
||||||
date: string;
|
date: string;
|
||||||
equipment?:
|
equipment?:
|
||||||
| string
|
| string
|
||||||
|
|||||||
1
models/index.js
Normal file
1
models/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./report.model.js";
|
||||||
50
models/report.model.js
Normal file
50
models/report.model.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/** @import { ReportColumns } from "#tables/report.table.js" */
|
||||||
|
|
||||||
|
export class ReportModel {
|
||||||
|
/**
|
||||||
|
* @type {ReportColumns['id'] | undefined}
|
||||||
|
*/
|
||||||
|
id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ReportColumns['date']}
|
||||||
|
*/
|
||||||
|
date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ReportColumns['reason_call']}
|
||||||
|
*/
|
||||||
|
reason_call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ReportColumns['job_description']}
|
||||||
|
*/
|
||||||
|
job_description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ReportColumns['root_cause']}
|
||||||
|
*/
|
||||||
|
root_cause;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ReportColumns['applicant_id']}
|
||||||
|
*/
|
||||||
|
applicant_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ReportColumns['equipment_id']}
|
||||||
|
*/
|
||||||
|
equipment_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Omit<ReportColumns, 'id'>} columns
|
||||||
|
*/
|
||||||
|
constructor(columns) {
|
||||||
|
this.date = columns.date;
|
||||||
|
this.reason_call = columns.reason_call;
|
||||||
|
this.job_description = columns.job_description;
|
||||||
|
this.root_cause = columns.root_cause;
|
||||||
|
this.applicant_id = columns.applicant_id;
|
||||||
|
this.equipment_id = columns.equipment_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,7 +25,11 @@
|
|||||||
"#root/*": "./*",
|
"#root/*": "./*",
|
||||||
"#handlers/*": "./handlers/*",
|
"#handlers/*": "./handlers/*",
|
||||||
"#services/*": "./services/*",
|
"#services/*": "./services/*",
|
||||||
"#schemas/*": "./schemas/*"
|
"#schemas/*": "./schemas/*",
|
||||||
|
"#repositories/*": "./repositories/*",
|
||||||
|
"#tables/*": "./tables/*",
|
||||||
|
"#models/*": "./models/*",
|
||||||
|
"#utils/*": "./utils/*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
|
|||||||
1
repositories/index.js
Normal file
1
repositories/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./report.repository.js";
|
||||||
61
repositories/report.repository.js
Normal file
61
repositories/report.repository.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import sql from "mssql";
|
||||||
|
import db from "#root/db.js";
|
||||||
|
import { ReportModel } from "#models/index.js";
|
||||||
|
import { ReportTable } from "#tables/index.js";
|
||||||
|
|
||||||
|
/** @import { ReportColumns } from "#tables/report.table.js" */
|
||||||
|
|
||||||
|
export const reportRepository = {
|
||||||
|
async getAll() {
|
||||||
|
const request = new sql.Request(db);
|
||||||
|
console.log(ReportTable.tableName);
|
||||||
|
|
||||||
|
const reports = await request.query(`select * from dbo.${ReportTable.tableName};`);
|
||||||
|
console.log(reports);
|
||||||
|
|
||||||
|
return reports.recordset;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Omit<ReportColumns, 'id'>} entry
|
||||||
|
*/
|
||||||
|
async create(entry) {
|
||||||
|
const report = new ReportModel(entry);
|
||||||
|
const request = new sql.Request(db);
|
||||||
|
|
||||||
|
request.input("date", ReportTable.getColumn("date"), report.date);
|
||||||
|
request.input("reasonCall", ReportTable.getColumn("reason_call"), report.reason_call);
|
||||||
|
request.input("jobDesc", ReportTable.getColumn("job_description"), report.job_description);
|
||||||
|
request.input("rootCause", ReportTable.getColumn("root_cause"), report.root_cause);
|
||||||
|
request.input("applicantId", ReportTable.getColumn("applicant_id"), report.applicant_id);
|
||||||
|
request.input("eqpId", ReportTable.getColumn("equipment_id"), report.equipment_id);
|
||||||
|
|
||||||
|
const createdReport = await request.query(
|
||||||
|
`insert into ${ReportTable.table.name}
|
||||||
|
(date, reason_call, job_description, root_cause, applicant_id, equipment_id)
|
||||||
|
output inserted.id
|
||||||
|
values (@date, @reasonCall, @jobDesc, @rootCause, @applicantId, @eqpId);
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
report.id = Number.parseInt(createdReport.recordset[0].id, 10);
|
||||||
|
|
||||||
|
return report;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Omit<ReportColumns, 'id'>[]} entries
|
||||||
|
* @param {sql.Transaction} [trx]
|
||||||
|
*/
|
||||||
|
async bulk(entries, trx) {
|
||||||
|
const request = trx ? new sql.Request(trx) : new sql.Request(db);
|
||||||
|
const table = ReportTable.createInstance();
|
||||||
|
|
||||||
|
for (const r of entries) {
|
||||||
|
table.rows.add(r.date, r.reason_call, r.job_description, r.root_cause, r.applicant_id, r.equipment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
await request.bulk(table);
|
||||||
|
return table.rows.length;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -23,4 +23,4 @@ export const reportItemSchema = y.object({
|
|||||||
executors: y.array(y.object({ id: y.number(), fullname: y.string() })).required()
|
executors: y.array(y.object({ id: y.number(), fullname: y.string() })).required()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const reportsSchema = y.object({ reports: y.array(reportItemSchema).required() }).required();
|
export const reportsSchema = y.object({ reports: y.array(reportItemSchema).min(1).required() }).required();
|
||||||
|
|||||||
@ -2,6 +2,8 @@ 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 mssql from "mssql";
|
||||||
|
import { reportRepository } from "#repositories/index.js";
|
||||||
|
// import * as utilsDate from "#utils/date.js";
|
||||||
|
|
||||||
/** @import { reportsSchema } from "#schemas/report.schema.js" */
|
/** @import { reportsSchema } from "#schemas/report.schema.js" */
|
||||||
/** @import { InferType } from "yup" */
|
/** @import { InferType } from "yup" */
|
||||||
@ -22,13 +24,13 @@ export const reportsService = {
|
|||||||
* Читает файл Excel и возвращает данные о репорте в массиве
|
* Читает файл Excel и возвращает данные о репорте в массиве
|
||||||
*
|
*
|
||||||
* @param {string} filepath
|
* @param {string} filepath
|
||||||
* @returns {ReportModel[]}
|
* @returns {ParsedReport[]}
|
||||||
*/
|
*/
|
||||||
parseExcelFile(filepath) {
|
parseExcelFile(filepath) {
|
||||||
const workbook = xlsx.readFile(filepath, xlsxParseOptions);
|
const workbook = xlsx.readFile(filepath, xlsxParseOptions);
|
||||||
const sheet = workbook.Sheets[sheetName];
|
const sheet = workbook.Sheets[sheetName];
|
||||||
|
|
||||||
/** @type {ReportModel[]} */
|
/** @type {ParsedReport[]} */
|
||||||
const reports = [];
|
const reports = [];
|
||||||
|
|
||||||
if (!sheet) throw new Error("Invalid reports workbook.");
|
if (!sheet) throw new Error("Invalid reports workbook.");
|
||||||
@ -77,9 +79,9 @@ export const reportsService = {
|
|||||||
* Проверяет отчет на наличие связей с БД АСУ и возвращает
|
* Проверяет отчет на наличие связей с БД АСУ и возвращает
|
||||||
* этот отчет с устанновленными связями или без них
|
* этот отчет с устанновленными связями или без них
|
||||||
*
|
*
|
||||||
* @param {ReportModel} report
|
* @param {ParsedReport} report
|
||||||
* @param {() => boolean} cancelCondFn
|
* @param {() => boolean} cancelCondFn
|
||||||
* @returns {Promise<ReportModel>}
|
* @returns {Promise<ParsedReport>}
|
||||||
*/
|
*/
|
||||||
async checkReportLinks(report, cancelCondFn) {
|
async checkReportLinks(report, cancelCondFn) {
|
||||||
const executorSurnames = extractSurnamesFromExecutorField(report.executorNames);
|
const executorSurnames = extractSurnamesFromExecutorField(report.executorNames);
|
||||||
@ -141,9 +143,9 @@ export const reportsService = {
|
|||||||
/**
|
/**
|
||||||
* Проверяет связи отчетов с БД и возвращает результат
|
* Проверяет связи отчетов с БД и возвращает результат
|
||||||
*
|
*
|
||||||
* @param {ReportModel[]} reports
|
* @param {ParsedReport[]} reports
|
||||||
* @param {() => boolean} cancelCondFn
|
* @param {() => boolean} cancelCondFn
|
||||||
* @param {(report: ReportModel) => Promise<void> | void} [progressCb]
|
* @param {(report: ParsedReport) => Promise<void> | void} [progressCb]
|
||||||
*/
|
*/
|
||||||
analyzeParseResult(reports, cancelCondFn, progressCb) {
|
analyzeParseResult(reports, cancelCondFn, progressCb) {
|
||||||
const limit = pLimit(10);
|
const limit = pLimit(10);
|
||||||
@ -174,38 +176,29 @@ export const reportsService = {
|
|||||||
*/
|
*/
|
||||||
async syncReportsWithDatabase(reports) {
|
async syncReportsWithDatabase(reports) {
|
||||||
const trx = new mssql.Transaction(db);
|
const trx = new mssql.Transaction(db);
|
||||||
const table = new mssql.Table("dbo.gpp_report_api_report");
|
const dbReports = await reportRepository.getAll();
|
||||||
|
|
||||||
table.create = false;
|
/**
|
||||||
|
* 1. проверить на существование дубликатов и отфильтровать
|
||||||
table.columns.add("date", mssql.Date, { nullable: false });
|
* 2. начать транзакцию на множественную вставку отчетов
|
||||||
table.columns.add("reason_call", mssql.NVarChar(mssql.MAX), { nullable: false });
|
* 3. установить связь добавленных отчетов с исполнителями и завершить транзакцию
|
||||||
table.columns.add("job_description", mssql.NVarChar(mssql.MAX), { nullable: false });
|
*/
|
||||||
table.columns.add("root_cause", mssql.NVarChar(mssql.MAX), { nullable: true });
|
|
||||||
table.columns.add("applicant_id", mssql.BigInt, { nullable: true });
|
|
||||||
table.columns.add("equipment_id", mssql.BigInt, { nullable: true });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const addReportsRequest = new mssql.Request(trx);
|
|
||||||
|
|
||||||
for (const r of reports) {
|
|
||||||
let date = new Date(r.date || "2026");
|
|
||||||
|
|
||||||
if (!(date instanceof Date && !Number.isNaN(date.valueOf()))) date = new Date("2026");
|
|
||||||
|
|
||||||
table.rows.add(
|
|
||||||
date,
|
|
||||||
r.reason_call || " ",
|
|
||||||
r.job_description || " ",
|
|
||||||
r.root_cause || " ",
|
|
||||||
r.applicant?.id || null,
|
|
||||||
r.equipment?.id || null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await trx.begin();
|
await trx.begin();
|
||||||
await addReportsRequest.bulk(table);
|
const countRows = await reportRepository.bulk(
|
||||||
|
reports.map((r) => ({
|
||||||
|
applicant_id: r.applicant?.id || null,
|
||||||
|
equipment_id: r.equipment?.id || null,
|
||||||
|
date: new Date("2026"),
|
||||||
|
job_description: r.job_description || "",
|
||||||
|
reason_call: r.reason_call || "",
|
||||||
|
root_cause: r.root_cause || ""
|
||||||
|
})),
|
||||||
|
trx
|
||||||
|
);
|
||||||
await trx.commit();
|
await trx.commit();
|
||||||
|
return countRows;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
await trx.rollback();
|
await trx.rollback();
|
||||||
|
|||||||
35
tables/base.table.js
Normal file
35
tables/base.table.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import sql from "mssql";
|
||||||
|
|
||||||
|
export class BaseTable {
|
||||||
|
static tableName = "";
|
||||||
|
static table = new sql.Table(this.tableName);
|
||||||
|
|
||||||
|
/** @type {Record<string, sql.ISqlType & sql.IColumnOptions>} */
|
||||||
|
static columns = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {typeof BaseTable} T
|
||||||
|
* @this {T}
|
||||||
|
* @param {keyof T['columns']} name
|
||||||
|
*/
|
||||||
|
static getColumn(name) {
|
||||||
|
const col = this.table.columns.find((col) => col.name === name);
|
||||||
|
if (!col) throw new Error(`Unknown column with name "${String(name)}"`);
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {sql.Table} [table]
|
||||||
|
*/
|
||||||
|
static init(table) {
|
||||||
|
table = table || this.table;
|
||||||
|
// TODO: fix types
|
||||||
|
Object.entries(this.columns).forEach(([name, { type, ...opts }]) => table.columns.add(name, type, opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
static createInstance() {
|
||||||
|
const table = new sql.Table(this.tableName);
|
||||||
|
this.init(table);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
tables/index.js
Normal file
1
tables/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./report.table.js";
|
||||||
23
tables/report.table.js
Normal file
23
tables/report.table.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import sql from "mssql";
|
||||||
|
import { BaseTable } from "./base.table.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{ id: number; date: Date; reason_call: string; job_description: string; root_cause: string | null; applicant_id: number | null; equipment_id: number | null; }} ReportColumns
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class ReportTable extends BaseTable {
|
||||||
|
static tableName = "gpp_report_api_report";
|
||||||
|
static table = new sql.Table(this.tableName);
|
||||||
|
|
||||||
|
static columns = {
|
||||||
|
date: { type: sql.Date, nullable: false },
|
||||||
|
reason_call: { type: sql.NVarChar(sql.MAX), nullable: false },
|
||||||
|
job_description: { type: sql.NVarChar(sql.MAX), nullable: false },
|
||||||
|
root_cause: { type: sql.NVarChar(sql.MAX), nullable: true },
|
||||||
|
applicant_id: { type: sql.BigInt, nullable: true },
|
||||||
|
equipment_id: { type: sql.BigInt, nullable: true }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ReportTable.table.create = false;
|
||||||
|
ReportTable.init();
|
||||||
7
utils/date.js
Normal file
7
utils/date.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* @param {unknown} date
|
||||||
|
* @returns {boolean is Date}
|
||||||
|
*/
|
||||||
|
export function isDateValid(date) {
|
||||||
|
return date instanceof Date && !Number.isNaN(date.valueOf());
|
||||||
|
}
|
||||||
1
utils/index.js
Normal file
1
utils/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./date.js";
|
||||||
Loading…
x
Reference in New Issue
Block a user