Skip to content

Commit 534228c

Browse files
authored
Merge pull request #29 from asepindrak/dev
Dev
2 parents bf8919f + d7a2dfb commit 534228c

35 files changed

Lines changed: 3914 additions & 1786 deletions

.changeset/cyan-ducks-sniff.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"commitflow": minor
3+
---
4+
5+
feat: my taks & report

backend/prisma/schema.prisma

Lines changed: 76 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -48,88 +48,103 @@ model chatMessage {
4848
}
4949

5050
model User {
51-
id String @id @default(cuid())
52-
email String? @unique
51+
id String @id @default(cuid())
52+
email String? @unique
5353
phone String?
5454
name String?
5555
photo String?
56-
role Role? @default(USER)
57-
password String? // kalau pake password
58-
session_token String? @unique
59-
refreshTokenHash String? // simpan hash dari refresh token
60-
refreshTokenExpiresAt DateTime?
61-
createdAt DateTime @default(now())
62-
updatedAt DateTime @updatedAt
63-
members TeamMember[] @relation("UserMembers")
64-
56+
role Role? @default(USER)
57+
password String? // kalau pake password
58+
session_token String? @unique
59+
refreshTokenHash String? // simpan hash dari refresh token
60+
refreshTokenExpiresAt DateTime?
61+
createdAt DateTime @default(now())
62+
updatedAt DateTime @updatedAt
63+
members TeamMember[] @relation("UserMembers")
6564
}
6665

6766
model TeamMember {
68-
id String @id @default(uuid()) // pilih uuid supaya beda style dari user.id
69-
clientId String? @unique
70-
userId String
71-
workspaceId String
72-
name String
73-
role String?
74-
email String?
75-
photo String?
76-
phone String?
77-
isTrash Boolean @default(false)
78-
isAdmin Boolean @default(false)
79-
createdAt DateTime @default(now())
80-
updatedAt DateTime?
81-
Task Task[] @relation("TaskMembers")
82-
TaskCreatedBy Task[] @relation("TaskCreatedBy")
67+
id String @id @default(uuid()) // pilih uuid supaya beda style dari user.id
68+
clientId String? @unique
69+
userId String
70+
workspaceId String
71+
name String
72+
role String?
73+
email String?
74+
photo String?
75+
phone String?
76+
isTrash Boolean @default(false)
77+
isAdmin Boolean @default(false)
78+
createdAt DateTime @default(now())
79+
updatedAt DateTime?
80+
Task Task[] @relation("TaskMembers")
81+
TaskCreatedBy Task[] @relation("TaskCreatedBy")
8382
8483
// FK ke Workspace
85-
workspace Workspace? @relation("WorkspaceMembers", fields: [workspaceId], references: [id])
86-
user User? @relation("UserMembers", fields: [userId], references: [id])
84+
workspace Workspace? @relation("WorkspaceMembers", fields: [workspaceId], references: [id])
85+
user User? @relation("UserMembers", fields: [userId], references: [id])
86+
taskAssignees TaskAssignee[]
8787
}
8888

8989
model Workspace {
90-
id String @id @default(uuid())
91-
clientId String? @unique
90+
id String @id @default(uuid())
91+
clientId String? @unique
9292
name String
9393
description String?
94-
isTrash Boolean @default(false)
95-
createdAt DateTime @default(now())
94+
isTrash Boolean @default(false)
95+
createdAt DateTime @default(now())
9696
updatedAt DateTime?
97-
projects Project[] @relation("WorkspaceProjects")
98-
members TeamMember[] @relation("WorkspaceMembers")
97+
projects Project[] @relation("WorkspaceProjects")
98+
members TeamMember[] @relation("WorkspaceMembers")
9999
}
100100

101101
model Project {
102-
id String @id @default(uuid())
103-
workspaceId String?
104-
clientId String? @unique
105-
name String
106-
description String?
107-
isTrash Boolean @default(false)
108-
createdAt DateTime @default(now())
109-
updatedAt DateTime?
110-
workspace Workspace? @relation("WorkspaceProjects", fields: [workspaceId], references: [id])
111-
tasks Task[] @relation("ProjectTasks")
102+
id String @id @default(uuid())
103+
workspaceId String?
104+
clientId String? @unique
105+
name String
106+
description String?
107+
isTrash Boolean @default(false)
108+
createdAt DateTime @default(now())
109+
updatedAt DateTime?
110+
workspace Workspace? @relation("WorkspaceProjects", fields: [workspaceId], references: [id])
111+
tasks Task[] @relation("ProjectTasks")
112112
}
113113

