feat(ui+backend): task categories edition

This commit is contained in:
wpetit 2020-09-14 08:11:42 +02:00
parent 833213e5fe
commit e3274cdecf
10 changed files with 449 additions and 76 deletions

View File

@ -27,14 +27,20 @@ fragment FullProjectParams on ProjectParams {
} }
`; `;
export const FRAGMENT_FULL_TASK_CATEGORY = gql`
fragment FullTaskCategory on TaskCategory {
id
label
costPerTimeUnit
}
`;
export const FRAGMENT_FULL_PROJECT = gql` export const FRAGMENT_FULL_PROJECT = gql`
fragment FullProject on Project { fragment FullProject on Project {
id id
title title
taskCategories { taskCategories {
id ...FullTaskCategory
label
costPerTimeUnit
} }
tasks { tasks {
...FullTask ...FullTask
@ -45,4 +51,5 @@ fragment FullProject on Project {
} }
${FRAGMENT_FULL_TASK} ${FRAGMENT_FULL_TASK}
${FRAGMENT_FULL_PROJECT_PARAMS} ${FRAGMENT_FULL_PROJECT_PARAMS}
${FRAGMENT_FULL_TASK_CATEGORY}
` `

View File

@ -1,5 +1,5 @@
import { gql, useMutation, PureQueryOptions } from '@apollo/client'; import { gql, useMutation, PureQueryOptions } from '@apollo/client';
import { FRAGMENT_FULL_PROJECT, FRAGMENT_FULL_TASK, FRAGMENT_FULL_PROJECT_PARAMS } from '../fragments/project'; import { FRAGMENT_FULL_PROJECT, FRAGMENT_FULL_TASK, FRAGMENT_FULL_PROJECT_PARAMS, FRAGMENT_FULL_TASK_CATEGORY } from '../fragments/project';
import { QUERY_PROJECTS } from '../queries/project'; import { QUERY_PROJECTS } from '../queries/project';
export const MUTATION_CREATE_PROJECT = gql` export const MUTATION_CREATE_PROJECT = gql`
@ -82,3 +82,39 @@ ${FRAGMENT_FULL_PROJECT_PARAMS}
export function useUpdateProjectParamsMutation() { export function useUpdateProjectParamsMutation() {
return useMutation(MUTATION_UPDATE_PROJECT_PARAMS); return useMutation(MUTATION_UPDATE_PROJECT_PARAMS);
} }
export const MUTATION_ADD_PROJECT_TASK_CATEGORY = gql`
mutation addProjectTaskCategory($projectId: ID!, $changes: ProjectTaskCategoryChanges!) {
addProjectTaskCategory(projectId: $projectId, changes: $changes) {
...FullTaskCategory
}
}
${FRAGMENT_FULL_TASK_CATEGORY}
`;
export function useAddProjectTaskCategoryMutation() {
return useMutation(MUTATION_ADD_PROJECT_TASK_CATEGORY);
}
export const MUTATION_UPDATE_PROJECT_TASK_CATEGORY = gql`
mutation updateProjectTaskCategory($projectId: ID!, $taskCategoryId: ID!, $changes: ProjectTaskCategoryChanges!) {
updateProjectTaskCategory(projectId: $projectId, taskCategoryId: $taskCategoryId, changes: $changes) {
...FullTaskCategory
}
}
${FRAGMENT_FULL_TASK_CATEGORY}
`;
export function useUpdateProjectTaskCategoryMutation() {
return useMutation(MUTATION_UPDATE_PROJECT_TASK_CATEGORY);
}
export const MUTATION_REMOVE_PROJECT_TASK_CATEGORY = gql`
mutation removeProjectTaskCategory($projectId: ID!, $taskCategoryId: ID!) {
removeProjectTaskCategory(projectId: $projectId, taskCategoryId: $taskCategoryId)
}
`;
export function useRemoveProjectTaskCategoryMutation() {
return useMutation(MUTATION_REMOVE_PROJECT_TASK_CATEGORY);
}

View File

@ -1,7 +1,22 @@
import { all, select, takeLatest, put, delay } from "redux-saga/effects"; import { all, select, takeLatest, put, delay } from "redux-saga/effects";
import { client } from '../gql/client'; import { client } from '../gql/client';
import { MUTATION_CREATE_PROJECT, MUTATION_UPDATE_PROJECT_TITLE, MUTATION_ADD_PROJECT_TASK, MUTATION_REMOVE_PROJECT_TASK, MUTATION_UPDATE_PROJECT_TASK, MUTATION_UPDATE_PROJECT_PARAMS } from "../gql/mutations/project"; import {
import { UPDATE_PROJECT_TITLE, resetProject, ADD_TASK, taskSaved, AddTask, taskRemoved, RemoveTask, REMOVE_TASK, UPDATE_TASK_ESTIMATION, updateTaskEstimation, UpdateTaskEstimation, UpdateTaskLabel, UPDATE_TASK_LABEL, UpdateParam, paramsSaved, UPDATE_PARAM } from "./useProjectReducer"; MUTATION_CREATE_PROJECT, MUTATION_UPDATE_PROJECT_TITLE,
MUTATION_ADD_PROJECT_TASK, MUTATION_REMOVE_PROJECT_TASK,
MUTATION_UPDATE_PROJECT_TASK, MUTATION_UPDATE_PROJECT_PARAMS,
MUTATION_ADD_PROJECT_TASK_CATEGORY,
MUTATION_UPDATE_PROJECT_TASK_CATEGORY,
MUTATION_REMOVE_PROJECT_TASK_CATEGORY
} from "../gql/mutations/project";
import {
UPDATE_PROJECT_TITLE, resetProject,
ADD_TASK, taskSaved, AddTask,
taskRemoved, RemoveTask, REMOVE_TASK,
UPDATE_TASK_ESTIMATION, UpdateTaskEstimation,
UpdateTaskLabel, UPDATE_TASK_LABEL, UpdateParam,
paramsSaved, UPDATE_PARAM, AddTaskCategory,
ADD_TASK_CATEGORY, taskCategorySaved, UpdateTaskCategoryLabel, UPDATE_TASK_CATEGORY_LABEL, UPDATE_TASK_CATEGORY_COST, updateTaskCategoryCost, UpdateTaskCategoryCost, RemoveTaskCategory, taskCategoryRemoved, REMOVE_TASK_CATEGORY, backendError
} from "./useProjectReducer";
import { Project } from "../types/project"; import { Project } from "../types/project";
export function* rootSaga() { export function* rootSaga() {
@ -13,6 +28,10 @@ export function* rootSaga() {
takeLatest(UPDATE_PARAM, updateProjectParamsSaga), takeLatest(UPDATE_PARAM, updateProjectParamsSaga),
takeLatest(ADD_TASK, addTaskSaga), takeLatest(ADD_TASK, addTaskSaga),
takeLatest(REMOVE_TASK, removeTaskSaga), takeLatest(REMOVE_TASK, removeTaskSaga),
takeLatest(ADD_TASK_CATEGORY, addProjectTaskCategorySaga),
takeLatest(UPDATE_TASK_CATEGORY_LABEL, updateProjectTaskCategoryLabelSaga),
takeLatest(UPDATE_TASK_CATEGORY_COST, updateProjectTaskCategoryCostSaga),
takeLatest(REMOVE_TASK_CATEGORY, removeProjectTaskCategorySaga),
]); ]);
} }
@ -163,4 +182,91 @@ export function* updateProjectParamsSaga({ name, value }: UpdateParam) {
}); });
yield put(paramsSaved({ ...data.updateProjectParams })); yield put(paramsSaved({ ...data.updateProjectParams }));
}
export function* addProjectTaskCategorySaga({ taskCategory }: AddTaskCategory) {
let project: Project = yield select();
if (project.id === undefined) {
project = yield createProjectSaga();
}
const { data } = yield client.mutate({
mutation: MUTATION_ADD_PROJECT_TASK_CATEGORY,
variables: {
projectId: project.id,
changes: {
label: taskCategory.label,
costPerTimeUnit: taskCategory.costPerTimeUnit,
}
}
});
yield put(taskCategorySaved({ ...data.addProjectTaskCategory }));
}
export function* updateProjectTaskCategoryLabelSaga({ categoryId, label }: UpdateTaskCategoryLabel) {
let project: Project = yield select();
if (project.id === undefined) {
project = yield createProjectSaga();
}
const { data } = yield client.mutate({
mutation: MUTATION_UPDATE_PROJECT_TASK_CATEGORY,
variables: {
projectId: project.id,
taskCategoryId: categoryId,
changes: {
label,
}
}
});
yield put(taskCategorySaved({ ...data.updateProjectTaskCategory }));
}
export function* updateProjectTaskCategoryCostSaga({ categoryId, costPerTimeUnit }: UpdateTaskCategoryCost) {
let project: Project = yield select();
if (project.id === undefined) {
project = yield createProjectSaga();
}
const { data } = yield client.mutate({
mutation: MUTATION_UPDATE_PROJECT_TASK_CATEGORY,
variables: {
projectId: project.id,
taskCategoryId: categoryId,
changes: {
costPerTimeUnit,
}
}
});
yield put(taskCategorySaved({ ...data.updateProjectTaskCategory }));
}
export function* removeProjectTaskCategorySaga({ taskCategoryId }: RemoveTaskCategory) {
let project: Project = yield select();
if (project.id === undefined) {
project = yield createProjectSaga();
}
try {
yield client.mutate({
mutation: MUTATION_REMOVE_PROJECT_TASK_CATEGORY,
variables: {
projectId: project.id,
taskCategoryId,
}
});
} catch(err) {
yield put(backendError(err));
return
}
yield put(taskCategoryRemoved(taskCategoryId));
} }

