diff --git a/src/main/java/com/budwk/app/zhgh/archive/constant/ArchiveConstant.java b/src/main/java/com/budwk/app/zhgh/archive/constant/ArchiveConstant.java new file mode 100644 index 00000000..fe82916e --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/archive/constant/ArchiveConstant.java @@ -0,0 +1,62 @@ +package com.budwk.app.zhgh.archive.constant; + +import org.nutz.lang.Strings; + +import java.util.List; +import java.util.Map; + +/** + * 活动电子档案常量。 + */ +public final class ArchiveConstant { + + public static final int PROJECT_DRAFT = 0; + public static final int PROJECT_FILING = 1; + public static final int PROJECT_COMPLETED = 2; + + public static final int DOCUMENT_DRAFT = 0; + public static final int DOCUMENT_SUBMITTED = 1; + + public static final String STAGE_PREPARE = "PREPARE"; + public static final String STAGE_PROCESS = "PROCESS"; + public static final String STAGE_SUMMARY = "SUMMARY"; + + public static final Map STAGE_LABELS = Map.of( + STAGE_PREPARE, "筹备阶段", + STAGE_PROCESS, "实施阶段", + STAGE_SUMMARY, "总结阶段" + ); + + private ArchiveConstant() { + } + + public static boolean validStage(String stage) { + return Strings.isNotBlank(stage) && STAGE_LABELS.containsKey(stage); + } + + public static String stageLabel(String stage) { + return STAGE_LABELS.getOrDefault(stage, stage); + } + + public static String projectStatusLabel(Integer status) { + if (status != null && status == PROJECT_COMPLETED) { + return "已完成"; + } + if (status != null && status == PROJECT_FILING) { + return "归档中"; + } + return "草稿"; + } + + public static String documentStatusLabel(Integer status) { + return status != null && status == DOCUMENT_SUBMITTED ? "已提交归档" : "草稿"; + } + + public static List> stageOptions() { + return List.of( + Map.of("value", STAGE_PREPARE, "label", STAGE_LABELS.get(STAGE_PREPARE)), + Map.of("value", STAGE_PROCESS, "label", STAGE_LABELS.get(STAGE_PROCESS)), + Map.of("value", STAGE_SUMMARY, "label", STAGE_LABELS.get(STAGE_SUMMARY)) + ); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/archive/controller/ArchiveController.java b/src/main/java/com/budwk/app/zhgh/archive/controller/ArchiveController.java new file mode 100644 index 00000000..ec1be988 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/archive/controller/ArchiveController.java @@ -0,0 +1,221 @@ +package com.budwk.app.zhgh.archive.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.annotation.SLog; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.archive.constant.ArchiveConstant; +import com.budwk.app.zhgh.archive.model.ArchiveDocType; +import com.budwk.app.zhgh.archive.model.ArchiveDocument; +import com.budwk.app.zhgh.archive.model.ArchiveProject; +import com.budwk.app.zhgh.archive.service.ArchiveService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Strings; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +/** + * 活动电子档案。 + */ +@IocBean +@Ok("json:full") +@At("/platform/archive") +@Api(tags = "活动电子档案") +public class ArchiveController { + + @Inject + private ArchiveService archiveService; + + @Inject + private Dao dao; + + @At("/docType") + @Ok("beetl:/platform/zhgh/archive/docType/index.html") + @SaCheckPermission("archive.docType") + public void docTypeIndex() { + } + + @At("/docType/pageData") + @SaCheckPermission("archive.docType") + public Result docTypePageData(PageForm pageForm, String name, + String projectStage, Boolean requiredFlag) { + return Result.success(archiveService.pageDocTypes( + pageForm, name, projectStage, requiredFlag)); + } + + @At("/docType/listData") + public Result docTypeListData(String projectStage) { + return Result.success(archiveService.listDocTypes(projectStage)); + } + + @At("/docType/fetchOne") + @SaCheckPermission("archive.docType") + public Result docTypeFetchOne(String id) { + return Result.success(dao.fetch(ArchiveDocType.class, id)); + } + + @At("/docType/onSubmit") + @SLog(tag = "活动电子档案-文档类型", msg = "新增或修改文档类型") + @SaCheckPermission("archive.docType") + public Result docTypeOnSubmit(ArchiveDocType docType) { + try { + archiveService.saveDocType(docType); + return Result.success(); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } + } + + @At("/docType/onDelete") + @SLog(tag = "活动电子档案-文档类型", msg = "删除文档类型") + @SaCheckPermission("archive.docType") + public Result docTypeOnDelete(String id) { + try { + archiveService.deleteDocType(id); + return Result.success(); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } + } + + @At("/project") + @Ok("beetl:/platform/zhgh/archive/project/index.html") + @SaCheckPermission("archive.project") + public void projectIndex() { + } + + @At("/project/pageData") + @SaCheckPermission("archive.project") + public Result projectPageData(PageForm pageForm, Integer year, + String projectName, String unionId, Integer status) { + return Result.success(archiveService.pageProjects( + pageForm, year, projectName, unionId, status)); + } + + @At("/project/listData") + public Result projectListData() { + return Result.success(archiveService.listProjects()); + } + + @At("/project/fetchOne") + @SaCheckPermission("archive.project") + public Result projectFetchOne(String id) { + return Result.success(dao.fetch(ArchiveProject.class, id)); + } + + @At("/project/onSubmit") + @SLog(tag = "活动电子档案-归档项目", msg = "新增或修改归档项目") + @SaCheckPermission("archive.project") + public Result projectOnSubmit(ArchiveProject project) { + try { + archiveService.saveProject(project); + return Result.success(); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } + } + + @At("/project/onDelete") + @SLog(tag = "活动电子档案-归档项目", msg = "删除归档项目") + @SaCheckPermission("archive.project") + public Result projectOnDelete(String id) { + try { + archiveService.deleteProject(id); + return Result.success(); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } + } + + @At("/filing") + @Ok("beetl:/platform/zhgh/archive/filing/index.html") + @SaCheckPermission("archive.filing") + public void filingIndex() { + } + + @At("/filing/pageData") + @SaCheckPermission("archive.filing") + public Result filingPageData(PageForm pageForm, String projectId, Integer year, + String projectStage, String docTypeId, + String documentName, Integer status) { + return Result.success(archiveService.pageDocuments(pageForm, projectId, year, + projectStage, docTypeId, documentName, status)); + } + + @At("/filing/fetchOne") + @SaCheckPermission("archive.filing") + public Result filingFetchOne(String id) { + return Result.success(archiveService.fetchDocument(id)); + } + + @At("/filing/saveDraft") + @ApiOperation("保存归档文档草稿") + @SLog(tag = "活动电子档案-资料归档", msg = "保存归档文档草稿") + @SaCheckPermission("archive.filing") + public Result filingSaveDraft(ArchiveDocument document) { + return saveDocument(document, false); + } + + @At("/filing/submit") + @ApiOperation("提交归档文档") + @SLog(tag = "活动电子档案-资料归档", msg = "提交归档文档") + @SaCheckPermission("archive.filing") + public Result filingSubmit(ArchiveDocument document) { + return saveDocument(document, true); + } + + @At("/filing/onDelete") + @SLog(tag = "活动电子档案-资料归档", msg = "删除归档文档") + @SaCheckPermission("archive.filing") + public Result filingOnDelete(String id) { + archiveService.deleteDocument(id); + return Result.success(); + } + + @At("/ledger") + @Ok("beetl:/platform/zhgh/archive/ledger/index.html") + @SaCheckPermission("archive.ledger") + public void ledgerIndex() { + } + + @At("/ledger/pageData") + @SaCheckPermission("archive.ledger") + public Result ledgerPageData(PageForm pageForm, Integer year, + String projectName, String unionId, Integer status) { + return Result.success(archiveService.pageProjects( + pageForm, year, projectName, unionId, status)); + } + + @At("/ledger/detail") + @SaCheckPermission("archive.ledger") + public Result ledgerDetail(PageForm pageForm, String projectId, + String projectStage, String docTypeId, + String documentName, Integer status) { + return Result.success(archiveService.pageDocuments(pageForm, projectId, + null, projectStage, docTypeId, documentName, status)); + } + + @At("/common/stageOptions") + public Result stageOptions() { + return Result.success(ArchiveConstant.stageOptions()); + } + + @At("/common/unionOptions") + public Result unionOptions() { + return Result.success(archiveService.listUnions()); + } + + private Result saveDocument(ArchiveDocument document, boolean submit) { + try { + archiveService.saveDocument(document, submit); + return Result.success(); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/archive/model/ArchiveDocType.java b/src/main/java/com/budwk/app/zhgh/archive/model/ArchiveDocType.java new file mode 100644 index 00000000..79e36270 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/archive/model/ArchiveDocType.java @@ -0,0 +1,55 @@ +package com.budwk.app.zhgh.archive.model; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.interceptor.annotation.PrevInsert; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.entity.annotation.TableMeta; + +/** + * 活动电子档案文档类型。 + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@Table("act_archive_doc_type") +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("活动电子档案文档类型") +public class ArchiveDocType extends BaseModel { + + @Name + @PrevInsert(uu32 = true) + @ColDefine(type = ColType.VARCHAR, width = 32) + @Comment("主键") + private String id; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 150) + @Comment("类型名称") + private String name; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 32) + @Comment("项目阶段") + private String projectStage; + + @Column + @Comment("排序编号") + private Integer sortNo; + + @Column + @Comment("是否必选") + private Boolean requiredFlag; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 600) + @Comment("备注") + private String remark; +} diff --git a/src/main/java/com/budwk/app/zhgh/archive/model/ArchiveDocument.java b/src/main/java/com/budwk/app/zhgh/archive/model/ArchiveDocument.java new file mode 100644 index 00000000..df0fea9e --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/archive/model/ArchiveDocument.java @@ -0,0 +1,78 @@ +package com.budwk.app.zhgh.archive.model; + +import cn.hutool.json.JSONObject; +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.interceptor.annotation.PrevInsert; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.entity.annotation.TableMeta; + +import java.util.List; + +/** + * 活动电子档案归档文档。 + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@Table("act_archive_document") +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("活动电子档案归档文档") +public class ArchiveDocument extends BaseModel { + + @Name + @PrevInsert(uu32 = true) + @ColDefine(type = ColType.VARCHAR, width = 32) + @Comment("主键") + private String id; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 32) + @Comment("项目ID") + private String projectId; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 300) + @Comment("文档名称") + private String documentName; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 32) + @Comment("项目阶段") + private String projectStage; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 32) + @Comment("文档类型ID") + private String docTypeId; + + @Column + @Comment("文档状态") + private Integer status; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 100) + @Comment("上传人") + private String uploader; + + @Column + @Comment("上传时间") + private Long uploadTime; + + @Column + @ColDefine(type = ColType.MYSQL_JSON) + @Comment("附件") + private List files; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 600) + @Comment("备注") + private String remark; +} diff --git a/src/main/java/com/budwk/app/zhgh/archive/model/ArchiveProject.java b/src/main/java/com/budwk/app/zhgh/archive/model/ArchiveProject.java new file mode 100644 index 00000000..3601137e --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/archive/model/ArchiveProject.java @@ -0,0 +1,55 @@ +package com.budwk.app.zhgh.archive.model; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.interceptor.annotation.PrevInsert; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.entity.annotation.TableMeta; + +/** + * 活动电子档案项目。 + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@Table("act_archive_project") +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("活动电子档案项目") +public class ArchiveProject extends BaseModel { + + @Name + @PrevInsert(uu32 = true) + @ColDefine(type = ColType.VARCHAR, width = 32) + @Comment("主键") + private String id; + + @Column + @Comment("年度") + private Integer year; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 300) + @Comment("项目名称") + private String projectName; + + @Column + @Comment("项目状态") + private Integer status; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 32) + @Comment("所属工会ID") + private String unionId; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 150) + @Comment("所属工会名称") + private String unionName; +} diff --git a/src/main/java/com/budwk/app/zhgh/archive/service/ArchiveService.java b/src/main/java/com/budwk/app/zhgh/archive/service/ArchiveService.java new file mode 100644 index 00000000..19b9a4a6 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/archive/service/ArchiveService.java @@ -0,0 +1,406 @@ +package com.budwk.app.zhgh.archive.service; + +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.sys.models.Sys_union; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.archive.constant.ArchiveConstant; +import com.budwk.app.zhgh.archive.model.ArchiveDocType; +import com.budwk.app.zhgh.archive.model.ArchiveDocument; +import com.budwk.app.zhgh.archive.model.ArchiveProject; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Chain; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 活动电子档案领域服务。 + */ +@IocBean(args = {"refer:dao"}) +public class ArchiveService extends BaseServiceImpl { + + public ArchiveService(Dao dao) { + super(dao); + } + + public Pagination pageDocTypes(PageForm pageForm, String name, + String projectStage, Boolean requiredFlag) { + Cnd cnd = Cnd.NEW(); + cnd.and(Cnd.likeEX("adt.name", name)); + cnd.andEX("adt.projectStage", "=", projectStage); + cnd.andEX("adt.requiredFlag", "=", requiredFlag); + cnd.and("adt.delFlag", "=", false); + cnd.asc("adt.projectStage").asc("adt.sortNo").asc("adt.createdAt"); + Sql sql = Sqls.create(""" + SELECT adt.*, + (SELECT COUNT(1) + FROM act_archive_document ad + WHERE ad.docTypeId = adt.id + AND ad.delFlag = 0) AS documentCount + FROM act_archive_doc_type adt + $condition + """); + sql.setCondition(cnd); + Pagination page = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + page.getList().forEach(this::enrichDocType); + return page; + } + + public List listDocTypes(String projectStage) { + Cnd cnd = Cnd.where("adt.delFlag", "=", false); + cnd.andEX("adt.projectStage", "=", projectStage); + cnd.asc("adt.projectStage").asc("adt.sortNo").asc("adt.createdAt"); + Sql sql = Sqls.create(""" + SELECT adt.*, + (SELECT COUNT(1) + FROM act_archive_document ad + WHERE ad.docTypeId = adt.id + AND ad.delFlag = 0) AS documentCount + FROM act_archive_doc_type adt + $condition + """); + sql.setCondition(cnd); + List list = listMap(sql); + list.forEach(this::enrichDocType); + return list; + } + + @Aop(TransAop.READ_COMMITTED) + public void saveDocType(ArchiveDocType docType) { + if (docType == null || Strings.isBlank(docType.getName())) { + throw new IllegalArgumentException("类型名称不能为空"); + } + if (!ArchiveConstant.validStage(docType.getProjectStage())) { + throw new IllegalArgumentException("项目阶段不正确"); + } + docType.setName(docType.getName().trim()); + if (docType.getSortNo() == null) { + docType.setSortNo(0); + } + if (docType.getRequiredFlag() == null) { + docType.setRequiredFlag(false); + } + Cnd duplicate = Cnd.where("projectStage", "=", docType.getProjectStage()) + .and("name", "=", docType.getName()) + .and("delFlag", "=", false); + if (Strings.isNotBlank(docType.getId())) { + duplicate.and("id", "<>", docType.getId()); + } + if (dao().count(ArchiveDocType.class, duplicate) > 0) { + throw new IllegalArgumentException("同一项目阶段下类型名称不能重复"); + } + if (Strings.isBlank(docType.getId())) { + dao().insert(docType); + } else { + ArchiveDocType old = dao().fetch(ArchiveDocType.class, docType.getId()); + if (old == null) { + throw new IllegalArgumentException("文档类型不存在"); + } + dao().updateIgnoreNull(docType); + } + } + + @Aop(TransAop.READ_COMMITTED) + public void deleteDocType(String id) { + if (Strings.isBlank(id)) { + return; + } + if (dao().count(ArchiveDocument.class, + Cnd.where("docTypeId", "=", id).and("delFlag", "=", false)) > 0) { + throw new IllegalArgumentException("文档类型已被归档文档引用,不能删除"); + } + dao().delete(ArchiveDocType.class, id); + } + + public Pagination pageProjects(PageForm pageForm, Integer year, + String projectName, String unionId, Integer status) { + Cnd cnd = Cnd.NEW(); + cnd.andEX("ap.year", "=", year); + cnd.and(Cnd.likeEX("ap.projectName", projectName)); + cnd.andEX("ap.unionId", "=", unionId); + cnd.andEX("ap.status", "=", status); + cnd.and("ap.delFlag", "=", false); + cnd.desc("ap.year").desc("ap.createdAt"); + Sql sql = Sqls.create(""" + SELECT ap.*, + (SELECT COUNT(1) + FROM act_archive_document ad + WHERE ad.projectId = ap.id + AND ad.delFlag = 0) AS documentCount + FROM act_archive_project ap + $condition + """); + sql.setCondition(cnd); + Pagination page = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + page.getList().forEach(this::enrichProject); + return page; + } + + public List listProjects() { + Cnd cnd = Cnd.where("ap.delFlag", "=", false); + cnd.desc("ap.year").desc("ap.createdAt"); + Sql sql = Sqls.create(""" + SELECT ap.*, + (SELECT COUNT(1) + FROM act_archive_document ad + WHERE ad.projectId = ap.id + AND ad.delFlag = 0) AS documentCount + FROM act_archive_project ap + $condition + """); + sql.setCondition(cnd); + List list = listMap(sql); + list.forEach(this::enrichProject); + return list; + } + + @Aop(TransAop.READ_COMMITTED) + public void saveProject(ArchiveProject project) { + if (project == null || project.getYear() == null) { + throw new IllegalArgumentException("年度不能为空"); + } + if (Strings.isBlank(project.getProjectName())) { + throw new IllegalArgumentException("项目名称不能为空"); + } + if (Strings.isBlank(project.getUnionId())) { + throw new IllegalArgumentException("所属工会不能为空"); + } + Sys_union union = dao().fetch(Sys_union.class, project.getUnionId()); + if (union == null) { + throw new IllegalArgumentException("所属工会不存在"); + } + project.setProjectName(project.getProjectName().trim()); + project.setUnionName(union.getName()); + Cnd duplicate = Cnd.where("year", "=", project.getYear()) + .and("projectName", "=", project.getProjectName()) + .and("unionId", "=", project.getUnionId()) + .and("delFlag", "=", false); + if (Strings.isNotBlank(project.getId())) { + duplicate.and("id", "<>", project.getId()); + } + if (dao().count(ArchiveProject.class, duplicate) > 0) { + throw new IllegalArgumentException("同年度同工会下项目名称不能重复"); + } + if (Strings.isBlank(project.getId())) { + project.setStatus(ArchiveConstant.PROJECT_DRAFT); + dao().insert(project); + } else { + ArchiveProject old = dao().fetch(ArchiveProject.class, project.getId()); + if (old == null) { + throw new IllegalArgumentException("归档项目不存在"); + } + project.setStatus(old.getStatus()); + dao().updateIgnoreNull(project); + } + } + + @Aop(TransAop.READ_COMMITTED) + public void deleteProject(String id) { + if (Strings.isBlank(id)) { + return; + } + if (dao().count(ArchiveDocument.class, + Cnd.where("projectId", "=", id).and("delFlag", "=", false)) > 0) { + throw new IllegalArgumentException("项目下已存在归档文档,不能删除"); + } + dao().delete(ArchiveProject.class, id); + } + + public Pagination pageDocuments(PageForm pageForm, String projectId, Integer year, + String projectStage, String docTypeId, + String documentName, Integer status) { + Cnd cnd = Cnd.NEW(); + cnd.andEX("ad.projectId", "=", projectId); + cnd.andEX("ap.year", "=", year); + cnd.andEX("ad.projectStage", "=", projectStage); + cnd.andEX("ad.docTypeId", "=", docTypeId); + cnd.and(Cnd.likeEX("ad.documentName", documentName)); + cnd.andEX("ad.status", "=", status); + cnd.and("ad.delFlag", "=", false); + cnd.and("ap.delFlag", "=", false); + cnd.desc("ap.year").desc("ad.uploadTime").desc("ad.createdAt"); + Sql sql = Sqls.create(""" + SELECT ad.*, ap.year, ap.projectName, ap.unionId, ap.unionName, + adt.name AS docTypeName, adt.requiredFlag AS docTypeRequiredFlag + FROM act_archive_document ad + JOIN act_archive_project ap + ON ap.id = ad.projectId + LEFT JOIN act_archive_doc_type adt + ON adt.id = ad.docTypeId + AND adt.delFlag = 0 + $condition + """); + sql.setCondition(cnd); + Pagination page = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + page.getList().forEach(this::enrichDocument); + return page; + } + + public NutMap fetchDocument(String id) { + Sql sql = Sqls.create(""" + SELECT ad.*, ap.year, ap.projectName, ap.unionId, ap.unionName, + adt.name AS docTypeName, adt.requiredFlag AS docTypeRequiredFlag + FROM act_archive_document ad + JOIN act_archive_project ap + ON ap.id = ad.projectId + LEFT JOIN act_archive_doc_type adt + ON adt.id = ad.docTypeId + AND adt.delFlag = 0 + WHERE ad.id = @id + AND ad.delFlag = 0 + AND ap.delFlag = 0 + """); + sql.params().set("id", id); + NutMap map = fetchMap(sql); + if (map != null) { + enrichDocument(map); + } + return map; + } + + @Aop(TransAop.READ_COMMITTED) + public void saveDocument(ArchiveDocument document, boolean submit) { + validateDocument(document, submit); + ArchiveDocument old = Strings.isBlank(document.getId()) + ? null : dao().fetch(ArchiveDocument.class, document.getId()); + if (Strings.isNotBlank(document.getId()) && old == null) { + throw new IllegalArgumentException("归档文档不存在"); + } + int targetStatus = submit ? ArchiveConstant.DOCUMENT_SUBMITTED : ArchiveConstant.DOCUMENT_DRAFT; + if (old != null && old.getStatus() != null + && old.getStatus() == ArchiveConstant.DOCUMENT_SUBMITTED && !submit) { + targetStatus = ArchiveConstant.DOCUMENT_SUBMITTED; + } + document.setStatus(targetStatus); + if (targetStatus == ArchiveConstant.DOCUMENT_SUBMITTED) { + String uploader = SecurityUtil.getUserUsername(); + if (Strings.isBlank(uploader)) { + uploader = SecurityUtil.getUserLoginname(); + } + document.setUploader(uploader); + document.setUploadTime(System.currentTimeMillis()); + } else if (old != null) { + document.setUploader(old.getUploader()); + document.setUploadTime(old.getUploadTime()); + } + String oldProjectId = old == null ? null : old.getProjectId(); + if (old == null) { + dao().insert(document); + } else { + dao().updateIgnoreNull(document); + } + refreshProjectStatus(document.getProjectId()); + if (Strings.isNotBlank(oldProjectId) && !oldProjectId.equals(document.getProjectId())) { + refreshProjectStatus(oldProjectId); + } + } + + @Aop(TransAop.READ_COMMITTED) + public void deleteDocument(String id) { + ArchiveDocument document = dao().fetch(ArchiveDocument.class, id); + if (document == null) { + return; + } + dao().delete(ArchiveDocument.class, id); + refreshProjectStatus(document.getProjectId()); + } + + public List listUnions() { + return dao().query(Sys_union.class, Cnd.where("delFlag", "=", false).asc("unionCode")); + } + + private void validateDocument(ArchiveDocument document, boolean submit) { + if (document == null || Strings.isBlank(document.getProjectId())) { + throw new IllegalArgumentException("归档项目不能为空"); + } + if (Strings.isBlank(document.getDocumentName())) { + throw new IllegalArgumentException("文档名称不能为空"); + } + ArchiveProject project = dao().fetch(ArchiveProject.class, + Cnd.where("id", "=", document.getProjectId()).and("delFlag", "=", false)); + if (project == null) { + throw new IllegalArgumentException("归档项目不存在"); + } + if (!ArchiveConstant.validStage(document.getProjectStage())) { + throw new IllegalArgumentException("项目阶段不正确"); + } + document.setDocumentName(document.getDocumentName().trim()); + if (Strings.isNotBlank(document.getDocTypeId())) { + ArchiveDocType docType = dao().fetch(ArchiveDocType.class, + Cnd.where("id", "=", document.getDocTypeId()).and("delFlag", "=", false)); + if (docType == null) { + throw new IllegalArgumentException("文档类型不存在"); + } + if (!document.getProjectStage().equals(docType.getProjectStage())) { + throw new IllegalArgumentException("文档类型与项目阶段不匹配"); + } + } + if (submit && (document.getFiles() == null || document.getFiles().isEmpty())) { + throw new IllegalArgumentException("提交归档前请先上传附件"); + } + } + + private void refreshProjectStatus(String projectId) { + if (Strings.isBlank(projectId)) { + return; + } + ArchiveProject project = dao().fetch(ArchiveProject.class, projectId); + if (project == null) { + return; + } + List submitted = dao().query(ArchiveDocument.class, + Cnd.where("projectId", "=", projectId) + .and("status", "=", ArchiveConstant.DOCUMENT_SUBMITTED) + .and("delFlag", "=", false)); + int status = ArchiveConstant.PROJECT_DRAFT; + if (!submitted.isEmpty()) { + List requiredTypes = dao().query(ArchiveDocType.class, + Cnd.where("requiredFlag", "=", true).and("delFlag", "=", false)); + if (requiredTypes.isEmpty()) { + status = ArchiveConstant.PROJECT_FILING; + } else { + Set submittedKeys = new HashSet<>(); + submitted.forEach(item -> { + if (Strings.isNotBlank(item.getDocTypeId())) { + submittedKeys.add(item.getProjectStage() + "_" + item.getDocTypeId()); + } + }); + boolean completed = requiredTypes.stream().allMatch(item -> + submittedKeys.contains(item.getProjectStage() + "_" + item.getId())); + status = completed ? ArchiveConstant.PROJECT_COMPLETED : ArchiveConstant.PROJECT_FILING; + } + } + dao().update(ArchiveProject.class, Chain.make("status", status), + Cnd.where("id", "=", projectId)); + } + + private void enrichDocType(NutMap map) { + map.put("projectStageLabel", ArchiveConstant.stageLabel(map.getString("projectStage"))); + } + + private void enrichProject(NutMap map) { + map.put("statusLabel", ArchiveConstant.projectStatusLabel(map.getInt("status"))); + } + + private void enrichDocument(NutMap map) { + map.put("projectStageLabel", ArchiveConstant.stageLabel(map.getString("projectStage"))); + map.put("statusLabel", ArchiveConstant.documentStatusLabel(map.getInt("status"))); + Object files = map.get("files"); + if (files == null || StrUtil.isBlank(String.valueOf(files))) { + map.put("files", Collections.emptyList()); + } + } +} diff --git a/src/main/resources/db/archive_init.sql b/src/main/resources/db/archive_init.sql new file mode 100644 index 00000000..b41bb77d --- /dev/null +++ b/src/main/resources/db/archive_init.sql @@ -0,0 +1,192 @@ +-- 活动电子档案模块初始化脚本(MySQL 8)。 +-- 表结构、索引和菜单均可独立部署,不包含数据库名限定。 + +CREATE TABLE IF NOT EXISTS act_archive_doc_type ( + id VARCHAR(32) NOT NULL COMMENT '主键', + name VARCHAR(150) NOT NULL COMMENT '类型名称', + projectStage VARCHAR(32) NOT NULL COMMENT '项目阶段:PREPARE/PROCESS/SUMMARY', + sortNo INT NOT NULL DEFAULT 0 COMMENT '排序编号', + requiredFlag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否必选', + remark VARCHAR(600) NULL COMMENT '备注', + createdBy VARCHAR(32) NULL COMMENT '创建人', + createdAt BIGINT NULL COMMENT '创建时间', + updatedBy VARCHAR(32) NULL COMMENT '修改人', + updatedAt BIGINT NULL COMMENT '修改时间', + delFlag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标记', + PRIMARY KEY (id), + UNIQUE KEY uk_archive_doc_type_stage_name (projectStage, name), + KEY idx_archive_doc_type_stage_sort (projectStage, sortNo, createdAt) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动电子档案文档类型'; + +CREATE TABLE IF NOT EXISTS act_archive_project ( + id VARCHAR(32) NOT NULL COMMENT '主键', + year INT NOT NULL COMMENT '年度', + projectName VARCHAR(300) NOT NULL COMMENT '项目名称', + status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0草稿、1归档中、2已完成', + unionId VARCHAR(32) NOT NULL COMMENT '所属工会ID', + unionName VARCHAR(150) NOT NULL COMMENT '所属工会名称', + createdBy VARCHAR(32) NULL COMMENT '创建人', + createdAt BIGINT NULL COMMENT '创建时间', + updatedBy VARCHAR(32) NULL COMMENT '修改人', + updatedAt BIGINT NULL COMMENT '修改时间', + delFlag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标记', + PRIMARY KEY (id), + UNIQUE KEY uk_archive_project_year_name_union (year, projectName, unionId), + KEY idx_archive_project_year_created (year, createdAt), + KEY idx_archive_project_union (unionId) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动电子档案项目'; + +CREATE TABLE IF NOT EXISTS act_archive_document ( + id VARCHAR(32) NOT NULL COMMENT '主键', + projectId VARCHAR(32) NOT NULL COMMENT '归档项目ID', + documentName VARCHAR(300) NOT NULL COMMENT '文档名称', + projectStage VARCHAR(32) NOT NULL COMMENT '项目阶段:PREPARE/PROCESS/SUMMARY', + docTypeId VARCHAR(32) NULL COMMENT '文档类型ID', + status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0草稿、1已提交归档', + uploader VARCHAR(100) NULL COMMENT '上传人', + uploadTime BIGINT NULL COMMENT '上传时间', + files JSON NULL COMMENT '附件上传结果', + remark VARCHAR(600) NULL COMMENT '备注', + createdBy VARCHAR(32) NULL COMMENT '创建人', + createdAt BIGINT NULL COMMENT '创建时间', + updatedBy VARCHAR(32) NULL COMMENT '修改人', + updatedAt BIGINT NULL COMMENT '修改时间', + delFlag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标记', + PRIMARY KEY (id), + KEY idx_archive_document_query (projectId, projectStage, docTypeId, uploadTime), + KEY idx_archive_document_project_status (projectId, status), + CONSTRAINT fk_archive_document_project + FOREIGN KEY (projectId) REFERENCES act_archive_project (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动电子档案归档文档'; + +-- 一级菜单。 +INSERT INTO sys_menu ( + id, parentId, path, name, aliasName, type, href, target, icon, showit, + disabled, permission, note, location, hasChildren, createdBy, createdAt, + updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, + initialPinyinName, isRecommendApp, isRecommendService +) +SELECT + '9a7d8d84b14f4d6e8ab2000000000001', + '', + LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'), + '活动电子档案', + 'Archive', + 'menu', + '', + '', + 'ti-folder', + 1, + 0, + 'archive', + NULL, + 992, + 1, + '', + UNIX_TIMESTAMP(NOW()) * 1000, + '', + UNIX_TIMESTAMP(NOW()) * 1000, + 0, + 'PC', + NULL, + NULL, + 'h', + 0, + 0 +FROM sys_menu +WHERE (parentId = '' OR parentId IS NULL) + AND CHAR_LENGTH(path) = 4 +HAVING NOT EXISTS ( + SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'archive') t +); + +-- 文档类型设置。 +INSERT INTO sys_menu ( + id, parentId, path, name, aliasName, type, href, target, icon, showit, + disabled, permission, note, location, hasChildren, createdBy, createdAt, + updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, + initialPinyinName, isRecommendApp, isRecommendService +) +SELECT + '9a7d8d84b14f4d6e8ab2000000000002', p.id, CONCAT(p.path, '0001'), + '文档类型设置', 'Archive Doc Type', 'menu', '/platform/archive/docType', + 'data-pjax', '', 1, 0, 'archive.docType', NULL, 1, 0, '', + UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, + 'PC', NULL, NULL, 'w', 0, 0 +FROM sys_menu p +WHERE p.permission = 'archive' + AND NOT EXISTS ( + SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'archive.docType') t +); + +-- 创建项目。 +INSERT INTO sys_menu ( + id, parentId, path, name, aliasName, type, href, target, icon, showit, + disabled, permission, note, location, hasChildren, createdBy, createdAt, + updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, + initialPinyinName, isRecommendApp, isRecommendService +) +SELECT + '9a7d8d84b14f4d6e8ab2000000000003', p.id, CONCAT(p.path, '0002'), + '创建项目', 'Archive Project', 'menu', '/platform/archive/project', + 'data-pjax', '', 1, 0, 'archive.project', NULL, 2, 0, '', + UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, + 'PC', NULL, NULL, 'c', 0, 0 +FROM sys_menu p +WHERE p.permission = 'archive' + AND NOT EXISTS ( + SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'archive.project') t +); + +-- 资料归档。 +INSERT INTO sys_menu ( + id, parentId, path, name, aliasName, type, href, target, icon, showit, + disabled, permission, note, location, hasChildren, createdBy, createdAt, + updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, + initialPinyinName, isRecommendApp, isRecommendService +) +SELECT + '9a7d8d84b14f4d6e8ab2000000000004', p.id, CONCAT(p.path, '0003'), + '资料归档', 'Archive Filing', 'menu', '/platform/archive/filing', + 'data-pjax', '', 1, 0, 'archive.filing', NULL, 3, 0, '', + UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, + 'PC', NULL, NULL, 'z', 0, 0 +FROM sys_menu p +WHERE p.permission = 'archive' + AND NOT EXISTS ( + SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'archive.filing') t +); + +-- 档案台账。 +INSERT INTO sys_menu ( + id, parentId, path, name, aliasName, type, href, target, icon, showit, + disabled, permission, note, location, hasChildren, createdBy, createdAt, + updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, + initialPinyinName, isRecommendApp, isRecommendService +) +SELECT + '9a7d8d84b14f4d6e8ab2000000000005', p.id, CONCAT(p.path, '0004'), + '档案台账', 'Archive Ledger', 'menu', '/platform/archive/ledger', + 'data-pjax', '', 1, 0, 'archive.ledger', NULL, 4, 0, '', + UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, + 'PC', NULL, NULL, 'd', 0, 0 +FROM sys_menu p +WHERE p.permission = 'archive' + AND NOT EXISTS ( + SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'archive.ledger') t +); + +-- 系统管理员默认授权,其他角色可在角色管理中按需分配。 +INSERT INTO sys_role_menu (roleId, menuId) +SELECT r.id, m.id +FROM sys_role r +JOIN sys_menu m ON m.permission IN ( + 'archive', + 'archive.docType', + 'archive.project', + 'archive.filing', + 'archive.ledger' +) +LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id +WHERE r.code = 'SYSADMIN' + AND rm.roleId IS NULL; diff --git a/src/main/resources/views/platform/zhgh/archive/ledger/index.html b/src/main/resources/views/platform/zhgh/archive/ledger/index.html new file mode 100644 index 00000000..6da274d8 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/archive/ledger/index.html @@ -0,0 +1,186 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ {{currentProject.projectName}} + + {{currentProject.statusLabel}} + +
+
+ 年度:{{currentProject.year}} + 所属工会:{{currentProject.unionName}} + 归档文档:{{currentProject.documentCount || 0}} +
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +