@things-factory/worklist 10.1.270 → 10.1.272
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +1 -0
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/components/activity-thread-timeline.d.ts +2 -2
- package/dist-client/components/person-question-todo.d.ts +16 -0
- package/dist-client/components/person-question-todo.js +75 -0
- package/dist-client/components/person-question-todo.js.map +1 -0
- package/dist-client/components/source-link.d.ts +1 -1
- package/dist-client/pages/activity-instance/activity-instance-view.d.ts +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view.d.ts +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/activity-approval/_abort.js +2 -0
- package/dist-server/controllers/activity-approval/_abort.js.map +1 -1
- package/dist-server/controllers/activity-approval/claim.js +6 -1
- package/dist-server/controllers/activity-approval/claim.js.map +1 -1
- package/dist-server/controllers/activity-instance/complete.js +4 -0
- package/dist-server/controllers/activity-instance/complete.js.map +1 -1
- package/dist-server/controllers/activity-instance/hand-out.d.ts +2 -0
- package/dist-server/controllers/activity-instance/hand-out.js +7 -0
- package/dist-server/controllers/activity-instance/hand-out.js.map +1 -1
- package/dist-server/controllers/common.js +2 -0
- package/dist-server/controllers/common.js.map +1 -1
- package/dist-server/service/activity/index.d.ts +1 -1
- package/dist-server/service/activity-approval/index.d.ts +1 -1
- package/dist-server/service/activity-instance/index.d.ts +1 -1
- package/dist-server/service/activity-template/index.d.ts +1 -1
- package/dist-server/service/activity-thread/index.d.ts +1 -1
- package/dist-server/service/approval-notice/approval-notice-rules.d.ts +9 -0
- package/dist-server/service/approval-notice/approval-notice-rules.js +25 -1
- package/dist-server/service/approval-notice/approval-notice-rules.js.map +1 -1
- package/dist-server/service/approval-notice/approval-notice.d.ts +22 -0
- package/dist-server/service/approval-notice/approval-notice.js +46 -0
- package/dist-server/service/approval-notice/approval-notice.js.map +1 -1
- package/dist-server/service/index.d.ts +5 -3
- package/dist-server/service/index.js +12 -2
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/person-question/index.d.ts +4 -0
- package/dist-server/service/person-question/index.js +19 -0
- package/dist-server/service/person-question/index.js.map +1 -0
- package/dist-server/service/person-question/person-question-activity.d.ts +67 -0
- package/dist-server/service/person-question/person-question-activity.js +46 -0
- package/dist-server/service/person-question/person-question-activity.js.map +1 -0
- package/dist-server/service/person-question/person-question-card.d.ts +1 -0
- package/dist-server/service/person-question/person-question-card.js +36 -0
- package/dist-server/service/person-question/person-question-card.js.map +1 -0
- package/dist-server/service/person-question/person-question-resolver.d.ts +10 -0
- package/dist-server/service/person-question/person-question-resolver.js +54 -0
- package/dist-server/service/person-question/person-question-resolver.js.map +1 -0
- package/dist-server/service/person-question/person-question-service.d.ts +17 -0
- package/dist-server/service/person-question/person-question-service.js +86 -0
- package/dist-server/service/person-question/person-question-service.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -10
- package/tests/approval-notice-db.test.ts +3 -1
- package/tests/complete-db.test.ts +2 -1
- package/tests/delegate-approval.test.ts +3 -1
- package/tests/hand-out-notice-db.test.ts +99 -0
- package/tests/person-question-db.test.ts +158 -0
- package/translations/en.json +21 -1
- package/translations/es.json +21 -1
- package/translations/ja.json +21 -1
- package/translations/ko.json +21 -1
- package/translations/ms.json +21 -1
- package/translations/zh.json +21 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/worklist",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.272",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -57,16 +57,17 @@
|
|
|
57
57
|
"@operato/p13n": "^10.29.43",
|
|
58
58
|
"@operato/popup": "^10.29.43",
|
|
59
59
|
"@operato/shell": "^10.29.69",
|
|
60
|
-
"@things-factory/
|
|
61
|
-
"@things-factory/
|
|
62
|
-
"@things-factory/
|
|
60
|
+
"@things-factory/ai-assistant": "^10.1.272",
|
|
61
|
+
"@things-factory/attachment-base": "^10.1.271",
|
|
62
|
+
"@things-factory/auth-base": "^10.1.271",
|
|
63
|
+
"@things-factory/board-service": "^10.1.271",
|
|
63
64
|
"@things-factory/context-ui": "^10.1.270",
|
|
64
|
-
"@things-factory/i18n-base": "^10.1.
|
|
65
|
-
"@things-factory/notification": "^10.1.
|
|
66
|
-
"@things-factory/organization": "^10.1.
|
|
67
|
-
"@things-factory/personalization": "^10.1.
|
|
68
|
-
"@things-factory/scheduler-client": "^10.1.
|
|
65
|
+
"@things-factory/i18n-base": "^10.1.271",
|
|
66
|
+
"@things-factory/notification": "^10.1.272",
|
|
67
|
+
"@things-factory/organization": "^10.1.272",
|
|
68
|
+
"@things-factory/personalization": "^10.1.271",
|
|
69
|
+
"@things-factory/scheduler-client": "^10.1.271",
|
|
69
70
|
"@things-factory/shell": "^10.1.270"
|
|
70
71
|
},
|
|
71
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "f87fefe0687eedf52a25cda1c54d59efabaf3dc2"
|
|
72
73
|
}
|
|
@@ -23,6 +23,8 @@ const { Activity } = require(join(DIST, 'service', 'activity', 'activity.js'))
|
|
|
23
23
|
const { Domain } = require('@things-factory/shell')
|
|
24
24
|
const { Role, User } = require('@things-factory/auth-base')
|
|
25
25
|
const { Employee } = require('@things-factory/organization')
|
|
26
|
+
/* the turn notices an approval closes are kept by notification (ADR-0164 amendment 1) */
|
|
27
|
+
const { Notification } = require('@things-factory/notification')
|
|
26
28
|
|
|
27
29
|
let db: any
|
|
28
30
|
let here: any
|
|
@@ -37,7 +39,7 @@ let leave: any
|
|
|
37
39
|
const seats: Record<string, any> = {}
|
|
38
40
|
|
|
39
41
|
beforeAll(async () => {
|
|
40
|
-
db = createSqliteTestDataSource([ActivityInstance, ActivityThread, ActivityApproval, Activity, Employee])
|
|
42
|
+
db = createSqliteTestDataSource([ActivityInstance, ActivityThread, ActivityApproval, Activity, Employee, Notification])
|
|
41
43
|
await db.initialize()
|
|
42
44
|
|
|
43
45
|
here = await db.getRepository(Domain).save({ name: 'here', subdomain: 'here' })
|
|
@@ -27,7 +27,8 @@ let lee: any
|
|
|
27
27
|
let todo: any
|
|
28
28
|
|
|
29
29
|
beforeAll(async () => {
|
|
30
|
-
|
|
30
|
+
/* Ending a case closes the notice that handed it out (ADR-0200 decision 3), so notification's rows are there. */
|
|
31
|
+
db = createSqliteTestDataSource([ActivityInstance, ActivityThread, ActivityApproval, Activity, Employee, require('@things-factory/notification').Notification])
|
|
31
32
|
await db.initialize()
|
|
32
33
|
here = await db.getRepository(Domain).save({ name: 'here', subdomain: 'complete-here' })
|
|
33
34
|
const person = (name: string) => db.getRepository(User).save({ name, email: `${name}@example.com`, password: 'x', status: 'activated', userType: 'user', domains: [here] })
|
|
@@ -17,6 +17,8 @@ const { ActivityApproval } = require(join(DIST, 'service', 'activity-approval',
|
|
|
17
17
|
const { delegate } = require(join(DIST, 'controllers', 'activity-approval', 'delegate.js'))
|
|
18
18
|
const { Domain } = require('@things-factory/shell')
|
|
19
19
|
const { User } = require('@things-factory/auth-base')
|
|
20
|
+
/* the turn notices an approval closes are kept by notification (ADR-0164 amendment 1) */
|
|
21
|
+
const { Notification } = require('@things-factory/notification')
|
|
20
22
|
|
|
21
23
|
let db: any
|
|
22
24
|
let here: any
|
|
@@ -27,7 +29,7 @@ let issuer: any
|
|
|
27
29
|
let stranger: any
|
|
28
30
|
|
|
29
31
|
beforeAll(async () => {
|
|
30
|
-
db = createSqliteTestDataSource([ActivityInstance, ActivityThread, ActivityApproval])
|
|
32
|
+
db = createSqliteTestDataSource([ActivityInstance, ActivityThread, ActivityApproval, Notification])
|
|
31
33
|
await db.initialize()
|
|
32
34
|
|
|
33
35
|
here = await db.getRepository(Domain).save({ name: 'here', subdomain: 'here' })
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { join } from 'path'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A to-do handed out tells its person (ADR-0200 decision 3) - through the real `handOut` and `complete` on sqlite.
|
|
5
|
+
*
|
|
6
|
+
* The question handed to the person a conversation is about, a legal training, a course: each is the person's 할 일,
|
|
7
|
+
* and nothing told them it came (found 2026-11-12 on :4730). They are told after the commit, the notice opens the to-do,
|
|
8
|
+
* and it closes when the work is done. Handing a to-do to oneself tells nobody.
|
|
9
|
+
*
|
|
10
|
+
* Reads dist-server - run the package's build:server first. The jest env has no translations, so the words are keys.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { createSqliteTestDataSource } = require(join(__dirname, '..', '..', 'ops-master', 'test', 'support', 'database.cjs'))
|
|
14
|
+
const DIST = join(__dirname, '..', 'dist-server')
|
|
15
|
+
const { Activity } = require(join(DIST, 'service', 'activity', 'activity.js'))
|
|
16
|
+
const { ActivityInstance } = require(join(DIST, 'service', 'activity-instance', 'activity-instance.js'))
|
|
17
|
+
const { ActivityInstanceHistory } = require(join(DIST, 'service', 'activity-instance', 'activity-instance-history.js'))
|
|
18
|
+
const { ActivityInstanceHistoryEntitySubscriber } = require(join(DIST, 'service', 'activity-instance', 'event-subscriber.js'))
|
|
19
|
+
const { ActivityThread } = require(join(DIST, 'service', 'activity-thread', 'activity-thread.js'))
|
|
20
|
+
const { ActivityApproval } = require(join(DIST, 'service', 'activity-approval', 'activity-approval.js'))
|
|
21
|
+
const { handOut } = require(join(DIST, 'controllers', 'activity-instance', 'hand-out.js'))
|
|
22
|
+
const { complete } = require(join(DIST, 'controllers', 'activity-instance', 'complete.js'))
|
|
23
|
+
const { Domain } = require('@things-factory/shell')
|
|
24
|
+
const { User } = require('@things-factory/auth-base')
|
|
25
|
+
const { Notification } = require('@things-factory/notification')
|
|
26
|
+
|
|
27
|
+
let db: any
|
|
28
|
+
let here: any
|
|
29
|
+
let manager: any
|
|
30
|
+
let employee: any
|
|
31
|
+
let activity: any
|
|
32
|
+
|
|
33
|
+
beforeAll(async () => {
|
|
34
|
+
db = createSqliteTestDataSource([Activity, ActivityInstance, ActivityInstanceHistory, ActivityThread, ActivityApproval, Notification], {
|
|
35
|
+
subscribers: [ActivityInstanceHistoryEntitySubscriber]
|
|
36
|
+
})
|
|
37
|
+
await db.initialize()
|
|
38
|
+
here = await db.getRepository(Domain).save({ name: 'here', subdomain: 'here' })
|
|
39
|
+
const person = (name: string) => db.getRepository(User).save({ name, email: `${name}@example.com`, password: 'x', status: 'activated', userType: 'user', locale: 'ko-KR', domains: [here] })
|
|
40
|
+
manager = await person('manager')
|
|
41
|
+
employee = await person('employee')
|
|
42
|
+
activity = await db.getRepository(Activity).save({ domain: here, name: 'Person Question', activityType: 'user', startingType: 'issue' })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
afterAll(async () => db?.destroy())
|
|
46
|
+
|
|
47
|
+
/** Runs `work` as `user` in a transaction with an after-commit list, then the list - as the @transaction directive does. */
|
|
48
|
+
async function asRequest(user: any, work: (context: any) => Promise<any>, { rollback = false } = {}) {
|
|
49
|
+
const told: any[] = []
|
|
50
|
+
const afterCommit: { name: string; run: () => unknown }[] = []
|
|
51
|
+
let toldBeforeCommit = -1
|
|
52
|
+
let result: any
|
|
53
|
+
try {
|
|
54
|
+
await db.transaction(async (tx: any) => {
|
|
55
|
+
const context = { state: { domain: here, user, tx, afterCommit, notify: (payload: any) => void told.push(payload) }, t: (key: string) => key }
|
|
56
|
+
result = await work(context)
|
|
57
|
+
toldBeforeCommit = told.length
|
|
58
|
+
if (rollback) throw new Error('rolled back')
|
|
59
|
+
})
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!rollback) throw error
|
|
62
|
+
return { told, toldBeforeCommit, result }
|
|
63
|
+
}
|
|
64
|
+
for (const step of afterCommit) await step.run()
|
|
65
|
+
for (let i = 0; i < 20 && told.length === 0; i++) await new Promise(resolve => setTimeout(resolve, 5))
|
|
66
|
+
await new Promise(resolve => setTimeout(resolve, 20))
|
|
67
|
+
return { told, toldBeforeCommit, result }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const foreground = (told: any[]) => told.filter(one => one.mode === 'foreground')
|
|
71
|
+
const handTo = (assignee: any) => (context: any) => handOut({ activityId: activity.id, name: '납품 건 금요일까지 되나?', assignee }, context)
|
|
72
|
+
|
|
73
|
+
it('★ a to-do handed to someone tells them after the commit, and the notice opens the to-do', async () => {
|
|
74
|
+
const { told, toldBeforeCommit, result } = await asRequest(manager, handTo(employee))
|
|
75
|
+
const thread = await db.getRepository(ActivityThread).findOne({ where: { activityInstance: { id: result.id } } })
|
|
76
|
+
expect(toldBeforeCommit).toBe(0)
|
|
77
|
+
expect(foreground(told)).toEqual([expect.objectContaining({ receivers: [employee.id], type: 'todo', title: 'todo-notice.subject', url: `activity-thread/${thread.id}` })])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('★ handing a to-do to oneself tells nobody', async () => {
|
|
81
|
+
const { told } = await asRequest(employee, handTo(employee))
|
|
82
|
+
expect(told).toEqual([])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('rolled back, nobody is told', async () => {
|
|
86
|
+
const { told } = await asRequest(manager, handTo(employee), { rollback: true })
|
|
87
|
+
expect(told).toEqual([])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('★ the work done, its notice is closed', async () => {
|
|
91
|
+
const { result } = await asRequest(manager, handTo(employee))
|
|
92
|
+
const thread = await db.getRepository(ActivityThread).findOne({ where: { activityInstance: { id: result.id } } })
|
|
93
|
+
/* The row as notification keeps it for the person - kept through the app's own data source, not this test's. */
|
|
94
|
+
const row = await db.getRepository(Notification).save({ domain: here, owner: employee, type: 'todo', subject: 'info', title: 't', sourceKey: `todo-thread:${thread.id}`, state: 'UNREAD' })
|
|
95
|
+
const kept = () => db.getRepository(Notification).findOne({ where: { id: row.id } })
|
|
96
|
+
expect((await kept())?.state).toBe('UNREAD')
|
|
97
|
+
await asRequest(employee, context => complete({ id: result.id, output: { answer: '가능합니다' } }, context))
|
|
98
|
+
expect((await kept())?.state).toBe('READ')
|
|
99
|
+
})
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { join } from 'path'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 본인 확인 요청 (ADR-0200 decision 3) - a question asked about someone that their records could not answer goes to them
|
|
5
|
+
* as a to-do, from the conversation about them; their answer in that conversation ends it. Real entities on sqlite.
|
|
6
|
+
*
|
|
7
|
+
* The conversation decides who is asked - the person it is about, never a value on the card - and who may ask: someone
|
|
8
|
+
* seated in it. Only the person's words to the room close it, and only their own to-dos from that conversation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { createSqliteTestDataSource } = require(join(__dirname, '..', '..', 'ops-master', 'test', 'support', 'database.cjs'))
|
|
12
|
+
const DIST = join(__dirname, '..', 'dist-server')
|
|
13
|
+
const { Activity } = require(join(DIST, 'service', 'activity', 'activity.js'))
|
|
14
|
+
const { ActivityInstance } = require(join(DIST, 'service', 'activity-instance', 'activity-instance.js'))
|
|
15
|
+
const { ActivityInstanceHistory } = require(join(DIST, 'service', 'activity-instance', 'activity-instance-history.js'))
|
|
16
|
+
const { ActivityInstanceHistoryEntitySubscriber } = require(join(DIST, 'service', 'activity-instance', 'event-subscriber.js'))
|
|
17
|
+
const { ActivityThread } = require(join(DIST, 'service', 'activity-thread', 'activity-thread.js'))
|
|
18
|
+
const { ActivityApproval } = require(join(DIST, 'service', 'activity-approval', 'activity-approval.js'))
|
|
19
|
+
const { myOpenThreadsWhere } = require(join(DIST, 'service', 'activity-thread', 'my-open-threads.js'))
|
|
20
|
+
const { askPerson, closePersonQuestions, PERSON_QUESTION_LIMIT } = require(join(DIST, 'service', 'person-question', 'person-question-service.js'))
|
|
21
|
+
const { PERSON_QUESTION_ACTIVITY } = require(join(DIST, 'service', 'person-question', 'person-question-activity.js'))
|
|
22
|
+
const { ChatSession, ChatMessage, ChatSessionParticipant } = require('@things-factory/ai-assistant')
|
|
23
|
+
const { Domain } = require('@things-factory/shell')
|
|
24
|
+
const { User } = require('@things-factory/auth-base')
|
|
25
|
+
|
|
26
|
+
let db: any
|
|
27
|
+
let here: any
|
|
28
|
+
let manager: any
|
|
29
|
+
let employee: any
|
|
30
|
+
let outsider: any
|
|
31
|
+
let activity: any
|
|
32
|
+
let room: any
|
|
33
|
+
|
|
34
|
+
beforeAll(async () => {
|
|
35
|
+
db = createSqliteTestDataSource([Activity, ActivityInstance, ActivityInstanceHistory, ActivityThread, ActivityApproval, ChatSession, ChatMessage, ChatSessionParticipant, require('@things-factory/notification').Notification], {
|
|
36
|
+
subscribers: [ActivityInstanceHistoryEntitySubscriber]
|
|
37
|
+
})
|
|
38
|
+
await db.initialize()
|
|
39
|
+
here = await db.getRepository(Domain).save({ name: 'here', subdomain: 'here' })
|
|
40
|
+
const person = (name: string) => db.getRepository(User).save({ name, email: `${name}@example.com`, password: 'x', status: 'activated', userType: 'user', domains: [here] })
|
|
41
|
+
manager = await person('manager')
|
|
42
|
+
employee = await person('employee')
|
|
43
|
+
outsider = await person('outsider')
|
|
44
|
+
activity = await db.getRepository(Activity).save({ domain: here, name: PERSON_QUESTION_ACTIVITY, activityType: 'user', startingType: 'issue' })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
afterAll(async () => db?.destroy())
|
|
48
|
+
|
|
49
|
+
/** A conversation about `subject` started by `owner`, as the doors make it: a group with the person seated. */
|
|
50
|
+
async function conversationAbout(subject: any, owner: any, anchorType = 'subject') {
|
|
51
|
+
const session = await db.getRepository(ChatSession).save({ domain: here, anchorType, anchorId: anchorType === 'subject' ? `person:${subject.id}` : here.id, visibility: 'group', creator: owner })
|
|
52
|
+
await db.getRepository(ChatSessionParticipant).save({ domain: here, session: { id: session.id }, user: { id: owner.id }, role: 'owner' })
|
|
53
|
+
if (subject.id !== owner.id) await db.getRepository(ChatSessionParticipant).save({ domain: here, session: { id: session.id }, user: { id: subject.id }, role: 'member' })
|
|
54
|
+
return session
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
beforeEach(async () => {
|
|
58
|
+
room = await conversationAbout(employee, manager)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const as = (user: any, tx: any) => ({ state: { domain: here, user, tx }, t: (_key: string, options?: any) => options?.defaultValue ?? _key }) as any
|
|
62
|
+
const ask = (user: any, question: any) => db.transaction((tx: any) => askPerson(question, as(user, tx)))
|
|
63
|
+
const said = (user: any, session: any, content: string) =>
|
|
64
|
+
db.transaction((tx: any) => closePersonQuestions({ domain: here, user, session, message: { id: 'm', content }, tx }))
|
|
65
|
+
|
|
66
|
+
const QUESTION = { question: '납품 건 금요일까지 되나?', answered: '납품 준비 할 일 3건 중 1건 끝 (할 일, 11-12)', decide: '금요일 납품 가능 여부', draft: '입고가 목요일이면 금요일 가능, 늦으면 월요일' }
|
|
67
|
+
|
|
68
|
+
async function openTodosOf(user: any): Promise<string[]> {
|
|
69
|
+
const rows = await db
|
|
70
|
+
.getRepository(ActivityThread)
|
|
71
|
+
.createQueryBuilder('at')
|
|
72
|
+
.innerJoin('at.activityInstance', 'ai')
|
|
73
|
+
.where(myOpenThreadsWhere({ userId: user.id, roles: [] }))
|
|
74
|
+
.select('ai.id', 'id')
|
|
75
|
+
.getRawMany()
|
|
76
|
+
return rows.map((one: any) => one.id)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
test('★ asked in a conversation about someone, the question is their to-do - with what it carries', async () => {
|
|
80
|
+
const id = await ask(manager, { sessionId: room.id, ...QUESTION })
|
|
81
|
+
expect(await openTodosOf(employee)).toContain(id)
|
|
82
|
+
expect(await openTodosOf(manager)).not.toContain(id)
|
|
83
|
+
const instance = await db.getRepository(ActivityInstance).findOne({ where: { id } })
|
|
84
|
+
expect(instance.input).toEqual({ sessionId: room.id, asker: 'manager', askerId: manager.id, ...QUESTION })
|
|
85
|
+
expect(instance.description).toBe(QUESTION.decide)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('★ someone not in the conversation cannot hand its question on', async () => {
|
|
89
|
+
await expect(ask(outsider, { sessionId: room.id, ...QUESTION })).rejects.toThrow(/only someone in this conversation/)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('★ only a conversation about someone else hands a question on', async () => {
|
|
93
|
+
const general = await conversationAbout(manager, manager, 'domain')
|
|
94
|
+
await expect(ask(manager, { sessionId: general.id, ...QUESTION })).rejects.toThrow(/about someone else/)
|
|
95
|
+
const self = await conversationAbout(employee, employee)
|
|
96
|
+
await expect(ask(employee, { sessionId: self.id, ...QUESTION })).rejects.toThrow(/about someone else/)
|
|
97
|
+
await expect(ask(manager, { sessionId: 'missing', ...QUESTION })).rejects.toThrow(/about someone else/)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('the question and what is theirs to decide are required, each part kept short', async () => {
|
|
101
|
+
await expect(ask(manager, { sessionId: room.id, question: ' ', decide: 'x' })).rejects.toThrow(/say the question/)
|
|
102
|
+
await expect(ask(manager, { sessionId: room.id, question: 'q', decide: '' })).rejects.toThrow(/say the question/)
|
|
103
|
+
await expect(ask(manager, { sessionId: room.id, question: 'q', decide: 'x', draft: 'a'.repeat(PERSON_QUESTION_LIMIT + 1) })).rejects.toThrow(/draft is longer/)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('★ the person answering in the conversation ends it, with what they said', async () => {
|
|
107
|
+
const id = await ask(manager, { sessionId: room.id, ...QUESTION })
|
|
108
|
+
expect(await said(employee, room, `@manager{userId:${manager.id}} 목요일 입고면 금요일 납품 가능합니다.`)).toBe(1)
|
|
109
|
+
expect(await openTodosOf(employee)).not.toContain(id)
|
|
110
|
+
const threads = await db.getRepository(ActivityThread).find({ where: { activityInstance: { id } } })
|
|
111
|
+
/* Said to the one who asked, as the to-do opens it; kept as a person reads it. */
|
|
112
|
+
expect(threads.map((one: any) => [one.state, one.output?.answer])).toEqual([['ended', '@manager 목요일 입고면 금요일 납품 가능합니다.']])
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('★ only the person\'s words in that conversation end it', async () => {
|
|
116
|
+
const id = await ask(manager, { sessionId: room.id, ...QUESTION })
|
|
117
|
+
expect(await said(manager, room, '언제 답 줄 수 있어?')).toBe(0)
|
|
118
|
+
const other = await conversationAbout(employee, outsider)
|
|
119
|
+
expect(await said(employee, other, '다른 대화의 말')).toBe(0)
|
|
120
|
+
expect(await openTodosOf(employee)).toContain(id)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('not installed on the site, nothing is handed out', async () => {
|
|
124
|
+
await db.getRepository(Activity).update(activity.id, { name: 'Renamed' })
|
|
125
|
+
try {
|
|
126
|
+
await expect(ask(manager, { sessionId: room.id, ...QUESTION })).rejects.toThrow(/not installed/)
|
|
127
|
+
} finally {
|
|
128
|
+
await db.getRepository(Activity).update(activity.id, { name: PERSON_QUESTION_ACTIVITY })
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('★ the card asks of the conversation it is pressed in - a conversation the model wrote is never used', () => {
|
|
133
|
+
require(join(DIST, 'service', 'person-question', 'index.js'))
|
|
134
|
+
const { entryTargetOf, PERSON_QUESTION_KEY } = require('@things-factory/ai-assistant')
|
|
135
|
+
const card = entryTargetOf(PERSON_QUESTION_KEY)
|
|
136
|
+
expect(card.mutation).toMatch(/askPersonToConfirm/)
|
|
137
|
+
const variables = card.variables({ values: { ...QUESTION, sessionId: 'made-up' } }, { sessionId: room.id })
|
|
138
|
+
expect(variables).toEqual({ question: { sessionId: room.id, question: QUESTION.question, answered: QUESTION.answered, decide: QUESTION.decide, draft: QUESTION.draft } })
|
|
139
|
+
expect(card.fields.map((one: any) => one.name)).not.toContain('sessionId')
|
|
140
|
+
/* In front only in a conversation about someone (ADR-0200 decision 2). */
|
|
141
|
+
expect(card.axes).toEqual(['person'])
|
|
142
|
+
/* Installable by name, so an app that lists it puts it in its domains (2026-11-12: Office's boot found none). */
|
|
143
|
+
const { ActivityInstallations } = require(join(DIST, 'controllers', 'activity-installation', 'activity-installation-controller.js'))
|
|
144
|
+
expect(ActivityInstallations.get(PERSON_QUESTION_ACTIVITY)?.uiSource).toBe('person-question-todo')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('★ the to-do opens its conversation with the draft said to the one who asked - an answer to the assistant closes nothing', () => {
|
|
148
|
+
const todo = require('fs').readFileSync(join(__dirname, '..', 'client', 'components', 'person-question-todo.ts'), 'utf8')
|
|
149
|
+
expect(todo).toMatch(/askAssistant\(\{ sessionId, draft: draft \|\| undefined, to: askerId && asker \? \{ id: askerId, name: asker \} : undefined \}\)/)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('words in a conversation not about the speaker read no to-dos at all - every room message passes here', async () => {
|
|
153
|
+
const untouchable = { getRepository: () => { throw new Error('read') } }
|
|
154
|
+
const general = { id: 'g', anchorType: 'domain', anchorId: here.id }
|
|
155
|
+
const aboutSomeoneElse = { id: 'p', anchorType: 'subject', anchorId: `person:${manager.id}` }
|
|
156
|
+
expect(await closePersonQuestions({ domain: here, user: employee, session: general, message: { id: 'm', content: 'hi' }, tx: untouchable })).toBe(0)
|
|
157
|
+
expect(await closePersonQuestions({ domain: here, user: employee, session: aboutSomeoneElse, message: { id: 'm', content: 'hi' }, tx: untouchable })).toBe(0)
|
|
158
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -397,6 +397,10 @@
|
|
|
397
397
|
"approval-notice.open-request": "Open the request",
|
|
398
398
|
"approval-notice.see-approval-box": "You can find it in your approval box.",
|
|
399
399
|
"approval-notice.see-request-box": "You can find it in your to-do list.",
|
|
400
|
+
"todo-notice.subject": "[To-do] {name}",
|
|
401
|
+
"todo-notice.body": "{actor} sent you \"{name}\" as a to-do.",
|
|
402
|
+
"todo-notice.body-unsaid": "\"{name}\" is a new to-do for you.",
|
|
403
|
+
"todo-notice.open": "Open the to-do",
|
|
400
404
|
"error.approver role is not set on the approval line": "Step {order} of the approval line names no role. Choose one in the approval line and submit again.",
|
|
401
405
|
"error.approver role not found": "The role of step {order} of the approval line is not in this domain any more. Choose the role again.",
|
|
402
406
|
"error.approver is not set on the approval line": "No approver could be found for step {order} of the approval line. Name one in the approval line, or check the department head, supervisor or user link.",
|
|
@@ -427,5 +431,21 @@
|
|
|
427
431
|
"label.filter": "Filter",
|
|
428
432
|
"text.approval-waiting-on": "Awaiting approval by {name}",
|
|
429
433
|
"label.approval-waiting": "Awaiting approval",
|
|
430
|
-
"label.outcome": "Outcome"
|
|
434
|
+
"label.outcome": "Outcome",
|
|
435
|
+
"activity.person-question.label": "Confirmation request",
|
|
436
|
+
"activity.person-question.guide.0": "Asks a person to confirm what their records cannot answer, from a conversation about them.",
|
|
437
|
+
"activity.person-question.guide.1": "The question, what the records already confirm, what they must decide and a draft answer are sent together.",
|
|
438
|
+
"activity.person-question.guide.2": "Answering in the conversation completes it.",
|
|
439
|
+
"label.person-question": "Confirmation request",
|
|
440
|
+
"field.person-question": "Question",
|
|
441
|
+
"field.person-question-asker": "Asked by",
|
|
442
|
+
"field.person-question-answered": "Confirmed from records",
|
|
443
|
+
"field.person-question-decide": "To decide",
|
|
444
|
+
"field.person-question-draft": "Draft answer",
|
|
445
|
+
"button.person-question-open": "Open conversation",
|
|
446
|
+
"error.person-question-incomplete": "Enter the question and what must be decided.",
|
|
447
|
+
"error.person-question-too-long": "Keep each item within 2,000 characters.",
|
|
448
|
+
"error.person-question-not-about-someone": "A confirmation can be requested only from a conversation about someone else.",
|
|
449
|
+
"error.person-question-not-seated": "Only participants of this conversation can request a confirmation.",
|
|
450
|
+
"error.person-question-not-installed": "Confirmation requests are not installed on this site."
|
|
431
451
|
}
|
package/translations/es.json
CHANGED
|
@@ -397,6 +397,10 @@
|
|
|
397
397
|
"approval-notice.open-request": "Abrir la solicitud",
|
|
398
398
|
"approval-notice.see-approval-box": "Puede verlo en su bandeja de aprobaciones.",
|
|
399
399
|
"approval-notice.see-request-box": "Puede verlo en su lista de tareas.",
|
|
400
|
+
"todo-notice.subject": "[Tarea] {name}",
|
|
401
|
+
"todo-notice.body": "{actor} le envió «{name}» como tarea.",
|
|
402
|
+
"todo-notice.body-unsaid": "«{name}» es una nueva tarea para usted.",
|
|
403
|
+
"todo-notice.open": "Abrir la tarea",
|
|
400
404
|
"error.approver role is not set on the approval line": "El paso {order} de la línea de aprobación no tiene rol. Elija uno y vuelva a enviar.",
|
|
401
405
|
"error.approver role not found": "El rol del paso {order} de la línea de aprobación ya no existe en este dominio. Elíjalo de nuevo.",
|
|
402
406
|
"error.approver is not set on the approval line": "No se encontró aprobador para el paso {order}. Indíquelo en la línea de aprobación o revise el jefe de departamento, el supervisor o el usuario vinculado.",
|
|
@@ -427,5 +431,21 @@
|
|
|
427
431
|
"label.filter": "Filtro",
|
|
428
432
|
"text.approval-waiting-on": "Pendiente de aprobación de {name}",
|
|
429
433
|
"label.approval-waiting": "Pendiente de aprobación",
|
|
430
|
-
"label.outcome": "Resultado"
|
|
434
|
+
"label.outcome": "Resultado",
|
|
435
|
+
"activity.person-question.label": "Solicitud de confirmación",
|
|
436
|
+
"activity.person-question.guide.0": "Pide a una persona que confirme lo que sus registros no pueden responder, desde una conversación sobre ella.",
|
|
437
|
+
"activity.person-question.guide.1": "Se envían juntos la pregunta, lo confirmado en los registros, lo que debe decidir y un borrador de respuesta.",
|
|
438
|
+
"activity.person-question.guide.2": "Responder en la conversación la completa.",
|
|
439
|
+
"label.person-question": "Solicitud de confirmación",
|
|
440
|
+
"field.person-question": "Pregunta",
|
|
441
|
+
"field.person-question-asker": "Solicitado por",
|
|
442
|
+
"field.person-question-answered": "Confirmado en los registros",
|
|
443
|
+
"field.person-question-decide": "Por decidir",
|
|
444
|
+
"field.person-question-draft": "Borrador de respuesta",
|
|
445
|
+
"button.person-question-open": "Abrir conversación",
|
|
446
|
+
"error.person-question-incomplete": "Indique la pregunta y lo que debe decidirse.",
|
|
447
|
+
"error.person-question-too-long": "Cada elemento debe tener como máximo 2.000 caracteres.",
|
|
448
|
+
"error.person-question-not-about-someone": "Solo se puede solicitar una confirmación desde una conversación sobre otra persona.",
|
|
449
|
+
"error.person-question-not-seated": "Solo los participantes de esta conversación pueden solicitar una confirmación.",
|
|
450
|
+
"error.person-question-not-installed": "Las solicitudes de confirmación no están instaladas en este sitio."
|
|
431
451
|
}
|
package/translations/ja.json
CHANGED
|
@@ -397,6 +397,10 @@
|
|
|
397
397
|
"approval-notice.open-request": "申請を開く",
|
|
398
398
|
"approval-notice.see-approval-box": "承認ボックスで確認できます。",
|
|
399
399
|
"approval-notice.see-request-box": "やることリストで確認できます。",
|
|
400
|
+
"todo-notice.subject": "[ToDo] {name}",
|
|
401
|
+
"todo-notice.body": "{actor}さんから「{name}」がToDoとして届きました。",
|
|
402
|
+
"todo-notice.body-unsaid": "「{name}」がToDoとして届きました。",
|
|
403
|
+
"todo-notice.open": "ToDoを開く",
|
|
400
404
|
"error.approver role is not set on the approval line": "承認ラインの{order}番目の段階にロールが指定されていません。ロールを選んでから再度提出してください。",
|
|
401
405
|
"error.approver role not found": "承認ラインの{order}番目の段階のロールがこの事業所にありません。ロールを選び直してください。",
|
|
402
406
|
"error.approver is not set on the approval line": "承認ラインの{order}番目の段階の承認者を決められません。承認者を指定するか、部署長・上司・ユーザーの紐付けを確認してください。",
|
|
@@ -427,5 +431,21 @@
|
|
|
427
431
|
"label.filter": "フィルター",
|
|
428
432
|
"text.approval-waiting-on": "{name} の承認待ち",
|
|
429
433
|
"label.approval-waiting": "承認待ち",
|
|
430
|
-
"label.outcome": "対応結果"
|
|
434
|
+
"label.outcome": "対応結果",
|
|
435
|
+
"activity.person-question.label": "本人確認依頼",
|
|
436
|
+
"activity.person-question.guide.0": "ある人についての会話で、記録では答えられない質問を本人に確認依頼します。",
|
|
437
|
+
"activity.person-question.guide.1": "質問、記録で確認された内容、確認事項、回答案をまとめて送ります。",
|
|
438
|
+
"activity.person-question.guide.2": "会話で回答すると完了します。",
|
|
439
|
+
"label.person-question": "本人確認依頼",
|
|
440
|
+
"field.person-question": "質問",
|
|
441
|
+
"field.person-question-asker": "依頼者",
|
|
442
|
+
"field.person-question-answered": "記録で確認された内容",
|
|
443
|
+
"field.person-question-decide": "確認事項",
|
|
444
|
+
"field.person-question-draft": "回答案",
|
|
445
|
+
"button.person-question-open": "会話を開く",
|
|
446
|
+
"error.person-question-incomplete": "質問と確認事項を入力してください。",
|
|
447
|
+
"error.person-question-too-long": "各項目は2,000文字以内で入力してください。",
|
|
448
|
+
"error.person-question-not-about-someone": "他の人についての会話でのみ本人確認を依頼できます。",
|
|
449
|
+
"error.person-question-not-seated": "この会話の参加者のみ本人確認を依頼できます。",
|
|
450
|
+
"error.person-question-not-installed": "この事業所には本人確認依頼がインストールされていません。"
|
|
431
451
|
}
|
package/translations/ko.json
CHANGED
|
@@ -397,6 +397,10 @@
|
|
|
397
397
|
"approval-notice.open-request": "요청 열기",
|
|
398
398
|
"approval-notice.see-approval-box": "내 결재함에서 확인할 수 있습니다.",
|
|
399
399
|
"approval-notice.see-request-box": "할 일에서 확인할 수 있습니다.",
|
|
400
|
+
"todo-notice.subject": "[할 일] {name}",
|
|
401
|
+
"todo-notice.body": "{actor}님이 「{name}」을(를) 할 일로 보냈습니다.",
|
|
402
|
+
"todo-notice.body-unsaid": "「{name}」이(가) 할 일로 왔습니다.",
|
|
403
|
+
"todo-notice.open": "할 일 열기",
|
|
400
404
|
"error.approver role is not set on the approval line": "결재선 {order}번째 단계에 역할이 지정되어 있지 않습니다. 결재선에서 역할을 고른 뒤 다시 올려 주세요.",
|
|
401
405
|
"error.approver role not found": "결재선 {order}번째 단계의 역할이 이 사업장에 없습니다. 결재선에서 역할을 다시 골라 주세요.",
|
|
402
406
|
"error.approver is not set on the approval line": "결재선 {order}번째 단계의 결재자를 정할 수 없습니다. 결재선에서 결재자를 지정하거나, 부서장·상사·사용자 연결을 확인해 주세요.",
|
|
@@ -427,5 +431,21 @@
|
|
|
427
431
|
"label.filter": "필터",
|
|
428
432
|
"text.approval-waiting-on": "{name} 결재 대기",
|
|
429
433
|
"label.approval-waiting": "결재 대기",
|
|
430
|
-
"label.outcome": "처리 결과"
|
|
434
|
+
"label.outcome": "처리 결과",
|
|
435
|
+
"activity.person-question.label": "본인 확인 요청",
|
|
436
|
+
"activity.person-question.guide.0": "한 사람에 대한 대화에서 기록으로 답할 수 없는 질문을 그 사람에게 확인 요청합니다.",
|
|
437
|
+
"activity.person-question.guide.1": "질문, 기록으로 확인된 내용, 확인할 사항, 답변 초안을 함께 보냅니다.",
|
|
438
|
+
"activity.person-question.guide.2": "대화에서 답하면 완료됩니다.",
|
|
439
|
+
"label.person-question": "본인 확인 요청",
|
|
440
|
+
"field.person-question": "질문",
|
|
441
|
+
"field.person-question-asker": "요청자",
|
|
442
|
+
"field.person-question-answered": "기록으로 확인된 내용",
|
|
443
|
+
"field.person-question-decide": "확인할 사항",
|
|
444
|
+
"field.person-question-draft": "답변 초안",
|
|
445
|
+
"button.person-question-open": "대화 열기",
|
|
446
|
+
"error.person-question-incomplete": "질문과 확인할 사항을 입력하십시오.",
|
|
447
|
+
"error.person-question-too-long": "각 항목은 2,000자 이내로 입력하십시오.",
|
|
448
|
+
"error.person-question-not-about-someone": "다른 사람에 대한 대화에서만 본인 확인을 요청할 수 있습니다.",
|
|
449
|
+
"error.person-question-not-seated": "이 대화의 참여자만 본인 확인을 요청할 수 있습니다.",
|
|
450
|
+
"error.person-question-not-installed": "이 사업장에는 본인 확인 요청이 설치되어 있지 않습니다."
|
|
431
451
|
}
|
package/translations/ms.json
CHANGED
|
@@ -397,6 +397,10 @@
|
|
|
397
397
|
"approval-notice.open-request": "Buka permintaan",
|
|
398
398
|
"approval-notice.see-approval-box": "Anda boleh melihatnya dalam kotak kelulusan anda.",
|
|
399
399
|
"approval-notice.see-request-box": "Anda boleh melihatnya dalam senarai tugasan anda.",
|
|
400
|
+
"todo-notice.subject": "[Tugasan] {name}",
|
|
401
|
+
"todo-notice.body": "{actor} menghantar \"{name}\" kepada anda sebagai tugasan.",
|
|
402
|
+
"todo-notice.body-unsaid": "\"{name}\" ialah tugasan baharu untuk anda.",
|
|
403
|
+
"todo-notice.open": "Buka tugasan",
|
|
400
404
|
"error.approver role is not set on the approval line": "Langkah {order} dalam barisan kelulusan tiada peranan. Pilih satu dan hantar semula.",
|
|
401
405
|
"error.approver role not found": "Peranan langkah {order} dalam barisan kelulusan tiada lagi dalam domain ini. Pilih semula.",
|
|
402
406
|
"error.approver is not set on the approval line": "Tiada pelulus untuk langkah {order}. Namakan satu dalam barisan kelulusan, atau semak ketua jabatan, penyelia atau pautan pengguna.",
|
|
@@ -427,5 +431,21 @@
|
|
|
427
431
|
"label.filter": "Penapis",
|
|
428
432
|
"text.approval-waiting-on": "Menunggu kelulusan {name}",
|
|
429
433
|
"label.approval-waiting": "Menunggu kelulusan",
|
|
430
|
-
"label.outcome": "Hasil"
|
|
434
|
+
"label.outcome": "Hasil",
|
|
435
|
+
"activity.person-question.label": "Permintaan pengesahan",
|
|
436
|
+
"activity.person-question.guide.0": "Meminta seseorang mengesahkan perkara yang tidak dapat dijawab oleh rekod mereka, daripada perbualan tentang mereka.",
|
|
437
|
+
"activity.person-question.guide.1": "Soalan, perkara yang disahkan daripada rekod, perkara untuk diputuskan dan draf jawapan dihantar bersama.",
|
|
438
|
+
"activity.person-question.guide.2": "Menjawab dalam perbualan akan melengkapkannya.",
|
|
439
|
+
"label.person-question": "Permintaan pengesahan",
|
|
440
|
+
"field.person-question": "Soalan",
|
|
441
|
+
"field.person-question-asker": "Diminta oleh",
|
|
442
|
+
"field.person-question-answered": "Disahkan daripada rekod",
|
|
443
|
+
"field.person-question-decide": "Untuk diputuskan",
|
|
444
|
+
"field.person-question-draft": "Draf jawapan",
|
|
445
|
+
"button.person-question-open": "Buka perbualan",
|
|
446
|
+
"error.person-question-incomplete": "Masukkan soalan dan perkara yang perlu diputuskan.",
|
|
447
|
+
"error.person-question-too-long": "Setiap item mestilah tidak melebihi 2,000 aksara.",
|
|
448
|
+
"error.person-question-not-about-someone": "Pengesahan hanya boleh diminta daripada perbualan tentang orang lain.",
|
|
449
|
+
"error.person-question-not-seated": "Hanya peserta perbualan ini boleh meminta pengesahan.",
|
|
450
|
+
"error.person-question-not-installed": "Permintaan pengesahan tidak dipasang di laman ini."
|
|
431
451
|
}
|
package/translations/zh.json
CHANGED
|
@@ -397,6 +397,10 @@
|
|
|
397
397
|
"approval-notice.open-request": "打开申请",
|
|
398
398
|
"approval-notice.see-approval-box": "可在我的审批箱中查看。",
|
|
399
399
|
"approval-notice.see-request-box": "可在待办事项中查看。",
|
|
400
|
+
"todo-notice.subject": "[待办] {name}",
|
|
401
|
+
"todo-notice.body": "{actor} 将「{name}」作为待办发给了您。",
|
|
402
|
+
"todo-notice.body-unsaid": "「{name}」已作为待办发给您。",
|
|
403
|
+
"todo-notice.open": "打开待办",
|
|
400
404
|
"error.approver role is not set on the approval line": "审批线第 {order} 步未指定角色。请在审批线中选择角色后重新提交。",
|
|
401
405
|
"error.approver role not found": "审批线第 {order} 步的角色已不在此域中。请重新选择角色。",
|
|
402
406
|
"error.approver is not set on the approval line": "无法确定审批线第 {order} 步的审批人。请在审批线中指定,或检查部门负责人、上级或用户关联。",
|
|
@@ -427,5 +431,21 @@
|
|
|
427
431
|
"label.filter": "筛选",
|
|
428
432
|
"text.approval-waiting-on": "等待 {name} 审批",
|
|
429
433
|
"label.approval-waiting": "等待审批",
|
|
430
|
-
"label.outcome": "处理结果"
|
|
434
|
+
"label.outcome": "处理结果",
|
|
435
|
+
"activity.person-question.label": "本人确认请求",
|
|
436
|
+
"activity.person-question.guide.0": "在关于某人的对话中,将记录无法回答的问题请本人确认。",
|
|
437
|
+
"activity.person-question.guide.1": "问题、记录已确认的内容、待确认事项和回答草稿一并发送。",
|
|
438
|
+
"activity.person-question.guide.2": "在对话中回答即完成。",
|
|
439
|
+
"label.person-question": "本人确认请求",
|
|
440
|
+
"field.person-question": "问题",
|
|
441
|
+
"field.person-question-asker": "请求人",
|
|
442
|
+
"field.person-question-answered": "记录已确认的内容",
|
|
443
|
+
"field.person-question-decide": "待确认事项",
|
|
444
|
+
"field.person-question-draft": "回答草稿",
|
|
445
|
+
"button.person-question-open": "打开对话",
|
|
446
|
+
"error.person-question-incomplete": "请输入问题和待确认事项。",
|
|
447
|
+
"error.person-question-too-long": "每项请控制在2,000字以内。",
|
|
448
|
+
"error.person-question-not-about-someone": "只能在关于他人的对话中请求本人确认。",
|
|
449
|
+
"error.person-question-not-seated": "只有此对话的参与者可以请求本人确认。",
|
|
450
|
+
"error.person-question-not-installed": "此站点未安装本人确认请求。"
|
|
431
451
|
}
|