'use strict'; /** * * * @author Mai * @date 2021/10/27 * @version */ const auditConst = require('../const/audit'); const sendToWormhole = require('stream-wormhole'); const path = require('path'); const advanceConst = require('../const/advance'); const FILE_MANAGEMENT_EXTRA_EXTENSIONS = [ '.sbp', '.jygs', '.gkgs', '.sjgs', '.sgys', '.qdys', '.gcjs', '.sjys', ]; module.exports = app => { class FileController extends app.BaseController { checkUnlock(ctx) { if (ctx.subProject.lock_file) throw '管理员锁定中,暂无法编辑分类&文件,仅可查看'; } checkLock(ctx) { if (!ctx.subProject.lock_file) throw '请先锁定,再管理分类数据'; } isAdmin(ctx) { return Number(ctx.session.sessionUser.is_admin) === 1; } getFileUploadWhitelist(ctx) { const defaultWhitelist = ctx.app.config.multipart.whitelist || []; return Array.from(new Set(defaultWhitelist.concat(FILE_MANAGEMENT_EXTRA_EXTENSIONS).map(ext => ext.toLowerCase()))); } isFileUploadExtensionAllowed(ctx, filename) { const ext = path.extname(filename || '').toLowerCase(); return this.getFileUploadWhitelist(ctx).indexOf(ext) >= 0; } getFileUploadCheck(ctx) { const whitelist = this.getFileUploadWhitelist(ctx); return (fieldname, fileStream, filename) => { if (!fileStream || !filename) return null; const ext = path.extname(filename).toLowerCase(); if (whitelist.indexOf(ext) >= 0) return null; const error = new Error(`资料管理不支持${ext || '无扩展名'}格式文件`); error.status = 400; return error; }; } hasFilePermission(ctx, permissionKey) { if (this.isAdmin(ctx)) return true; const permission = ctx.service.subProjPermission.PermissionConst.file[permissionKey]; const filePermission = ctx.subProject.permission.file_permission || []; return permission && filePermission.indexOf(permission.value) >= 0; } hasFileConfigPermission(ctx) { return this.hasFilePermission(ctx, 'manage_dir') || this.hasFilePermission(ctx, 'auth_user'); } applyFileConfigViewPermission(ctx, permissionMap) { if (!this.hasFileConfigPermission(ctx)) return permissionMap; Object.keys(permissionMap || {}).forEach(filingId => { permissionMap[filingId].can_view = 1; }); return permissionMap; } checkFilePermission(ctx, permissionKey) { if (!this.hasFilePermission(ctx, permissionKey)) throw '您无权进行该操作'; } hasLegacyFilePermission(ctx, value) { if (this.isAdmin(ctx)) return true; return (ctx.subProject.permission.file_permission || []).indexOf(value) >= 0; } async getProjectFiling(ctx, filingId) { const filing = await ctx.service.filing.getDataById(filingId); if (!filing || filing.is_deleted || filing.spid !== ctx.subProject.id) throw '分类不存在'; return filing; } async fillFilingAddUserNames(ctx, filingList) { const rows = filingList || []; const userIds = this.app._.uniq(rows.map(x => Number(x.create_uid)).filter(x => x > 0)); const users = userIds.length > 0 ? await ctx.service.projectAccount.getAllDataByCondition({ columns: ['id', 'name'], where: { id: userIds }, }) : []; const userNameMap = {}; users.forEach(user => { userNameMap[Number(user.id)] = user.name || ''; }); rows.forEach(filing => { filing.add_user = userNameMap[Number(filing.create_uid)] || ''; }); return rows; } async fillFilingPermissionCreatorNames(ctx, filingList, permissionRows) { const rows = filingList || []; const exactPermissionRows = (permissionRows || []).filter(permission => { return permission.filing_id && Number(permission.create_uid) > 0; }); const creatorIds = this.app._.uniq(exactPermissionRows .map(permission => Number(permission.create_uid)).filter(uid => uid > 0)); const creators = creatorIds.length > 0 ? await ctx.service.projectAccount.getAllDataByCondition({ columns: ['id', 'name'], where: { id: creatorIds }, }) : []; const creatorNameMap = {}; creators.forEach(creator => { creatorNameMap[Number(creator.id)] = creator.name || ''; }); rows.forEach(filing => { const creatorNames = this.app._.uniq(exactPermissionRows.filter(permission => { return String(permission.filing_id) === String(filing.id); }).map(permission => creatorNameMap[Number(permission.create_uid)]).filter(Boolean)); if (creatorNames.length > 0) filing.add_user = creatorNames.join('、'); }); return rows; } async checkFilingView(ctx, filing) { const permission = await ctx.service.subProjectFilingPermission.getResolvedPermission( ctx.subProject.id, ctx.session.sessionUser.accountId, filing.id, filing.filing_type, ctx.subProject.permission.file_permission, ctx.subProject.permission.filing_type, this.isAdmin(ctx) ); if (this.hasFileConfigPermission(ctx)) permission.can_view = 1; if (!permission.can_view) throw '您无权查看该资料目录'; return permission; } async getFilingOperationPermission(ctx, filing) { return await this.checkFilingView(ctx, filing); } async checkFilingOperation(ctx, filing, permissionField) { const permission = await this.getFilingOperationPermission(ctx, filing); if (!permission[permissionField]) throw '您无权进行该操作'; return permission; } async getFilingFromDirectoryData(ctx, data) { const filingId = data.id || data.tree_pre_id || (data.tree_pid && data.tree_pid !== '-1' ? data.tree_pid : ''); if (!filingId) throw '请先选择资料类别'; return await this.getProjectFiling(ctx, filingId); } filterVisibleFiling(filingList, permissionMap) { const filingMap = {}; (filingList || []).forEach(filing => { filingMap[String(filing.id)] = filing; }); const visibleIds = new Set(); (filingList || []).forEach(filing => { const permission = permissionMap[filing.id]; if (!permission || !permission.can_view) return; let current = filing; while (current) { const currentId = String(current.id); if (visibleIds.has(currentId)) break; visibleIds.add(currentId); if (current.tree_pid === '-1' || current.tree_pid === -1) break; current = filingMap[String(current.tree_pid)]; } }); return (filingList || []).filter(filing => visibleIds.has(String(filing.id))); } /** * 概算投资 * * @param ctx * @returns {Promise} */ async index(ctx) { try { if (!ctx.subProject.page_show.openFile) { throw '该功能已关闭或无法查看'; } const renderData = { jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.file.index), auditConst, }; renderData.projectList = await ctx.service.subProject.getFileProject(ctx.session.sessionProject.id, ctx.session.sessionUser.accountId, ctx.session.sessionUser.is_admin); for (const p of renderData.projectList) { if (!p.is_folder) p.file_count = await this.service.filing.sumFileCount(p.id); } renderData.tenderList = await ctx.service.tender.getList4Select('stage'); renderData.categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject); await this.layout('file/index.ejs', renderData, 'file/modal.ejs'); } catch (err) { ctx.log(err); ctx.session.postError = err.toString(); ctx.redirect(this.menu.menu.dashboard.url); } } async file(ctx) { try { if (!this.hasLegacyFilePermission(ctx, 1)) throw '您无权查看旧版资料管理'; const renderData = { jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.file.file), }; renderData.filing = await ctx.service.filing.getValidFiling(ctx.params.id, ctx.subProject.permission.filing_type); renderData.categoryData = await ctx.service.category.getAllCategory(ctx.subProject); renderData.canFiling = !ctx.subProject.lock_file && this.hasLegacyFilePermission(ctx, 3); renderData.canUpload = !ctx.subProject.lock_file && this.hasLegacyFilePermission(ctx, 2); renderData.canEdit = !ctx.subProject.lock_file && this.hasLegacyFilePermission(ctx, 4); renderData.fileReferenceList = await ctx.service.subProject.getFileReference(ctx.subProject, ctx.service.subProject.FileReferenceType.file); await this.layout('file/file.ejs', renderData, 'file/file_modal.ejs'); } catch (err) { ctx.log(err); } } /** * SpreadJS 替代 zTree 展示资料分类树 Demo * * @param {Object} ctx - egg context */ async fileSjsDemo(ctx) { try { const renderData = { jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.file.sjs_demo), }; renderData.filing = await ctx.service.filing.getValidFiling(ctx.params.id, ctx.subProject.permission.filing_type); renderData.categoryData = await ctx.service.category.getAllCategory(ctx.subProject); // 根据 lock_file 判断是否可编辑 const canEdit = !ctx.subProject.lock_file; renderData.canFiling = canEdit; renderData.canUpload = canEdit; renderData.canEdit = canEdit; renderData.fileReferenceList = await ctx.service.subProject.getFileReference(ctx.subProject, ctx.service.subProject.FileReferenceType.file); await this.layout('file/file_sjs_demo.ejs', renderData); } catch (err) { ctx.log(err); } } /** * 资料管理 SpreadJS 版本 * 使用 SpreadJS 替代 zTree 展示文件分类树 * * @param {Object} ctx - egg context */ async filesjs(ctx) { try { const renderData = { jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.file.filesjs), fileUploadWhitelist: this.getFileUploadWhitelist(ctx), }; const allFiling = await ctx.service.filing.getValidFiling(ctx.params.id, 'all'); const canManageDir = this.hasFilePermission(ctx, 'manage_dir'); renderData.categoryData = await ctx.service.category.getAllCategory(ctx.subProject); renderData.filingPermissionMap = await ctx.service.subProjectFilingPermission.getPermissionMap( ctx.subProject.id, ctx.session.sessionUser.accountId, allFiling, ctx.subProject.permission.filing_type, ctx.subProject.permission.file_permission, this.isAdmin(ctx) ); this.applyFileConfigViewPermission(ctx, renderData.filingPermissionMap); renderData.filing = this.filterVisibleFiling(allFiling, renderData.filingPermissionMap); // 共用 file_modal.ejs 需要渲染全部弹窗,实际显示与操作由 filingPermissionMap 控制。 renderData.canFiling = true; renderData.canUpload = true; renderData.canEdit = false; renderData.canManageDir = this.hasFileConfigPermission(ctx); renderData.needFilingInitialization = canManageDir && allFiling.length === 0; renderData.filingInitializationTemplates = []; renderData.filingInitializationProjects = []; if (renderData.needFilingInitialization) { renderData.filingInitializationTemplates = await ctx.service.filingTemplateList.getAllDataByCondition({ columns: ['id', 'name'], where: { ft_type: ctx.service.filingTemplateList.FtType.org }, orders: [['create_time', 'asc']], }); renderData.filingInitializationProjects = await ctx.service.subProject.getManageDirProjects( ctx.session.sessionProject.id, ctx.session.sessionUser.accountId, this.isAdmin(ctx), ctx.subProject.id ); } renderData.fileReferenceList = await ctx.service.subProject.getFileReference(ctx.subProject, ctx.service.subProject.FileReferenceType.file); await this.layout('file/filesjs.ejs', renderData, 'file/file_modal.ejs'); } catch (err) { ctx.log(err); } } async initializeFiling(ctx) { try { this.checkFilePermission(ctx, 'manage_dir'); const data = JSON.parse(ctx.request.body.data); if (!data || !data.init_type) throw '请选择初始化方式'; if (data.init_type === 'template') throw '模板库初始化功能暂未开放'; let sourceData = []; if (data.init_type === 'project') { if (!data.source_spid) throw '请选择来源项目'; const sourceProjects = await ctx.service.subProject.getManageDirProjects( ctx.session.sessionProject.id, ctx.session.sessionUser.accountId, this.isAdmin(ctx), ctx.subProject.id ); const sourceProject = sourceProjects.find(project => { return String(project.id) === String(data.source_spid); }); if (!sourceProject) throw '来源项目不存在或您没有管理目录权限'; sourceData = await ctx.service.filing.getValidFiling(sourceProject.id, 'all'); } const result = await ctx.service.filing.initializeDirectory( ctx.subProject.id, data.init_type, sourceData, ctx.session.sessionUser.accountId ); ctx.body = { err: 0, msg: '', data: result }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '初始化资料目录失败'); } } async configDir(ctx) { try { const canManageDir = this.hasFilePermission(ctx, 'manage_dir'); const canAuthUser = this.hasFilePermission(ctx, 'auth_user'); if (!canManageDir && !canAuthUser) throw '您无权查看配置目录'; const renderData = { jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.file.config_dir), }; renderData.canManageDir = canManageDir; renderData.canAuthUser = canAuthUser; renderData.filing = await ctx.service.filing.getValidFiling(ctx.params.id, 'all'); await this.fillFilingAddUserNames(ctx, renderData.filing); renderData.permissionData = renderData.canAuthUser ? await ctx.service.subProjPermission.getPermission(ctx.params.id) : []; renderData.filingPermissionData = renderData.canAuthUser ? await ctx.service.subProjectFilingPermission.getConfigPermissionRows(ctx.params.id, renderData.filing) : []; if (renderData.canAuthUser) { await this.fillFilingPermissionCreatorNames( ctx, renderData.filing, renderData.filingPermissionData ); } renderData.accountList = renderData.canAuthUser ? await ctx.service.subProjDataRange.getSelectableAccounts(ctx.subProject, 'file') : []; await this.layout('file/config_dir.ejs', renderData); } catch (err) { ctx.log(err); ctx.session.postError = err.toString(); ctx.redirect(this.menu.menu.dashboard.url); } } async configDirLock(ctx) { const redirectUrl = `/sp/${ctx.subProject.id}/config-dir`; try { this.checkFilePermission(ctx, 'manage_dir'); const lock = Number(ctx.request.body.lock); if (lock !== 0 && lock !== 1) throw '锁定状态参数错误'; await ctx.service.subProject.save({ id: ctx.subProject.id, lock_file: lock }); ctx.redirect(redirectUrl); } catch (err) { ctx.log(err); ctx.postError(err, '资料管理锁定状态修改失败'); ctx.redirect(redirectUrl); } } async configDirUpdate(ctx) { try { this.checkFilePermission(ctx, 'manage_dir'); this.checkLock(ctx); const data = JSON.parse(ctx.request.body.data); const result = await this.updateFiling(ctx, data); if (result && result.create) await this.fillFilingAddUserNames(ctx, result.create); ctx.body = { err: 0, msg: '', data: result }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '修改失败'); } } async getFilingNodePermission(ctx) { try { this.checkFilePermission(ctx, 'auth_user'); const filingId = ctx.request.body.filing_id; const filing = await this.getProjectFiling(ctx, filingId); const selectableAccounts = await ctx.service.subProjDataRange.getSelectableAccounts(ctx.subProject, 'file'); const projectAccounts = await ctx.service.subProjPermission.getPermission(ctx.subProject.id); const permissionRows = await ctx.service.subProjectFilingPermission.getConfigPermissionRows( ctx.subProject.id, [filing] ); const authorizedIds = permissionRows.filter(row => { return String(row.filing_id || '') === String(filing.id); }).map(row => Number(row.uid)); const accountMap = {}; projectAccounts.forEach(permission => { const userId = Number(permission.uid); accountMap[userId] = { id: userId, name: permission.name, company: permission.company, role: permission.role, }; }); selectableAccounts.forEach(account => { accountMap[Number(account.id)] = account; }); const authorizedUsers = authorizedIds.map(uid => accountMap[uid]).filter(Boolean).map(account => { return { id: Number(account.id), name: account.name, company: account.company, role: account.role, }; }).filter((account, index, list) => { return list.findIndex(item => Number(item.id) === Number(account.id)) === index; }); /* * 下拉选择仍只使用数据范围内账号;这里额外合并项目账号, * 是为了让已经存在的旧授权不会因数据范围调整而从弹窗消失。 */ const selectableIds = selectableAccounts.map(account => Number(account.id)); authorizedUsers.forEach(account => { account.selectable = selectableIds.indexOf(Number(account.id)) >= 0; }); ctx.body = { err: 0, msg: '', data: authorizedUsers }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '获取授权用户失败'); } } async saveFilingNodePermission(ctx) { try { this.checkFilePermission(ctx, 'auth_user'); this.checkLock(ctx); const data = JSON.parse(ctx.request.body.data); if (!data || !data.filing_id) throw '请选择需要授权的资料目录'; if (!(data.user_permissions instanceof Array)) throw '授权用户权限格式错误'; if (data.user_permissions.find(x => !x || typeof x !== 'object')) throw '授权用户权限格式错误'; const filing = await this.getProjectFiling(ctx, data.filing_id); const allFiling = await ctx.service.filing.getValidFiling(ctx.subProject.id, 'all'); const filingMap = {}; allFiling.forEach(item => { filingMap[String(item.id)] = item; }); const ancestorIds = []; let parentId = String(filing.tree_pid); while (parentId !== '-1') { const parent = filingMap[parentId]; if (!parent) break; ancestorIds.push(String(parent.id)); parentId = String(parent.tree_pid); } if (ancestorIds.length > 0) { const ancestorPermissionRows = await ctx.service.subProjectFilingPermission.getAllDataByCondition({ columns: ['filing_id'], where: { spid: ctx.subProject.id, filing_id: ancestorIds }, limit: 1, }); if (ancestorPermissionRows.length > 0) throw '父级目录已授权,当前目录不能单独授权'; } const userPermissions = data.user_permissions; const requestedUserIds = userPermissions.map(x => Number(x.uid !== undefined ? x.uid : x.id)); if (requestedUserIds.find(uid => !Number.isInteger(uid) || uid <= 0)) throw '授权用户数据错误'; if (new Set(requestedUserIds).size !== requestedUserIds.length) throw '授权用户数据重复'; const selectableAccounts = await ctx.service.subProjDataRange.getSelectableAccounts(ctx.subProject, 'file'); const selectableIds = selectableAccounts.map(account => Number(account.id)); const currentPermissionRows = await ctx.service.subProjectFilingPermission.getConfigPermissionRows( ctx.subProject.id, [filing] ); const currentUserIds = currentPermissionRows.filter(row => { return String(row.filing_id || '') === String(filing.id); }).map(row => Number(row.uid)); const invalidUserId = requestedUserIds.find(uid => { return selectableIds.indexOf(uid) < 0 && currentUserIds.indexOf(uid) < 0; }); if (invalidUserId) throw '选择的用户超出资料管理数据范围'; const result = await ctx.service.subProjectFilingPermission.savePermissionsToSubtree( ctx.subProject, filing, allFiling, userPermissions, ctx.session.sessionUser.accountId ); const savedPermissionRows = await ctx.service.subProjectFilingPermission.getRows(ctx.subProject.id); await this.fillFilingAddUserNames(ctx, result.filings); await this.fillFilingPermissionCreatorNames(ctx, result.filings, savedPermissionRows); ctx.body = { err: 0, msg: '', data: { permissions: result.permissions, filings: result.filings.map(item => ({ id: item.id, add_user: item.add_user || '', })), }, }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '保存授权用户失败'); } } async addFilingNodePermissions(ctx) { await this._saveFilingNodePermissionsToOther(ctx, false); } async coverFilingNodePermissions(ctx) { await this._saveFilingNodePermissionsToOther(ctx, true); } async _saveFilingNodePermissionsToOther(ctx, replaceExisting) { try { this.checkFilePermission(ctx, 'auth_user'); this.checkLock(ctx); const data = JSON.parse(ctx.request.body.data); if (!data || !data.source_filing_id) throw '请选择来源资料目录'; if (!(data.target_filing_ids instanceof Array) || data.target_filing_ids.length === 0) { throw '请选择目标资料目录'; } if (data.target_filing_ids.length > 500) throw '一次最多选择500个目标资料目录'; if (!(data.user_permissions instanceof Array) || data.user_permissions.length === 0) { throw '请选择需要添加的授权用户'; } if (data.user_permissions.find(x => !x || typeof x !== 'object')) { throw '授权用户权限格式错误'; } const sourceFiling = await this.getProjectFiling(ctx, data.source_filing_id); const targetFilingIds = data.target_filing_ids.map(id => String(id || '').trim()); if (targetFilingIds.find(id => !id)) throw '目标资料目录数据错误'; if (new Set(targetFilingIds).size !== targetFilingIds.length) throw '目标资料目录重复'; if (targetFilingIds.indexOf(String(sourceFiling.id)) >= 0) throw '当前目录不能作为目标目录'; const targetFilingRows = await ctx.service.filing.getAllDataByCondition({ where: { id: targetFilingIds, spid: ctx.subProject.id, is_deleted: 0, }, }); const targetFilingMap = {}; targetFilingRows.forEach(filing => { targetFilingMap[String(filing.id)] = filing; }); if (targetFilingIds.find(filingId => !targetFilingMap[filingId])) throw '目标资料目录不存在'; const targetFilings = targetFilingIds.map(filingId => targetFilingMap[filingId]); const userPermissions = data.user_permissions; const requestedUserIds = userPermissions.map(x => Number(x.uid !== undefined ? x.uid : x.id)); if (requestedUserIds.find(uid => !Number.isInteger(uid) || uid <= 0)) throw '授权用户数据错误'; if (new Set(requestedUserIds).size !== requestedUserIds.length) throw '授权用户数据重复'; const selectableAccounts = await ctx.service.subProjDataRange.getSelectableAccounts(ctx.subProject, 'file'); const selectableIds = selectableAccounts.map(account => Number(account.id)); const sourcePermissionRows = await ctx.service.subProjectFilingPermission.getConfigPermissionRows( ctx.subProject.id, [sourceFiling] ); const sourceUserIds = sourcePermissionRows.filter(row => { return String(row.filing_id || '') === String(sourceFiling.id); }).map(row => Number(row.uid)); const invalidUserId = requestedUserIds.find(uid => { return selectableIds.indexOf(uid) < 0 && sourceUserIds.indexOf(uid) < 0; }); if (invalidUserId) throw '选择的用户超出资料管理数据范围'; const permissionService = ctx.service.subProjectFilingPermission; if (replaceExisting) { await permissionService.coverPermissionsToFilings( ctx.subProject, targetFilings, userPermissions, ctx.session.sessionUser.accountId ); } else { await permissionService.addPermissionsToFilings( ctx.subProject, targetFilings, userPermissions, ctx.session.sessionUser.accountId ); } const savedPermissionRows = await ctx.service.subProjectFilingPermission.getRows(ctx.subProject.id); const targetPermissionRows = await ctx.service.subProjectFilingPermission.getConfigPermissionRows( ctx.subProject.id, targetFilings ); await this.fillFilingAddUserNames(ctx, targetFilings); await this.fillFilingPermissionCreatorNames(ctx, targetFilings, savedPermissionRows); ctx.body = { err: 0, msg: '', data: { permissions: targetPermissionRows, filings: targetFilings.map(filing => ({ id: filing.id, add_user: filing.add_user || '', })), }, }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, replaceExisting ? '覆盖授权用户至其他目录失败' : '添加授权用户至其他目录失败'); } } async getFilingTypePermission(ctx) { try { if (ctx.subProject.project_id !== this.ctx.session.sessionProject.id) throw '您无权操作该数据'; const filingType = await ctx.service.subProjPermission.getFilingType(ctx.subProject.id); ctx.body = { err: 0, msg: '', data: filingType }; } catch(err) { ctx.log(err); ctx.ajaxErrorBody(err, '获取授权用户数据错误'); } } async saveFilingTypePermission(ctx) { try { const data = JSON.parse(ctx.request.body.data); await ctx.service.subProjPermission.saveFilingType(data); ctx.body = { err: 0, msg: '', data: '' }; } catch(err) { ctx.log(err); ctx.ajaxErrorBody(err, '保存授权用户信息错误'); } } async addFiling(ctx) { try { this.checkUnlock(ctx); const data = JSON.parse(ctx.request.body.data); const filing = await this.getFilingFromDirectoryData(ctx, data); await this.checkFilingOperation(ctx, filing, 'can_edit_dir'); const result = await ctx.service.filing.add(data); ctx.body = { err: 0, msg: '', data: result }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '新增分类失败'); } } async delFiling(ctx) { try { this.checkUnlock(ctx); const data = JSON.parse(ctx.request.body.data); const filing = await this.getFilingFromDirectoryData(ctx, data); await this.checkFilingOperation(ctx, filing, 'can_edit_dir'); const result = await ctx.service.filing.del(data); ctx.body = { err: 0, msg: '', data: result }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '删除分类失败'); } } async saveFiling(ctx) { try { this.checkUnlock(ctx); const data = JSON.parse(ctx.request.body.data); const filing = await this.getFilingFromDirectoryData(ctx, data); await this.checkFilingOperation(ctx, filing, 'can_edit_dir'); const result = await ctx.service.filing.save(data); ctx.body = { err: 0, msg: '', data: result }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '保存分类数据失败'); } } async moveFiling(ctx) { try { this.checkUnlock(ctx); const data = JSON.parse(ctx.request.body.data); if (!data.id || !(data.tree_order >= 0)) throw '数据错误'; const filing = await this.getFilingFromDirectoryData(ctx, data); await this.checkFilingOperation(ctx, filing, 'can_edit_dir'); const result = await ctx.service.filing.move(data); ctx.body = { err: 0, msg: '', data: result }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '移动分类失败'); } } async loadFile(ctx) { try { const data = JSON.parse(ctx.request.body.data); const filing = await this.getProjectFiling(ctx, data.filing_id); await this.checkFilingView(ctx, filing); const order = data.order.split('|'); if (order.length !== 2) throw '加载文件错误'; if (order[0] !== 'filename' && order[0] !== 'create_time') throw '加载文件错误'; if (order[1] !== 'asc' && order[1] !== 'desc') throw '加载文件错误'; const result = await ctx.service.file.getFiles({ where: { filing_id: data.filing_id, is_deleted: 0 }, orders: [order], limit: data.count, offset: (data.page-1)*data.count, }, order); ctx.body = { err: 0, msg: '', data: result }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '加载文件失败'); } } async checkCanUpload(ctx, filing) { this.checkUnlock(ctx); await this.checkFilingOperation(ctx, filing, 'can_upload'); } async checkFiling(filing) { const child = await this.ctx.service.filing.getDataByCondition({ tree_pid: filing.id, is_deleted: 0 }); if (child) throw '该分类下存在子分类,请在子分类下上传、导入文件'; } async checkFiles(ctx) { try{ const data = JSON.parse(ctx.request.body.data); if (!data.filing_id || !data.files) throw '缺少参数'; const filing = await this.getProjectFiling(ctx, data.filing_id); await this.checkCanUpload(ctx, filing); const result = await ctx.service.file.checkFiles(data.filing_id, data.files); ctx.body = { err: 0, msg: '', data: result }; } catch(error) { this.log(error); ctx.ajaxErrorBody(error, '检查附件错误'); } } async uploadFile(ctx){ let stream; try { const parts = ctx.multipart({ autoFields: true, checkFile: this.getFileUploadCheck(ctx), }); let index = 0; const create_time = Date.parse(new Date()) / 1000; stream = await parts(); const user = await ctx. service.projectAccount.getDataById(ctx.session.sessionUser.accountId); const filing = await this.getProjectFiling(ctx, parts.field.filing_id); await this.checkCanUpload(ctx, filing); await this.checkFiling(filing); const uploadfiles = []; while (stream !== undefined) { if (!stream.filename) throw '未发现上传文件!'; const fileInfo = path.parse(stream.filename); const filepath = `sp/file/${filing.spid}/${ctx.moment().format('YYYYMMDD')}/${create_time + '_' + index + fileInfo.ext}`; // 保存文件 await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream); await sendToWormhole(stream); // 插入到stage_pay对应的附件列表中 uploadfiles.push({ filename: fileInfo.name, fileext: fileInfo.ext, filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size, filepath, }); ++index; if (Array.isArray(parts.field.size) && index < parts.field.size.length) { stream = await parts(); } else { stream = undefined; } } const result = await ctx.service.file.addFiles(filing, uploadfiles, user); ctx.body = {err: 0, msg: '', data: result }; } catch (error) { ctx.helper.log(error); // 失败需要消耗掉stream 以防卡死 if (stream) await sendToWormhole(stream); ctx.body = this.ajaxErrorBody(error, '上传附件失败,请重试'); } } async delFile(ctx) { try{ this.checkUnlock(ctx); const data = JSON.parse(ctx.request.body.data); if (!data.del) throw '缺少参数'; const result = await ctx.service.file.delFiles(data.del); ctx.body = { err: 0, msg: '', data: result }; } catch(error) { this.log(error); ctx.ajaxErrorBody(error, '删除附件失败'); } } async saveFile(ctx) { try { this.checkUnlock(ctx); const data = JSON.parse(ctx.request.body.data); if (!data.id) throw '缺少参数'; const result = await ctx.service.file.saveFile(data.id, data.filename); ctx.body = { err: 0, msg: '', data: result }; } catch (error) { this.log(error); ctx.ajaxErrorBody(error, '编辑附件失败'); } } async lockFile(ctx) { try { this.checkUnlock(ctx); const data = JSON.parse(ctx.request.body.data); if (!data || !data.id) throw '缺少参数'; const result = await ctx.service.file.setLocked(data.id, data.is_locked); ctx.body = { err: 0, msg: '', data: result }; } catch (error) { this.log(error); ctx.ajaxErrorBody(error, '修改文件锁定状态失败'); } } async moveFile(ctx) { try { this.checkUnlock(ctx); const data = JSON.parse(ctx.request.body.data); if (!data.id || !data.filingId) throw '缺少参数'; const targetFiling = await this.getProjectFiling(ctx, data.filingId); await this.checkFilingOperation(ctx, targetFiling, 'can_upload'); const result = await ctx.service.file.moveFile(data.id, data.filingId); ctx.body = { err: 0, msg: '', data: result }; } catch (error) { this.log(error); ctx.ajaxErrorBody(error, '编辑附件失败'); } } async uploadBigFile(ctx) { try { const data = JSON.parse(ctx.request.body.data); if (!data.type || !data.filing_id || !data.fileInfo) throw '缺少参数'; const filing = await this.getProjectFiling(ctx, data.filing_id); await this.checkCanUpload(ctx, filing); let result; const fileInfo = path.parse(data.fileInfo.filename); if (!this.isFileUploadExtensionAllowed(ctx, data.fileInfo.filename)) { throw `资料管理不支持${fileInfo.ext || '无扩展名'}格式文件`; } switch(data.type) { case 'begin': const create_time = Date.parse(new Date()) / 1000; result = { filename: `sp/file/${filing.spid}/${ctx.moment().format('YYYYMMDD')}/${create_time + '_' + fileInfo.ext}`, }; result.filepath = ctx.app.config.fujianOssFolder + result.filename; // todo 写入ossToken result.oss = await ctx.helper.getOssToken(ctx.app.fujianOss); break; case 'end': const user = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId); const uploadFiles = [{ filepath: data.filepath, filename: fileInfo.name, fileext: fileInfo.ext, filesize: data.fileInfo.filesize, }]; result = await ctx.service.file.addFiles(filing, uploadFiles, user); break; } ctx.body = {err: 0, msg: '', data: result }; } catch (error) { ctx.log(error); ctx.body = this.ajaxErrorBody(error, '上传附件失败,请重试'); } } async loadValidRelaTender(ctx) { try { const data = JSON.parse(ctx.request.body.data); if (data.type) throw '参数错误'; const accountInfo = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId); const userPermission = accountInfo !== undefined && accountInfo.permission !== '' ? JSON.parse(accountInfo.permission) : null; const tenders = await ctx.service.tender.getList('', userPermission, ctx.session.sessionUser.is_admin); for (const r of tenders) { r.advance = await ctx.service.advance.getAllDataByCondition({ columns: ['id', 'order', 'type'], where: { tid: r.id }}); r.advance.forEach(a => { const type = advanceConst.typeCol.find(x => { return x.type === a.type }); if (type) a.type_str = type.name; }); r.stage = await ctx.service.stage.getAllDataByCondition({ columns: ['id', 'order'], where: { tid: r.id, status: auditConst.stage.status.checked } }); r.change = await ctx.service.change.getAllDataByCondition({ columns: ['cid', 'code'], where: { tid: r.id, status: auditConst.flow.status.checked }, orders: [['in_time', 'asc']] }); r.change_apply = await ctx.service.changeApply.getAllDataByCondition({ columns: ['id', 'code'], where: { tid: r.id, status: auditConst.flow.status.checked } }); r.change_plan = await ctx.service.changePlan.getAllDataByCondition({ columns: ['id', 'code'], where: { tid: r.id, status: auditConst.flow.status.checked } }); r.change_project = await ctx.service.changeProject.getAllDataByCondition({ columns: ['id', 'code'], where: { tid: r.id, status: auditConst.flow.status.checked } }); } const category = await this.ctx.service.category.getAllCategory(ctx.subProject); ctx.body = {err: 0, msg: '', data: { category, tenders, selfCategoryLevel: this.ctx.subProject.permission.self_category_level} }; } catch (error) { ctx.helper.log(error); ctx.body = this.ajaxErrorBody(error, '加载标段信息失败'); } } async _loadLedgerAtt(data) { if (!data.tender_id) throw '参数错误'; return await this.ctx.service.ledgerAtt.getAllDataByCondition({ where: { tid: data.tender_id }, order: [['id', 'desc']]}); } async _loadStageAtt(data) { if (!data.tender_id || !data.stage || !data.sub_type) throw '参数错误'; const stage = await this.ctx.service.stage.getDataById(data.stage); switch (data.sub_type) { case 'att': return await this.ctx.service.stageAtt.getAllDataByCondition({ where: { tid: data.tender_id, sid: stage.order }, orders: [['id', 'desc']]}); case 'dealPay': const payAtt = await this.ctx.service.payAtt.getAllDataByCondition({ where: { sid: stage.id}, orders: [['id', 'desc']] }); return payAtt; case 'stageIm': const imFiles = []; const stageIm = await this.ctx.service.stageDetailAtt.getAllDataByCondition({ where: { sid: stage.id} }); stageIm.forEach(x => { x.attachment = x.attachment ? JSON.parse(x.attachment) : []; if (x.attachment.length > 0) imFiles.push(...x.attachment); }); return imFiles; } } async _loadAdvanceAtt(data) { if (!data.stage) throw '参数错误'; const self = this; const result = await this.ctx.service.advanceFile.getAllDataByCondition({ where: { vid: data.stage }, order: [['id', 'desc']]}); result.forEach(x => { const info = path.parse(x.filename); x.filename = info.name; x.filesize = self.ctx.helper.sizeToBytes(x.filesize); }); return result; } async _loadChangeAtt(data) { if (!data.selectId) throw '参数错误'; const result = await this.ctx.service.changeAtt.getAllDataByCondition({ where: { cid: data.selectId }, order: [['id', 'desc']]}); return result; } async _loadChangePlanAtt(data) { if (!data.selectId) throw '参数错误'; const self = this; const result = await this.ctx.service.changePlanAtt.getAllDataByCondition({ where: { cpid: data.selectId }, order: [['id', 'desc']]}); result.forEach(x => { const info = path.parse(x.filename); x.filename = info.name; x.filesize = self.ctx.helper.sizeToBytes(x.filesize); }); return result; } async _loadChangeProjectAtt(data) { if (!data.selectId) throw '参数错误'; const self = this; const result = await this.ctx.service.changeProjectAtt.getAllDataByCondition({ where: { cpid: data.selectId }, order: [['id', 'desc']]}); result.forEach(x => { const info = path.parse(x.filename); x.filename = info.name; x.filesize = self.ctx.helper.sizeToBytes(x.filesize); }); return result; } async _loadChangeApplyAtt(data) { if (!data.selectId) throw '参数错误'; const self = this; const result = await this.ctx.service.changeApplyAtt.getAllDataByCondition({ where: { caid: data.selectId }, order: [['id', 'desc']]}); result.forEach(x => { const info = path.parse(x.filename); x.filename = info.name; x.filesize = self.ctx.helper.sizeToBytes(x.filesize); }); return result; } async loadRelaFiles(ctx) { try { const data = JSON.parse(ctx.request.body.data); if (!data.type) throw '参数错误'; let files; switch(data.type) { case 'ledger': files = await this._loadLedgerAtt(data); break; case 'stage': files = await this._loadStageAtt(data); break; case 'advance': files = await this._loadAdvanceAtt(data); break; case 'change': files = await this._loadChangeAtt(data); break; case 'change_plan': files = await this._loadChangePlanAtt(data); break; case 'change_project': files = await this._loadChangeProjectAtt(data); break; case 'change_apply': files = await this._loadChangeApplyAtt(data); break; default: throw '未知文件类型'; } ctx.body = {err: 0, msg: '', data: files }; } catch (error) { ctx.helper.log(error); ctx.body = this.ajaxErrorBody(error, '加载附件失败,请重试'); } } async relaFile(ctx) { try { const data = JSON.parse(ctx.request.body.data); if (!data.filing_id || !data.files) throw '缺少参数'; const user = await ctx. service.projectAccount.getDataById(ctx.session.sessionUser.accountId); const filing = await this.getProjectFiling(ctx, data.filing_id); await this.checkCanUpload(ctx, filing); await this.checkFiling(filing); const result = await ctx.service.file.relaFiles(filing, data.files, user); ctx.body = {err: 0, msg: '', data: result }; } catch (error) { ctx.helper.log(error); ctx.body = this.ajaxErrorBody(error, '导入附件失败,请重试'); } } async template(ctx) { const defaultTemplate = await ctx.service.filingTemplateList.getOriginTemplate(); ctx.redirect('/file/template/' + defaultTemplate.id); } async templateDetail(ctx) { try { const renderData = { jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.file.template), }; renderData.templateList = await ctx.service.filingTemplateList.getAllTemplate(ctx.session.sessionProject.id); renderData.shareTemplate = await ctx.service.filingTemplateList.getShareTemplate(ctx.session.sessionProject.id); renderData.FtType = ctx.service.filingTemplateList.FtType; renderData.template = renderData.templateList.find(x => { return x.id === ctx.params.id }); if (!renderData.template) throw '查看的资料模板不存在'; renderData.templateData = await ctx.service.filingTemplate.getData(renderData.template.id); await this.layout('file/template.ejs', renderData, 'file/template_modal.ejs'); } catch (err) { ctx.log(err); ctx.session.postError = err.toString(); ctx.redirect(this.menu.menu.dashboard.url); } } async saveTemplate(ctx) { try { const id = ctx.query.id; const name = ctx.request.body.name; const is_share = ctx.request.body.is_share ? parseInt(ctx.request.body.is_share) : undefined; const share_id = ctx.request.body.share_id; const [save, templateId] = share_id ? await ctx.service.filingTemplateList.copy(share_id) : await ctx.service.filingTemplateList.save(name, is_share, id); if (!save) throw '保存数据失败'; ctx.redirect('/file/template/' + templateId); } catch(err) { ctx.log(err); ctx.session.postError = err.toString(); ctx.redirect('/file/template'); } } async resetTemplate(ctx) { try { const id = ctx.query.id; await ctx.service.filingTemplateList.reset(id); ctx.redirect('/file/template/' + id); } catch (err) { ctx.log(err); ctx.postError(err, '重置模板失败'); ctx.redirect('/file/template'); } } async delTemplate(ctx) { try { const id = ctx.query.id; await ctx.service.filingTemplateList.delete(id); if (ctx.request.headers.referer.indexOf(id) > 0) { ctx.redirect('/file/template'); } else { ctx.redirect(ctx.request.headers.referer); } } catch (err) { ctx.log(err); ctx.postError(err, '删除模板失败'); ctx.redirect('/file/template'); } } async updateTemplate(ctx) { try { const data = JSON.parse(ctx.request.body.data); if (!data.updateType) throw '数据错误'; let result; if (data.updateType === 'add') { result = await ctx.service.filingTemplate.add(ctx.params.id, data); } else if (data.updateType === 'del') { result = await ctx.service.filingTemplate.del(ctx.params.id, data); } else if (data.updateType === 'save') { result = await ctx.service.filingTemplate.save(data); } else if (data.updateType === 'move') { if (!data.id || !(data.tree_order >= 0)) throw '数据错误'; result = await ctx.service.filingTemplate.move(ctx.params.id, data); } else if (data.updateType === 'import') { result = await ctx.service.filingTemplate.import(ctx.params.id, data.data); } else if (data.updateType === 'multi' ) { result = await ctx.service.filingTemplate.multiUpdate(ctx.params.id, data.data); } ctx.body = { err: 0, msg: '', data: result }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '修改失败'); } } async search(ctx) { try { const limit = 1000; const data = JSON.parse(ctx.request.body.data); if (!data.keyword) throw '数据错误'; if (data.filing_id instanceof Array) { const filingIds = this.app._.uniq(data.filing_id.map(id => String(id || '')).filter(Boolean)); if (filingIds.length === 0) throw '数据错误'; const filings = await ctx.service.filing.getAllDataByCondition({ where: { id: filingIds, spid: ctx.subProject.id, is_deleted: 0 }, }); const permissionMap = await ctx.service.subProjectFilingPermission.getPermissionMap( ctx.subProject.id, ctx.session.sessionUser.accountId, filings, ctx.subProject.permission.filing_type, ctx.subProject.permission.file_permission, this.isAdmin(ctx) ); this.applyFileConfigViewPermission(ctx, permissionMap); const validFilingIds = filings.filter(filing => { return permissionMap[filing.id] && permissionMap[filing.id].can_view; }).map(filing => filing.id); const result = await ctx.service.file.searchByFilingIds(validFilingIds, data.keyword, limit); ctx.body = { err: 0, msg: '', data: { list: result, limit } }; return; } if (!data.filing_type) throw '数据错误'; const validFilingType = []; for (const f of data.filing_type) { const filingType = Number(f); if (!Number.isInteger(filingType) || filingType <= 0) continue; if (ctx.subProject.permission.filing_type === 'all' || ctx.subProject.permission.filing_type.indexOf(filingType) >= 0) validFilingType.push(filingType); } const result = await ctx.service.file.search(validFilingType, data.keyword, limit); ctx.body = { err: 0, msg: '', data: { list: result, limit } }; } catch(err) { ctx.log(err); ctx.ajaxErrorBody(err, '搜索文件失败'); } } async manage(ctx) { try { const renderData = { jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.file.manage), }; renderData.filingData = await ctx.service.filing.getValidFiling(ctx.params.id, ctx.subProject.permission.filing_type); const permissionData = await ctx.service.subProjPermission.getFilingType(ctx.subProject.id); permissionData.forEach(x => { x.filing_type = x.filing_type.split(','); }); renderData.filingData.forEach(x => { if (!x.is_fixed) { x.permission_count = 0; } else { const rela = permissionData.filter(y => { return y.filing_type.indexOf(x.filing_type + '') >= 0; }); x.permission_count = rela.length; } }); await this.layout('file/manage.ejs', renderData, 'file/manage_modal.ejs'); } catch (err) { ctx.log(err); ctx.session.postError = err.toString(); ctx.redirect(this.menu.menu.dashboard.url); } } async lockFiling(ctx) { try { await ctx.service.subProject.save({ id: ctx.subProject.id, lock_file: ctx.query.lock }); ctx.redirect(`/sp/${ctx.subProject.id}/fm`); } catch(err) { ctx.log(err); ctx.postError(err, '资料归集分类锁定错误'); ctx.redirect(`/sp/${ctx.subProject.id}/fm`); } } async manageUpdate(ctx) { try { this.checkLock(ctx); const data = JSON.parse(ctx.request.body.data); const result = await this.updateFiling(ctx, data); ctx.body = { err: 0, msg: '', data: result }; } catch (err) { ctx.log(err); ctx.ajaxErrorBody(err, '修改失败'); } } async updateFiling(ctx, data) { if (!data.updateType) throw '数据错误'; const updateData = JSON.parse(JSON.stringify(data)); delete updateData.updateType; if (data.updateType === 'add') { return await ctx.service.filing.add(updateData); } else if (data.updateType === 'del') { return await ctx.service.filing.del(updateData); } else if (data.updateType === 'save') { return await ctx.service.filing.save(updateData); } else if (data.updateType === 'edit') { return await ctx.service.filing.editDirectory(updateData); } else if (data.updateType === 'move') { if (!data.id || !(data.tree_order >= 0)) throw '数据错误'; return await ctx.service.filing.move(updateData); } else if (data.updateType === 'multi') { return await ctx.service.filing.multiUpdate(ctx.subProject.id, data.data); } throw '未知的修改类型'; } } return FileController; };