feat(work-results): support historical project outcomes
This commit is contained in:
@@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
- 管理端业务路由集中在 `frontend/src/router/index.ts`,覆盖首页、AI 面试、训练运营、问题榜、案例、知识空间、SOP、资料处理、系统配置、组织权限和成长激励;侧栏过滤在 `frontend/src/layout/components/Sidebar/index.vue`,不要恢复若依默认全量菜单。
|
- 管理端业务路由集中在 `frontend/src/router/index.ts`,覆盖首页、AI 面试、训练运营、问题榜、案例、知识空间、SOP、资料处理、系统配置、组织权限和成长激励;侧栏过滤在 `frontend/src/layout/components/Sidebar/index.vue`,不要恢复若依默认全量菜单。
|
||||||
- 用户侧只改 `mobile-uni/src/pages/*`;员工、候选人、主管首页分别为 `/pages/user/today/index`、`/pages/candidate/index/index`、`/pages/supervisor/index/index`,旧 `/h5/user|candidate|supervisor|employee|admin` 只做兼容重定向。底栏固定「今日/练/问/我」;问师傅文字和现场附件查询复用 `/api/knowledge/query`、`/api/knowledge/query-media`,内部登录端可带 `conversationId/contextVersion` 使用 30 分钟、最近 6 轮的短期会话,原文件/视频只通过 `/api/knowledge/resources/{attachmentId}/content` 重新鉴权交付;总结卡复用 `/api/knowledge/summary-card`,失败不兜假数据。显式“记一下/帮我记/保存一下”复用同一 query 返回确认卡,确认后按 `PRIVATE/COMPANY` 写 `aihr_assistant_capture`;COMPANY 只表示 `PENDING` 待流转,不得冒充已派单或已送达。外部知识 API 保持无状态。
|
- 用户侧只改 `mobile-uni/src/pages/*`;员工、候选人、主管首页分别为 `/pages/user/today/index`、`/pages/candidate/index/index`、`/pages/supervisor/index/index`,旧 `/h5/user|candidate|supervisor|employee|admin` 只做兼容重定向。底栏固定「今日/练/问/我」;问师傅文字和现场附件查询复用 `/api/knowledge/query`、`/api/knowledge/query-media`,内部登录端可带 `conversationId/contextVersion` 使用 30 分钟、最近 6 轮的短期会话,原文件/视频只通过 `/api/knowledge/resources/{attachmentId}/content` 重新鉴权交付;总结卡复用 `/api/knowledge/summary-card`,失败不兜假数据。显式“记一下/帮我记/保存一下”复用同一 query 返回确认卡,确认后按 `PRIVATE/COMPANY` 写 `aihr_assistant_capture`;COMPANY 只表示 `PENDING` 待流转,不得冒充已派单或已送达。外部知识 API 保持无状态。
|
||||||
- 术语和数据边界:工作助手承载日常事实与确认式记忆;现有 `/pages/user/report/index`、`/api/aihr/work-report/**` 和 `aihr_work_report` 只承载 CASE/VIDEO/SOP/KNOWLEDGE 四类投稿审核,产品名为“成果投稿”;“今日工作成果”按员工 + 项目 + 自然日从已确认记录确定性聚合,员工页为 `/pages/user/work-results/index`,主管页为 `/pages/supervisor/work-results/index`。不要把三条链路合表或自动互写。多项目交互只让用户按项目名称选择,内部项目编码必须由服务端按当前身份复核,不让用户输入编码。
|
- 术语和数据边界:工作助手承载日常事实与确认式记忆;现有 `/pages/user/report/index`、`/api/aihr/work-report/**` 和 `aihr_work_report` 只承载 CASE/VIDEO/SOP/KNOWLEDGE 四类投稿审核,产品名为“成果投稿”;“今日工作成果”按员工 + 项目 + 自然日从已确认记录确定性聚合,员工页为 `/pages/user/work-results/index`,主管页为 `/pages/supervisor/work-results/index`,默认当日但允许查看历史日期且不得选择未来日期。不要把三条链路合表或自动互写。多项目交互只让用户按项目名称选择,内部项目编码必须由服务端按当前身份复核,不让用户输入编码。
|
||||||
- 不要把三端概括成全模态:员工对练/每日题和问师傅支持文字/语音,问师傅图片/视频只用于本次分析,案例传音频;候选面试支持文字/语音,资料可传文档/图片但图片不参与评分;主管写文字复盘/指派并回听录音。员工对练图像/视频情境、服务录像回放和方言正式验收仍未完成;语音继续复用 `services/speech.ts` 与 `/api/ai/asr`。
|
- 不要把三端概括成全模态:员工对练/每日题和问师傅支持文字/语音,问师傅图片/视频只用于本次分析,案例传音频;候选面试支持文字/语音,资料可传文档/图片但图片不参与评分;主管写文字复盘/指派并回听录音。员工对练图像/视频情境、服务录像回放和方言正式验收仍未完成;语音继续复用 `services/speech.ts` 与 `/api/ai/asr`。
|
||||||
- 问题榜奖励只记积分/学习学分,不得擅自增加现金、提现、预算或税务语义;选最佳答案和考试提交必须保持请求幂等。
|
- 问题榜奖励只记积分/学习学分,不得擅自增加现金、提现、预算或税务语义;选最佳答案和考试提交必须保持请求幂等。
|
||||||
- 全网 AI 与企业问师傅必须分入口、分来源和分免责声明;提供方仅允许公网 HTTPS,修改地址/密钥后必须连接测试成功才能启用,未配置时明确不可用且不生成假答案。
|
- 全网 AI 与企业问师傅必须分入口、分来源和分免责声明;提供方仅允许公网 HTTPS,修改地址/密钥后必须连接测试成功才能启用,未配置时明确不可用且不生成假答案。
|
||||||
@@ -80,6 +80,7 @@
|
|||||||
- AI 陪练二期推进(阶段一执行):`docs/AI陪练二期开发推进计划.md`
|
- AI 陪练二期推进(阶段一执行):`docs/AI陪练二期开发推进计划.md`
|
||||||
- 数字师傅产品定案(2026-07-08 学练问报/问题榜/学分):`docs/20260708/数字师傅学练问报整合方案.md`
|
- 数字师傅产品定案(2026-07-08 学练问报/问题榜/学分):`docs/20260708/数字师傅学练问报整合方案.md`
|
||||||
- 工作助手后续施工顺序:`docs/工作助手与今日工作成果迭代计划-20260721.md`
|
- 工作助手后续施工顺序:`docs/工作助手与今日工作成果迭代计划-20260721.md`
|
||||||
|
- 产品功能优先级与本轮增量:`docs/银城帮道产品功能优先级与实施计划-20260721.md`
|
||||||
- AI 陪练缺口修订:`docs/AI陪练功能优化与缺口分析(修订).md`
|
- AI 陪练缺口修订:`docs/AI陪练功能优化与缺口分析(修订).md`
|
||||||
- 后端 API 对接:`docs/API_INTEGRATION.md`
|
- 后端 API 对接:`docs/API_INTEGRATION.md`
|
||||||
- 对外会议版口径:`docs/AI人力资源系统项目规划方案与AI接口说明.md`
|
- 对外会议版口径:`docs/AI人力资源系统项目规划方案与AI接口说明.md`
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
| [后端 API 对接指南](docs/API_INTEGRATION.md) | 后端模块落点、正式试点 CSV、移动端训练/复盘、候选资料上传/审核和 SOP 上传解析接口 |
|
| [后端 API 对接指南](docs/API_INTEGRATION.md) | 后端模块落点、正式试点 CSV、移动端训练/复盘、候选资料上传/审核和 SOP 上传解析接口 |
|
||||||
| [问师傅多轮会话与原始资料交付设计](docs/20260718/问师傅多轮会话与原始资料交付设计.md) | 已实现的短期会话、指代改写、意图路由、原文件/视频安全交付及分层验收记录 |
|
| [问师傅多轮会话与原始资料交付设计](docs/20260718/问师傅多轮会话与原始资料交付设计.md) | 已实现的短期会话、指代改写、意图路由、原文件/视频安全交付及分层验收记录 |
|
||||||
| [工作助手与今日工作成果迭代计划](docs/工作助手与今日工作成果迭代计划-20260721.md) | 项目名称选择、日常采集、来源追溯、今日成果、主管视图、成果投稿和外部流转的实施顺序;含当前本地实现、390×844 证据、权威高保真路径和剩余生产边界 |
|
| [工作助手与今日工作成果迭代计划](docs/工作助手与今日工作成果迭代计划-20260721.md) | 项目名称选择、日常采集、来源追溯、今日成果、主管视图、成果投稿和外部流转的实施顺序;含当前本地实现、390×844 证据、权威高保真路径和剩余生产边界 |
|
||||||
|
| [银城帮道产品功能优先级与实施计划](docs/银城帮道产品功能优先级与实施计划-20260721.md) | 《银城帮道介绍》十项能力的事实校正、P0–P3 优先级、明确不做项和本轮实施证据 |
|
||||||
| [BRD 功能审查记录](docs/BRD_IMPLEMENTATION_AUDIT.md) | 现阶段结论、三端输入匹配矩阵、权限、迁移、线上资源和未完成项证据 |
|
| [BRD 功能审查记录](docs/BRD_IMPLEMENTATION_AUDIT.md) | 现阶段结论、三端输入匹配矩阵、权限、迁移、线上资源和未完成项证据 |
|
||||||
| [BRD 生产迁移 Runbook](docs/BRD_PRODUCTION_MIGRATION_RUNBOOK.md) | 生产 schema/内置内容迁移、只读预检、发布和回滚边界 |
|
| [BRD 生产迁移 Runbook](docs/BRD_PRODUCTION_MIGRATION_RUNBOOK.md) | 生产 schema/内置内容迁移、只读预检、发布和回滚边界 |
|
||||||
| [ruoyi-ai 能力分片迁移计划](docs/RUOYI_AI_INCREMENTAL_MIGRATION.md) | 知识库、模型能力、文档解析、RAG、chat 的渐进迁移边界 |
|
| [ruoyi-ai 能力分片迁移计划](docs/RUOYI_AI_INCREMENTAL_MIGRATION.md) | 知识库、模型能力、文档解析、RAG、chat 的渐进迁移边界 |
|
||||||
|
|||||||
+2
-2
@@ -1387,9 +1387,9 @@ public class AihrMemoryService {
|
|||||||
return new java.util.LinkedHashSet<>(jdbcTemplate.queryForList("""
|
return new java.util.LinkedHashSet<>(jdbcTemplate.queryForList("""
|
||||||
select distinct project_code
|
select distinct project_code
|
||||||
from aihr_org_snapshot
|
from aihr_org_snapshot
|
||||||
where tenant_id = ? and ext_party_id = ? and employment_status = 'active'
|
where tenant_id = ? and (ext_party_id = ? or person_phone = ?) and employment_status = 'active'
|
||||||
and position_level in ('主管', '项目经理')
|
and position_level in ('主管', '项目经理')
|
||||||
""", String.class, principal.tenantId(), principal.extPartyId()));
|
""", String.class, principal.tenantId(), principal.extPartyId(), principal.extPartyId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServiceMemoryResponse mapServiceMemory(ResultSet rs, List<ServiceMemoryVersionResponse> versions)
|
private ServiceMemoryResponse mapServiceMemory(ResultSet rs, List<ServiceMemoryVersionResponse> versions)
|
||||||
|
|||||||
+8
-3
@@ -212,21 +212,26 @@ public class AihrWorkResultService {
|
|||||||
if (!"app_user".equals(principal.userType())) return;
|
if (!"app_user".equals(principal.userType())) return;
|
||||||
Integer rows = jdbcTemplate.queryForObject("""
|
Integer rows = jdbcTemplate.queryForObject("""
|
||||||
select count(*) from aihr_org_snapshot
|
select count(*) from aihr_org_snapshot
|
||||||
where tenant_id = ? and ext_party_id = ? and project_code = ?
|
where tenant_id = ? and (ext_party_id = ? or person_phone = ?) and project_code = ?
|
||||||
and employment_status = 'active' and position_level in ('主管', '项目经理')
|
and employment_status = 'active' and position_level in ('主管', '项目经理')
|
||||||
""", Integer.class, principal.tenantId(), principal.extPartyId(), projectCode);
|
""", Integer.class, principal.tenantId(), principal.extPartyId(), principal.extPartyId(), projectCode);
|
||||||
if (rows == null || rows < 1) {
|
if (rows == null || rows < 1) {
|
||||||
throw new ServiceException("当前项目没有主管权限", HttpStatus.FORBIDDEN);
|
throw new ServiceException("当前项目没有主管权限", HttpStatus.FORBIDDEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LocalDate workDate(String raw) {
|
private static LocalDate workDate(String raw) {
|
||||||
|
LocalDate date;
|
||||||
if (raw == null || raw.isBlank()) return LocalDate.now(WORK_ZONE);
|
if (raw == null || raw.isBlank()) return LocalDate.now(WORK_ZONE);
|
||||||
try {
|
try {
|
||||||
return LocalDate.parse(raw.trim());
|
date = LocalDate.parse(raw.trim());
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new ServiceException("workDate 必须为 yyyy-MM-dd", HttpStatus.BAD_REQUEST);
|
throw new ServiceException("workDate 必须为 yyyy-MM-dd", HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
if (date.isAfter(LocalDate.now(WORK_ZONE))) {
|
||||||
|
throw new ServiceException("workDate 不能晚于当天", HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String json(Object value) {
|
private String json(Object value) {
|
||||||
|
|||||||
+22
@@ -9,6 +9,7 @@ import org.dromara.aihr.memory.AihrMemoryDto.ServiceMemoryVersionResponse;
|
|||||||
import org.dromara.common.core.exception.ServiceException;
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
import org.junit.jupiter.api.Tag;
|
import org.junit.jupiter.api.Tag;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.core.PreparedStatementCreator;
|
import org.springframework.jdbc.core.PreparedStatementCreator;
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
@@ -25,6 +26,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
@@ -32,6 +34,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
|
||||||
@Tag("dev")
|
@Tag("dev")
|
||||||
class AihrMemoryServiceTest {
|
class AihrMemoryServiceTest {
|
||||||
@@ -159,6 +164,23 @@ class AihrMemoryServiceTest {
|
|||||||
assertTrue(plan.missingFields().isEmpty());
|
assertTrue(plan.missingFields().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phoneMappedSupervisorCanReadProjectCaptureSource() {
|
||||||
|
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
||||||
|
AihrKnowledgePrincipalResolver resolver = mock(AihrKnowledgePrincipalResolver.class);
|
||||||
|
AihrKnowledgePrincipal supervisor = new AihrKnowledgePrincipal(
|
||||||
|
"000000", 7L, "app_user", "13900000101", Set.of("supervisor"), Set.of("P2"), "app");
|
||||||
|
when(resolver.current()).thenReturn(supervisor);
|
||||||
|
ArgumentCaptor<Object[]> projectsArgs = ArgumentCaptor.forClass(Object[].class);
|
||||||
|
when(jdbc.queryForList(anyString(), eq(String.class), projectsArgs.capture())).thenReturn(List.of("P2"));
|
||||||
|
when(jdbc.query(anyString(), any(RowMapper.class), any(Object[].class)))
|
||||||
|
.thenReturn(List.of("{\"ossId\":123}"));
|
||||||
|
AihrMemoryService service = new AihrMemoryService(jdbc, new ObjectMapper(), resolver);
|
||||||
|
|
||||||
|
assertEquals(123L, service.authorizedCaptureSourceOssId(9L));
|
||||||
|
assertArrayEquals(new Object[]{"000000", "13900000101", "13900000101"}, projectsArgs.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sameConversationLocationFollowupUpgradesOriginalNeedsInputCandidate() throws Exception {
|
void sameConversationLocationFollowupUpgradesOriginalNeedsInputCandidate() throws Exception {
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|||||||
+39
-1
@@ -8,16 +8,21 @@ import org.dromara.aihr.workresult.AihrWorkResultDto.WorkResultResponse;
|
|||||||
import org.dromara.common.core.exception.ServiceException;
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
import org.junit.jupiter.api.Tag;
|
import org.junit.jupiter.api.Tag;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@@ -52,13 +57,46 @@ class AihrWorkResultServiceTest {
|
|||||||
AihrKnowledgePrincipalResolver resolver = mock(AihrKnowledgePrincipalResolver.class);
|
AihrKnowledgePrincipalResolver resolver = mock(AihrKnowledgePrincipalResolver.class);
|
||||||
when(resolver.current()).thenReturn(new AihrKnowledgePrincipal(
|
when(resolver.current()).thenReturn(new AihrKnowledgePrincipal(
|
||||||
"000000", 7L, "app_user", "EMP-1", Set.of("employee", "supervisor"), Set.of("P1", "P2"), "app"));
|
"000000", 7L, "app_user", "EMP-1", Set.of("employee", "supervisor"), Set.of("P1", "P2"), "app"));
|
||||||
when(jdbc.queryForObject(anyString(), any(Class.class), any(Object[].class))).thenReturn(0);
|
ArgumentCaptor<String> sql = ArgumentCaptor.forClass(String.class);
|
||||||
|
when(jdbc.queryForObject(sql.capture(), any(Class.class), any(Object[].class))).thenReturn(0);
|
||||||
AihrWorkResultService service = new AihrWorkResultService(jdbc, new ObjectMapper(), resolver);
|
AihrWorkResultService service = new AihrWorkResultService(jdbc, new ObjectMapper(), resolver);
|
||||||
|
|
||||||
ServiceException error = assertThrows(ServiceException.class, () -> service.project("P2", "2026-07-21"));
|
ServiceException error = assertThrows(ServiceException.class, () -> service.project("P2", "2026-07-21"));
|
||||||
|
|
||||||
assertEquals(403, error.getCode());
|
assertEquals(403, error.getCode());
|
||||||
assertEquals("当前项目没有主管权限", error.getMessage());
|
assertEquals("当前项目没有主管权限", error.getMessage());
|
||||||
|
assertTrue(sql.getValue().contains("ext_party_id = ? or person_phone = ?"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phoneMappedSupervisorCanReadOwnProject() {
|
||||||
|
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
||||||
|
AihrKnowledgePrincipalResolver resolver = mock(AihrKnowledgePrincipalResolver.class);
|
||||||
|
when(resolver.current()).thenReturn(new AihrKnowledgePrincipal(
|
||||||
|
"000000", 7L, "app_user", "13900000101", Set.of("supervisor"), Set.of("P2"), "app"));
|
||||||
|
ArgumentCaptor<Object[]> args = ArgumentCaptor.forClass(Object[].class);
|
||||||
|
when(jdbc.queryForObject(anyString(), any(Class.class), args.capture())).thenReturn(1);
|
||||||
|
when(jdbc.queryForList(anyString(), any(Class.class), any(Object[].class))).thenReturn(List.of());
|
||||||
|
AihrWorkResultService service = new AihrWorkResultService(jdbc, new ObjectMapper(), resolver);
|
||||||
|
|
||||||
|
service.project("P2", "2026-07-21");
|
||||||
|
|
||||||
|
assertArrayEquals(new Object[]{"000000", "13900000101", "13900000101", "P2"}, args.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void futureWorkDateIsRejectedBeforeAnyWrite() {
|
||||||
|
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
||||||
|
AihrKnowledgePrincipalResolver resolver = mock(AihrKnowledgePrincipalResolver.class);
|
||||||
|
when(resolver.current()).thenReturn(new AihrKnowledgePrincipal(
|
||||||
|
"000000", 7L, "app_user", "EMP-1", Set.of("employee"), Set.of("P1"), "app"));
|
||||||
|
AihrWorkResultService service = new AihrWorkResultService(jdbc, new ObjectMapper(), resolver);
|
||||||
|
|
||||||
|
ServiceException error = assertThrows(ServiceException.class, () -> service.generateMine("P1",
|
||||||
|
LocalDate.now(ZoneId.of("Asia/Shanghai")).plusDays(1).toString()));
|
||||||
|
|
||||||
|
assertEquals(400, error.getCode());
|
||||||
|
assertEquals("workDate 不能晚于当天", error.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static WorkResultResponse response(int version, String generatedAt, String status) {
|
private static WorkResultResponse response(int version, String generatedAt, String status) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
| 案例媒体安全 | `GET /api/knowledge/case/records/{caseId}/media` | 案例详情只返回 `mediaOssId`,不返回原始 `sys_oss.url`;媒体下载会重复执行登录、后台角色或 APP 项目范围校验,再由服务端流式读取 OSS。管理端与 `mobile-uni` 通过鉴权 blob/temp 文件播放,关闭详情页时释放本地对象 URL |
|
| 案例媒体安全 | `GET /api/knowledge/case/records/{caseId}/media` | 案例详情只返回 `mediaOssId`,不返回原始 `sys_oss.url`;媒体下载会重复执行登录、后台角色或 APP 项目范围校验,再由服务端流式读取 OSS。管理端与 `mobile-uni` 通过鉴权 blob/temp 文件播放,关闭详情页时释放本地对象 URL |
|
||||||
| 问师傅与 SOP 知识库 `/pages/user/sop/index`、`/knowledge/sop` | `POST /api/knowledge/query`、`POST /api/knowledge/query-media`、`GET /api/knowledge/resources/{attachmentId}/content`、`POST /api/knowledge/answer-feedback`、`GET /api/knowledge/position-sop`、`GET /api/aihr/mobile/onboard/tasks`、`POST /api/aihr/mobile/onboard/tasks/{id}/complete`、`GET /api/aihr/mobile/qualification`、`POST /api/knowledge/doc/upload` | 文字/现场附件查询走统一知识空间授权和 MySQL Fulltext + Qdrant 混合召回;内部登录端可选传 `conversationId/contextVersion`,服务端保留 30 分钟不活跃过期、最近 6 轮脱敏截断上下文,用现有 chat 模型或保守规则输出 `QA/FILE/VIDEO/DATA_TOOL` 和 `rewrittenQuery`,旧版本冲突返回 `409`。原文件/视频仅从当前命中且仍有授权的附件返回,内容接口每次重新计算租户、应用和主体权限;无资源时明确返回空列表,不生成假链接。旧客户端和外部 API 保持单轮无状态。搜索继续返回 `reviewId/promptVersion`,员工反馈保存评审批次;详细边界见 [专项设计](20260718/问师傅多轮会话与原始资料交付设计.md)。员工学习页按当前 APP 身份读取正式岗前/入职任务,资格证据无正式数据时明确返回 `NOT_CONFIGURED` |
|
| 问师傅与 SOP 知识库 `/pages/user/sop/index`、`/knowledge/sop` | `POST /api/knowledge/query`、`POST /api/knowledge/query-media`、`GET /api/knowledge/resources/{attachmentId}/content`、`POST /api/knowledge/answer-feedback`、`GET /api/knowledge/position-sop`、`GET /api/aihr/mobile/onboard/tasks`、`POST /api/aihr/mobile/onboard/tasks/{id}/complete`、`GET /api/aihr/mobile/qualification`、`POST /api/knowledge/doc/upload` | 文字/现场附件查询走统一知识空间授权和 MySQL Fulltext + Qdrant 混合召回;内部登录端可选传 `conversationId/contextVersion`,服务端保留 30 分钟不活跃过期、最近 6 轮脱敏截断上下文,用现有 chat 模型或保守规则输出 `QA/FILE/VIDEO/DATA_TOOL` 和 `rewrittenQuery`,旧版本冲突返回 `409`。原文件/视频仅从当前命中且仍有授权的附件返回,内容接口每次重新计算租户、应用和主体权限;无资源时明确返回空列表,不生成假链接。旧客户端和外部 API 保持单轮无状态。搜索继续返回 `reviewId/promptVersion`,员工反馈保存评审批次;详细边界见 [专项设计](20260718/问师傅多轮会话与原始资料交付设计.md)。员工学习页按当前 APP 身份读取正式岗前/入职任务,资格证据无正式数据时明确返回 `NOT_CONFIGURED` |
|
||||||
| 工作助手确认式统一采集 `/pages/user/sop/index`、`/pages/user/assistant/memories` | 既有 `POST /api/knowledge/query` 和 `POST /api/knowledge/query-media` 可选返回 `memoryCandidate`;`GET /api/aihr/personal-assistant/memory-candidates?status=DRAFT`、`POST /api/aihr/personal-assistant/memory-candidates/{id}/confirm`、`POST .../{id}/dismiss`、`GET /api/aihr/personal-assistant/assistant-captures?limit=50`、`POST /assistant-captures/{id}/status`、`GET /assistant-captures/{id}/status-history`、`GET /assistant-captures/{id}/source`;旧项目记录继续使用 `/api/aihr/service-memories` | 文字、ASR 转写和现场媒体复用同一候选检测。项目工作必须从 `/api/aihr/mobile/me` 返回的项目名称列表选择当前项目,内部 `projectCode` 仍由服务端按当前登录主体授权收窄;个人笔记可以不绑定项目。显式“记一下”生成 `ASSISTANT_CAPTURE/DRAFT`,系统建议 `ATTENDANCE/INSPECTION/SERVICE_LEAD/RESIDENT_PROFILE/CASE/PERSONAL_NOTE/FOLLOW_UP/PROJECT_NOTE`。确认必须带 `expectedVersion + idempotencyKey + saveScope(PRIVATE|COMPANY)`;PRIVATE 仅本人可见,COMPANY 写入 PENDING 待流转。状态更新只允许 `RECORDED/PENDING/IN_PROGRESS/COMPLETED/VOID`,幂等键不得跨记录或目标状态复用。来源文件重新鉴权交付;正式外部接收端未配置前不得显示已送达 |
|
| 工作助手确认式统一采集 `/pages/user/sop/index`、`/pages/user/assistant/memories` | 既有 `POST /api/knowledge/query` 和 `POST /api/knowledge/query-media` 可选返回 `memoryCandidate`;`GET /api/aihr/personal-assistant/memory-candidates?status=DRAFT`、`POST /api/aihr/personal-assistant/memory-candidates/{id}/confirm`、`POST .../{id}/dismiss`、`GET /api/aihr/personal-assistant/assistant-captures?limit=50`、`POST /assistant-captures/{id}/status`、`GET /assistant-captures/{id}/status-history`、`GET /assistant-captures/{id}/source`;旧项目记录继续使用 `/api/aihr/service-memories` | 文字、ASR 转写和现场媒体复用同一候选检测。项目工作必须从 `/api/aihr/mobile/me` 返回的项目名称列表选择当前项目,内部 `projectCode` 仍由服务端按当前登录主体授权收窄;个人笔记可以不绑定项目。显式“记一下”生成 `ASSISTANT_CAPTURE/DRAFT`,系统建议 `ATTENDANCE/INSPECTION/SERVICE_LEAD/RESIDENT_PROFILE/CASE/PERSONAL_NOTE/FOLLOW_UP/PROJECT_NOTE`。确认必须带 `expectedVersion + idempotencyKey + saveScope(PRIVATE|COMPANY)`;PRIVATE 仅本人可见,COMPANY 写入 PENDING 待流转。状态更新只允许 `RECORDED/PENDING/IN_PROGRESS/COMPLETED/VOID`,幂等键不得跨记录或目标状态复用。来源文件重新鉴权交付;正式外部接收端未配置前不得显示已送达 |
|
||||||
| 今日工作成果 `/pages/user/work-results/index`、`/pages/supervisor/work-results/index` | 员工 `POST /api/aihr/work-results/mine/generate?projectCode=...&workDate=YYYY-MM-DD`;主管 `GET /api/aihr/work-results/project?projectCode=...&workDate=YYYY-MM-DD` | 按员工 + 项目 + Asia/Shanghai 自然日确定性聚合已确认记录;内容未变化时重复生成不增加版本,记录或状态变化后版本递增。主管只可查看本人在目标项目具备主管身份的团队结果,普通员工或跨项目请求返回 403。外部接口未接通时只显示本地状态和 PENDING,不伪造派单或考勤同步成功 |
|
| 今日工作成果 `/pages/user/work-results/index`、`/pages/supervisor/work-results/index` | 员工 `POST /api/aihr/work-results/mine/generate?projectCode=...&workDate=YYYY-MM-DD`;主管 `GET /api/aihr/work-results/project?projectCode=...&workDate=YYYY-MM-DD` | 按员工 + 项目 + Asia/Shanghai 自然日确定性聚合已确认记录;两页默认当天并可按日期查看历史成果,客户端禁止选择未来日期。内容未变化时重复生成不增加版本,记录或状态变化后版本递增。主管只可查看本人在目标项目具备主管身份的团队结果;APP 登录身份同时兼容组织快照中的外部人员 ID 与手机号,普通员工或跨项目请求返回 403。外部接口未接通时只显示本地状态和 PENDING,不伪造派单或考勤同步成功 |
|
||||||
| 成果投稿(当前页面名“工作上报”)`/pages/user/report/index` | `POST /api/aihr/work-report/organize`、`POST /attachment`、`POST /reports`、`GET /reports/mine`;主管/运营另有列表和审核接口 | 只承载 CASE/VIDEO/SOP/KNOWLEDGE 四类投稿。会话式页面、无状态整理、附件、幂等正式提交和历史状态已部署;审核通过不自动入知识库。当前整理服务不读取图片/视频内容,只把附件名称作为不可信元数据;不得与日常工作记录或“今日工作成果”混用 |
|
| 成果投稿(当前页面名“工作上报”)`/pages/user/report/index` | `POST /api/aihr/work-report/organize`、`POST /attachment`、`POST /reports`、`GET /reports/mine`;主管/运营另有列表和审核接口 | 只承载 CASE/VIDEO/SOP/KNOWLEDGE 四类投稿。会话式页面、无状态整理、附件、幂等正式提交和历史状态已部署;审核通过不自动入知识库。当前整理服务不读取图片/视频内容,只把附件名称作为不可信元数据;不得与日常工作记录或“今日工作成果”混用 |
|
||||||
| 资料处理 `/knowledge/processing` | `GET /api/knowledge/processing/overview`、`POST /api/knowledge/doc/upload-async`、`GET /api/knowledge/doc/upload-items`、`POST /api/knowledge/doc/upload-items/{id}/retry` | 已接入解析任务状态聚合;页面只保留“批量导入”,接口暂存+入队即秒回,后台 worker(并发 2)逐条解析/归类/向量化;ZIP 在 worker 内安全解压后把支持的子文件继续入同一批次队列,页面按批次轮询进度、失败可单文件重试;不提供浏览器目录选择或服务端目录导入入口 |
|
| 资料处理 `/knowledge/processing` | `GET /api/knowledge/processing/overview`、`POST /api/knowledge/doc/upload-async`、`GET /api/knowledge/doc/upload-items`、`POST /api/knowledge/doc/upload-items/{id}/retry` | 已接入解析任务状态聚合;页面只保留“批量导入”,接口暂存+入队即秒回,后台 worker(并发 2)逐条解析/归类/向量化;ZIP 在 worker 内安全解压后把支持的子文件继续入同一批次队列,页面按批次轮询进度、失败可单文件重试;不提供浏览器目录选择或服务端目录导入入口 |
|
||||||
| 组织人员同步 | `POST /api/aihr/org/sync` | 从开放组织同步系统的 `/api/open/v1/sync/snapshot` 拉取 `company/department/employee/employee_project_assignment` 快照,分页参数使用 `limit`;员工手机号只落 `person_phone` 用于移动端身份映射,不在组织列表响应暴露;岗位识别 `position/job_title/post/job_name/role/title` 等字段。`dryRun` 必须显式传入 `true`(预检)或 `false`(写入),省略或传 `null` 直接拒绝,避免空请求意外写库;默认 `replaceExisting=true`,写入前必须先用 `{"dryRun":true}`;dry-run 不访问本地快照表、不执行 DDL/写库,返回 `phoneLinked/maskedPhone/suspectText/warnings` 且 `syncedCount=0`。非 dry-run 覆盖写入遇到员工被跳过、手机号不完整、脱敏手机号或疑似乱码时默认拒绝,只有确认 dry-run 结果后显式传 `allowPartialReplace=true` 才允许覆盖。相同员工可由多条有效项目分配展开为多个项目成员行,唯一约束为 `tenant_id + project_code + ext_party_id`;只拒绝同一项目内的重复成员关系。`replaceExisting=false` 不触发不完整快照覆盖闸门,但仍拒绝同项目重复关系。2026-07-15 线上已用 `/api/open/v1` 前缀完成配置、dry-run 和覆盖同步:开放平台返回公司 17、部门 963、员工 3417;生产 `aihr_org_snapshot` 为 3417 行,`phoneLinked=3392`、`maskedPhone=25`、`suspectText=0`,在职 2943、离职 474。该统计发生在多项目成员迁移前;发布迁移后必须重新 dry-run 并核对展开后的项目成员行数。当前剩余 25 人手机号不可用属于上游数据质量问题 |
|
| 组织人员同步 | `POST /api/aihr/org/sync` | 从开放组织同步系统的 `/api/open/v1/sync/snapshot` 拉取 `company/department/employee/employee_project_assignment` 快照,分页参数使用 `limit`;员工手机号只落 `person_phone` 用于移动端身份映射,不在组织列表响应暴露;岗位识别 `position/job_title/post/job_name/role/title` 等字段。`dryRun` 必须显式传入 `true`(预检)或 `false`(写入),省略或传 `null` 直接拒绝,避免空请求意外写库;默认 `replaceExisting=true`,写入前必须先用 `{"dryRun":true}`;dry-run 不访问本地快照表、不执行 DDL/写库,返回 `phoneLinked/maskedPhone/suspectText/warnings` 且 `syncedCount=0`。非 dry-run 覆盖写入遇到员工被跳过、手机号不完整、脱敏手机号或疑似乱码时默认拒绝,只有确认 dry-run 结果后显式传 `allowPartialReplace=true` 才允许覆盖。相同员工可由多条有效项目分配展开为多个项目成员行,唯一约束为 `tenant_id + project_code + ext_party_id`;只拒绝同一项目内的重复成员关系。`replaceExisting=false` 不触发不完整快照覆盖闸门,但仍拒绝同项目重复关系。2026-07-15 线上已用 `/api/open/v1` 前缀完成配置、dry-run 和覆盖同步:开放平台返回公司 17、部门 963、员工 3417;生产 `aihr_org_snapshot` 为 3417 行,`phoneLinked=3392`、`maskedPhone=25`、`suspectText=0`,在职 2943、离职 474。该统计发生在多项目成员迁移前;发布迁移后必须重新 dry-run 并核对展开后的项目成员行数。当前剩余 25 人手机号不可用属于上游数据质量问题 |
|
||||||
@@ -226,7 +226,7 @@ Content-Type: application/json
|
|||||||
|
|
||||||
## 工作助手确认式统一采集
|
## 工作助手确认式统一采集
|
||||||
|
|
||||||
本节记录当前已经部署的接口。当前版本仍允许候选项目为空,也没有当前项目下拉、项目化短会话、原始媒体绑定或今日工作成果接口;这些属于[《工作助手与今日工作成果迭代计划》](工作助手与今日工作成果迭代计划-20260721.md),在代码和接口落地前不得提前写入本指南的可调用接口表。
|
本节记录当前已经部署的接口。个人笔记候选可不绑定项目;晨会、巡检、业主服务、画像和线索等项目工作必须由员工从项目名称列表选择当前项目,再由服务端按登录主体复核。项目化短会话、受保护原始媒体绑定、员工成果生成和主管项目成果查询均已在可调用接口表中列出;后续外部线索、工单、考勤接收端仍不得提前写成已送达。
|
||||||
|
|
||||||
该链路复用上面的短会话请求,不新增另一套问答 session。登录员工说“帮我记一下三栋 3203 需要保洁服务”时,`POST /api/knowledge/query` 在正常回答字段之外可返回;缺少单元不会阻断用户保存:
|
该链路复用上面的短会话请求,不新增另一套问答 session。登录员工说“帮我记一下三栋 3203 需要保洁服务”时,`POST /api/knowledge/query` 在正常回答字段之外可返回;缺少单元不会阻断用户保存:
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
工作助手“记一下”统一采集已提交并部署:生产 `ruoyi-aihr` 包包含 `AihrMemoryService`,H5 包含确认卡资源,生产库存在 `aihr_memory_candidate/aihr_assistant_capture` 及关键确认、范围和流转字段。会话式工作上报也已出现在生产 H5 与后端包中,生产 `aihr_work_report` 已有幂等字段。以上属于生产基础存在性复核;本轮未重新执行正式账号端到端、多项目、真机媒体或真实审核样本验收。
|
工作助手“记一下”统一采集已提交并部署:生产 `ruoyi-aihr` 包包含 `AihrMemoryService`,H5 包含确认卡资源,生产库存在 `aihr_memory_candidate/aihr_assistant_capture` 及关键确认、范围和流转字段。会话式工作上报也已出现在生产 H5 与后端包中,生产 `aihr_work_report` 已有幂等字段。以上属于生产基础存在性复核;本轮未重新执行正式账号端到端、多项目、真机媒体或真实审核样本验收。
|
||||||
|
|
||||||
`PRIVATE` 仅本人可见,`COMPANY` 只写 `PENDING`。项目名称选择、项目化会话、确认记录来源绑定、员工今日成果和主管项目成果已完成本地实现与 390×844 验证,尚未部署和生产验证;外部线索/工单投递、正式回执及个人文件/网页知识空间仍未实现。后续顺序以[《工作助手与今日工作成果迭代计划》](工作助手与今日工作成果迭代计划-20260721.md)为准。
|
`PRIVATE` 仅本人可见,`COMPANY` 只写 `PENDING`。2026-07-21 已发布项目名称选择、项目化会话、确认记录来源绑定、员工工作成果和主管项目成果;生产已完成正式员工账号的主链路回归,但多项目生产样本、主管正式账号和真机媒体仍待验收。本次日期历史查看与主管手机号授权兼容修复在当前工作树完成本地验证,发布状态以本轮发布记录为准。外部线索/工单投递、正式回执及个人文件/网页知识空间仍未实现。后续顺序以[《工作助手与今日工作成果迭代计划》](工作助手与今日工作成果迭代计划-20260721.md)为准。
|
||||||
|
|
||||||
## 2. 三端输入与 BRD 匹配
|
## 2. 三端输入与 BRD 匹配
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
## 发布前置条件
|
## 发布前置条件
|
||||||
|
|
||||||
1. 确认发布窗口、DB 备份和回滚负责人;迁移脚本包含 `ALTER TABLE`,不能在无备份状态执行。
|
1. 确认发布窗口、DB 备份和回滚负责人;迁移脚本包含 `ALTER TABLE`,不能在无备份状态执行。
|
||||||
2. 先执行 `scripts/release-preflight.sh` 的只读检查;生产静态资源、后端包和当前提交必须一致。若要同时核对线上结构,追加 `RELEASE_VERIFY_REMOTE_SCHEMA=true`,该开关只读检查必需表,不执行迁移。
|
2. 发布前先执行 `scripts/release-preflight.sh` 的本地只读检查。静态资源和后端包发布完成后,再用 `RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh` 核对线上产物与必需表;后两个开关必须同时使用,schema 检查只读、不执行迁移。
|
||||||
3. 确认迁移目标库、租户和字符集为 MySQL 8 / `utf8mb4`;脚本只允许在目标业务库执行。
|
3. 确认迁移目标库、租户和字符集为 MySQL 8 / `utf8mb4`;脚本只允许在目标业务库执行。
|
||||||
4. 生产组织同步凭据、AI 供应商留存/费用口径和正式试点窗口仍未闭合时,不要把迁移完成宣称为 BRD 验收完成。
|
4. 生产组织同步凭据、AI 供应商留存/费用口径和正式试点窗口仍未闭合时,不要把迁移完成宣称为 BRD 验收完成。
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 一期 MVP 演示与验收清单
|
# 一期 MVP 演示与验收清单
|
||||||
|
|
||||||
本文件是当前能力的 MVP 现场演示入口:先跑预检,再按演示流逐条点击。它不包含尚未实施的项目名称选择、项目化会话、原始来源绑定、今日工作成果或主管项目日报;这些以[后续迭代计划](工作助手与今日工作成果迭代计划-20260721.md)为准。当前证明一期 MVP 路径可讲、可点、可截图;用户侧三端已在 `mobile-uni/` 按真实页面栈重建,portless 入口为 `https://wygj-mobile-uni.localhost/h5/`,已接手机号登录、问师傅、员工训练、员工学习、案例素材、候选人面试/资料和主管复盘/派专项;员工端训练完成后写入 `aihr_practice_session`,用于训练历史、主管待复盘列表和能力画像;候选人端面试复用 AI 面试 API,补充资料上传写 `sys_oss` 和 `aihr_candidate_material`,HR 可在管理端审核通过/驳回;SOP 检索已可查本地 MySQL seed 片段,并支持 txt/md/PDF/Word/Excel/PPT 上传解析为本地 fragment,配置 vector 模型后会写入 embedding 并尽力同步 Qdrant,管理端 AI 面试和案例沉淀按真实模型/ASR 优先验收。
|
本文件是当前能力的 MVP 现场演示入口:先跑预检,再按演示流逐条点击。默认演示脚本不覆盖项目名称选择、项目化会话、原始来源绑定、员工工作成果或主管项目成果;这些能力的当前状态与专项回归见[《工作助手与今日工作成果迭代计划》](工作助手与今日工作成果迭代计划-20260721.md)。当前证明一期 MVP 路径可讲、可点、可截图;用户侧三端已在 `mobile-uni/` 按真实页面栈重建,portless 入口为 `https://wygj-mobile-uni.localhost/h5/`,已接手机号登录、问师傅、员工训练、员工学习、案例素材、候选人面试/资料和主管复盘/派专项;员工端训练完成后写入 `aihr_practice_session`,用于训练历史、主管待复盘列表和能力画像;候选人端面试复用 AI 面试 API,补充资料上传写 `sys_oss` 和 `aihr_candidate_material`,HR 可在管理端审核通过/驳回;SOP 检索已可查本地 MySQL seed 片段,并支持 txt/md/PDF/Word/Excel/PPT 上传解析为本地 fragment,配置 vector 模型后会写入 embedding 并尽力同步 Qdrant,管理端 AI 面试和案例沉淀按真实模型/ASR 优先验收。
|
||||||
|
|
||||||
## 演示前预检
|
## 演示前预检
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ RELEASE_REMOTE_URL=https://peilian.njzhmj.top ./scripts/release-preflight.sh
|
|||||||
RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true ./scripts/release-preflight.sh
|
RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true ./scripts/release-preflight.sh
|
||||||
# 同时通过 SSH 校验线上后端 jar;默认使用本机 SSH alias YCWY;比较 ruoyi-aihr 模块内容,避免 ZIP 打包时间戳造成误报
|
# 同时通过 SSH 校验线上后端 jar;默认使用本机 SSH alias YCWY;比较 ruoyi-aihr 模块内容,避免 ZIP 打包时间戳造成误报
|
||||||
RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true ./scripts/release-preflight.sh
|
RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true ./scripts/release-preflight.sh
|
||||||
|
# 发布后同时核对线上静态资源、后端包和必需表(schema 检查为只读)
|
||||||
|
RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
`RELEASE_VERIFY_REMOTE_BACKEND=true` 必须与 `RELEASE_VERIFY_REMOTE_MATCH=true` 同时使用;预检会同时打印整包 jar SHA-256 和 `ruoyi-aihr` 模块内容 SHA-256,实际匹配以模块内容 hash 为准。远端后端默认核对 `/opt/wygj/app/ruoyi-admin.jar`,如发布路径不同可通过 `RELEASE_REMOTE_BACKEND_PATH` 覆盖;路径必须是安全的绝对路径。
|
`RELEASE_VERIFY_REMOTE_BACKEND=true` 必须与 `RELEASE_VERIFY_REMOTE_MATCH=true` 同时使用;预检会同时打印整包 jar SHA-256 和 `ruoyi-aihr` 模块内容 SHA-256,实际匹配以模块内容 hash 为准。远端后端默认核对 `/opt/wygj/app/ruoyi-admin.jar`,如发布路径不同可通过 `RELEASE_REMOTE_BACKEND_PATH` 覆盖;路径必须是安全的绝对路径。
|
||||||
|
|||||||
+3
-2
@@ -8,10 +8,10 @@
|
|||||||
|
|
||||||
1. [物业AI人力资源系统业务需求文档BRD.md](物业AI人力资源系统业务需求文档BRD.md) 定义当前业务范围,[物业AI人力资源系统开发规格TechSpec.md](物业AI人力资源系统开发规格TechSpec.md) 定义工程契约。
|
1. [物业AI人力资源系统业务需求文档BRD.md](物业AI人力资源系统业务需求文档BRD.md) 定义当前业务范围,[物业AI人力资源系统开发规格TechSpec.md](物业AI人力资源系统开发规格TechSpec.md) 定义工程契约。
|
||||||
2. [BRD_IMPLEMENTATION_AUDIT.md](BRD_IMPLEMENTATION_AUDIT.md) 与 [API_INTEGRATION.md](API_INTEGRATION.md) 说明当前实现、部署证据和接口限制。
|
2. [BRD_IMPLEMENTATION_AUDIT.md](BRD_IMPLEMENTATION_AUDIT.md) 与 [API_INTEGRATION.md](API_INTEGRATION.md) 说明当前实现、部署证据和接口限制。
|
||||||
3. 专项 TechSpec 细化单个领域;实施计划只描述尚未完成的施工顺序,不构成“已经实现”的证据。
|
3. 专项 TechSpec 细化单个领域;实施计划可记录本地实现与验证,但部署和生产状态仍以审查文档、接口事实和真实环境证据为准。
|
||||||
4. `prototypes/` 只作视觉回归基准;会议纪要、历史 Prompt、`legacy/` 与 `archive/` 只作需求来源和追溯材料。
|
4. `prototypes/` 只作视觉回归基准;会议纪要、历史 Prompt、`legacy/` 与 `archive/` 只作需求来源和追溯材料。
|
||||||
|
|
||||||
工作助手相关术语、当前本地实现证据与文档关系集中在[《工作助手与今日工作成果迭代计划》](工作助手与今日工作成果迭代计划-20260721.md) §0、§1.5;不得把“本地今日成果已验证”外推为“已经生产发布、完整个人知识空间或外部流转已经实现”。
|
工作助手相关术语、实施状态与文档关系集中在[《工作助手与今日工作成果迭代计划》](工作助手与今日工作成果迭代计划-20260721.md);当前产品优先级与本轮本地增量见[《银城帮道产品功能优先级与实施计划》](银城帮道产品功能优先级与实施计划-20260721.md)。不得把本地验证外推为完整个人知识空间或外部流转已经实现。
|
||||||
|
|
||||||
## 当前项目文档
|
## 当前项目文档
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
| [银城员工端APP分阶段实施总纲.md](银城员工端APP分阶段实施总纲.md) | 三阶段总纲:陪练 → 大喇叭+知识学习平台(含学模块、开放问题榜、员工个人 AI 助理) → 北森对接;身份三轴模型与防遗漏分发策略 |
|
| [银城员工端APP分阶段实施总纲.md](银城员工端APP分阶段实施总纲.md) | 三阶段总纲:陪练 → 大喇叭+知识学习平台(含学模块、开放问题榜、员工个人 AI 助理) → 北森对接;身份三轴模型与防遗漏分发策略 |
|
||||||
| [个人AI助理阶段二专项TechSpec.md](个人AI助理阶段二专项TechSpec.md) | 工作助手确认式统一采集的当前契约,以及后续个人知识空间的数据分域、检索、权限、安全和测试规格 |
|
| [个人AI助理阶段二专项TechSpec.md](个人AI助理阶段二专项TechSpec.md) | 工作助手确认式统一采集的当前契约,以及后续个人知识空间的数据分域、检索、权限、安全和测试规格 |
|
||||||
| [工作助手与今日工作成果迭代计划-20260721.md](工作助手与今日工作成果迭代计划-20260721.md) | 工作助手项目选择、统一采集、来源追溯、今日成果、主管视图、成果投稿和外部流转的分阶段计划;含本地实施状态、390×844 证据和剩余生产边界 |
|
| [工作助手与今日工作成果迭代计划-20260721.md](工作助手与今日工作成果迭代计划-20260721.md) | 工作助手项目选择、统一采集、来源追溯、今日成果、主管视图、成果投稿和外部流转的分阶段计划;含本地实施状态、390×844 证据和剩余生产边界 |
|
||||||
|
| [银城帮道产品功能优先级与实施计划-20260721.md](银城帮道产品功能优先级与实施计划-20260721.md) | 对《银城帮道介绍》十项能力进行事实校正,给出 P0-P3、明确不做项、发布批次和本轮实施记录;产品全景施工入口 |
|
||||||
| [个人AI助理阶段二开发推进计划.md](个人AI助理阶段二开发推进计划.md) | 后续个人资料/网页/PPT 能力的 P0/P1 任务计划;“记一下”当前实现以专项 TechSpec 为准 |
|
| [个人AI助理阶段二开发推进计划.md](个人AI助理阶段二开发推进计划.md) | 后续个人资料/网页/PPT 能力的 P0/P1 任务计划;“记一下”当前实现以专项 TechSpec 为准 |
|
||||||
| [prototypes/personal-assistant-work-helper-voice-first-v2.png](prototypes/personal-assistant-work-helper-voice-first-v2.png) | 个人助理并入「问 · 数字师傅」后的语音优先会话、自动内部路由与确认记忆卡实现基准 |
|
| [prototypes/personal-assistant-work-helper-voice-first-v2.png](prototypes/personal-assistant-work-helper-voice-first-v2.png) | 个人助理并入「问 · 数字师傅」后的语音优先会话、自动内部路由与确认记忆卡实现基准 |
|
||||||
| [prototypes/work-report-voice-conversation-v1.png](prototypes/work-report-voice-conversation-v1.png) | 当前“工作上报”(产品目标名“成果投稿”)会话式页面的视觉回归基准;不代表工作助手日常采集或今日成果页 |
|
| [prototypes/work-report-voice-conversation-v1.png](prototypes/work-report-voice-conversation-v1.png) | 当前“工作上报”(产品目标名“成果投稿”)会话式页面的视觉回归基准;不代表工作助手日常采集或今日成果页 |
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@@ -1,6 +1,6 @@
|
|||||||
# 员工个人 AI 助理 / 个人知识空间专项 TechSpec
|
# 员工个人 AI 助理 / 个人知识空间专项 TechSpec
|
||||||
|
|
||||||
> 版本:v1.4 | 日期:2026-07-21
|
> 版本:v1.5 | 日期:2026-07-22
|
||||||
>
|
>
|
||||||
> 归属:《银城员工端 APP 分阶段实施总纲》阶段二
|
> 归属:《银城员工端 APP 分阶段实施总纲》阶段二
|
||||||
>
|
>
|
||||||
@@ -14,9 +14,11 @@
|
|||||||
>
|
>
|
||||||
> v1.3 文档收口:v1.2 描述的是 2026-07-20 已部署行为;多项目工作采集按[《工作助手与今日工作成果迭代计划》](工作助手与今日工作成果迭代计划-20260721.md)执行。个人笔记允许项目为空,项目工作记录必须先按名称选择当前项目;项目编码只作内部授权字段,不由用户输入。
|
> v1.3 文档收口:v1.2 描述的是 2026-07-20 已部署行为;多项目工作采集按[《工作助手与今日工作成果迭代计划》](工作助手与今日工作成果迭代计划-20260721.md)执行。个人笔记允许项目为空,项目工作记录必须先按名称选择当前项目;项目编码只作内部授权字段,不由用户输入。
|
||||||
>
|
>
|
||||||
> v1.4 本地实施回填:项目名称选择、项目化短会话、项目化确认卡、来源快照、业务状态历史、员工今日成果和主管项目成果已完成本地实现、真实 HTTP 与 390×844 验证,尚未提交、部署或生产验证。外部线索/工单/考勤投递仍保持 PENDING。
|
> v1.4 发布前本地实施快照:项目名称选择、项目化短会话、项目化确认卡、来源快照、业务状态历史、员工今日成果和主管项目成果已完成本地实现、真实 HTTP 与 390×844 验证;该快照记录的是发布前状态,外部线索/工单/考勤投递仍保持 PENDING。
|
||||||
|
|
||||||
当前代码状态(2026-07-21 复核):2026-07-20 生产版本已包含候选检测、可编辑确认、PRIVATE/COMPANY 持久化、可选跟进时间、“我的资料与记忆”列表和受控召回。当前工作树进一步完成多项目、`ATTENDANCE/INSPECTION` 分类、来源绑定、状态闭环和今日成果,但这部分仍只完成本地验证。个人文件/网页知识空间、PPT 自动生成、外部线索/工单投递 Worker 和正式回执仍是后续工作;开发者不得从 `PENDING` 推断已派单或已送达。
|
> v1.5 发布与本轮回填:v1.4 所列多项目、项目化确认采集、来源/状态、员工今日成果和主管项目成果已于 2026-07-21 部署;当前工作树新增成果历史日期选择、服务端拒绝未来日期,以及主管手机号授权兜底,均已完成本地验证,待本轮发布。外部线索/工单/考勤投递仍保持 PENDING。
|
||||||
|
|
||||||
|
当前代码状态(2026-07-22 复核):已部署版本包含候选检测、可编辑确认、PRIVATE/COMPANY 持久化、项目名称选择、项目化短会话、`ATTENDANCE/INSPECTION` 分类、来源绑定、状态闭环、员工今日成果和主管项目成果。本轮本地增量只涉及历史日期查看、未来日期拒绝和手机号授权兼容;个人文件/网页知识空间、PPT 自动生成、外部线索/工单投递 Worker 和正式回执仍是后续工作。开发者不得从 `PENDING` 推断已派单或已送达。
|
||||||
|
|
||||||
## 0. 交互设计基准
|
## 0. 交互设计基准
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# 工作助手与今日工作成果迭代计划
|
# 工作助手与今日工作成果迭代计划
|
||||||
|
|
||||||
> 版本:v1.5
|
> 版本:v1.6
|
||||||
> 日期:2026-07-21
|
> 日期:2026-07-22
|
||||||
> 状态:迭代 0-4 已实现、提交并部署;目标测试、真实 HTTP、390×844 视觉交互及生产真实员工账号主链路已验证,真机媒体和外部正式接口仍待验收
|
> 状态:迭代 0-4 已于 2026-07-21 提交并部署;目标测试、真实 HTTP、390×844 视觉交互及生产真实员工账号主链路已验证。本工作树新增历史日期选择、未来日期拒绝和主管手机号授权兜底,已完成本地验证,待随本轮发布
|
||||||
> 适用范围:员工端工作助手、今日工作成果、主管项目视图、成果投稿及后续外部流转
|
> 适用范围:员工端工作助手、今日工作成果、主管项目视图、成果投稿及后续外部流转
|
||||||
> v1.5:在 v1.4 基础上补记生产发布、真实账号回归和生产截图;确认卡改为优先展示 AI 识别结论,再由用户向下查看并执行保存动作。
|
> v1.5:在 v1.4 基础上补记生产发布、真实账号回归和生产截图;确认卡改为优先展示 AI 识别结论,再由用户向下查看并执行保存动作。
|
||||||
|
> v1.6:员工与主管成果页支持历史日期选择;前后端均拒绝未来日期;主管手机号登录时以手机号兼容组织身份授权。以上是本轮本地增量,不倒填为 2026-07-21 已发布能力。
|
||||||
|
|
||||||
## 0. 文档权威关系与冲突裁决
|
## 0. 文档权威关系与冲突裁决
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
| [个人助理专项 TechSpec](个人AI助理阶段二专项TechSpec.md) | 说明确认式采集、权限、记忆和个人知识空间边界 | 工作助手专项契约 |
|
| [个人助理专项 TechSpec](个人AI助理阶段二专项TechSpec.md) | 说明确认式采集、权限、记忆和个人知识空间边界 | 工作助手专项契约 |
|
||||||
| [API 对接指南](API_INTEGRATION.md) | 只列当前已经存在的接口 | 不把本文规划中的接口写成可调用接口 |
|
| [API 对接指南](API_INTEGRATION.md) | 只列当前已经存在的接口 | 不把本文规划中的接口写成可调用接口 |
|
||||||
| [BRD 功能审查](BRD_IMPLEMENTATION_AUDIT.md) | 区分实现、部署、生产基础验证和正式验收 | 当前状态事实源 |
|
| [BRD 功能审查](BRD_IMPLEMENTATION_AUDIT.md) | 区分实现、部署、生产基础验证和正式验收 | 当前状态事实源 |
|
||||||
| 本文 | 项目选择、统一采集、今日成果、主管视图和外部流转的实施顺序与阶段证据 | 只能按本文状态列声明本地实现;部署和生产状态仍以审查文档及真实环境为准 |
|
| 本文 | 项目选择、统一采集、今日成果、主管视图和外部流转的实施顺序与阶段证据 | 按状态列记录计划与证据摘要;部署和生产状态仍以审查文档及真实环境交叉复核为准 |
|
||||||
| [数字师傅整合方案](20260708/数字师傅学练问报整合方案.md) | 保留产品演进背景和“学练问报”决策 | 与当前事实冲突时以上述事实文档为准 |
|
| [数字师傅整合方案](20260708/数字师傅学练问报整合方案.md) | 保留产品演进背景和“学练问报”决策 | 与当前事实冲突时以上述事实文档为准 |
|
||||||
|
|
||||||
已裁决的矛盾:
|
已裁决的矛盾:
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
3. **日常记录放在哪里**:日常事实统一进入工作助手和 `aihr_assistant_capture`;不得为了日报把既有 `aihr_work_report` 改造成万能工作记录表。
|
3. **日常记录放在哪里**:日常事实统一进入工作助手和 `aihr_assistant_capture`;不得为了日报把既有 `aihr_work_report` 改造成万能工作记录表。
|
||||||
4. **附件是否已经被 AI 看懂**:工作助手的现场图片/视频已有本次分析能力;确认式采集已能把受保护 OSS 来源绑定到记录并重新鉴权回看。成果投稿链仍不能仅凭附件名称声称已经理解图片或视频内容。
|
4. **附件是否已经被 AI 看懂**:工作助手的现场图片/视频已有本次分析能力;确认式采集已能把受保护 OSS 来源绑定到记录并重新鉴权回看。成果投稿链仍不能仅凭附件名称声称已经理解图片或视频内容。
|
||||||
5. **“个人助理已实现”是什么意思**:确认式采集已经提交并部署,但个人文件、网页收藏、独立个人向量库、分享和 PPT 生成仍未实现;两者不能合并宣称完成。
|
5. **“个人助理已实现”是什么意思**:确认式采集已经提交并部署,但个人文件、网页收藏、独立个人向量库、分享和 PPT 生成仍未实现;两者不能合并宣称完成。
|
||||||
6. **今日成果与外部流转**:员工今日成果和主管项目成果已完成本地最小闭环;真实线索/工单投递仍未接通,`COMPANY/PENDING` 只表示待流转。
|
6. **今日成果与外部流转**:员工今日成果和主管项目成果已于 2026-07-21 部署;真实线索/工单投递仍未接通,`COMPANY/PENDING` 只表示待流转。
|
||||||
7. **部署与验收**:2026-07-21 已完成本轮数据库幂等迁移、后端 JAR 与 H5 发布,并用正式员工账号验证项目读取、越权拒绝、确认卡、撤销和按项目/日期幂等生成成果;生产账号当前只有一个授权项目,因此多项目切换仍以本地双项目证据为准,真机媒体和正式业务窗口仍未验收。
|
7. **部署与验收**:2026-07-21 已完成本轮数据库幂等迁移、后端 JAR 与 H5 发布,并用正式员工账号验证项目读取、越权拒绝、确认卡、撤销和按项目/日期幂等生成成果;生产账号当前只有一个授权项目,因此多项目切换仍以本地双项目证据为准,真机媒体和正式业务窗口仍未验收。
|
||||||
|
|
||||||
## 1. 目标与边界
|
## 1. 目标与边界
|
||||||
@@ -93,6 +94,7 @@ UI 与代码页面的对应关系:
|
|||||||
| 项目化确认卡、来源与业务状态 | `/pages/user/sop/index` | `mobile-uni/src/components/chat/MemoryCandidateCard.vue` | 确认卡展示项目、工作日期、来源和状态,并区分个人记录与待公司处理 |
|
| 项目化确认卡、来源与业务状态 | `/pages/user/sop/index` | `mobile-uni/src/components/chat/MemoryCandidateCard.vue` | 确认卡展示项目、工作日期、来源和状态,并区分个人记录与待公司处理 |
|
||||||
| 今日工作成果摘要与员工详情 | `/pages/user/sop/index`、`/pages/user/work-results/index` | `mobile-uni/src/pages/user/sop/index.vue`、`mobile-uni/src/pages/user/work-results/index.vue` | 会话内给出当日摘要入口,详情按员工 + 项目 + 日期聚合 |
|
| 今日工作成果摘要与员工详情 | `/pages/user/sop/index`、`/pages/user/work-results/index` | `mobile-uni/src/pages/user/sop/index.vue`、`mobile-uni/src/pages/user/work-results/index.vue` | 会话内给出当日摘要入口,详情按员工 + 项目 + 日期聚合 |
|
||||||
| 主管项目成果 | `/pages/supervisor/work-results/index` | `mobile-uni/src/pages/supervisor/work-results/index.vue` | 按项目查看记录人数、成果人数、待跟进和高优问题,不展示无权限项目 |
|
| 主管项目成果 | `/pages/supervisor/work-results/index` | `mobile-uni/src/pages/supervisor/work-results/index.vue` | 按项目查看记录人数、成果人数、待跟进和高优问题,不展示无权限项目 |
|
||||||
|
| 历史日期选择与中性标题 | `/pages/user/work-results/index`、`/pages/supervisor/work-results/index` | `mobile-uni/src/components/WorkDateSelector.vue`、两个成果页 | 默认当天;允许查看历史日期;前后端均拒绝未来日期;历史视图不再误称“今日” |
|
||||||
| 原“工作上报”分流为“成果投稿” | `/pages/user/report/index` | `mobile-uni/src/pages/user/report/index.vue` | 仅承载优秀案例、操作视频、完整 SOP 和知识投稿,不承载日常事实记录 |
|
| 原“工作上报”分流为“成果投稿” | `/pages/user/report/index` | `mobile-uni/src/pages/user/report/index.vue` | 仅承载优秀案例、操作视频、完整 SOP 和知识投稿,不承载日常事实记录 |
|
||||||
|
|
||||||
### 1.5 本地运行视觉证据(390×844)
|
### 1.5 本地运行视觉证据(390×844)
|
||||||
@@ -106,6 +108,10 @@ UI 与代码页面的对应关系:
|
|||||||
| 巡检确认卡阶段性定位证据 | `docs/visual-evidence/20260721/work-assistant-inspection-card-autoscroll-390x844.png` | 当次本地验证证明四个动作可操作;生产回归发现卡片开头会被顶出屏幕,已由 §1.6 的“识别结论优先”实现取代 |
|
| 巡检确认卡阶段性定位证据 | `docs/visual-evidence/20260721/work-assistant-inspection-card-autoscroll-390x844.png` | 当次本地验证证明四个动作可操作;生产回归发现卡片开头会被顶出屏幕,已由 §1.6 的“识别结论优先”实现取代 |
|
||||||
| 员工今日工作成果 | `docs/visual-evidence/20260721/employee-daily-work-result-fixed-390x844.png` | 按项目和日期聚合;已记录事项不再错误显示处理动作 |
|
| 员工今日工作成果 | `docs/visual-evidence/20260721/employee-daily-work-result-fixed-390x844.png` | 按项目和日期聚合;已记录事项不再错误显示处理动作 |
|
||||||
| 主管项目成果 | `docs/visual-evidence/20260721/supervisor-project-work-results-fixed-390x844.png` | 显示记录人数、成果人数、待跟进、高优问题和员工来源类型;非文字记录可打开受保护原始来源 |
|
| 主管项目成果 | `docs/visual-evidence/20260721/supervisor-project-work-results-fixed-390x844.png` | 显示记录人数、成果人数、待跟进、高优问题和员工来源类型;非文字记录可打开受保护原始来源 |
|
||||||
|
| 员工当天成果日期选择 | `docs/visual-evidence/20260721/employee-work-results-date-selector-today-390x844.png` | 默认当天,日期入口与统计清晰可见 |
|
||||||
|
| 员工历史日期空状态 | `docs/visual-evidence/20260721/employee-work-results-history-date-390x844.png` | 历史日期使用“这一天”等中性文案,可回到今天 |
|
||||||
|
| 主管当天成果日期选择 | `docs/visual-evidence/20260721/supervisor-work-results-date-selector-today-390x844.png` | 项目汇总与日期入口不互相遮挡 |
|
||||||
|
| 主管历史日期空状态 | `docs/visual-evidence/20260721/supervisor-work-results-history-date-390x844.png` | 历史视图人数标签采用中性“记录人数” |
|
||||||
|
|
||||||
绝对目录:`/Users/yuanjiantsui/dev/11-project/wygj/docs/visual-evidence/20260721/`。这些文件是已纳入仓库的本地验收证据;正式设计目标仍只以 `docs/prototypes/` 下的权威高保真图为准。
|
绝对目录:`/Users/yuanjiantsui/dev/11-project/wygj/docs/visual-evidence/20260721/`。这些文件是已纳入仓库的本地验收证据;正式设计目标仍只以 `docs/prototypes/` 下的权威高保真图为准。
|
||||||
|
|
||||||
@@ -394,7 +400,7 @@ UI 与代码页面的对应关系:
|
|||||||
|
|
||||||
### 页面入口
|
### 页面入口
|
||||||
|
|
||||||
本迭代 UI 设计输入:`docs/prototypes/work-assistant-project-context-v3.png`,详见 [§1.3](#13-ui-变化与高保真基准) 和 [§1.4](#14-ui-设计交付清单)。工作助手摘要入口、员工详情页和主管项目视图均已本地实现并完成 390×844 验证,运行截图见 §1.5。
|
本迭代 UI 设计输入:`docs/prototypes/work-assistant-project-context-v3.png`,详见 [§1.3](#13-ui-变化与高保真基准) 和 [§1.4](#14-ui-设计交付清单)。工作助手摘要入口、员工详情页和主管项目视图已于 2026-07-21 发布并完成 390×844 验证;本轮历史日期选择的本地运行截图见 §1.5,待随当前批次发布。
|
||||||
|
|
||||||
员工端:
|
员工端:
|
||||||
|
|
||||||
@@ -404,7 +410,7 @@ UI 与代码页面的对应关系:
|
|||||||
|
|
||||||
主管端:
|
主管端:
|
||||||
|
|
||||||
- 今日记录人数。
|
- 记录人数。
|
||||||
- 已形成成果人数。
|
- 已形成成果人数。
|
||||||
- 待跟进数量。
|
- 待跟进数量。
|
||||||
- 高优先级问题。
|
- 高优先级问题。
|
||||||
@@ -602,6 +608,14 @@ UI 与代码页面的对应关系:
|
|||||||
- 旧移动端组合合同测试仍有 15 条失败:其中多数是旧首页/旧“工作上报”文案合同与本轮产品定案冲突,另有 uni-app 测试打包器与当前 Vue 版本的既有兼容错误;不得据此声称全量移动端测试通过,提交前需把过期合同迁移或明确列为基线债务。
|
- 旧移动端组合合同测试仍有 15 条失败:其中多数是旧首页/旧“工作上报”文案合同与本轮产品定案冲突,另有 uni-app 测试打包器与当前 Vue 版本的既有兼容错误;不得据此声称全量移动端测试通过,提交前需把过期合同迁移或明确列为基线债务。
|
||||||
- 本地仍未覆盖且已在生产记录中保留边界:生产真机媒体回看、真实组织多项目 dry-run、主管正式账号回归、外部线索/工单/考勤投递与回执。
|
- 本地仍未覆盖且已在生产记录中保留边界:生产真机媒体回看、真实组织多项目 dry-run、主管正式账号回归、外部线索/工单/考勤投递与回执。
|
||||||
|
|
||||||
|
### 8.3 v1.6 当前工作树验证快照(待本轮发布)
|
||||||
|
|
||||||
|
- 本节与 §8.2 的历史 `7/7` 通过、全量组合合同 `15` 条既有失败并列记录,不修改或倒填历史快照。
|
||||||
|
- 员工与主管成果页已完成历史日期切换;默认当天,历史页使用中性标题和空状态,可回到今天。
|
||||||
|
- 前端日期选择与服务端 `workDate` 均拒绝未来日期;不能通过直接请求绕过页面限制。
|
||||||
|
- 主管使用手机号作为组织身份时,项目成果查询和受保护来源读取均兼容手机号授权匹配;未匹配仍保持拒绝访问。
|
||||||
|
- 本轮定向测试、类型检查、H5 构建和四张 390×844 截图见 [§1.5](#15-本地运行视觉证据390844);生产发布与生产复验须另行记录,不得提前写为已完成。
|
||||||
|
|
||||||
## 9. 推荐施工顺序
|
## 9. 推荐施工顺序
|
||||||
|
|
||||||
内部 MVP 已按以下顺序在 R1-R3 范围内完成并于 2026-07-21 合并发布:
|
内部 MVP 已按以下顺序在 R1-R3 范围内完成并于 2026-07-21 合并发布:
|
||||||
@@ -613,4 +627,4 @@ UI 与代码页面的对应关系:
|
|||||||
5. 原始来源追溯。
|
5. 原始来源追溯。
|
||||||
6. 今日工作成果。
|
6. 今日工作成果。
|
||||||
|
|
||||||
以上六项已经形成可供客户真实体验的一线工作闭环;主管项目汇总已完成本地实现和权限验证,成果投稿分流已上线。后续只把正式外部接口、主管正式账号生产回归和真机媒体验收作为独立批次处理。
|
以上六项已经形成可供客户真实体验的一线工作闭环;主管项目汇总和成果投稿分流已上线。后续只把正式外部接口、主管正式账号生产回归和真机媒体验收作为独立批次处理。
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 物业行业 AI 人力资源系统 · 开发规格(Tech Spec)
|
# 物业行业 AI 人力资源系统 · 开发规格(Tech Spec)
|
||||||
|
|
||||||
> 版本:v1.8 | 日期:2026-07-21
|
> 版本:v1.9 | 日期:2026-07-22
|
||||||
> 定位:**开发层唯一依据**。回答"怎么建"——工程结构、数据表、API 契约、Prompt 规格、集成适配。
|
> 定位:**开发层唯一依据**。回答"怎么建"——工程结构、数据表、API 契约、Prompt 规格、集成适配。
|
||||||
> v1.1 变更:数据模型对齐《手册》卷1 第9章 HR 主干,补 3 张 backbone 表(职位职责/雇员绩效/雇用终止),新增附录 D 对照表。
|
> v1.1 变更:数据模型对齐《手册》卷1 第9章 HR 主干,补 3 张 backbone 表(职位职责/雇员绩效/雇用终止),新增附录 D 对照表。
|
||||||
> v1.2 变更:第 6 章按 Codex 设计评审重写——状态机补异常/终止态、COACH_CHECK 工程化、评分引擎分层(P0 单 LLM / P1 融合)、澄清"单场对练评分 vs 跨期能力画像"两套评分、Prompt 加固 + RAG 硬约束、新增 6.5 P0/P1 切分。
|
> v1.2 变更:第 6 章按 Codex 设计评审重写——状态机补异常/终止态、COACH_CHECK 工程化、评分引擎分层(P0 单 LLM / P1 融合)、澄清"单场对练评分 vs 跨期能力画像"两套评分、Prompt 加固 + RAG 硬约束、新增 6.5 P0/P1 切分。
|
||||||
@@ -9,7 +9,8 @@
|
|||||||
> v1.5 变更:当前用户侧以 `mobile-uni/` 为准;员工工作上报复用 `ChatComposer` 改为语音优先会话,增加无状态 AI 整理草稿契约,确认后继续提交既有 `aihr_work_report` 审核链。
|
> v1.5 变更:当前用户侧以 `mobile-uni/` 为准;员工工作上报复用 `ChatComposer` 改为语音优先会话,增加无状态 AI 整理草稿契约,确认后继续提交既有 `aihr_work_report` 审核链。
|
||||||
> v1.6 变更:工作助手显式“记一下”复用 `/api/knowledge/query` 的 `memoryCandidate`,确认后写 `aihr_assistant_capture`;位置字段改为可选,增加 PRIVATE/COMPANY 范围、跟进时间和待流转状态。音色不在本次实现范围。
|
> v1.6 变更:工作助手显式“记一下”复用 `/api/knowledge/query` 的 `memoryCandidate`,确认后写 `aihr_assistant_capture`;位置字段改为可选,增加 PRIVATE/COMPANY 范围、跟进时间和待流转状态。音色不在本次实现范围。
|
||||||
> v1.7 文档收口:明确当前“工作上报”是四类成果投稿审核链,工作助手负责日常事实采集;项目名称选择、项目化会话、来源追溯和今日工作成果仍属后续迭代,不能从规划文档推断已实现。详见[迭代计划](工作助手与今日工作成果迭代计划-20260721.md)。
|
> v1.7 文档收口:明确当前“工作上报”是四类成果投稿审核链,工作助手负责日常事实采集;项目名称选择、项目化会话、来源追溯和今日工作成果仍属后续迭代,不能从规划文档推断已实现。详见[迭代计划](工作助手与今日工作成果迭代计划-20260721.md)。
|
||||||
> v1.8 本地实施回填:项目名称选择、项目化会话、确认记录来源/状态、员工今日成果、主管项目成果和“成果投稿”界面名称已完成本地实现与 390×844 验证,尚未部署和生产验证;外部线索、工单和考勤投递仍保持 PENDING。
|
> v1.8 发布前本地实施快照:项目名称选择、项目化会话、确认记录来源/状态、员工今日成果、主管项目成果和“成果投稿”界面名称已完成本地实现与 390×844 验证;该快照记录的是发布前状态,外部线索、工单和考勤投递仍保持 PENDING。
|
||||||
|
> v1.9 发布与本轮回填: v1.8 所列多项目、项目化确认采集、来源/状态、员工今日成果、主管项目成果和“成果投稿”界面名称已于 2026-07-21 部署。本工作树新增成果历史日期选择、服务端拒绝未来日期及主管手机号授权兜底,已完成本地验证,待本轮发布;外部线索、工单和考勤投递仍保持 PENDING。
|
||||||
> 配套:需求见[《物业AI人力资源系统业务需求文档BRD》](物业AI人力资源系统业务需求文档BRD.md);2026-07 MVP 执行计划已归档到[《AI人力资源系统一期MVP版作战清单》](archive/2026-07-mvp-delivery/AI人力资源系统一期MVP版作战清单.md)。
|
> 配套:需求见[《物业AI人力资源系统业务需求文档BRD》](物业AI人力资源系统业务需求文档BRD.md);2026-07 MVP 执行计划已归档到[《AI人力资源系统一期MVP版作战清单》](archive/2026-07-mvp-delivery/AI人力资源系统一期MVP版作战清单.md)。
|
||||||
> **优先级图例**:`P0`=2026-07-05 MVP 演示必需 · `P1`=一期必需 · `P2`=二期/推迟。
|
> **优先级图例**:`P0`=2026-07-05 MVP 演示必需 · `P1`=一期必需 · `P2`=二期/推迟。
|
||||||
> 决策基线:若依基座 / 集中式前后端分离 / 本地登录 / 公有大模型API / 一期RAG / 组织人员外部同步(MVP 用快照) / 数据范围以项目为主体。
|
> 决策基线:若依基座 / 集中式前后端分离 / 本地登录 / 公有大模型API / 一期RAG / 组织人员外部同步(MVP 用快照) / 数据范围以项目为主体。
|
||||||
@@ -180,9 +181,9 @@ mobile/
|
|||||||
| `/h5/#/pages/user/today/index` | 员工端首页,手机号登录后可“开始训练”并同步主管待复盘计数;旧 `/h5/user` 兼容重定向 | P0跑通 |
|
| `/h5/#/pages/user/today/index` | 员工端首页,手机号登录后可“开始训练”并同步主管待复盘计数;旧 `/h5/user` 兼容重定向 | P0跑通 |
|
||||||
| `/h5/#/pages/candidate/index/index` | 候选人端首页,手机号登录后可面试练习、查岗位 SOP/案例、上传补充资料;旧 `/h5/candidate` 兼容重定向 | P0跑通 |
|
| `/h5/#/pages/candidate/index/index` | 候选人端首页,手机号登录后可面试练习、查岗位 SOP/案例、上传补充资料;旧 `/h5/candidate` 兼容重定向 | P0跑通 |
|
||||||
| `/h5/#/pages/supervisor/index/index` | 主管端首页,查看团队、待复盘、案例/SOP 工具;旧 `/h5/supervisor` 兼容重定向 | P0跑通 |
|
| `/h5/#/pages/supervisor/index/index` | 主管端首页,查看团队、待复盘、案例/SOP 工具;旧 `/h5/supervisor` 兼容重定向 | P0跑通 |
|
||||||
| `/h5/#/pages/user/sop/index` | 工作助手;按项目名称选择当前项目,语音/文字/照片/视频统一采集,确认后形成日常工作记录 | 本地已实现;媒体生产真机回归未完成 |
|
| `/h5/#/pages/user/sop/index` | 工作助手;按项目名称选择当前项目,语音/文字/照片/视频统一采集,确认后形成日常工作记录 | 2026-07-21 已部署;媒体生产真机回归未完成 |
|
||||||
| `/h5/#/pages/user/work-results/index` | 员工今日工作成果;按员工、项目、自然日汇总确认记录和待跟进 | 本地已实现并完成 390×844 验证 |
|
| `/h5/#/pages/user/work-results/index` | 员工工作成果;按员工、项目、自然日汇总确认记录和待跟进 | 2026-07-21 已部署;本工作树新增历史日期选择和未来日期拒绝,待本轮发布 |
|
||||||
| `/h5/#/pages/supervisor/work-results/index` | 主管项目成果;按目标项目权限查看员工成果和高优问题 | 本地已实现并完成 390×844 验证 |
|
| `/h5/#/pages/supervisor/work-results/index` | 主管项目成果;按目标项目权限查看员工成果和高优问题 | 2026-07-21 已部署;本工作树新增历史日期选择和手机号授权兜底,待本轮发布 |
|
||||||
| `/h5/#/pages/user/report/index` | 员工成果投稿;复用「问」的微信式语音会话,AI 整理确认后提交既有审核链 | 会话式页面和接口已部署;本地界面已正式改名;项目归属和媒体内容理解未实现 |
|
| `/h5/#/pages/user/report/index` | 员工成果投稿;复用「问」的微信式语音会话,AI 整理确认后提交既有审核链 | 会话式页面和接口已部署;本地界面已正式改名;项目归属和媒体内容理解未实现 |
|
||||||
| `/train/daily` | 每日一练 | P1 |
|
| `/train/daily` | 每日一练 | P1 |
|
||||||
| `/train/camp` | 专项训练营 | P1 |
|
| `/train/camp` | 专项训练营 | P1 |
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
| MVP | 按日期范围、主题、来源查询“我哪些天收藏了什么”;基于选定资料问答、总结、提炼工作想法,答案必须带来源 | 阶段二首批 |
|
| MVP | 按日期范围、主题、来源查询“我哪些天收藏了什么”;基于选定资料问答、总结、提炼工作想法,答案必须带来源 | 阶段二首批 |
|
||||||
| MVP | 将冲突处理、工作灵感、保洁管理等问题交给个人助理分析;允许同时引用个人资料与有权限的企业知识 | 阶段二首批 |
|
| MVP | 将冲突处理、工作灵感、保洁管理等问题交给个人助理分析;允许同时引用个人资料与有权限的企业知识 | 阶段二首批 |
|
||||||
| MVP | 显式“记一下/帮我记/保存一下”生成可编辑确认卡;用户选择仅自己保存或提交公司处理,后者在正式接收端接入前只显示待流转 | 已实现并部署;生产基础存在性已复核,正式账号/真机/业务验收仍待完成 |
|
| MVP | 显式“记一下/帮我记/保存一下”生成可编辑确认卡;用户选择仅自己保存或提交公司处理,后者在正式接收端接入前只显示待流转 | 已实现并部署;生产基础存在性已复核,正式账号/真机/业务验收仍待完成 |
|
||||||
| 2026-07-21 本地增量 | 多项目员工按名称选择当前项目;日常工作按员工 + 项目 + 自然日汇总为今日工作成果;现有四类工作上报收敛为成果投稿 | 已完成本地实现、真实 HTTP、H5 构建和 390×844 交互验证;尚未部署和生产验证,详见《工作助手与今日工作成果迭代计划》 |
|
| 2026-07-21 工作助手增量 | 多项目员工按名称选择当前项目;日常工作按员工 + 项目 + 自然日汇总为今日工作成果;现有四类工作上报收敛为成果投稿 | 已发布并完成正式员工账号主链路回归;多项目生产样本、主管正式账号和真机媒体仍待验收,详见《工作助手与今日工作成果迭代计划》 |
|
||||||
| 增强 | 多份资料对比、周报/月报、结构化汇报提纲、个人知识标签和长期记忆 | 阶段二后半 |
|
| 增强 | 多份资料对比、周报/月报、结构化汇报提纲、个人知识标签和长期记忆 | 阶段二后半 |
|
||||||
| 增强 | 将总结导出为 PPT/汇报材料;先生成可编辑大纲,再生成文件,不把“一键 PPT”作为首批阻塞项 | 阶段二后半/P1 |
|
| 增强 | 将总结导出为 PPT/汇报材料;先生成可编辑大纲,再生成文件,不把“一键 PPT”作为首批阻塞项 | 阶段二后半/P1 |
|
||||||
| 外部数据 | 北森、考勤、请假、组织任职等个人数据接入后再做跨系统分析 | 阶段三 |
|
| 外部数据 | 北森、考勤、请假、组织任职等个人数据接入后再做跨系统分析 | 阶段三 |
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
# 银城帮道产品功能优先级与实施计划
|
||||||
|
|
||||||
|
> 版本:v1.2
|
||||||
|
> 日期:2026-07-22
|
||||||
|
> 状态:2026-07-21 的多项目、确认式采集和今日工作成果已部署;R0 当前工作树补充历史日期选择、未来日期拒绝和主管手机号授权兜底,已完成本地测试与 390×844 视觉交互验证,待本轮提交、部署和生产验证
|
||||||
|
> 需求输入:微信临时文件《银城帮道 介绍(1).html》、2026-07-19/20 客户沟通、当前 BRD/TechSpec 与线上事实
|
||||||
|
> 适用范围:员工端、主管端、管理端及后续外部业务接口
|
||||||
|
|
||||||
|
## 0. 决策摘要
|
||||||
|
|
||||||
|
当前不应按介绍页的十个模块平均投入,也不能把设计概念写成“已完成”。实施顺序收敛为:
|
||||||
|
|
||||||
|
1. **先把已上线核心做成可验收闭环**:AI 陪练、工作助手、今日工作成果。
|
||||||
|
2. **再补每天都会用的公司信息与共创能力**:大喇叭/公司消息、开放问题榜、成果历史。
|
||||||
|
3. **随后增强管理价值**:岗位能力模型、画像趋势、情绪趋势。
|
||||||
|
4. **最后等待正式接口与明确业务价值**:考勤、工单、线索、北森、方言与环境模拟。
|
||||||
|
|
||||||
|
本计划不把“页面存在”“本地测试通过”“已部署”和“正式业务验收”混为一谈。当前实现状态继续以 [BRD 功能审查](BRD_IMPLEMENTATION_AUDIT.md)、[API 对接指南](API_INTEGRATION.md) 和真实环境证据为准。
|
||||||
|
|
||||||
|
## 1. 产品全景与当前事实
|
||||||
|
|
||||||
|
| 介绍页功能 | 当前事实 | 优先级 | 处理决定 |
|
||||||
|
|---|---|---:|---|
|
||||||
|
| 员工 AI 陪练 | 训练、评分、主管复盘、再练和画像已有主链路;正式试点仍需按同一业务窗口和唯一在职身份验收 | P0 | 不再扩页面,先完成正式试点证据闭环 |
|
||||||
|
| 公司消息系统 | 框架只有普通公告;缺少按项目/岗位定向、已读未读、“与我有关”和补推 | P1 | 建独立“大喇叭”最小闭环,不把 `sys_notice` 伪装成已完成 |
|
||||||
|
| 岗位能力模型 | 已有训练维度、分数和部分画像;岗位/职级权重配置仍不完整 | P2 | 先用真实训练数据验证,再做后台配置化 |
|
||||||
|
| 情绪动态与仪表盘 | 单次对练已有情绪/红旗信号,缺少跨周期趋势与干预闭环 | P2 | 先保留单次证据,后做趋势页 |
|
||||||
|
| 方言与环境模拟 | 尚无正式验收需求;音色本轮也不考虑 | P3 | 暂不投入,不因展示稿引入新模型或音频依赖 |
|
||||||
|
| 画像扩展与案例沉淀 | 员工画像、训练历史、成果投稿和知识沉淀已有部分链路 | P2 | 补趋势和证据关联,不另建平行画像体系 |
|
||||||
|
| 有奖问答与征集 | 问题榜、回答、最佳答案和积分已有基础 | P1 | 补生产流程、幂等、贡献可追溯;奖励仅为积分/学习学分 |
|
||||||
|
| 经验师与带徒 | 2026-07-08 已明确否决定向师带徒 | 不做 | 由开放问题榜和主管复盘/派训承载帮带 |
|
||||||
|
| 生活顾问 | 工作助手、企业知识查询、确认式记录和待跟进已有主链路 | P0/P1 | 先完成真机媒体和主管验收,再扩行政/福利内容 |
|
||||||
|
| 智能日报 | 员工/主管按项目、自然日聚合已部署;历史日期查看已在当前工作树完成本地验证,待本轮发布;更丰富筛选和自动推送规则尚未完整 | P1 | 发布历史日期查看;推送对象和时间明确后再做自动推送 |
|
||||||
|
|
||||||
|
## 2. 必须立即纠正的介绍口径
|
||||||
|
|
||||||
|
原介绍页是愿景稿,以下表述不能作为当前版本事实:
|
||||||
|
|
||||||
|
- “已上线 · 全员使用中”“已覆盖全员 · 日活 92%”。
|
||||||
|
- “优秀经验变现”“带徒弟多赚钱”“积分商城/现金收益”。
|
||||||
|
- “住户画像已生成”“工程部已接单”。正式画像和工单接口未接通前只能显示“已记录”或“待公司处理”。
|
||||||
|
- “18:00 自动推送管理者”。当前支持按日按项目生成与查看,但不能声称外部推送成功。
|
||||||
|
- “方言 TTS/克隆 + 环境噪音模拟”已具备。
|
||||||
|
|
||||||
|
对外材料统一使用四种状态:
|
||||||
|
|
||||||
|
- **已实现**:代码和本地链路存在。
|
||||||
|
- **已部署**:对应版本已经发布到生产。
|
||||||
|
- **生产验证**:正式环境完成指定场景验证。
|
||||||
|
- **规划中**:尚未形成可用闭环。
|
||||||
|
|
||||||
|
## 3. 分级实施清单
|
||||||
|
|
||||||
|
### P0:核心可验收
|
||||||
|
|
||||||
|
#### P0-1 AI 陪练正式试点闭环
|
||||||
|
|
||||||
|
- 用正式在职组织身份完成训练开始、语音/文字轮次、结束评分、主管复盘、派发再练和员工画像回看。
|
||||||
|
- 按统一日期窗口导出正式试点 CSV;完训定义保持每人至少 10 次。
|
||||||
|
- 明确记录“工程就绪”和“正式试点通过”的差异,不用 seed 或开发身份代替。
|
||||||
|
|
||||||
|
验收:员工、主管、管理端三方数据一致;训练记录、评分、复盘和再练可互相追溯。
|
||||||
|
|
||||||
|
#### P0-2 工作助手生产收口
|
||||||
|
|
||||||
|
- 正式主管账号验证团队成果页和权限隔离。
|
||||||
|
- 真实多项目员工验证项目名称选择,不让用户输入编码。
|
||||||
|
- 真机验证语音、照片、视频发送、AI 确认卡和受保护原始来源回看。
|
||||||
|
- 外部接口未接通时继续使用 `COMPANY/PENDING`,不得显示“已派单/已送达”。
|
||||||
|
|
||||||
|
验收:同一员工两个项目的记录和成果严格隔离;员工和主管能追溯原始来源。
|
||||||
|
|
||||||
|
#### P0-3 产品事实口径
|
||||||
|
|
||||||
|
- 介绍材料删除未经证据支持的“已完成、全员、日活、变现、已接单、已推送”。
|
||||||
|
- [文档索引](README.md) 将本计划作为产品全景的施工入口。
|
||||||
|
- 新增功能必须在 BRD/TechSpec、接口事实和运行证据之间建立链接。
|
||||||
|
|
||||||
|
### P1:高频使用闭环
|
||||||
|
|
||||||
|
#### P1-1 工作成果历史日期查看(本轮已完成本地实现,待发布)
|
||||||
|
|
||||||
|
- 员工和主管成果页已支持按日期切换,默认今天,禁止选择未来日期。
|
||||||
|
- 切换后沿用现有项目权限和真实成果接口,不复制数据模型。
|
||||||
|
- 页面标题已不再把历史日期误称为“今日”。
|
||||||
|
|
||||||
|
发布前验收:前一天、指定历史日期、回到今天均正确请求;员工和主管看到同一项目同一天的数据。
|
||||||
|
|
||||||
|
#### P1-2 银城大喇叭/公司消息 MVP
|
||||||
|
|
||||||
|
- 全员频道兜底,任何员工可查看租户内已发布内容。
|
||||||
|
- 支持按项目、岗位、层级置顶或提醒;匹配失败不能导致消息丢失。
|
||||||
|
- 每份文件保存 AI 摘要和“与我有关”提取结果。
|
||||||
|
- 记录已读/未读、发布时间、发布人、适用范围和失败补推状态。
|
||||||
|
- 高管直通车、匿名建议另立业务和权限设计,不混入首版消息表。
|
||||||
|
|
||||||
|
验收:目标员工收到置顶,全员仍可从频道查看;文件摘要有原文件来源;已读状态可审计。
|
||||||
|
|
||||||
|
#### P1-3 开放问题榜与贡献闭环
|
||||||
|
|
||||||
|
- 复用现有提问、回答、最佳答案和积分链路。
|
||||||
|
- 补齐正式账号生产回归、请求幂等、贡献人和审核人追溯。
|
||||||
|
- 优质答案确认后才能沉淀企业知识;不自动发布,不引入现金或提现。
|
||||||
|
|
||||||
|
#### P1-4 智能日报完善
|
||||||
|
|
||||||
|
- 补充按项目/日期组合筛选等高频查看能力;当前成果页的历史日期查看不再重复建设。
|
||||||
|
- 报告中的每项内容可回到原始文字、语音、照片或视频。
|
||||||
|
- 自动推送在接收对象、时间、渠道和重试规则确认前不实施。
|
||||||
|
|
||||||
|
### P2:管理与成长价值
|
||||||
|
|
||||||
|
#### P2-1 岗位能力模型配置
|
||||||
|
|
||||||
|
- 管理端配置岗位维度、职级权重和合格阈值。
|
||||||
|
- 训练评分、待复盘、再练和画像统一引用同一版本模型。
|
||||||
|
- 模型版本变更不回写历史得分,报告展示所用版本。
|
||||||
|
|
||||||
|
#### P2-2 画像与情绪趋势
|
||||||
|
|
||||||
|
- 员工本人看成长趋势、低分维度和证据;主管只看授权团队。
|
||||||
|
- 情绪只作为训练表现和风险信号,不推断员工心理疾病或用于自动惩罚。
|
||||||
|
- 从趋势可进入对应训练回放、主管复盘和再练任务。
|
||||||
|
|
||||||
|
#### P2-3 生活顾问内容扩展
|
||||||
|
|
||||||
|
- 在企业知识空间补充行政、后勤、福利和项目生活指南。
|
||||||
|
- 继续通过工作助手自动路由,不让一线员工选择复杂标签。
|
||||||
|
|
||||||
|
### P3:外部接口与低确定性能力
|
||||||
|
|
||||||
|
- 正式考勤、工单、线索、客户画像、北森接口:合同、字段、权限、回执和重试规则明确后再接。
|
||||||
|
- 方言与环境模拟:只有出现明确业务场景、样本和验收口径后才立项。
|
||||||
|
- 外部接口失败必须保留 `PENDING/FAILED` 和可重试记录,不伪造成功状态。
|
||||||
|
|
||||||
|
## 4. 明确不做
|
||||||
|
|
||||||
|
- 不做固定师徒关系、经验师收益、现金变现、提现或积分商城。
|
||||||
|
- 不另建第二套聊天、上传、记忆或日报数据模型。
|
||||||
|
- 不要求员工输入项目编码、业务枚举或外部系统 ID。
|
||||||
|
- 不在用户确认前自动提交企业共享、线索、工单、案例或知识。
|
||||||
|
- 不把工作助手的 24 小时/自然日聚合扩散成问师傅全局 24 小时会话;问师傅仍保持 30 分钟、最近六轮短会话。
|
||||||
|
|
||||||
|
## 5. 发布批次
|
||||||
|
|
||||||
|
| 批次 | 交付内容 | 前置条件 | 完成证据 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| R0 口径与可用性 | 本计划、文档索引、成果历史日期查看 | 无外部依赖 | 已完成本地定向测试、类型检查、构建、390×844 交互截图;待本轮发布 |
|
||||||
|
| R1 核心正式验收 | AI 陪练严格试点、工作助手主管/多项目/真机媒体 | 正式账号、人员、项目和业务窗口 | HTTP、数据库、真机、主管页和正式试点 CSV |
|
||||||
|
| R2 高频协作 | 大喇叭 MVP、问题榜贡献闭环、智能日报筛选 | 消息发布权限和目标规则确认 | 发布/阅读/摘要/贡献全链路证据 |
|
||||||
|
| R3 管理增强 | 能力模型配置、画像/情绪趋势 | R1 产生稳定真实数据 | 历史版本、权限、趋势到证据的回归 |
|
||||||
|
| R4 外部协同 | 考勤、工单、线索、北森 | 正式接口合同和测试环境 | 回执、失败、重试、幂等和审计证据 |
|
||||||
|
|
||||||
|
## 6. 本轮实施记录
|
||||||
|
|
||||||
|
### 2026-07-21 / R0
|
||||||
|
|
||||||
|
- [x] 审计《银城帮道 介绍(1).html》十项能力与当前项目事实。
|
||||||
|
- [x] 明确 `sys_notice` 只有普通公告能力,不能冒充定向公司消息。
|
||||||
|
- [x] 将优先级、边界、批次和验收口径落盘为本计划。
|
||||||
|
- [x] 员工成果页支持历史日期切换;历史空状态使用“这一天”,可一键回到今天。
|
||||||
|
- [x] 主管成果页支持历史日期切换;标题、统计和空状态随历史日期使用中性文案,不误称“今日”。
|
||||||
|
- [x] 修复 APP 主管以手机号登录时,成果权限仍只匹配 `ext_party_id` 导致误报 403 的问题;同一规则同步用于受保护来源访问范围。
|
||||||
|
- [x] 运行定向测试、类型检查与 H5 构建。
|
||||||
|
- [x] 在 390×844 视口验证员工/主管日期切换、历史空状态和回到今天。
|
||||||
|
|
||||||
|
本轮本地验证证据:
|
||||||
|
|
||||||
|
| 验证项 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| 移动端工作成果定向测试 | `4/4` 通过 |
|
||||||
|
| 后端工作成果与确认记录权限测试 | `24/24` 通过 |
|
||||||
|
| 移动端类型检查 | 通过 |
|
||||||
|
| H5 生产构建 | 通过;只有既有 Sass 弃用警告 |
|
||||||
|
| 主管真实本地 HTTP | 项目经理手机号登录后,`GET /api/aihr/work-results/project` 从错误 403 修复为 200 |
|
||||||
|
| 员工今天成果视觉 | `docs/visual-evidence/20260721/employee-work-results-date-selector-today-390x844.png` |
|
||||||
|
| 员工历史空状态视觉 | `docs/visual-evidence/20260721/employee-work-results-history-date-390x844.png` |
|
||||||
|
| 主管成果视觉 | `docs/visual-evidence/20260721/supervisor-work-results-date-selector-today-390x844.png` |
|
||||||
|
| 主管历史空状态视觉 | `docs/visual-evidence/20260721/supervisor-work-results-history-date-390x844.png` |
|
||||||
|
|
||||||
|
全量移动端测试在本轮之前已有 18 项源码字符串/旧契约失败;本轮新增的工作成果用例全部通过,因此不能把全量套件写成绿色,也不能把既有失败归因于本轮日期功能。
|
||||||
|
|
||||||
|
后续每个 Agent 开始开发前先阅读本计划,再按对应专项文档实施:
|
||||||
|
|
||||||
|
- 工作助手与日报:[工作助手与今日工作成果迭代计划](工作助手与今日工作成果迭代计划-20260721.md)
|
||||||
|
- AI 陪练:[AI 陪练二期开发推进计划](AI陪练二期开发推进计划.md)
|
||||||
|
- 总阶段边界:[银城员工端 APP 分阶段实施总纲](银城员工端APP分阶段实施总纲.md)
|
||||||
|
- 工程契约:[物业 AI 人力资源系统开发规格 TechSpec](物业AI人力资源系统开发规格TechSpec.md)
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<view class="work-date-selector" aria-label="选择工作成果日期">
|
||||||
|
<button class="date-step" :disabled="disabled" @click="shiftDate(-1)">前一天</button>
|
||||||
|
<picker
|
||||||
|
class="date-picker"
|
||||||
|
mode="date"
|
||||||
|
:value="modelValue"
|
||||||
|
:end="today"
|
||||||
|
:disabled="disabled"
|
||||||
|
@change="selectDate"
|
||||||
|
>
|
||||||
|
<view class="date-current">
|
||||||
|
<text class="date-label">{{ displayDate }}</text>
|
||||||
|
<text class="date-hint">{{ modelValue === today ? '选择历史日期' : '点此选择日期' }}</text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
<button class="date-step" :disabled="disabled || modelValue >= today" @click="shiftDate(1)">后一天</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
modelValue: string;
|
||||||
|
maxDate?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}>(), {
|
||||||
|
disabled: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
change: [value: string];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const currentDate = () => new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' });
|
||||||
|
const today = computed(() => props.maxDate || currentDate());
|
||||||
|
|
||||||
|
const displayDate = computed(() => {
|
||||||
|
if (props.modelValue === today.value) return `今天 · ${props.modelValue}`;
|
||||||
|
const [year, month, day] = props.modelValue.split('-').map(Number);
|
||||||
|
if (!year || !month || !day) return props.modelValue;
|
||||||
|
return new Intl.DateTimeFormat('zh-CN', {
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
weekday: 'short',
|
||||||
|
timeZone: 'Asia/Shanghai'
|
||||||
|
}).format(new Date(Date.UTC(year, month - 1, day, 4)));
|
||||||
|
});
|
||||||
|
|
||||||
|
const shiftDate = (days: number) => {
|
||||||
|
if (props.disabled) return;
|
||||||
|
const [year, month, day] = props.modelValue.split('-').map(Number);
|
||||||
|
if (!year || !month || !day) return;
|
||||||
|
const nextDate = new Date(Date.UTC(year, month - 1, day + days)).toISOString().slice(0, 10);
|
||||||
|
if (nextDate > today.value || nextDate === props.modelValue) return;
|
||||||
|
emit('change', nextDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectDate = (event: { detail: { value: string } }) => {
|
||||||
|
const value = event.detail.value;
|
||||||
|
if (!value || value > today.value || value === props.modelValue) return;
|
||||||
|
emit('change', value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.work-date-selector{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:stretch;gap:8px;margin-bottom:14px;padding:8px;border:1px solid #e5e7eb;border-radius:18px;background:#fff;box-shadow:0 6px 20px rgba(31,38,50,.04)}
|
||||||
|
.date-step{display:flex;align-items:center;justify-content:center;min-width:72px;min-height:48px;margin:0;padding:0 10px;border:0;border-radius:13px;background:#f5f6f8;color:#303744;font-size:14px;line-height:1}
|
||||||
|
.date-step::after{border:0}
|
||||||
|
.date-step[disabled]{background:#fafafa;color:#b9bec7;opacity:1}
|
||||||
|
.date-picker{min-width:0}
|
||||||
|
.date-current{display:flex;align-items:center;justify-content:center;min-height:48px;flex-direction:column;border-radius:13px;background:#fff0f1;text-align:center}
|
||||||
|
.date-label{color:#171b23;font-size:15px;font-weight:800}
|
||||||
|
.date-hint{margin-top:3px;color:#d9081c;font-size:11px}
|
||||||
|
html[data-aihr-font-size='extra-large'] .work-date-selector{grid-template-columns:1fr 1.5fr 1fr}
|
||||||
|
html[data-aihr-font-size='extra-large'] .date-step{min-width:0;padding:0 6px;font-size:13px}
|
||||||
|
html[data-aihr-font-size='extra-large'] .date-label{font-size:14px}
|
||||||
|
</style>
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/user/work-results/index",
|
"path": "pages/user/work-results/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "今日工作成果"
|
"navigationBarTitleText": "工作成果"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/supervisor/work-results/index",
|
"path": "pages/supervisor/work-results/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "团队今日成果"
|
"navigationBarTitleText": "团队工作成果"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,12 +2,19 @@
|
|||||||
<view class="page supervisor-result-page">
|
<view class="page supervisor-result-page">
|
||||||
<view class="page-head">
|
<view class="page-head">
|
||||||
<button class="back" @click="goBack">‹</button>
|
<button class="back" @click="goBack">‹</button>
|
||||||
<view class="head-copy"><text class="title">团队今日成果</text><text class="subtitle">{{ result?.projectName || currentProject?.projectName || '当前项目' }} · {{ workDate }}</text></view>
|
<view class="head-copy"><text class="title">团队工作成果</text><text class="subtitle">{{ result?.projectName || currentProject?.projectName || '当前项目' }} · {{ workDate }}</text></view>
|
||||||
<button class="refresh" @click="load">刷新</button>
|
<button class="refresh" @click="load">刷新</button>
|
||||||
</view>
|
</view>
|
||||||
|
<WorkDateSelector
|
||||||
|
v-if="currentProject"
|
||||||
|
:model-value="workDate"
|
||||||
|
:max-date="today"
|
||||||
|
:disabled="loading"
|
||||||
|
@change="changeWorkDate"
|
||||||
|
/>
|
||||||
<view v-if="message" class="message">{{ message }}</view>
|
<view v-if="message" class="message">{{ message }}</view>
|
||||||
<view v-if="result" class="metrics">
|
<view v-if="result" class="metrics">
|
||||||
<view><text class="num">{{ result.contributorCount }}</text><text>今日记录人数</text></view>
|
<view><text class="num">{{ result.contributorCount }}</text><text>记录人数</text></view>
|
||||||
<view><text class="num">{{ result.resultCount }}</text><text>已形成成果</text></view>
|
<view><text class="num">{{ result.resultCount }}</text><text>已形成成果</text></view>
|
||||||
<view><text class="num warn">{{ result.pendingCount }}</text><text>待跟进</text></view>
|
<view><text class="num warn">{{ result.pendingCount }}</text><text>待跟进</text></view>
|
||||||
<view><text class="num danger">{{ result.highPriorityCount }}</text><text>高优问题</text></view>
|
<view><text class="num danger">{{ result.highPriorityCount }}</text><text>高优问题</text></view>
|
||||||
@@ -29,13 +36,13 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="result && !result.members.length" class="empty">当前项目今天还没有已确认的工作记录。</view>
|
<view v-if="result && !result.members.length" class="empty">{{ emptyMessage }}</view>
|
||||||
<SupervisorTabBar active="today" />
|
<SupervisorTabBar active="today" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { onShow } from '@dcloudio/uni-app';
|
import { onShow } from '@dcloudio/uni-app';
|
||||||
import type { MobileProject, ProjectWorkResult, WorkResult, WorkResultItem } from '@/types/api';
|
import type { MobileProject, ProjectWorkResult, WorkResult, WorkResultItem } from '@/types/api';
|
||||||
import { getCurrentProject, reconcileCurrentProject } from '@/services/auth';
|
import { getCurrentProject, reconcileCurrentProject } from '@/services/auth';
|
||||||
@@ -44,12 +51,18 @@ import { updateAssistantCaptureStatus } from '@/services/personal-assistant';
|
|||||||
import { openProtectedSource } from '@/services/source-preview';
|
import { openProtectedSource } from '@/services/source-preview';
|
||||||
import { getProjectWorkResult } from '@/services/work-results';
|
import { getProjectWorkResult } from '@/services/work-results';
|
||||||
import SupervisorTabBar from '@/components/SupervisorTabBar.vue';
|
import SupervisorTabBar from '@/components/SupervisorTabBar.vue';
|
||||||
|
import WorkDateSelector from '@/components/WorkDateSelector.vue';
|
||||||
|
|
||||||
const result = ref<ProjectWorkResult | null>(null);
|
const result = ref<ProjectWorkResult | null>(null);
|
||||||
const currentProject = ref<MobileProject | null>(getCurrentProject());
|
const currentProject = ref<MobileProject | null>(getCurrentProject());
|
||||||
const message = ref('');
|
const message = ref('');
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const workDate = new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' });
|
const currentDate = () => new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' });
|
||||||
|
const today = ref(currentDate());
|
||||||
|
const workDate = ref(today.value);
|
||||||
|
const emptyMessage = computed(() => workDate.value === today.value
|
||||||
|
? '当前项目今天还没有已确认的工作记录。'
|
||||||
|
: '当前项目这一天没有已确认的工作记录。');
|
||||||
const memberItems = (member: WorkResult) => Object.values(member.sections || {}).flat();
|
const memberItems = (member: WorkResult) => Object.values(member.sections || {}).flat();
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
@@ -60,13 +73,18 @@ const load = async () => {
|
|||||||
const identity = await fetchMobileIdentity();
|
const identity = await fetchMobileIdentity();
|
||||||
currentProject.value = reconcileCurrentProject(identity.projects || []);
|
currentProject.value = reconcileCurrentProject(identity.projects || []);
|
||||||
if (!currentProject.value) throw new Error('请先选择当前项目');
|
if (!currentProject.value) throw new Error('请先选择当前项目');
|
||||||
result.value = await getProjectWorkResult(currentProject.value.projectCode, workDate);
|
result.value = await getProjectWorkResult(currentProject.value.projectCode, workDate.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.value = error instanceof Error ? error.message : '团队成果读取失败';
|
message.value = error instanceof Error ? error.message : '团队成果读取失败';
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const changeWorkDate = (value: string) => {
|
||||||
|
workDate.value = value;
|
||||||
|
result.value = null;
|
||||||
|
void load();
|
||||||
|
};
|
||||||
const advance = async (item: WorkResultItem) => {
|
const advance = async (item: WorkResultItem) => {
|
||||||
const next = item.businessStatus === 'IN_PROGRESS' ? 'COMPLETED' : 'IN_PROGRESS';
|
const next = item.businessStatus === 'IN_PROGRESS' ? 'COMPLETED' : 'IN_PROGRESS';
|
||||||
try { await updateAssistantCaptureStatus(item.captureId, next); await load(); }
|
try { await updateAssistantCaptureStatus(item.captureId, next); await load(); }
|
||||||
@@ -75,7 +93,10 @@ const advance = async (item: WorkResultItem) => {
|
|||||||
const openSource = (item: WorkResultItem) => openProtectedSource(item.sourceUrl, item.sourceType);
|
const openSource = (item: WorkResultItem) => openProtectedSource(item.sourceUrl, item.sourceType);
|
||||||
const sourceLabel = (value: string) => ({ TEXT: '文字来源', VOICE: '原始语音', IMAGE: '现场照片', VIDEO: '现场视频' }[value] || '原始来源');
|
const sourceLabel = (value: string) => ({ TEXT: '文字来源', VOICE: '原始语音', IMAGE: '现场照片', VIDEO: '现场视频' }[value] || '原始来源');
|
||||||
const goBack = () => uni.navigateBack({ fail: () => uni.redirectTo({ url: '/pages/supervisor/index/index' }) });
|
const goBack = () => uni.navigateBack({ fail: () => uni.redirectTo({ url: '/pages/supervisor/index/index' }) });
|
||||||
onShow(load);
|
onShow(() => {
|
||||||
|
today.value = currentDate();
|
||||||
|
load();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -3,12 +3,20 @@
|
|||||||
<view class="page-head">
|
<view class="page-head">
|
||||||
<button class="back" aria-label="返回" @click="goBack">‹</button>
|
<button class="back" aria-label="返回" @click="goBack">‹</button>
|
||||||
<view class="head-copy">
|
<view class="head-copy">
|
||||||
<text class="title">今日工作成果</text>
|
<text class="title">工作成果</text>
|
||||||
<text class="subtitle">{{ result?.projectName || currentProject?.projectName || '请选择项目' }} · {{ workDate }}</text>
|
<text class="subtitle">{{ result?.projectName || currentProject?.projectName || '请选择项目' }} · {{ workDate }}</text>
|
||||||
</view>
|
</view>
|
||||||
<button class="refresh" :disabled="loading" @click="load">{{ loading ? '整理中' : '重新整理' }}</button>
|
<button class="refresh" :disabled="loading" @click="load">{{ loading ? '整理中' : '重新整理' }}</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<WorkDateSelector
|
||||||
|
v-if="currentProject"
|
||||||
|
:model-value="workDate"
|
||||||
|
:max-date="today"
|
||||||
|
:disabled="loading"
|
||||||
|
@change="changeWorkDate"
|
||||||
|
/>
|
||||||
|
|
||||||
<view v-if="message" class="message">{{ message }}</view>
|
<view v-if="message" class="message">{{ message }}</view>
|
||||||
<view v-if="!currentProject && !loading" class="empty-card">
|
<view v-if="!currentProject && !loading" class="empty-card">
|
||||||
<text class="empty-title">请先在工作助手选择当前项目</text>
|
<text class="empty-title">请先在工作助手选择当前项目</text>
|
||||||
@@ -48,9 +56,9 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="!result.total" class="empty-card">
|
<view v-if="!result.total" class="empty-card">
|
||||||
<text class="empty-title">今天还没有已确认的工作记录</text>
|
<text class="empty-title">{{ emptyTitle }}</text>
|
||||||
<text class="empty-desc">在工作助手里按住说话或发照片,确认后会自动汇总到这里。</text>
|
<text class="empty-desc">{{ emptyDescription }}</text>
|
||||||
<button class="primary" @click="goAssistant">去记录一件事</button>
|
<button class="primary" @click="handleEmptyAction">{{ emptyActionLabel }}</button>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
@@ -65,14 +73,23 @@ import { fetchMobileIdentity } from '@/services/position';
|
|||||||
import { updateAssistantCaptureStatus } from '@/services/personal-assistant';
|
import { updateAssistantCaptureStatus } from '@/services/personal-assistant';
|
||||||
import { openProtectedSource } from '@/services/source-preview';
|
import { openProtectedSource } from '@/services/source-preview';
|
||||||
import { generateMyWorkResult } from '@/services/work-results';
|
import { generateMyWorkResult } from '@/services/work-results';
|
||||||
|
import WorkDateSelector from '@/components/WorkDateSelector.vue';
|
||||||
|
|
||||||
const result = ref<WorkResult | null>(null);
|
const result = ref<WorkResult | null>(null);
|
||||||
const currentProject = ref<MobileProject | null>(getCurrentProject());
|
const currentProject = ref<MobileProject | null>(getCurrentProject());
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const message = ref('');
|
const message = ref('');
|
||||||
const workDate = new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' });
|
const currentDate = () => new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' });
|
||||||
|
const today = ref(currentDate());
|
||||||
|
const workDate = ref(today.value);
|
||||||
|
const isToday = computed(() => workDate.value === today.value);
|
||||||
|
const emptyTitle = computed(() => isToday.value ? '今天还没有已确认的工作记录' : '这一天没有已确认的工作记录');
|
||||||
|
const emptyDescription = computed(() => isToday.value
|
||||||
|
? '在工作助手里按住说话或发照片,确认后会自动汇总到这里。'
|
||||||
|
: '可以继续查看其他日期,或回到今天记录新的工作。');
|
||||||
|
const emptyActionLabel = computed(() => isToday.value ? '去记录一件事' : '回到今天');
|
||||||
const sectionLabels: Record<string, string> = {
|
const sectionLabels: Record<string, string> = {
|
||||||
completed: '今日已完成',
|
completed: '当日已完成',
|
||||||
inspection: '巡检发现',
|
inspection: '巡检发现',
|
||||||
services: '业主服务',
|
services: '业主服务',
|
||||||
leads: '画像与服务线索',
|
leads: '画像与服务线索',
|
||||||
@@ -94,14 +111,20 @@ const load = async () => {
|
|||||||
result.value = null;
|
result.value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
result.value = await generateMyWorkResult(currentProject.value.projectCode, workDate);
|
result.value = await generateMyWorkResult(currentProject.value.projectCode, workDate.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.value = error instanceof Error ? error.message : '今日工作成果读取失败';
|
message.value = error instanceof Error ? error.message : '工作成果读取失败';
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const changeWorkDate = (value: string) => {
|
||||||
|
workDate.value = value;
|
||||||
|
result.value = null;
|
||||||
|
void load();
|
||||||
|
};
|
||||||
|
|
||||||
const advance = async (item: WorkResultItem) => {
|
const advance = async (item: WorkResultItem) => {
|
||||||
const next = item.businessStatus === 'IN_PROGRESS' ? 'COMPLETED' : 'IN_PROGRESS';
|
const next = item.businessStatus === 'IN_PROGRESS' ? 'COMPLETED' : 'IN_PROGRESS';
|
||||||
try {
|
try {
|
||||||
@@ -120,9 +143,13 @@ const statusLabel = (value: string) => ({
|
|||||||
const sourceLabel = (value: string) => ({ TEXT: '文字', VOICE: '语音', IMAGE: '照片', VIDEO: '视频' }[value] || '文字');
|
const sourceLabel = (value: string) => ({ TEXT: '文字', VOICE: '语音', IMAGE: '照片', VIDEO: '视频' }[value] || '文字');
|
||||||
const displayTime = (value: string) => value?.replace('T', ' ').slice(11, 16) || '--:--';
|
const displayTime = (value: string) => value?.replace('T', ' ').slice(11, 16) || '--:--';
|
||||||
const goAssistant = () => uni.switchTab({ url: '/pages/user/sop/index' });
|
const goAssistant = () => uni.switchTab({ url: '/pages/user/sop/index' });
|
||||||
|
const handleEmptyAction = () => isToday.value ? goAssistant() : changeWorkDate(today.value);
|
||||||
const goBack = () => uni.navigateBack({ fail: () => uni.switchTab({ url: '/pages/user/today/index' }) });
|
const goBack = () => uni.navigateBack({ fail: () => uni.switchTab({ url: '/pages/user/today/index' }) });
|
||||||
|
|
||||||
onShow(load);
|
onShow(() => {
|
||||||
|
today.value = currentDate();
|
||||||
|
load();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -44,3 +44,31 @@ test('员工与主管成果页都能追溯非文字原始来源', async () => {
|
|||||||
assert.match(preview, /previewMedia/);
|
assert.match(preview, /previewMedia/);
|
||||||
assert.doesNotMatch(employeePage, /openDocument/);
|
assert.doesNotMatch(employeePage, /openDocument/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('员工与主管成果页复用日期选择器查看历史成果且禁止未来日期', async () => {
|
||||||
|
const [employeePage, supervisorPage, dateSelector, pages] = await Promise.all([
|
||||||
|
read('../src/pages/user/work-results/index.vue'),
|
||||||
|
read('../src/pages/supervisor/work-results/index.vue'),
|
||||||
|
read('../src/components/WorkDateSelector.vue'),
|
||||||
|
read('../src/pages.json')
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.match(employeePage, /WorkDateSelector/);
|
||||||
|
assert.match(supervisorPage, /WorkDateSelector/);
|
||||||
|
assert.match(employeePage, /:max-date="today"/);
|
||||||
|
assert.match(supervisorPage, /:max-date="today"/);
|
||||||
|
assert.match(employeePage, /generateMyWorkResult\(currentProject\.value\.projectCode, workDate\.value\)/);
|
||||||
|
assert.match(supervisorPage, /getProjectWorkResult\(currentProject\.value\.projectCode, workDate\.value\)/);
|
||||||
|
assert.match(dateSelector, /mode="date"/);
|
||||||
|
assert.match(dateSelector, /:end="today"/);
|
||||||
|
assert.match(dateSelector, /props\.maxDate \|\| currentDate\(\)/);
|
||||||
|
assert.match(dateSelector, /nextDate > today\.value/);
|
||||||
|
assert.match(employeePage, /这一天没有已确认的工作记录/);
|
||||||
|
assert.match(employeePage, /回到今天/);
|
||||||
|
assert.match(employeePage, /completed: '当日已完成'/);
|
||||||
|
assert.match(supervisorPage, /当前项目这一天没有已确认的工作记录/);
|
||||||
|
assert.match(supervisorPage, />记录人数</);
|
||||||
|
assert.doesNotMatch(supervisorPage, /今日记录人数/);
|
||||||
|
assert.match(pages, /"navigationBarTitleText": "工作成果"/);
|
||||||
|
assert.match(pages, /"navigationBarTitleText": "团队工作成果"/);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user