114114
model Task {
115-
id String @id @default(uuid())
115+
id String @id @default(uuid())
116116
title String
117117
description String?
118-
status String @default("todo")
118+
status String @default("todo")
119119
projectId String?
120-
project Project? @relation("ProjectTasks", fields: [projectId], references: [id])
120+
project Project? @relation("ProjectTasks", fields: [projectId], references: [id])
121121
assigneeId String?
122122
startDate String?
123123
dueDate String?
124+
finishDate DateTime?
124125
priority String?
125-
assignee TeamMember? @relation("TaskMembers", fields: [assigneeId], references: [id])
126-
isTrash Boolean @default(false)
127-
comments Comment[] @relation("TaskComments")
128-
clientId String? @unique
129-
createdAt DateTime @default(now())
126+
assignee TeamMember? @relation("TaskMembers", fields: [assigneeId], references: [id])
127+
isTrash Boolean @default(false)
128+
comments Comment[] @relation("TaskComments")
129+
clientId String? @unique
130+
createdAt DateTime @default(now())
130131
createdById String?
131-
createdBy TeamMember? @relation("TaskCreatedBy", fields: [createdById], references: [id])
132+
createdBy TeamMember? @relation("TaskCreatedBy", fields: [createdById], references: [id])
132133
updatedAt DateTime?
134+
taskAssignees TaskAssignee[]
135+
}
136+
137+
model TaskAssignee {
138+
id String @id @default(uuid())
139+
taskId String
140+
memberId String
141+
142+
assignedAt DateTime @default(now())
143+
144+
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
145+
member TeamMember @relation(fields: [memberId], references: [id], onDelete: Cascade)
146+
147+
@@unique([taskId, memberId])
133148
}
134149

135150
model Comment {
@@ -145,16 +160,16 @@ model Comment {
145160
}
146161

147162
model Invite {
148-
id String @id @default(uuid())
149-
workspaceId String
150-
email String
151-
invitedBy String
152-
isInvited Boolean @default(false)
153-
createdAt DateTime @default(now())
154-
updatedAt DateTime?
163+
id String @id @default(uuid())
164+
workspaceId String
165+
email String
166+
invitedBy String
167+
isInvited Boolean @default(false)
168+
createdAt DateTime @default(now())
169+
updatedAt DateTime?
155170
}
156171

157172
enum Role {
158173
USER
159174
ADMIN
160-
}
175+
}