View File

@ -5,6 +5,10 @@ import { rootSaga } from "./useProjectReducer.sagas";
import { uuid } from "../util/uuid"; import { uuid } from "../util/uuid";
import { Params } from "../types/params"; import { Params } from "../types/params";
export interface ProjectState extends Project {
lastBackendError: Error
}
export interface Action { export interface Action {
type: string type: string
} }
@ -23,13 +27,16 @@ UpdateTaskCategoryLabel |
UpdateTaskCategoryCost | UpdateTaskCategoryCost |
AddTaskCategory | AddTaskCategory |
RemoveTaskCategory | RemoveTaskCategory |
ResetProject TaskCategoryRemoved |
TaskCategorySaved |
ResetProject |
BackendError
export function useProjectReducer(project: Project) { export function useProjectReducer(project: Project) {
return useReducerAndSaga<Project>(projectReducer, project, rootSaga); return useReducerAndSaga<ProjectState>(projectReducer, project, rootSaga);
} }
export function projectReducer(project: Project, action: ProjectReducerActions): Project { export function projectReducer(project: ProjectState, action: ProjectReducerActions): ProjectState {
console.log(action.type, action); console.log(action.type, action);
switch(action.type) { switch(action.type) {
@ -50,21 +57,21 @@ export function projectReducer(project: Project, action: ProjectReducerActions):
case UPDATE_PARAM: case UPDATE_PARAM:
return handleUpdateParam(project, action as UpdateParam); return handleUpdateParam(project, action as UpdateParam);
case ADD_TASK_CATEGORY: case PARAMS_SAVED:
return handleAddTaskCategory(project, action as AddTaskCategory); return handleParamsSaved(project, action as ParamsSaved);
case REMOVE_TASK_CATEGORY: case TASK_CATEGORY_SAVED:
return handleRemoveTaskCategory(project, action as RemoveTaskCategory); return handleTaskCategorySaved(project, action as TaskCategorySaved);
case UPDATE_TASK_CATEGORY_LABEL: case TASK_CATEGORY_REMOVED:
return handleUpdateTaskCategoryLabel(project, action as UpdateTaskCategoryLabel); return handleTaskCategoryRemoved(project, action as TaskCategoryRemoved);
case UPDATE_TASK_CATEGORY_COST:
return handleUpdateTaskCategoryCost(project, action as UpdateTaskCategoryCost);
case RESET_PROJECT: case RESET_PROJECT:
return handleResetProject(project, action as ResetProject); return handleResetProject(project, action as ResetProject);
case BACKEND_ERROR:
return handleBackendError(project, action as BackendError);
} }
@ -95,7 +102,7 @@ export function taskSaved(task: Task): TaskSaved {
return { type: TASK_SAVED, task }; return { type: TASK_SAVED, task };
} }
export function handleTaskSaved(project: Project, action: TaskSaved): Project { export function handleTaskSaved(project: ProjectState, action: TaskSaved): ProjectState {
const taskIndex = project.tasks.findIndex(t => t.id === action.task.id); const taskIndex = project.tasks.findIndex(t => t.id === action.task.id);
const tasks = [ ...project.tasks ]; const tasks = [ ...project.tasks ];
if (taskIndex === -1) { if (taskIndex === -1) {
@ -130,7 +137,7 @@ export function taskRemoved(id: number): TaskRemoved {
} }
export function handleTaskRemoved(project: Project, action: TaskRemoved): Project { export function handleTaskRemoved(project: ProjectState, action: TaskRemoved): ProjectState {
const tasks = [...project.tasks]; const tasks = [...project.tasks];
const taskIndex = project.tasks.findIndex(t => t.id === action.id); const taskIndex = project.tasks.findIndex(t => t.id === action.id);
if (taskIndex === -1) return project; if (taskIndex === -1) return project;
@ -153,7 +160,7 @@ export function updateTaskEstimation(id: number, confidence: EstimationConfidenc
return { type: UPDATE_TASK_ESTIMATION, id, confidence, value }; return { type: UPDATE_TASK_ESTIMATION, id, confidence, value };
} }
export function handleUpdateTaskEstimation(project: Project, action: UpdateTaskEstimation): Project { export function handleUpdateTaskEstimation(project: ProjectState, action: UpdateTaskEstimation): ProjectState {
const tasks = [...project.tasks]; const tasks = [...project.tasks];
const taskIndex = project.tasks.findIndex(t => t.id === action.id); const taskIndex = project.tasks.findIndex(t => t.id === action.id);
if (taskIndex === -1) return project; if (taskIndex === -1) return project;
@ -182,7 +189,7 @@ export function updateProjectTitle(title: string): UpdateProjectTitle {
return { type: UPDATE_PROJECT_TITLE, title }; return { type: UPDATE_PROJECT_TITLE, title };
} }
export function handleUpdateProjectTitle(project: Project, action: UpdateProjectTitle): Project { export function handleUpdateProjectTitle(project: ProjectState, action: UpdateProjectTitle): ProjectState {
return { return {
...project, ...project,
title: action.title title: action.title
@ -200,7 +207,7 @@ export function updateTaskLabel(id: number, label: string): UpdateTaskLabel {
return { type: UPDATE_TASK_LABEL, id, label }; return { type: UPDATE_TASK_LABEL, id, label };
} }
export function handleUpdateTaskLabel(project: Project, action: UpdateTaskLabel): Project { export function handleUpdateTaskLabel(project: ProjectState, action: UpdateTaskLabel): ProjectState {
const tasks = [...project.tasks]; const tasks = [...project.tasks];
const taskIndex = project.tasks.findIndex(t => t.id === action.id); const taskIndex = project.tasks.findIndex(t => t.id === action.id);
if (taskIndex === -1) return project; if (taskIndex === -1) return project;
@ -224,7 +231,7 @@ export function updateParam(name: string, value: any): UpdateParam {
return { type: UPDATE_PARAM, name, value }; return { type: UPDATE_PARAM, name, value };
} }
export function handleUpdateParam(project: Project, action: UpdateParam): Project { export function handleUpdateParam(project: ProjectState, action: UpdateParam): ProjectState {
return { return {
...project, ...project,
params: { params: {
@ -244,6 +251,41 @@ export function paramsSaved(params: Params): ParamsSaved {
return { type: PARAMS_SAVED, params }; return { type: PARAMS_SAVED, params };
} }
function handleParamsSaved(project: ProjectState, action: ParamsSaved): ProjectState {
return {
...project,
params: {
...action.params
}
}
}
export interface TaskCategorySaved extends Action {
taskCategory: TaskCategory
}
export const TASK_CATEGORY_SAVED = "TASK_CATEGORY_SAVED";
export function taskCategorySaved(taskCategory: TaskCategory): TaskCategorySaved {
return { type: TASK_CATEGORY_SAVED, taskCategory };
}
export function handleTaskCategorySaved(project: ProjectState, action: TaskCategorySaved): ProjectState {
const taskCategories = [...project.taskCategories];
const taskCategoryIndex = taskCategories.findIndex(tc => tc.id === action.taskCategory.id);
if (taskCategoryIndex === -1) {
taskCategories.push({ ...action.taskCategory });
} else {
taskCategories[taskCategoryIndex] = { ...action.taskCategory };
}
return {
...project,
taskCategories
};
}
export interface UpdateTaskCategoryLabel extends Action { export interface UpdateTaskCategoryLabel extends Action {
categoryId: TaskCategoryID categoryId: TaskCategoryID
label: string label: string
@ -255,19 +297,6 @@ export function updateTaskCategoryLabel(categoryId: TaskCategoryID, label: strin
return { type: UPDATE_TASK_CATEGORY_LABEL, categoryId, label }; return { type: UPDATE_TASK_CATEGORY_LABEL, categoryId, label };
} }
export function handleUpdateTaskCategoryLabel(project: Project, action: UpdateTaskCategoryLabel): Project {
return {
...project,
taskCategories: {
...project.taskCategories,
[action.categoryId]: {
...project.taskCategories[action.categoryId],
label: action.label
},
}
};
}
export interface UpdateTaskCategoryCost extends Action { export interface UpdateTaskCategoryCost extends Action {
categoryId: TaskCategoryID categoryId: TaskCategoryID
costPerTimeUnit: number costPerTimeUnit: number
@ -279,19 +308,6 @@ export function updateTaskCategoryCost(categoryId: TaskCategoryID, costPerTimeUn
return { type: UPDATE_TASK_CATEGORY_COST, categoryId, costPerTimeUnit }; return { type: UPDATE_TASK_CATEGORY_COST, categoryId, costPerTimeUnit };
} }
export function handleUpdateTaskCategoryCost(project: Project, action: UpdateTaskCategoryCost): Project {
return {
...project,
taskCategories: {
...project.taskCategories,
[action.categoryId]: {
...project.taskCategories[action.categoryId],
costPerTimeUnit: action.costPerTimeUnit
},
}
};
}
export const ADD_TASK_CATEGORY = "ADD_TASK_CATEGORY"; export const ADD_TASK_CATEGORY = "ADD_TASK_CATEGORY";
export interface AddTaskCategory extends Action { export interface AddTaskCategory extends Action {
@ -302,17 +318,6 @@ export function addTaskCategory(taskCategory: TaskCategory): AddTaskCategory {
return { type: ADD_TASK_CATEGORY, taskCategory }; return { type: ADD_TASK_CATEGORY, taskCategory };
} }
export function handleAddTaskCategory(project: Project, action: AddTaskCategory): Project {
const taskCategory = { ...action.taskCategory };
return {
...project,
taskCategories: {
...project.taskCategories,
[taskCategory.id]: taskCategory,
}
};
}
export interface RemoveTaskCategory extends Action { export interface RemoveTaskCategory extends Action {
taskCategoryId: TaskCategoryID taskCategoryId: TaskCategoryID
} }
@ -323,18 +328,28 @@ export function removeTaskCategory(taskCategoryId: TaskCategoryID): RemoveTaskCa
return { type: REMOVE_TASK_CATEGORY, taskCategoryId }; return { type: REMOVE_TASK_CATEGORY, taskCategoryId };
} }
export function handleRemoveTaskCategory(project: Project, action: RemoveTaskCategory): Project { export interface TaskCategoryRemoved extends Action {
const taskCategories = { ...project.taskCategories }; id: number
delete taskCategories[action.taskCategoryId]; }
export const TASK_CATEGORY_REMOVED = "TASK_CATEGORY_REMOVED";
export function taskCategoryRemoved(id: number): TaskCategoryRemoved {
return { type: TASK_CATEGORY_REMOVED, id };
}
export function handleTaskCategoryRemoved(project: ProjectState, action: TaskCategoryRemoved): ProjectState {
const taskCategories = [...project.taskCategories];
const taskCategoryIndex = project.taskCategories.findIndex(tc => tc.id === action.id);
if (taskCategoryIndex === -1) return project;
taskCategories.splice(taskCategoryIndex, 1);
return { return {
...project, ...project,
taskCategories: { taskCategories
...project.taskCategories,
...taskCategories
}
}; };
} }
export interface ResetProject extends Action { export interface ResetProject extends Action {
project: Project project: Project
} }
@ -355,9 +370,26 @@ export function resetProject(project: Project): ResetProject {
return { type: RESET_PROJECT, project: newProject }; return { type: RESET_PROJECT, project: newProject };
} }
export function handleResetProject(project: Project, action: ResetProject): Project { export function handleResetProject(project: ProjectState, action: ResetProject): ProjectState {
return { return {
...project, ...project,
...action.project, ...action.project,
}; };
} }
export interface BackendError extends Action {
err: Error
}
export const BACKEND_ERROR = "BACKEND_ERROR";
export function backendError(err: Error): BackendError {
return { type: BACKEND_ERROR, err };
}
export function handleBackendError(project: ProjectState, action: BackendError): ProjectState {
return {
...project,
lastBackendError: action.err,
};
}

14
internal/graph/error.go Normal file
View File

@ -0,0 +1,14 @@
package graph
import (
"github.com/vektah/gqlparser/v2/gqlerror"
)
var (
ErrAssociatedTaskExist = &gqlerror.Error{
Message: "",
Extensions: map[string]interface{}{
"code": "associated-task-exist",
},
}
)

View File

@ -30,6 +30,11 @@ input TimeUnitChanges {
acronym: String acronym: String
} }
input ProjectTaskCategoryChanges {
label: String
costPerTimeUnit: Float
}
type Mutation { type Mutation {
updateUser(id: ID!, changes: UserChanges!): User! updateUser(id: ID!, changes: UserChanges!): User!
createProject(changes: CreateProjectChanges!): Project! createProject(changes: CreateProjectChanges!): Project!
@ -37,5 +42,8 @@ type Mutation {
addProjectTask(projectId: ID!, changes: ProjectTaskChanges!): Task! addProjectTask(projectId: ID!, changes: ProjectTaskChanges!): Task!
removeProjectTask(projectId: ID!, taskId: ID!): Boolean! removeProjectTask(projectId: ID!, taskId: ID!): Boolean!
updateProjectTask(projectId: ID!, taskId: ID!, changes: ProjectTaskChanges!): Task! updateProjectTask(projectId: ID!, taskId: ID!, changes: ProjectTaskChanges!): Task!
addProjectTaskCategory(projectId: ID!, changes: ProjectTaskCategoryChanges!): TaskCategory!
updateProjectTaskCategory(projectId: ID!, taskCategoryId: ID!, changes: ProjectTaskCategoryChanges!): TaskCategory!
removeProjectTaskCategory(projectId: ID!, taskCategoryId: ID!): Boolean!
updateProjectParams(projectId: ID!, changes: ProjectParamsChanges!): ProjectParams! updateProjectParams(projectId: ID!, changes: ProjectParamsChanges!): ProjectParams!
} }

View File

@ -34,6 +34,18 @@ func (r *mutationResolver) UpdateProjectTask(ctx context.Context, projectID int6
return handleUpdateProjectTask(ctx, projectID, taskID, changes) return handleUpdateProjectTask(ctx, projectID, taskID, changes)
} }
func (r *mutationResolver) AddProjectTaskCategory(ctx context.Context, projectID int64, changes model.ProjectTaskCategoryChanges) (*model.TaskCategory, error) {
return handleAddProjectTaskCategory(ctx, projectID, changes)
}
func (r *mutationResolver) UpdateProjectTaskCategory(ctx context.Context, projectID int64, taskCategoryID int64, changes model.ProjectTaskCategoryChanges) (*model.TaskCategory, error) {
return handleUpdateProjectTaskCategory(ctx, projectID, taskCategoryID, changes)
}
func (r *mutationResolver) RemoveProjectTaskCategory(ctx context.Context, projectID int64, taskCategoryID int64) (bool, error) {
return handleRemoveProjectTaskCategory(ctx, projectID, taskCategoryID)
}
func (r *mutationResolver) UpdateProjectParams(ctx context.Context, projectID int64, changes model.ProjectParamsChanges) (*model.ProjectParams, error) { func (r *mutationResolver) UpdateProjectParams(ctx context.Context, projectID int64, changes model.ProjectParamsChanges) (*model.ProjectParams, error) {
return handleUpdateProjectParams(ctx, projectID, changes) return handleUpdateProjectParams(ctx, projectID, changes)
} }

View File

@ -3,6 +3,8 @@ package graph
import ( import (
"context" "context"
"github.com/99designs/gqlgen/graphql"
"forge.cadoles.com/Cadoles/guesstimate/internal/model" "forge.cadoles.com/Cadoles/guesstimate/internal/model"
model1 "forge.cadoles.com/Cadoles/guesstimate/internal/model" model1 "forge.cadoles.com/Cadoles/guesstimate/internal/model"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -126,3 +128,56 @@ func handleUpdateProjectParams(ctx context.Context, projectID int64, changes mod
return project.Params, nil return project.Params, nil
} }
func handleAddProjectTaskCategory(ctx context.Context, projectID int64, changes model.ProjectTaskCategoryChanges) (*model.TaskCategory, error) {
db, err := getDB(ctx)
if err != nil {
return nil, errors.WithStack(err)
}
repo := model.NewProjectRepository(db)
taskCategory, err := repo.AddTaskCategory(ctx, projectID, changes)
if err != nil {
return nil, errors.WithStack(err)
}
return taskCategory, nil
}
func handleUpdateProjectTaskCategory(ctx context.Context, projectID int64, taskCategoryID int64, changes model.ProjectTaskCategoryChanges) (*model.TaskCategory, error) {
db, err := getDB(ctx)
if err != nil {
return nil, errors.WithStack(err)
}
repo := model.NewProjectRepository(db)
taskCategory, err := repo.UpdateTaskCategory(ctx, projectID, taskCategoryID, changes)
if err != nil {
return nil, errors.WithStack(err)
}
return taskCategory, nil
}
func handleRemoveProjectTaskCategory(ctx context.Context, projectID int64, taskCategoryID int64) (bool, error) {
db, err := getDB(ctx)
if err != nil {
return false, errors.WithStack(err)
}
repo := model.NewProjectRepository(db)
if err := repo.RemoveTaskCategory(ctx, projectID, taskCategoryID); err != nil {
if errors.Is(err, model.ErrAssociatedTaskExist) {
graphql.AddError(ctx, ErrAssociatedTaskExist)
return false, nil
}
return false, errors.WithStack(err)
}
return true, nil
}

7
internal/model/error.go Normal file
View File

@ -0,0 +1,7 @@
package model
import "errors"
var (
ErrAssociatedTaskExist = errors.New("associated task exist")
)

View File

@ -144,10 +144,6 @@ func (r *ProjectRepository) AddTask(ctx context.Context, projectID int64, change
return errors.WithStack(err) return errors.WithStack(err)
} }
if err := tx.Save(task).Error; err != nil {
return errors.Wrap(err, "could not create task")
}
err := tx.Model(project).Association("Tasks").Append(task).Error err := tx.Model(project).Association("Tasks").Append(task).Error
if err != nil { if err != nil {
return errors.Wrap(err, "could not add task") return errors.Wrap(err, "could not add task")
@ -279,6 +275,94 @@ func (r *ProjectRepository) UpdateParams(ctx context.Context, projectID int64, c
return project, nil return project, nil
} }
func (r *ProjectRepository) AddTaskCategory(ctx context.Context, projectID int64, changes ProjectTaskCategoryChanges) (*TaskCategory, error) {
project := &Project{}
project.ID = projectID
taskCategory := &TaskCategory{}
err := r.db.Transaction(func(tx *gorm.DB) error {
if err := updateTaskCategoryWithChanges(tx, taskCategory, changes); err != nil {
return errors.WithStack(err)
}
err := tx.Model(project).Association("TaskCategories").Append(taskCategory).Error
if err != nil {
return errors.Wrap(err, "could not add task category")
}
return nil
})
if err != nil {
return nil, errors.Wrap(err, "could not add task category")
}
return taskCategory, nil
}
func (r *ProjectRepository) RemoveTaskCategory(ctx context.Context, projectID int64, taskCategoryID int64) error {
project := &Project{}
project.ID = projectID
err := r.db.Transaction(func(tx *gorm.DB) error {
taskCategory := &TaskCategory{}
taskCategory.ID = taskCategoryID
var totalAssociatedTasks int
if err := tx.Model(&Task{}).Where("category_id = ?", taskCategoryID).Count(&totalAssociatedTasks).Error; err != nil {
return errors.Wrap(err, "could not count associated tasks")
}
if totalAssociatedTasks != 0 {
return errors.WithStack(ErrAssociatedTaskExist)
}
err := tx.Model(project).Association("TaskCategories").Delete(taskCategory).Error
if err != nil {
return errors.Wrap(err, "could not remove task category relationship")
}
err = tx.Delete(taskCategory, "id = ? AND project_id = ?", taskCategoryID, projectID).Error
if err != nil {
return errors.Wrap(err, "could not delete task category")
}
return nil
})
if err != nil {
return errors.Wrap(err, "could not remove task category")
}
return nil
}
func (r *ProjectRepository) UpdateTaskCategory(ctx context.Context, projectID, taskCategoryID int64, changes ProjectTaskCategoryChanges) (*TaskCategory, error) {
taskCategory := &TaskCategory{}
err := r.db.Transaction(func(tx *gorm.DB) error {
err := tx.Model(taskCategory).
First(taskCategory, "id = ? AND project_id = ?", taskCategoryID, projectID).
Error
if err != nil {
return errors.WithStack(err)
}
if err := updateTaskCategoryWithChanges(tx, taskCategory, changes); err != nil {
return errors.WithStack(err)
}
if err := tx.Save(taskCategory).Error; err != nil {
return errors.WithStack(err)
}
return nil
})
if err != nil {
return nil, errors.Wrap(err, "could not update task category")
}
return taskCategory, nil
}
func updateTaskWithChanges(db *gorm.DB, task *Task, changes ProjectTaskChanges) error { func updateTaskWithChanges(db *gorm.DB, task *Task, changes ProjectTaskChanges) error {
if changes.Label != nil { if changes.Label != nil {
task.Label = changes.Label task.Label = changes.Label
@ -330,6 +414,18 @@ func updateTaskWithChanges(db *gorm.DB, task *Task, changes ProjectTaskChanges)
return nil return nil
} }
func updateTaskCategoryWithChanges(db *gorm.DB, taskCategory *TaskCategory, changes ProjectTaskCategoryChanges) error {
if changes.Label != nil {
taskCategory.Label = *changes.Label
}
if changes.CostPerTimeUnit != nil {
taskCategory.CostPerTimeUnit = *changes.CostPerTimeUnit
}
return nil
}
func NewProjectRepository(db *gorm.DB) *ProjectRepository { func NewProjectRepository(db *gorm.DB) *ProjectRepository {
return &ProjectRepository{db} return &ProjectRepository{db}
} }