|
@@ -0,0 +1,269 @@
|
|
|
|
|
+'use strict';
|
|
|
|
|
+
|
|
|
|
|
+module.exports = app => {
|
|
|
|
|
+ class ProjectScreenTender extends app.BaseService {
|
|
|
|
|
+ constructor(ctx) {
|
|
|
|
|
+ super(ctx);
|
|
|
|
|
+ this.tableName = 'project_screen_tender';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getProject(pid, transaction = null, lock = false) {
|
|
|
|
|
+ const db = transaction || this.db;
|
|
|
|
|
+ if (!lock) return await db.get(this.ctx.service.project.tableName, { id: pid });
|
|
|
|
|
+ const sql = 'SELECT id, page_show, common_json FROM ?? WHERE id = ? FOR UPDATE';
|
|
|
|
|
+ const sqlParam = [this.ctx.service.project.tableName, pid];
|
|
|
|
|
+ return await db.queryOne(sql, sqlParam);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getProjects(pid, transaction = null) {
|
|
|
|
|
+ return await this.ctx.service.subProject.getAllDataByCondition({
|
|
|
|
|
+ where: { project_id: pid, is_delete: 0 },
|
|
|
|
|
+ orders: [['tree_level', 'asc'], ['tree_order', 'asc'], ['id', 'asc']],
|
|
|
|
|
+ }, transaction);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getTenders(pid, spids, transaction = null) {
|
|
|
|
|
+ if (!spids.length) return [];
|
|
|
|
|
+ return await this.ctx.service.tender.getAllDataByCondition({
|
|
|
|
|
+ where: { project_id: pid, spid: spids },
|
|
|
|
|
+ orders: [['id', 'asc']],
|
|
|
|
|
+ }, transaction);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getList(pid, spid = '', transaction = null) {
|
|
|
|
|
+ const where = { pid };
|
|
|
|
|
+ if (spid) where.spid = spid;
|
|
|
|
|
+ return await (transaction || this.db).select(this.tableName, { where });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async saveChanges(pid, changes, transaction) {
|
|
|
|
|
+ if (!changes.length) return;
|
|
|
|
|
+ await transaction.delete(this.tableName, { pid, tid: changes.map(item => item.tid) });
|
|
|
|
|
+ const excluded = changes.filter(item => !item.checked).map(item => ({ pid, spid: item.spid, tid: item.tid }));
|
|
|
|
|
+ await this.insertDatasSplit(transaction, this.tableName, excluded);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async saveSettings(pid, pageShow, transaction) {
|
|
|
|
|
+ return await transaction.update(this.ctx.service.project.tableName, {
|
|
|
|
|
+ page_show: JSON.stringify(pageShow),
|
|
|
|
|
+ }, { where: { id: pid } });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async withTransaction(action) {
|
|
|
|
|
+ const transaction = await this.db.beginTransaction();
|
|
|
|
|
+ try {
|
|
|
|
|
+ const result = await action(transaction);
|
|
|
|
|
+ await transaction.commit();
|
|
|
|
|
+ return result;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ await transaction.rollback();
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async syncTenderProject(pid, spid, tid, transaction) {
|
|
|
|
|
+ return await transaction.update(this.tableName, { spid }, { where: { pid, tid } });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getChangedTenders(pid, tids, transaction) {
|
|
|
|
|
+ if (!tids.length) return [];
|
|
|
|
|
+ const sql = 'SELECT id, spid FROM ?? WHERE project_id = ? AND id IN (?) ORDER BY id FOR UPDATE';
|
|
|
|
|
+ const sqlParam = [this.ctx.service.tender.tableName, pid, tids];
|
|
|
|
|
+ return await transaction.query(sql, sqlParam);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async saveScopeSettings(pid, pageShow, commonJson, transaction) {
|
|
|
|
|
+ return await transaction.update(this.ctx.service.project.tableName, {
|
|
|
|
|
+ page_show: JSON.stringify(pageShow),
|
|
|
|
|
+ common_json: JSON.stringify(commonJson),
|
|
|
|
|
+ }, { where: { id: pid } });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ parseSettings(project) {
|
|
|
|
|
+ if (!project) throw '项目不存在';
|
|
|
|
|
+ const pageShow = project.page_show ? (typeof project.page_show === 'string' ? JSON.parse(project.page_show) : project.page_show) : {};
|
|
|
|
|
+ const commonJson = project.common_json ? (typeof project.common_json === 'string' ? JSON.parse(project.common_json) : project.common_json) : {};
|
|
|
|
|
+ const excludedProjectIds = Array.isArray(commonJson.projectScreenExcludedProjectIds) ? commonJson.projectScreenExcludedProjectIds :
|
|
|
|
|
+ Array.isArray(pageShow.projectScreenExcludedProjectIds) ? pageShow.projectScreenExcludedProjectIds : [];
|
|
|
|
|
+ return {
|
|
|
|
|
+ pageShow,
|
|
|
|
|
+ commonJson,
|
|
|
|
|
+ version: Number(pageShow.projectScreenTenderVersion) || 0,
|
|
|
|
|
+ excludedProjectIds: excludedProjectIds.filter(id => typeof id === 'string' && id && id.length <= 36),
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getTenderState(pid, transaction) {
|
|
|
|
|
+ const settings = this.parseSettings(await this.getProject(pid, transaction));
|
|
|
|
|
+ const projects = await this.getProjects(pid, transaction);
|
|
|
|
|
+ const spids = projects.filter(project => !project.is_folder).map(project => project.id);
|
|
|
|
|
+ const tenders = await this.getTenders(pid, spids, transaction);
|
|
|
|
|
+ const excluded = await this.getList(pid, '', transaction);
|
|
|
|
|
+ const validIds = new Set(tenders.map(tender => Number(tender.id)));
|
|
|
|
|
+ const excludedIds = Array.from(new Set(excluded.map(item => Number(item.tid)).filter(id => validIds.has(id))));
|
|
|
|
|
+ return { settings, projects, tenders, excludedIds };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async initTenderSettings(pid) {
|
|
|
|
|
+ return await this.withTransaction(async transaction => {
|
|
|
|
|
+ const state = await this.getTenderState(pid, transaction);
|
|
|
|
|
+ const counts = new Map();
|
|
|
|
|
+ const excludedCounts = new Map();
|
|
|
|
|
+ const excluded = new Set(state.excludedIds);
|
|
|
|
|
+ const excludedProjects = new Set(state.settings.excludedProjectIds);
|
|
|
|
|
+ state.tenders.forEach(tender => counts.set(tender.spid, (counts.get(tender.spid) || 0) + 1));
|
|
|
|
|
+ state.tenders.filter(tender => excluded.has(Number(tender.id))).forEach(tender => {
|
|
|
|
|
+ excludedCounts.set(tender.spid, (excludedCounts.get(tender.spid) || 0) + 1);
|
|
|
|
|
+ });
|
|
|
|
|
+ return {
|
|
|
|
|
+ version: state.settings.version,
|
|
|
|
|
+ projects: state.projects.map(project => ({
|
|
|
|
|
+ id: project.id, name: project.name, tree_pid: project.tree_pid,
|
|
|
|
|
+ tree_order: project.tree_order, is_folder: project.is_folder,
|
|
|
|
|
+ total: counts.get(project.id) || 0,
|
|
|
|
|
+ excludedTotal: excludedCounts.get(project.id) || 0,
|
|
|
|
|
+ checked: !excludedProjects.has(project.id),
|
|
|
|
|
+ })),
|
|
|
|
|
+ total: state.tenders.length, excludedIds: state.excludedIds,
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async loadTenderSettings(pid, spid) {
|
|
|
|
|
+ if (typeof spid !== 'string' || !spid || spid.length > 36) throw '项目参数错误';
|
|
|
|
|
+ return await this.withTransaction(async transaction => {
|
|
|
|
|
+ const settings = this.parseSettings(await this.getProject(pid, transaction));
|
|
|
|
|
+ const projects = await this.getProjects(pid, transaction);
|
|
|
|
|
+ const project = projects.find(item => item.id === spid && !item.is_folder);
|
|
|
|
|
+ if (!project) throw '项目不存在或不属于当前总项目';
|
|
|
|
|
+ const tenders = await this.getTenders(pid, [spid], transaction);
|
|
|
|
|
+ const excluded = await this.getList(pid, spid, transaction);
|
|
|
|
|
+ const categories = await this.ctx.service.category.getAllCategory(project);
|
|
|
|
|
+ return {
|
|
|
|
|
+ version: settings.version, project: { id: project.id, name: project.name },
|
|
|
|
|
+ nodes: this.buildTenderTree(tenders, categories),
|
|
|
|
|
+ excludedIds: excluded.map(item => Number(item.tid)),
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ buildTenderTree(tenders, categories) {
|
|
|
|
|
+ const levels = categories.filter(item => Number(item.level) > 0).sort((a, b) => a.level - b.level);
|
|
|
|
|
+ const root = { id: '', children: [], tree_level: -1 };
|
|
|
|
|
+ for (const tender of tenders) {
|
|
|
|
|
+ const values = typeof tender.category === 'string' ? JSON.parse(tender.category || '[]') : tender.category;
|
|
|
|
|
+ const selected = Array.isArray(values) ? values : [];
|
|
|
|
|
+ let parent = root;
|
|
|
|
|
+ let path = [];
|
|
|
|
|
+ for (let index = 0; index < levels.length; index++) {
|
|
|
|
|
+ const item = levels[index];
|
|
|
|
|
+ const value = selected.find(value => String(value.cid) === String(item.id));
|
|
|
|
|
+ if (value) path.push({ item, valueId: value.value });
|
|
|
|
|
+ else {
|
|
|
|
|
+ if (index === 0) {
|
|
|
|
|
+ path = selected.map(value => ({
|
|
|
|
|
+ item: categories.find(item => String(item.id) === String(value.cid)), valueId: value.value,
|
|
|
|
|
+ })).filter(value => value.item);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const entry of path) {
|
|
|
|
|
+ const value = entry.item.value.find(value => String(value.id) === String(entry.valueId));
|
|
|
|
|
+ if (!value) break;
|
|
|
|
|
+ const id = `${parent.id}/category-${entry.item.id}-${value.id}`;
|
|
|
|
|
+ let folder = parent.children.find(child => child.id === id);
|
|
|
|
|
+ if (!folder) {
|
|
|
|
|
+ folder = { id, parentId: parent.id, name: value.value, sort: value.sort,
|
|
|
|
|
+ tree_level: parent.tree_level + 1, is_folder: true, children: [] };
|
|
|
|
|
+ parent.children.push(folder);
|
|
|
|
|
+ }
|
|
|
|
|
+ parent = folder;
|
|
|
|
|
+ }
|
|
|
|
|
+ parent.children.push({ id: String(tender.id), tid: Number(tender.id), parentId: parent.id,
|
|
|
|
|
+ name: tender.name, tree_level: parent.tree_level + 1, is_folder: false });
|
|
|
|
|
+ }
|
|
|
|
|
+ const result = [];
|
|
|
|
|
+ const visit = parent => {
|
|
|
|
|
+ parent.children.sort((a, b) => {
|
|
|
|
|
+ if (!!a.is_folder !== !!b.is_folder) return a.is_folder ? -1 : 1;
|
|
|
|
|
+ return a.is_folder ? Number(a.sort || 0) - Number(b.sort || 0) : a.name.localeCompare(b.name, 'zh-CN');
|
|
|
|
|
+ });
|
|
|
|
|
+ for (const node of parent.children) {
|
|
|
|
|
+ const row = Object.assign({}, node);
|
|
|
|
|
+ delete row.children;
|
|
|
|
|
+ result.push(row);
|
|
|
|
|
+ if (node.children) visit(node);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ visit(root);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async saveTenderSettings(pid, data) {
|
|
|
|
|
+ const projectChanges = data.projectChanges === undefined ? [] : data.projectChanges;
|
|
|
|
|
+ if (!Number.isSafeInteger(data.version) || data.version < 0 || !Array.isArray(data.changes) || !Array.isArray(projectChanges)) throw '提交数据错误';
|
|
|
|
|
+ const projectIds = new Set();
|
|
|
|
|
+ for (const item of projectChanges) {
|
|
|
|
|
+ if (!item || typeof item.spid !== 'string' || !item.spid || item.spid.length > 36 || typeof item.checked !== 'boolean') throw '项目参数错误';
|
|
|
|
|
+ if (projectIds.has(item.spid)) throw '项目重复提交,请刷新后重试';
|
|
|
|
|
+ projectIds.add(item.spid);
|
|
|
|
|
+ }
|
|
|
|
|
+ const byId = new Map();
|
|
|
|
|
+ for (const item of data.changes) {
|
|
|
|
|
+ if (!item || !Number.isSafeInteger(item.tid) || item.tid <= 0 || typeof item.checked !== 'boolean' ||
|
|
|
|
|
+ typeof item.spid !== 'string' || !item.spid || item.spid.length > 36) throw '标段参数错误';
|
|
|
|
|
+ if (byId.has(item.tid)) throw '标段重复提交,请刷新后重试';
|
|
|
|
|
+ byId.set(item.tid, item);
|
|
|
|
|
+ }
|
|
|
|
|
+ const changes = Array.from(byId.values());
|
|
|
|
|
+ const result = await this.withTransaction(async transaction => {
|
|
|
|
|
+ const settings = this.parseSettings(await this.getProject(pid, transaction, true));
|
|
|
|
|
+ if (settings.version !== data.version) throw '配置已被其他管理员修改,请刷新页面后重新设置';
|
|
|
|
|
+ const projects = await this.getProjects(pid, transaction);
|
|
|
|
|
+ const spids = new Set(projects.filter(project => !project.is_folder).map(project => project.id));
|
|
|
|
|
+ if (changes.concat(projectChanges).some(item => !spids.has(item.spid))) throw '项目不存在或不属于当前总项目';
|
|
|
|
|
+ const tenders = await this.getChangedTenders(pid, changes.map(item => item.tid), transaction);
|
|
|
|
|
+ const tenderById = new Map(tenders.map(tender => [Number(tender.id), tender]));
|
|
|
|
|
+ if (changes.some(item => !tenderById.has(item.tid) || tenderById.get(item.tid).spid !== item.spid)) throw '标段已变更或不属于当前项目,请刷新后重试';
|
|
|
|
|
+ await this.saveChanges(pid, changes, transaction);
|
|
|
|
|
+ const excludedProjects = new Set(settings.excludedProjectIds.filter(id => spids.has(id)));
|
|
|
|
|
+ projectChanges.forEach(item => {
|
|
|
|
|
+ if (item.checked) excludedProjects.delete(item.spid);
|
|
|
|
|
+ else excludedProjects.add(item.spid);
|
|
|
|
|
+ });
|
|
|
|
|
+ settings.commonJson.projectScreenExcludedProjectIds = Array.from(excludedProjects);
|
|
|
|
|
+ delete settings.pageShow.projectScreenExcludedProjectIds;
|
|
|
|
|
+ delete settings.pageShow.projectScreenTenderMode;
|
|
|
|
|
+ settings.pageShow.projectScreenTenderVersion = settings.version + 1;
|
|
|
|
|
+ await this.saveScopeSettings(pid, settings.pageShow, settings.commonJson, transaction);
|
|
|
|
|
+ return { version: settings.version + 1, pageShow: settings.pageShow };
|
|
|
|
|
+ });
|
|
|
|
|
+ this.ctx.session.sessionProject.page_show = await this.ctx.service.projectAccount.getPageShow(JSON.stringify(result.pageShow));
|
|
|
|
|
+ return { version: result.version };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getDisplayTenders(pid, spids) {
|
|
|
|
|
+ if (!spids.length) return [];
|
|
|
|
|
+ return await this.withTransaction(async transaction => {
|
|
|
|
|
+ const settings = this.parseSettings(await this.getProject(pid, transaction));
|
|
|
|
|
+ const excludedProjects = new Set(settings.excludedProjectIds);
|
|
|
|
|
+ const excluded = new Set((await this.getList(pid, '', transaction)).map(item => Number(item.tid)));
|
|
|
|
|
+ const tenders = (await this.getTenders(pid, spids.filter(id => !excludedProjects.has(id)), transaction))
|
|
|
|
|
+ .filter(tender => !excluded.has(Number(tender.id)));
|
|
|
|
|
+ return tenders.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'));
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getDisplayProjects(pid) {
|
|
|
|
|
+ return await this.withTransaction(async transaction => {
|
|
|
|
|
+ const settings = this.parseSettings(await this.getProject(pid, transaction));
|
|
|
|
|
+ const excluded = new Set(settings.excludedProjectIds);
|
|
|
|
|
+ const projects = await this.getProjects(pid, transaction);
|
|
|
|
|
+ return projects.filter(project => project.is_folder || !excluded.has(project.id));
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ProjectScreenTender;
|
|
|
|
|
+};
|