backend/src/ai-agent/ask.service.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
getMembers,
1818
getProjects,
1919
getTodoTasks,
20+
getQaTasks,
21+
getDeployTasks,
2022
getUnassignedTasks,
2123
getUrgentTasks,
2224
} from "./project.service";
@@ -87,6 +89,10 @@ export class AskService {
8789
return await getTodoTasks(args?.projectId);
8890
} else if (fn === "getInProgressTasks") {
8991
return await getInProgressTasks(args?.projectId);
92+
} else if (fn === "getQaTasks") {
93+
return await getQaTasks(args?.projectId);
94+
} else if (fn === "getDeployTasks") {
95+
return await getDeployTasks(args?.projectId);
9096
} else if (fn === "getDoneTasks") {
9197
return await getDoneTasks(args?.projectId);
9298
} else if (fn === "getUnassignedTasks") {
@@ -279,8 +285,7 @@ export class AskService {
279285
if (!followRes.ok) {
280286
logger.error(`OpenAI follow-up error: ${followRes.status}`);
281287
throw new Error(
282-
`OpenAI follow-up error ${
283-
followRes.status
288+
`OpenAI follow-up error ${followRes.status
284289
} ${await followRes.text()}`
285290
);
286291
}
@@ -387,9 +392,8 @@ export class AskService {
387392
choices: [
388393
{
389394
delta: {
390-
content: `An error occurred: ${
391-
error?.message || String(error)
392-
}`,
395+
content: `An error occurred: ${error?.message || String(error)
396+
}`,
393397
},
394398
},
395399
],

backend/src/ai-agent/project.service.ts

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,17 @@ export async function getProjects(workspaceId: string) {
2424
select: {
2525
id: true,
2626
status: true,
27-
assigneeId: true,
27+
taskAssignees: {
28+
select: {
29+
memberId: true,
30+
},
31+
},
2832
},
2933
},
3034
},
3135
});
3236

37+
3338
const enriched = results.map((project) => {
3439
const tasks = project.tasks;
3540

@@ -44,6 +49,8 @@ export async function getProjects(workspaceId: string) {
4449
total: tasks.length,
4550
todo: tasks.filter((t) => t.status === "todo").length,
4651
inprogress: tasks.filter((t) => t.status === "inprogress").length,
52+
qa: tasks.filter((t) => t.status === "qa").length,
53+
deploy: tasks.filter((t) => t.status === "deploy").length,
4754
done: tasks.filter((t) => t.status === "done").length,
4855
},
4956
};
@@ -63,13 +70,18 @@ export async function getProjects(workspaceId: string) {
6370
*/
6471
function baseTaskInclude() {
6572
return {
66-
assignee: {
67-
select: {
68-
id: true,
69-
name: true,
70-
role: true,
71-
email: true,
72-
photo: true,
73+
taskAssignees: {
74+
include: {
75+
member: {
76+
select: {
77+
id: true,
78+
name: true,
79+
role: true,
80+
email: true,
81+
photo: true,
82+
phone: true,
83+
},
84+
},
7385
},
7486
},
7587
project: {
@@ -82,6 +94,7 @@ function baseTaskInclude() {
8294
};
8395
}
8496

97+
8598
function buildTaskWhere(projectId?: string, status?: string) {
8699
// Only filter by project (no cross-workspace lookup)
87100
const where: any = { isTrash: false };
@@ -164,6 +177,50 @@ export async function getInProgressTasks(projectId = "") {
164177
}
165178
}
166179

180+
/**
181+
* =====================================
182+
* QA TASKS
183+
* =====================================
184+
*/
185+
export async function getQaTasks(projectId = "") {
186+
try {
187+
console.log("getQaTasks");
188+
189+
const results = await prisma.task.findMany({
190+
where: await buildTaskWhere(projectId, "qa"),
191+
include: baseTaskInclude(),
192+
orderBy: { createdAt: "desc" },
193+
});
194+
195+
return results;
196+
} catch (error) {
197+
logger.error("error fetching getQaTasks", error);
198+
return [];
199+
}
200+
}
201+
202+
/**
203+
* =====================================
204+
* DEPLOY TASKS
205+
* =====================================
206+
*/
207+
export async function getDeployTasks(projectId = "") {
208+
try {
209+
console.log("getDeployTasks");
210+
211+
const results = await prisma.task.findMany({
212+
where: await buildTaskWhere(projectId, "deploy"),
213+
include: baseTaskInclude(),
214+
orderBy: { createdAt: "desc" },
215+
});
216+
217+
return results;
218+
} catch (error) {
219+
logger.error("error fetching getDeployTasks", error);
220+
return [];
221+
}
222+
}
223+
167224
/**
168225
* =====================================
169226
* DONE TASKS
@@ -229,6 +286,8 @@ export async function getMembers(workspaceId: string) {
229286
total: member.Task.length,
230287
todo: member.Task.filter((t) => t.status === "todo").length,
231288
inprogress: member.Task.filter((t) => t.status === "inprogress").length,
289+
qa: member.Task.filter((t) => t.status === "qa").length,
290+
deploy: member.Task.filter((t) => t.status === "deploy").length,
232291
done: member.Task.filter((t) => t.status === "done").length,
233292
},
234293
tasks: member.Task,
@@ -247,10 +306,14 @@ export async function getUnassignedTasks(projectId: string = "") {
247306

248307
const where: any = {
249308
isTrash: false,
250-
assigneeId: null,
309+
taskAssignees: {
310+
none: {}, // 🔥 inti multi-assignee
311+
},
251312
};
252313

253-
if (projectId) where.projectId = projectId;
314+
if (projectId) {
315+
where.projectId = projectId;
316+
}
254317

255318
const results = await prisma.task.findMany({
256319
where,
@@ -265,6 +328,7 @@ export async function getUnassignedTasks(projectId: string = "") {
265328
}
266329
}
267330

331+
268332
export async function getUrgentTasks(projectId: string = "") {
269333
try {
270334
console.log("getUrgentTasks");

0 commit comments

Comments
 (0)