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");
|
||||
}
|
||||
|
||||
/** @param {ReportModel} report */
|
||||
/** @param {ParsedReport} report */
|
||||
const onParseProgress = async (report) => sseClientCtx.res.sendSSEJson(report, "progress");
|
||||
|
||||
const cancelAnalyzeCond = () => Boolean(sseClientCtx.local?.isClosed);
|
||||
|
||||
@ -14,6 +14,6 @@ export default async ({ res, data }) => {
|
||||
throw new BadRequestError(err.message);
|
||||
});
|
||||
|
||||
await reportsService.syncReportsWithDatabase(reports);
|
||||
res.sendJson({ data: { message: "OK" } });
|
||||
const rows = await reportsService.syncReportsWithDatabase(reports);
|
||||
res.sendJson({ data: { rows } });
|
||||
};
|
||||
|
||||
@ -11,7 +11,11 @@
|
||||
"#root/*": ["./*"],
|
||||
"#handlers/*": ["./handlers/*"],
|
||||
"#services/*": ["./services/*"],
|
||||
"#schemas/*": ["./schemas/*"]
|
||||
"#schemas/*": ["./schemas/*"],
|
||||
"#repositories/*": ["./repositories/*"],
|
||||
"#tables/*": ["./tables/*"],
|
||||
"#models/*": ["./models/*"],
|
||||
"#utils/*": ["./utils/*"]
|
||||
}
|
||||
},
|
||||
"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;
|
||||
equipment?:
|
||||
| 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/*": "./*",
|
||||
"#handlers/*": "./handlers/*",
|
||||
"#services/*": "./services/*",
|
||||
"#schemas/*": "./schemas/*"
|
||||
"#schemas/*": "./schemas/*",
|
||||
"#repositories/*": "./repositories/*",
|
||||
"#tables/*": "./tables/*",
|
||||
"#models/*": "./models/*",
|
||||
"#utils/*": "./utils/*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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()
|
||||
});
|
||||
|
||||
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 db from "#root/db.js";
|
||||
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 { InferType } from "yup" */
|
||||
@ -22,13 +24,13 @@ export const reportsService = {
|
||||
* Читает файл Excel и возвращает данные о репорте в массиве
|
||||
*
|
||||
* @param {string} filepath
|
||||
* @returns {ReportModel[]}
|
||||
* @returns {ParsedReport[]}
|
||||
*/
|
||||
parseExcelFile(filepath) {
|
||||
const workbook = xlsx.readFile(filepath, xlsxParseOptions);
|
||||
const sheet = workbook.Sheets[sheetName];
|
||||
|
||||
/** @type {ReportModel[]} */
|
||||
/** @type {ParsedReport[]} */
|
||||
const reports = [];
|
||||
|
||||
if (!sheet) throw new Error("Invalid reports workbook.");
|
||||
@ -77,9 +79,9 @@ export const reportsService = {
|
||||
* Проверяет отчет на наличие связей с БД АСУ и возвращает
|
||||
* этот отчет с устанновленными связями или без них
|
||||
*
|
||||
* @param {ReportModel} report
|
||||
* @param {ParsedReport} report
|
||||
* @param {() => boolean} cancelCondFn
|
||||
* @returns {Promise<ReportModel>}
|
||||
* @returns {Promise<ParsedReport>}
|
||||
*/
|
||||
async checkReportLinks(report, cancelCondFn) {
|
||||
const executorSurnames = extractSurnamesFromExecutorField(report.executorNames);
|
||||
@ -141,9 +143,9 @@ export const reportsService = {
|
||||
/**
|
||||
* Проверяет связи отчетов с БД и возвращает результат
|
||||
*
|
||||
* @param {ReportModel[]} reports
|
||||
* @param {ParsedReport[]} reports
|
||||
* @param {() => boolean} cancelCondFn
|
||||
* @param {(report: ReportModel) => Promise<void> | void} [progressCb]
|
||||
* @param {(report: ParsedReport) => Promise<void> | void} [progressCb]
|
||||
*/
|
||||
analyzeParseResult(reports, cancelCondFn, progressCb) {
|
||||
const limit = pLimit(10);
|
||||
@ -174,38 +176,29 @@ export const reportsService = {
|
||||
*/
|
||||
async syncReportsWithDatabase(reports) {
|
||||
const trx = new mssql.Transaction(db);
|
||||
const table = new mssql.Table("dbo.gpp_report_api_report");
|
||||
const dbReports = await reportRepository.getAll();
|
||||
|
||||
table.create = false;
|
||||
|
||||
table.columns.add("date", mssql.Date, { nullable: false });
|
||||
table.columns.add("reason_call", mssql.NVarChar(mssql.MAX), { nullable: false });
|
||||
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 });
|
||||
/**
|
||||
* 1. проверить на существование дубликатов и отфильтровать
|
||||
* 2. начать транзакцию на множественную вставку отчетов
|
||||
* 3. установить связь добавленных отчетов с исполнителями и завершить транзакцию
|
||||
*/
|
||||
|
||||
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 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();
|
||||
return countRows;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
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