feat: 生产部署 + 会议全链路 (执行方分配/费用结算/签到脱敏/H5相机)

- 生产 nginx 三路径: ry-vue3 /hg/, ry-h5 /camera/, API /hg-api, .env 分环境
- 签约链接/摄像头 base-url 走 yml, 不再硬编码 /hg 与 localhost
- 新增 biz_project_executor_assign (镜像 sponsor_assign) 执行方项目级分配
- 会议费用后台汇总 FeeCalcScheduler + 结算回写项目金额 + 状态流转调度
- 签到表拍照高斯模糊脱敏 extra_oss_url, 新增 PosterService/StageDeriver
- 清理 biz_support_intent 旧表 (6 Java/XML 删除)
- ry-h5 相机黑屏修复: 显式 video.play() + 动态 apiBase 上传
- 资源: simhei 字体 / logo.png / qrcode_1.png / favicon.ico

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
郭庆泰
2026-08-24 01:00:09 +08:00
co-authored by Claude
parent 80a276e661
commit 904e28710f
123 changed files with 6008 additions and 1706 deletions
@@ -39,34 +39,35 @@
<result property="projectName" column="project_name" />
<result property="projectNo" column="project_no" />
<result property="isDeleted" column="is_deleted" />
<result property="isEsigned" column="is_esigned" />
<result property="isInvited" column="is_invited" />
</resultMap>
<insert id="insert" parameterType="BizMeetingAttendee">
insert into biz_meeting_attendee(meeting_id, user_id, create_by, create_time)
values(#{meetingId}, #{userId}, #{createBy}, sysdate())
insert into biz_meeting_attendee(id, meeting_id, user_id, create_by, create_time)
values(#{id}, #{meetingId}, #{userId}, #{createBy}, sysdate())
</insert>
<!--
管理端新增参会人 (MeetingDetail 参会人 CRUD 用):
一次性写入 meeting_id+user_id+档案字段 (name/phone/work_unit/...).
若档案字段为 NULL 则不写 (COALESCE 在调用方给空字符串兜底).
useGeneratedKeys 让调用方能拿到新 attendee.id (用于 #5 触发邀请通知).
一次性写入 id+meeting_id+user_id+档案字段 (name/phone/work_unit/...).
id 由调用方用雪花 ID (IdGenerator) 生成, 不走 DB 自增.
-->
<insert id="insertWithProfile" parameterType="BizMeetingAttendee" useGeneratedKeys="true" keyProperty="id">
insert into biz_meeting_attendee(meeting_id, user_id, name, phone, work_unit, department, title,
<insert id="insertWithProfile" parameterType="BizMeetingAttendee">
insert into biz_meeting_attendee(id, meeting_id, user_id, name, phone, work_unit, department, title,
id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name,
id_card_attachments, labor_form, fee_pre_tax, tax, fee, vat_and_surcharge, summary, on_site_photos,
create_by, create_time)
values(#{meetingId}, #{userId}, #{name}, #{phone}, #{workUnit}, #{department}, #{title},
values(#{id}, #{meetingId}, #{userId}, #{name}, #{phone}, #{workUnit}, #{department}, #{title},
#{idCard}, #{bankCard}, #{bankName}, #{bankBranch}, #{bankRegion}, #{bankAddress}, #{accountName},
#{idCardAttachments}, #{laborForm}, #{feePreTax}, #{tax}, #{fee},
#{vatAndSurcharge}, #{summary}, #{onSitePhotos},
#{createBy}, sysdate())
</insert>
<!-- 批量插入参会人 (BizMeetingController.add 调用) -->
<!-- 批量插入参会人 (BizMeetingController.add/edit 调用), id 由调用方雪花 ID 填好 -->
<insert id="insertBatch">
insert into biz_meeting_attendee(meeting_id, user_id, create_by, create_time)
insert into biz_meeting_attendee(id, meeting_id, user_id, create_by, create_time)
values
<foreach collection="userIds" item="userId" separator=",">
(#{meetingId}, #{userId}, #{createBy}, sysdate())
<foreach collection="list" item="a" separator=",">
(#{a.id}, #{a.meetingId}, #{a.userId}, #{a.createBy}, sysdate())
</foreach>
</insert>
<!-- 医生填写信息保存草稿: 更新所有签字字段 + 劳务信息 (不含签名/PDF) -->
@@ -124,6 +125,18 @@
update_time = sysdate()
where id = #{id}
</update>
<!-- 推送电子签后置 is_esigned=1 -->
<update id="markEsignedById" parameterType="Long">
update biz_meeting_attendee
set is_esigned = 1, update_time = sysdate()
where id = #{id}
</update>
<!-- 邀请参会后置 is_invited=1 -->
<update id="markInvitedById" parameterType="Long">
update biz_meeting_attendee
set is_invited = 1, update_time = sysdate()
where id = #{id}
</update>
<delete id="deleteByMeetingId" parameterType="Long">
delete from biz_meeting_attendee where meeting_id = #{meetingId}
</delete>
@@ -139,34 +152,51 @@
delete from biz_meeting_attendee where meeting_id = #{meetingId} and user_id = #{userId}
</delete>
<select id="selectByMeetingId" resultMap="BizMeetingAttendeeResult" parameterType="Long">
select id, meeting_id, user_id, name, phone, work_unit, department, title, id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name, id_card_attachments, labor_form, fee_pre_tax, tax, fee, vat_and_surcharge, summary, on_site_photos, signed_at, signed_ip, handsign, labor_protocol, create_by, create_time, is_deleted
select id, meeting_id, user_id, name, phone, work_unit, department, title, id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name, id_card_attachments, labor_form, fee_pre_tax, tax, fee, vat_and_surcharge, summary, on_site_photos, signed_at, signed_ip, handsign, labor_protocol, labor_protocol_masked, create_by, create_time, is_deleted, is_esigned, is_invited
from biz_meeting_attendee where meeting_id = #{meetingId} and is_deleted = 0
</select>
<select id="selectByUserId" resultMap="BizMeetingAttendeeResult" parameterType="Long">
select id, meeting_id, user_id, name, phone, work_unit, department, title, id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name, id_card_attachments, labor_form, fee_pre_tax, tax, fee, vat_and_surcharge, summary, on_site_photos, signed_at, signed_ip, handsign, labor_protocol, create_by, create_time, is_deleted
select id, meeting_id, user_id, name, phone, work_unit, department, title, id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name, id_card_attachments, labor_form, fee_pre_tax, tax, fee, vat_and_surcharge, summary, on_site_photos, signed_at, signed_ip, handsign, labor_protocol, create_by, create_time, is_deleted, is_esigned, is_invited
from biz_meeting_attendee where user_id = #{userId} and is_deleted = 0
</select>
<select id="selectById" resultMap="BizMeetingAttendeeResult" parameterType="Long">
select id, meeting_id, user_id, name, phone, work_unit, department, title, id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name, id_card_attachments, labor_form, fee_pre_tax, tax, fee, vat_and_surcharge, summary, on_site_photos, signed_at, signed_ip, handsign, labor_protocol, create_by, create_time, is_deleted
select id, meeting_id, user_id, name, phone, work_unit, department, title, id_card, bank_card, bank_name, bank_branch, bank_region, bank_address, account_name, id_card_attachments, labor_form, fee_pre_tax, tax, fee, vat_and_surcharge, summary, on_site_photos, signed_at, signed_ip, handsign, labor_protocol, create_by, create_time, is_deleted, is_esigned, is_invited
from biz_meeting_attendee where id = #{id} and is_deleted = 0
</select>
<!--
当前用户的"待签署"会议列表 (任一未签: handsign 或 labor_protocol 为 NULL)
当前用户的"待签署协议"列表: 已推送电子签 (is_esigned=1) 且 任一未签 (handsign 或 labor_protocol 为)
INNER JOIN biz_meeting 取会议名/时间/项目名, 用于 /doctor/home 工作台
字段别名 + resultMap 上面的 transient property 接收
两表都需 is_deleted=0 过滤: 删除会议后, 参会人的待签署列表也不显示
-->
<select id="selectUnsignedByUserId" resultType="BizMeetingAttendee" parameterType="Long">
select a.id, a.meeting_id, a.user_id, a.handsign, a.labor_protocol, a.create_by, a.create_time,
a.is_esigned as isEsigned,
m.meeting_name as meetingName, m.start_time as startTime,
m.end_time as endTime, m.project_name as projectName, m.project_no as projectNo
from biz_meeting_attendee a
inner join biz_meeting m on m.meeting_id = a.meeting_id
where a.user_id = #{userId}
and a.is_deleted = 0 and m.is_deleted = 0
and a.is_esigned = 1
and (a.handsign is null or a.handsign = '' or a.labor_protocol is null or a.labor_protocol = '')
order by m.start_time asc
</select>
<!--
当前用户的"待参加"会议列表: 已邀请参会 (is_invited=1)
INNER JOIN biz_meeting 取会议名/时间/项目名, 用于 /doctor/home 工作台
-->
<select id="selectInvitedByUserId" resultType="BizMeetingAttendee" parameterType="Long">
select a.id, a.meeting_id, a.user_id, a.create_by, a.create_time,
m.meeting_name as meetingName, m.start_time as startTime,
m.end_time as endTime, m.project_name as projectName, m.project_no as projectNo
from biz_meeting_attendee a
inner join biz_meeting m on m.meeting_id = a.meeting_id
where a.user_id = #{userId}
and a.is_deleted = 0 and m.is_deleted = 0
and a.is_invited = 1
order by m.start_time asc
</select>
<!--
给 BizMeetingController.edit 做差集用: 查该会议已存在的参会人 userId 列表.
用于 #5 会议邀请: 仅给"新加入"的 userId 发通知, 已存在的用户不重发.
@@ -175,4 +205,10 @@
<select id="selectUserIdsByMeetingId" resultType="java.lang.Long" parameterType="Long">
select user_id from biz_meeting_attendee where meeting_id = #{meetingId} and is_deleted = 0
</select>
<!-- 费用汇总用: 某会议应发金额 (fee_pre_tax) 合计 -->
<select id="sumFeePreTaxByMeetingId" resultType="java.math.BigDecimal" parameterType="Long">
select ifnull(sum(fee_pre_tax), 0)
from biz_meeting_attendee
where meeting_id = #{meetingId} and is_deleted = 0
</select>
</mapper>
@@ -7,7 +7,10 @@
<result property="meetingId" column="meeting_id" />
<result property="auditor" column="auditor" />
<result property="opinion" column="opinion" />
<result property="currentStage" column="current_stage" />
<result property="executorStage" column="executor_stage" />
<result property="sponsorStage" column="sponsor_stage" />
<result property="managerStage" column="manager_stage" />
<result property="adminStage" column="admin_stage" />
<result property="createTime" column="create_time" />
<result property="auditTime" column="audit_time" />
<result property="auditType" column="audit_type" />
@@ -16,7 +19,7 @@
</resultMap>
<sql id="selectFields">
select id, meeting_id, auditor, opinion, current_stage, create_time, audit_time, audit_type, audit_result, is_deleted
select id, meeting_id, auditor, opinion, executor_stage, sponsor_stage, manager_stage, admin_stage, create_time, audit_time, audit_type, audit_result, is_deleted
from biz_meeting_audit_log
</sql>
@@ -30,7 +33,6 @@
<where>
is_deleted = 0
<if test="meetingId != null">and meeting_id = #{meetingId}</if>
<if test="currentStage != null and currentStage != ''">and current_stage = #{currentStage}</if>
<if test="auditor != null and auditor != ''">and auditor = #{auditor}</if>
</where>
order by id desc
@@ -42,7 +44,10 @@
<if test="meetingId != null">meeting_id,</if>
<if test="auditor != null and auditor != ''">auditor,</if>
<if test="opinion != null and opinion != ''">opinion,</if>
<if test="currentStage != null and currentStage != ''">current_stage,</if>
<if test="executorStage != null and executorStage != ''">executor_stage,</if>
<if test="sponsorStage != null and sponsorStage != ''">sponsor_stage,</if>
<if test="managerStage != null and managerStage != ''">manager_stage,</if>
<if test="adminStage != null and adminStage != ''">admin_stage,</if>
<if test="createTime != null">create_time,</if>
<if test="auditTime != null">audit_time,</if>
<if test="auditType != null and auditType != ''">audit_type,</if>
@@ -52,7 +57,10 @@
<if test="meetingId != null">#{meetingId},</if>
<if test="auditor != null and auditor != ''">#{auditor},</if>
<if test="opinion != null and opinion != ''">#{opinion},</if>
<if test="currentStage != null and currentStage != ''">#{currentStage},</if>
<if test="executorStage != null and executorStage != ''">#{executorStage},</if>
<if test="sponsorStage != null and sponsorStage != ''">#{sponsorStage},</if>
<if test="managerStage != null and managerStage != ''">#{managerStage},</if>
<if test="adminStage != null and adminStage != ''">#{adminStage},</if>
<if test="createTime != null">#{createTime},</if>
<if test="auditTime != null">#{auditTime},</if>
<if test="auditType != null and auditType != ''">#{auditType},</if>
@@ -65,7 +73,10 @@
<trim prefix="SET" suffixOverrides=",">
<if test="auditor != null and auditor != ''">auditor = #{auditor},</if>
<if test="opinion != null and opinion != ''">opinion = #{opinion},</if>
<if test="currentStage != null and currentStage != ''">current_stage = #{currentStage},</if>
<if test="executorStage != null and executorStage != ''">executor_stage = #{executorStage},</if>
<if test="sponsorStage != null and sponsorStage != ''">sponsor_stage = #{sponsorStage},</if>
<if test="managerStage != null and managerStage != ''">manager_stage = #{managerStage},</if>
<if test="adminStage != null and adminStage != ''">admin_stage = #{adminStage},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="auditTime != null">audit_time = #{auditTime},</if>
<if test="auditType != null and auditType != ''">audit_type = #{auditType},</if>
@@ -21,9 +21,28 @@
<result property="supervisionTime" column="supervision_time" />
<result property="materialAuditStage" column="material_audit_stage" />
<result property="voucherAuditStage" column="voucher_audit_stage" />
<result property="isExecuted" column="is_executed" />
<result property="executeTime" column="execute_time" />
<result property="isSettled" column="is_settled" />
<result property="settleTime" column="settle_time" />
<result property="isFinished" column="is_finished" />
<result property="finishTime" column="finish_time" />
<result property="isFrozen" column="is_frozen" />
<result property="freezeTime" column="freeze_time" />
<result property="materialAuditTime" column="material_audit_time" />
<result property="voucherAuditTime" column="voucher_audit_time" />
<result property="materialComplianceApproved" column="material_compliance_approved" />
<result property="voucherComplianceApproved" column="voucher_compliance_approved" />
<result property="invitationUrl" column="invitation_url" />
<result property="scheduleUrl" column="schedule_url" />
<result property="posterUrl" column="poster_url" />
<result property="laborSigned" column="labor_signed" />
<result property="laborFee" column="labor_fee" />
<result property="meetingFee" column="meeting_fee" />
<result property="totalFee" column="total_fee" />
<result property="feeCalcStatus" column="fee_calc_status" />
<result property="attendeeId" column="attendee_id" />
<result property="attendeeLaborProtocol" column="attendee_labor_protocol" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
@@ -31,15 +50,19 @@
<result property="isDeleted" column="is_deleted" />
</resultMap>
<sql id="selectFields">
select meeting_id, business_id, project_id, project_no, project_name, meeting_name, period_no, total_periods, project_form, start_time, end_time, org_name, address, current_stage, supervision_opinion, supervision_by, supervision_time, material_audit_stage, voucher_audit_stage, invitation_url, schedule_url, labor_signed, create_by, create_time, update_by, update_time, is_deleted
from biz_meeting
meeting_id, business_id, project_id, project_no, project_name, meeting_name, period_no, total_periods, project_form, start_time, end_time, org_name, address, current_stage, supervision_opinion, supervision_by, supervision_time, material_audit_stage, voucher_audit_stage, is_executed, execute_time, is_settled, settle_time, is_finished, finish_time, is_frozen, freeze_time, material_audit_time, voucher_audit_time, material_compliance_approved, voucher_compliance_approved, invitation_url, schedule_url, poster_url, labor_signed, labor_fee, meeting_fee, total_fee, fee_calc_status, create_by, create_time, update_by, update_time, is_deleted
</sql>
<select id="selectByPrimaryKey" resultMap="BizMeetingResult" parameterType="Long">
<include refid="selectFields"/>
select <include refid="selectFields"/>
from biz_meeting
where meeting_id = #{meetingId} and is_deleted = 0
</select>
<select id="selectList" resultMap="BizMeetingResult" parameterType="BizMeeting">
select
<if test="userId != null">(select a.id from biz_meeting_attendee a where a.meeting_id = biz_meeting.meeting_id and a.user_id = #{userId} and a.is_deleted = 0 limit 1) as attendee_id,</if>
<if test="userId != null">(select a.labor_protocol from biz_meeting_attendee a where a.meeting_id = biz_meeting.meeting_id and a.user_id = #{userId} and a.is_deleted = 0 limit 1) as attendee_labor_protocol,</if>
<include refid="selectFields"/>
from biz_meeting
<where>
is_deleted = 0
<if test="projectNo != null and projectNo != ''">and project_no like concat('%', #{projectNo}, '%')</if>
@@ -52,6 +75,21 @@
<if test="endTime != null">and end_time &lt;= #{endTime}</if>
<!-- doctor 角色按 user_id 过滤 (走 biz_meeting_attendee 中间表, 同时 attendee 也需 is_deleted=0) -->
<if test="userId != null">and exists (select 1 from biz_meeting_attendee a where a.meeting_id = biz_meeting.meeting_id and a.user_id = #{userId} and a.is_deleted = 0)</if>
<!-- sponsor 数据权限: 只看"我的项目"下的会议 (project_id ∈ 我的项目). MAIN 走 sponsor_admin_user_id, SUB 走 sponsor_assign.monitor_user_id.
刻意不带 biz_publicity_support_intent 关联 (与项目列表 selectSponsorList 的区别点) -->
<if test="params.sponsorAdminUserId != null">and project_id in (select project_id from biz_project where sponsor_admin_user_id = #{params.sponsorAdminUserId} and is_deleted = 0)</if>
<if test="params.monitorUserId != null">and project_id in (select distinct project_id from biz_project_sponsor_assign where monitor_user_id = #{params.monitorUserId} and is_deleted = 0)</if>
<!-- executor 数据权限: 只看"我的项目"下的会议. MAIN 走 biz_project_assign (exec_user_id / execution_unit_id), SUB(执行人) 走 biz_project_executor_assign.staff_user_id -->
<if test="params.executorUserId != null">and project_id in (
select distinct a.project_id from biz_project_assign a
where a.is_deleted = 0
and (a.exec_user_id = #{params.executorUserId}
or a.execution_unit_id = (select org_id from biz_org where user_id = #{params.executorUserId} and org_type = 'executor'))
)</if>
<if test="params.executorStaffUserId != null">and project_id in (
select distinct a.project_id from biz_project_executor_assign a
where a.is_deleted = 0 and a.staff_user_id = #{params.executorStaffUserId}
)</if>
</where>
order by meeting_id desc
</select>
@@ -79,7 +117,12 @@
<if test="voucherAuditStage != null and voucherAuditStage != ''">voucher_audit_stage,</if>
<if test="invitationUrl != null and invitationUrl != ''">invitation_url,</if>
<if test="scheduleUrl != null and scheduleUrl != ''">schedule_url,</if>
<if test="posterUrl != null and posterUrl != ''">poster_url,</if>
<if test="laborSigned != null and laborSigned != ''">labor_signed,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null and updateBy != ''">update_by,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="meetingId != null">#{meetingId},</if>
@@ -103,33 +146,54 @@
<if test="voucherAuditStage != null and voucherAuditStage != ''">#{voucherAuditStage},</if>
<if test="invitationUrl != null and invitationUrl != ''">#{invitationUrl},</if>
<if test="scheduleUrl != null and scheduleUrl != ''">#{scheduleUrl},</if>
<if test="posterUrl != null and posterUrl != ''">#{posterUrl},</if>
<if test="laborSigned != null and laborSigned != ''">#{laborSigned},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null and updateBy != ''">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<update id="updateByPrimaryKey" parameterType="BizMeeting">
update biz_meeting
<trim prefix="SET" suffixOverrides=",">
<if test="businessId != null and businessId != ''">business_id = #{businessId},</if>
<if test="projectId != null and projectId != ''">project_id = #{projectId},</if>
<if test="projectId != null">project_id = #{projectId},</if>
<if test="projectNo != null and projectNo != ''">project_no = #{projectNo},</if>
<if test="projectName != null and projectName != ''">project_name = #{projectName},</if>
<if test="meetingName != null and meetingName != ''">meeting_name = #{meetingName},</if>
<if test="periodNo != null and periodNo != ''">period_no = #{periodNo},</if>
<if test="totalPeriods != null and totalPeriods != ''">total_periods = #{totalPeriods},</if>
<if test="periodNo != null">period_no = #{periodNo},</if>
<if test="totalPeriods != null">total_periods = #{totalPeriods},</if>
<if test="projectForm != null and projectForm != ''">project_form = #{projectForm},</if>
<if test="startTime != null and startTime != ''">start_time = #{startTime},</if>
<if test="endTime != null and endTime != ''">end_time = #{endTime},</if>
<!-- Date/Long 字段不能用 != '' (OGNL 会把 Date 和 String 做非法比较), 只判 null -->
<if test="startTime != null">start_time = #{startTime},</if>
<if test="endTime != null">end_time = #{endTime},</if>
<if test="orgName != null and orgName != ''">org_name = #{orgName},</if>
<if test="address != null and address != ''">address = #{address},</if>
<if test="currentStage != null and currentStage != ''">current_stage = #{currentStage},</if>
<if test="supervisionOpinion != null and supervisionOpinion != ''">supervision_opinion = #{supervisionOpinion},</if>
<if test="supervisionBy != null and supervisionBy != ''">supervision_by = #{supervisionBy},</if>
<if test="supervisionTime != null and supervisionTime != ''">supervision_time = #{supervisionTime},</if>
<if test="supervisionTime != null">supervision_time = #{supervisionTime},</if>
<if test="materialAuditStage != null and materialAuditStage != ''">material_audit_stage = #{materialAuditStage},</if>
<if test="voucherAuditStage != null and voucherAuditStage != ''">voucher_audit_stage = #{voucherAuditStage},</if>
<if test="isExecuted != null">is_executed = #{isExecuted},</if>
<if test="executeTime != null">execute_time = #{executeTime},</if>
<if test="isSettled != null">is_settled = #{isSettled},</if>
<if test="settleTime != null">settle_time = #{settleTime},</if>
<if test="isFinished != null">is_finished = #{isFinished},</if>
<if test="finishTime != null">finish_time = #{finishTime},</if>
<if test="isFrozen != null">is_frozen = #{isFrozen},</if>
<if test="freezeTime != null">freeze_time = #{freezeTime},</if>
<if test="materialAuditTime != null">material_audit_time = #{materialAuditTime},</if>
<if test="voucherAuditTime != null">voucher_audit_time = #{voucherAuditTime},</if>
<if test="materialComplianceApproved != null">material_compliance_approved = #{materialComplianceApproved},</if>
<if test="voucherComplianceApproved != null">voucher_compliance_approved = #{voucherComplianceApproved},</if>
<if test="invitationUrl != null and invitationUrl != ''">invitation_url = #{invitationUrl},</if>
<if test="scheduleUrl != null and scheduleUrl != ''">schedule_url = #{scheduleUrl},</if>
<if test="posterUrl != null and posterUrl != ''">poster_url = #{posterUrl},</if>
<if test="laborSigned != null and laborSigned != ''">labor_signed = #{laborSigned},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where meeting_id = #{meetingId}
</update>
@@ -150,4 +214,66 @@
<select id="selectIdListByProjectId" resultType="Long" parameterType="Long">
select meeting_id from biz_meeting where project_id = #{projectId}
</select>
<!-- 建会限额用: 某项目下未软删的会议数 -->
<select id="countByProjectId" resultType="int" parameterType="Long">
select count(*) from biz_meeting where project_id = #{projectId} and is_deleted = 0
</select>
<!-- 自动流转 (MeetingStageScheduler 每分钟调): start_time 已过 且 material 未提交 且未执行的会议 → is_executed=1 + RUNNING. 已软删/已冻结/已提交材料的不动. -->
<update id="markExecuted">
update biz_meeting
set is_executed = 1,
execute_time = NOW(),
current_stage = 'RUNNING'
where is_deleted = 0
and is_executed = 0
and is_frozen = 0
and start_time is not null
and start_time &lt;= NOW()
and material_audit_stage = 'NOT_SUBMITTED'
</update>
<!-- 自动流转 (MeetingStageScheduler 每分钟调): material 未提交 且 end_time + submit_deadline_days 已过 → 冻结 -->
<update id="markFrozen">
update biz_meeting m
join biz_project p on p.project_id = m.project_id and p.is_deleted = 0
set m.is_frozen = 1,
m.freeze_time = NOW(),
m.current_stage = 'FROZEN'
where m.is_deleted = 0
and m.is_frozen = 0
and m.material_audit_stage = 'NOT_SUBMITTED'
and m.end_time is not null
and p.submit_deadline_days is not null
and date_add(m.end_time, interval p.submit_deadline_days day) &lt;= NOW()
</update>
<!-- 自动流转 (MeetingStageScheduler 每分钟调): material+voucher 都 APPROVED 且 最晚审核时间已过 1 自然日 → AWAITING_SETTLEMENT (待结算 24h 慢路径) -->
<update id="markSettlementReady">
update biz_meeting
set current_stage = 'AWAITING_SETTLEMENT'
where is_deleted = 0
and is_frozen = 0
and is_settled = 0
and is_finished = 0
and material_audit_stage = 'APPROVED'
and voucher_audit_stage = 'APPROVED'
and current_stage = 'SUPERVISION_APPROVED'
and greatest(material_audit_time, voucher_audit_time) is not null
and greatest(material_audit_time, voucher_audit_time) &lt;= (NOW() - INTERVAL 1 DAY)
</update>
<!-- 费用汇总调度器: 查 fee_calc_status=0 且未软删的会议 id -->
<select id="selectPendingFeeCalcIds" resultType="Long">
select meeting_id from biz_meeting where fee_calc_status = 0 and is_deleted = 0
</select>
<!-- 置未汇总 (人员/材料变化触发, 幂等) -->
<update id="markFeeCalcPending" parameterType="Long">
update biz_meeting set fee_calc_status = 0 where meeting_id = #{meetingId}
</update>
<!-- 汇总回写 3 个费用字段 + 置已汇总 -->
<update id="updateFeeSummary">
update biz_meeting
set labor_fee = #{laborFee},
meeting_fee = #{meetingFee},
total_fee = #{totalFee},
fee_calc_status = 1
where meeting_id = #{meetingId}
</update>
</mapper>
@@ -9,14 +9,16 @@
<result property="subType" column="sub_type" />
<result property="fileName" column="file_name" />
<result property="ossUrl" column="oss_url" />
<result property="extraOssUrl" column="extra_oss_url" />
<result property="amount" column="amount" />
<result property="creatorId" column="creator_id" />
<result property="createTime" column="create_time" />
<result property="isDeleted" column="is_deleted" />
<result property="feeStatus" column="fee_status" />
</resultMap>
<sql id="selectFields">
select id, meeting_id, material_type, sub_type, file_name, oss_url, amount, creator_id, create_time, is_deleted
select id, meeting_id, material_type, sub_type, file_name, oss_url, extra_oss_url, amount, creator_id, create_time, is_deleted, fee_status
from biz_meeting_material
</sql>
@@ -39,6 +41,7 @@
<if test="subType != null and subType != ''">sub_type,</if>
<if test="fileName != null and fileName != ''">file_name,</if>
<if test="ossUrl != null and ossUrl != ''">oss_url,</if>
<if test="extraOssUrl != null and extraOssUrl != ''">extra_oss_url,</if>
<if test="amount != null">amount,</if>
<if test="creatorId != null">creator_id,</if>
<if test="createTime != null">create_time,</if>
@@ -49,6 +52,7 @@
<if test="subType != null and subType != ''">#{subType},</if>
<if test="fileName != null and fileName != ''">#{fileName},</if>
<if test="ossUrl != null and ossUrl != ''">#{ossUrl},</if>
<if test="extraOssUrl != null and extraOssUrl != ''">#{extraOssUrl},</if>
<if test="amount != null">#{amount},</if>
<if test="creatorId != null">#{creatorId},</if>
<if test="createTime != null">#{createTime},</if>
@@ -56,11 +60,11 @@
</insert>
<insert id="insertBatch" parameterType="java.util.List">
insert into biz_meeting_material (meeting_id, material_type, sub_type, file_name, oss_url, amount, creator_id, create_time)
insert into biz_meeting_material (meeting_id, material_type, sub_type, file_name, oss_url, extra_oss_url, amount, creator_id, create_time, fee_status)
values
<foreach collection="list" item="item" separator=",">
(#{item.meetingId}, #{item.materialType}, #{item.subType}, #{item.fileName}, #{item.ossUrl},
#{item.amount}, #{item.creatorId}, #{item.createTime})
#{item.extraOssUrl}, #{item.amount}, #{item.creatorId}, #{item.createTime}, #{item.feeStatus})
</foreach>
</insert>
@@ -71,6 +75,7 @@
<if test="subType != null and subType != ''">sub_type = #{subType},</if>
<if test="fileName != null and fileName != ''">file_name = #{fileName},</if>
<if test="ossUrl != null and ossUrl != ''">oss_url = #{ossUrl},</if>
<if test="extraOssUrl != null and extraOssUrl != ''">extra_oss_url = #{extraOssUrl},</if>
<if test="amount != null">amount = #{amount},</if>
</trim>
where id = #{id}
@@ -80,6 +85,10 @@
update biz_meeting_material set amount = #{amount} where id = #{id}
</update>
<update id="updateFeeStatus">
update biz_meeting_material set fee_status = #{feeStatus} where id = #{id}
</update>
<delete id="deleteByPrimaryKey" parameterType="Long">
delete from biz_meeting_material where id = #{id}
</delete>
@@ -15,6 +15,7 @@
<result property="unitType" column="unit_type" />
<result property="accountType" column="account_type" />
<result property="parentUserId" column="parent_user_id" />
<result property="account" column="user_name" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
@@ -31,7 +32,8 @@
select p.person_id, p.name, p.phone, p.org_id, o.org_name, o.org_type,
p.department, p.position, p.role, p.unit_type, p.user_id,
p.create_by, p.create_time, p.update_by, p.update_time,
u.account_type, u.parent_user_id, u.status, u.del_flag as user_del_flag
u.account_type, u.parent_user_id, u.status, u.del_flag as user_del_flag,
u.user_name as user_name
from biz_person p
left join biz_org o on p.org_id = o.org_id
left join sys_user u on p.user_id = u.user_id
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.business.mapper.BizProjectExecutorAssignMapper">
<resultMap id="BaseResultMap" type="BizProjectExecutorAssign">
<id property="id" column="id" />
<result property="projectId" column="project_id" />
<result property="executorUserId" column="executor_user_id" />
<result property="staffUserId" column="staff_user_id" />
<result property="assignDesc" column="assign_desc" />
<result property="assignPoints" column="assign_points" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="executorUserName" column="executor_user_name" />
<result property="staffUserName" column="staff_user_name" />
<result property="isDeleted" column="is_deleted" />
</resultMap>
<insert id="insertAssign" parameterType="BizProjectExecutorAssign" useGeneratedKeys="true" keyProperty="id">
INSERT INTO biz_project_executor_assign
(project_id, executor_user_id, staff_user_id, assign_desc, assign_points, create_by, create_time)
VALUES
(#{projectId}, #{executorUserId}, #{staffUserId}, #{assignDesc}, #{assignPoints}, #{createBy}, sysdate())
</insert>
<!-- 按 project_id 全删 (执行方分配策略: 先删后插) -->
<delete id="deleteByProjectId" parameterType="String">
delete from biz_project_executor_assign where project_id = #{projectId}
</delete>
<!-- 软删除: 项目级联删除时按 project_id (String) 置 is_deleted=1 -->
<update id="softDeleteByProjectId" parameterType="String">
update biz_project_executor_assign set is_deleted = 1 where project_id = #{projectId}
</update>
<select id="selectByProjectId" resultMap="BaseResultMap">
SELECT a.*,
e.user_name AS executor_user_name,
s.user_name AS staff_user_name
FROM biz_project_executor_assign a
LEFT JOIN sys_user e ON a.executor_user_id = e.user_id
LEFT JOIN sys_user s ON a.staff_user_id = s.user_id
WHERE a.project_id = #{projectId} and a.is_deleted = 0
ORDER BY a.create_time DESC
</select>
</mapper>
@@ -8,6 +8,7 @@
<result property="totalSessions" column="total_sessions" />
<result property="assignedSessions" column="assigned_sessions" />
<result property="assignedAmount" column="assigned_amount" />
<result property="meetingCount" column="meeting_count" />
<result property="doneSessions" column="done_sessions" />
<result property="todoSessions" column="todo_sessions" />
<result property="totalAmount" column="total_amount" />
@@ -96,7 +97,32 @@
#{id}
</foreach>
</if>
<if test="params.sponsorAdminUserId != null">and p.sponsor_admin_user_id = #{params.sponsorAdminUserId}</if>
<!--
sponsor 视角: 可见项目 = MAIN/ADMIN 默认 (sponsor_admin_user_id) OR SUB/监察员 (sponsor_assign.monitor_user_id), 任一路径同时 UNION biz_publicity_support_intent
业务: sponsor (无论 MAIN/SUB) 提交过支持意向的项目, 同样要在 /sponsor/my-projects 看到, 跟被分配监察员同等地位
分页: PageHelper 加 LIMIT 到外层, 自动生成 COUNT(*) FROM biz_project WHERE ... — 子查询内 DISTINCT 避免重复 count
-->
<if test="params.sponsorAdminUserId != null">and (
p.sponsor_admin_user_id = #{params.sponsorAdminUserId}
or p.project_id in (
select distinct i.project_id
from biz_publicity_support_intent i
where i.user_id = #{params.sponsorAdminUserId}
)
)</if>
<if test="params.monitorUserId != null">and (
p.project_id in (
select distinct a.project_id
from biz_project_sponsor_assign a
where a.is_deleted = 0
and a.monitor_user_id = #{params.monitorUserId}
)
or p.project_id in (
select distinct i.project_id
from biz_publicity_support_intent i
where i.user_id = #{params.monitorUserId}
)
)</if>
<if test="projectNo != null and projectNo != ''">and p.project_no like concat('%', #{projectNo}, '%')</if>
<if test="projectName != null and projectName != ''">and p.project_name like concat('%', #{projectName}, '%')</if>
<if test="startTime != null">and p.start_time &gt;= #{startTime}</if>
@@ -133,16 +159,67 @@
where bpa4.project_id = p.project_id
and bpa4.is_deleted = 0
and (bpa4.exec_user_id = #{params.executorUserId}
or bpa4.execution_unit_id = (select org_id from biz_org where user_id = #{params.executorUserId} and org_type = 'executor'))) as assigned_amount
or bpa4.execution_unit_id = (select org_id from biz_org where user_id = #{params.executorUserId} and org_type = 'executor'))) as assigned_amount,
(select count(*) from biz_meeting m where m.project_id = p.project_id and m.is_deleted = 0) as meeting_count
from biz_project p
join biz_project_assign a on a.project_id = p.project_id and a.is_deleted = 0
<!-- 执行方专属 join: 把 executor 限定条件放进 ON (而不是 WHERE), 这样 join 只命中分给当前执行方的 assignment, 1 行/项目. SELECT DISTINCT 保留以防 LEFT JOIN 副作用 -->
join biz_project_assign a on a.project_id = p.project_id
and a.is_deleted = 0
and (a.exec_user_id = #{params.executorUserId}
or a.execution_unit_id = (select org_id from biz_org where user_id = #{params.executorUserId} and org_type = 'executor'))
left join biz_org o on o.user_id = p.sponsor_admin_user_id and o.org_type = 'sponsor'
left join sys_user lu on lu.user_id = p.lead_user_id
left join biz_person bp on bp.user_id = p.create_user_id
<where>
p.is_deleted = 0
(a.exec_user_id = #{params.executorUserId}
or a.execution_unit_id = (select org_id from biz_org where user_id = #{params.executorUserId} and org_type = 'executor'))
<if test="projectNo != null and projectNo != ''">and p.project_no like concat('%', #{projectNo}, '%')</if>
<if test="projectName != null and projectName != ''">and p.project_name like concat('%', #{projectName}, '%')</if>
<if test="startTime != null">and p.start_time &gt;= #{startTime}</if>
<if test="endTime != null">and p.end_time &lt;= #{endTime}</if>
<if test="isFinished != null and isFinished != ''">and p.is_finished = #{isFinished}</if>
</where>
order by p.project_id desc
</select>
<!--
executor 执行人 (SUB 子账号) 专属列表: 反查 biz_project_executor_assign.staff_user_id
(主账号把执行人派到项目上后, 执行人登录看自己被派到的项目)
严格隔离: 不 JOIN biz_project_assign (避免主账号过滤), 不 UNION intent (executor 与 biz_*_intent 完全无关)
场次/金额列 assigned_sessions/assigned_amount 按 params.executorUserId (主账号/本公司) 聚合 — 执行人看到的是公司数据, 不是个人数据
-->
<select id="selectExecutorStaffList" resultMap="BizProjectResult" parameterType="BizProject">
select p.project_id, p.project_no, p.project_name, p.total_sessions, p.done_sessions, p.todo_sessions, p.total_amount, p.available_amount, p.paid_labor_amount, p.paid_meeting_amount, p.manager_score, p.sponsor_score, p.sponsor_admin_user_name, p.project_form, p.is_finished, p.is_settled, p.sponsor_admin_user_id, p.lead_user_id, p.is_bid_project, p.create_by, p.create_user_id, p.create_time, p.update_by, p.update_time, p.manage_fee, p.role_labor, p.start_time, p.end_time, p.submit_deadline_days, p.support_contract_url, p.execute_contract_url, p.invitation_url, p.support_letter_url, p.publish_url,
o.org_name as sponsor_org_name,
lu.user_name as lead_user_name,
bp.name as create_user_name,
(select group_concat(distinct o2.org_name separator ',')
from biz_project_assign bpa2
join biz_org o2 on o2.org_id = bpa2.execution_unit_id and o2.org_type = 'executor'
where bpa2.project_id = p.project_id and bpa2.is_deleted = 0) as exec_org_names,
(select coalesce(sum(bpa3.sessions), 0)
from biz_project_assign bpa3
where bpa3.project_id = p.project_id
and bpa3.is_deleted = 0
and (bpa3.exec_user_id = #{params.executorUserId}
or bpa3.execution_unit_id = (select org_id from biz_org where user_id = #{params.executorUserId} and org_type = 'executor'))) as assigned_sessions,
(select coalesce(sum(bpa4.amount), 0)
from biz_project_assign bpa4
where bpa4.project_id = p.project_id
and bpa4.is_deleted = 0
and (bpa4.exec_user_id = #{params.executorUserId}
or bpa4.execution_unit_id = (select org_id from biz_org where user_id = #{params.executorUserId} and org_type = 'executor'))) as assigned_amount,
(select count(*) from biz_meeting m where m.project_id = p.project_id and m.is_deleted = 0) as meeting_count
from biz_project p
left join biz_org o on o.user_id = p.sponsor_admin_user_id and o.org_type = 'sponsor'
left join sys_user lu on lu.user_id = p.lead_user_id
left join biz_person bp on bp.user_id = p.create_user_id
<where>
p.is_deleted = 0
and p.project_id in (
select distinct a.project_id
from biz_project_executor_assign a
where a.is_deleted = 0 and a.staff_user_id = #{params.executorStaffUserId}
)
<if test="projectNo != null and projectNo != ''">and p.project_no like concat('%', #{projectNo}, '%')</if>
<if test="projectName != null and projectName != ''">and p.project_name like concat('%', #{projectName}, '%')</if>
<if test="startTime != null">and p.start_time &gt;= #{startTime}</if>
@@ -324,4 +401,57 @@
<select id="selectProjectNoById" resultType="String" parameterType="Long">
select project_no from biz_project where project_id = #{projectId}
</select>
<!-- 建会限额用: 某项目分配给该执行方 (MAIN) 的总场次 = biz_project_assign.sessions 之和 (exec_user_id 或 其执行单位 org) -->
<select id="countAssignedSessions" resultType="int">
select coalesce(sum(a.sessions), 0)
from biz_project_assign a
where a.project_id = #{projectId}
and a.is_deleted = 0
and (a.exec_user_id = #{executorUserId}
or a.execution_unit_id = (select org_id from biz_org where user_id = #{executorUserId} and org_type = 'executor'))
</select>
<!-- 提交权限用: 判断 user 是否该项目的执行方.
MAIN: biz_project_assign (exec_user_id 或 其执行单位 org); SUB(执行人): biz_project_executor_assign.staff_user_id.
与会议列表 executor 可见性 (BizMeetingMapper.selectList) 同源, 替代原来的 biz_meeting_executor (会议级执行人员) 判定.
注: biz_project_executor_assign.project_id 是 varchar, 与 Long #{projectId} 比较走 MySQL 隐式转换 (与 selectExecutorStaffList 同款). -->
<select id="countExecutorOfProject" resultType="int">
select count(*) from (
select 1
from biz_project_assign a
where a.project_id = #{projectId}
and a.is_deleted = 0
and (a.exec_user_id = #{userId}
or a.execution_unit_id = (select org_id from biz_org where user_id = #{userId} and org_type = 'executor'))
union
select 1
from biz_project_executor_assign b
where b.project_id = #{projectId}
and b.is_deleted = 0
and b.staff_user_id = #{userId}
) t
</select>
<!-- 会议结算后重算项目金额: 全量 SUM 已结算会议 (is_settled=1) 的 labor_fee/meeting_fee,
幂等回写 paid_labor_amount / paid_meeting_amount, 并重算 available_amount = 总金额 - 管理费 - 已支付劳务 - 已支付会务.
单条 UPDATE 原子执行, 多会议并发结算同一项目时无丢失更新 (每次都是全量重算). -->
<update id="recomputeSettledAmounts" parameterType="Long">
update biz_project p
set p.paid_labor_amount = (
select ifnull(sum(m.labor_fee), 0)
from biz_meeting m
where m.project_id = p.project_id and m.is_settled = 1 and m.is_deleted = 0
),
p.paid_meeting_amount = (
select ifnull(sum(m.meeting_fee), 0)
from biz_meeting m
where m.project_id = p.project_id and m.is_settled = 1 and m.is_deleted = 0
),
p.available_amount = p.total_amount - ifnull(p.manage_fee, 0)
- (select ifnull(sum(m.labor_fee), 0)
from biz_meeting m
where m.project_id = p.project_id and m.is_settled = 1 and m.is_deleted = 0)
- (select ifnull(sum(m.meeting_fee), 0)
from biz_meeting m
where m.project_id = p.project_id and m.is_settled = 1 and m.is_deleted = 0)
where p.project_id = #{projectId}
</update>
</mapper>
@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.business.mapper.BizSupportIntentMapper">
<resultMap type="BizSupportIntent" id="BizSupportIntentResult">
<id property="intentId" column="intent_id" />
<result property="projectNo" column="project_no" />
<result property="projectName" column="project_name" />
<result property="name" column="name" />
<result property="workUnit" column="work_unit" />
<result property="department" column="department" />
<result property="position" column="position" />
<result property="phone" column="phone" />
<result property="accountStatus" column="account_status" />
<result property="createTime" column="create_time" />
</resultMap>
<sql id="selectFields">
select intent_id, project_no, project_name, name, work_unit, department, position, phone, account_status, create_by, create_time, update_by, update_time
from biz_support_intent
</sql>
<select id="selectByPrimaryKey" resultMap="BizSupportIntentResult" parameterType="String">
<include refid="selectFields"/>
where intent_id = #{intentId}
</select>
<select id="selectList" resultMap="BizSupportIntentResult" parameterType="BizSupportIntent">
<include refid="selectFields"/>
<where>
<if test="name != null and name != ''"> and name = #{name}</if>
<if test="department != null and department != ''"> and department = #{department}</if>
<if test="position != null and position != ''"> and position = #{position}</if>
<if test="phone != null and phone != ''"> and phone = #{phone}</if>
</where>
order by intent_id desc
</select>
<insert id="insert" parameterType="BizSupportIntent">
insert into biz_support_intent
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="intentId != null and intentId != ''">intent_id,</if>
<if test="name != null">name,</if>
<if test="department != null">department,</if>
<if test="position != null">position,</if>
<if test="phone != null">phone,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="intentId != null and intentId != ''">#{intentId},</if>
<if test="name != null">#{name},</if>
<if test="department != null">#{department},</if>
<if test="position != null">#{position},</if>
<if test="phone != null">#{phone},</if>
</trim>
</insert>
<update id="updateByPrimaryKey" parameterType="BizSupportIntent">
update biz_support_intent
<trim prefix="SET" suffixOverrides=",">
<if test="projectNo != null and projectNo != ''">project_no = #{projectNo},</if>
<if test="projectName != null and projectName != ''">project_name = #{projectName},</if>
<if test="workUnit != null and workUnit != ''">work_unit = #{workUnit},</if>
<if test="accountStatus != null and accountStatus != ''">account_status = #{accountStatus},</if>
<if test="name != null">name = #{name},</if>
<if test="department != null">department = #{department},</if>
<if test="position != null">position = #{position},</if>
<if test="phone != null">phone = #{phone},</if>
</trim>
where intent_id = #{intentId}
</update>
<delete id="deleteByPrimaryKey" parameterType="String">
delete from biz_support_intent where intent_id = #{intentId}
</delete>
<delete id="deleteByPrimaryKeys" parameterType="String">
delete from biz_support_intent where intent_id in
<foreach collection="intentIds" item="intentId" open="(" separator="," close=")">
#{intentId}
</foreach>
</delete>
</mapper>