feat(ui+backend): base of data persistence
This commit is contained in:
@ -11,7 +11,6 @@ export interface TimeUnit {
|
||||
}
|
||||
|
||||
export interface Params {
|
||||
taskCategories: TaskCategoriesIndex
|
||||
timeUnit: TimeUnit
|
||||
currency: string
|
||||
roundUpEstimations: boolean
|
||||
@ -19,53 +18,38 @@ export interface Params {
|
||||
}
|
||||
|
||||
export const defaults = {
|
||||
taskCategories: {
|
||||
"RQ15CD3iX1Ey2f9kat7tfLGZmUx9GGc15nS6A7fYtZv76SnS4": {
|
||||
id: "RQ15CD3iX1Ey2f9kat7tfLGZmUx9GGc15nS6A7fYtZv76SnS4",
|
||||
label: "Développement",
|
||||
costPerTimeUnit: 500,
|
||||
},
|
||||
"QRdGS5Pr5si9SSjU84WAq19cjxQ3rUL71jKh8oHSMZSY4bBH9": {
|
||||
id: "QRdGS5Pr5si9SSjU84WAq19cjxQ3rUL71jKh8oHSMZSY4bBH9",
|
||||
label: "Conduite de projet",
|
||||
costPerTimeUnit: 500,
|
||||
},
|
||||
"RPcqFMLdQrgBSomv7Sao7EQSb7on6rtjfDQK5JZNhNSg9DwEo": {
|
||||
id: "RPcqFMLdQrgBSomv7Sao7EQSb7on6rtjfDQK5JZNhNSg9DwEo",
|
||||
label: "Recette",
|
||||
costPerTimeUnit: 500,
|
||||
},
|
||||
},
|
||||
params: {
|
||||
timeUnit: {
|
||||
label: "jour/homme",
|
||||
acronym: "j/h",
|
||||
label: "jour/homme",
|
||||
acronym: "j/h",
|
||||
},
|
||||
roundUpEstimations: true,
|
||||
currency: "€ H.T.",
|
||||
costPerTimeUnit: 500,
|
||||
hideFinancialPreviewOnPrint: false,
|
||||
}
|
||||
}
|
||||
|
||||
export function getTimeUnit(project: Project): TimeUnit {
|
||||
return project.params.timeUnit ? project.params.timeUnit : defaults.timeUnit;
|
||||
return project.params.timeUnit ? project.params.timeUnit : defaults.params.timeUnit;
|
||||
}
|
||||
|
||||
export function getRoundUpEstimations(project: Project): boolean {
|
||||
return project.params.hasOwnProperty("roundUpEstimations") ? project.params.roundUpEstimations : defaults.roundUpEstimations;
|
||||
return project.params.hasOwnProperty("roundUpEstimations") ? project.params.roundUpEstimations : defaults.params.roundUpEstimations;
|
||||
}
|
||||
|
||||
export function getCurrency(project: Project): string {
|
||||
return project.params.currency ? project.params.currency : defaults.currency;
|
||||
return project.params.currency ? project.params.currency : defaults.params.currency;
|
||||
}
|
||||
|
||||
export function getTaskCategories(project: Project): TaskCategoriesIndex {
|
||||
return project.params.taskCategories ? project.params.taskCategories : defaults.taskCategories;
|
||||
export function getTaskCategories(project: Project): TaskCategory[] {
|
||||
return project.taskCategories ? project.taskCategories : [];
|
||||
}
|
||||
|
||||
export function getTaskCategoryCost(taskCategory: TaskCategory): number {
|
||||
return taskCategory.hasOwnProperty("costPerTimeUnit") ? taskCategory.costPerTimeUnit : defaults.costPerTimeUnit;
|
||||
return taskCategory.hasOwnProperty("costPerTimeUnit") ? taskCategory.costPerTimeUnit : defaults.params.costPerTimeUnit;
|
||||
}
|
||||
|
||||
export function getHideFinancialPreviewOnPrint(project: Project): boolean {
|
||||
return project.params.hasOwnProperty("hideFinancialPreviewOnPrint") ? project.params.hideFinancialPreviewOnPrint : defaults.hideFinancialPreviewOnPrint;
|
||||
return project.params.hasOwnProperty("hideFinancialPreviewOnPrint") ? project.params.hideFinancialPreviewOnPrint : defaults.params.hideFinancialPreviewOnPrint;
|
||||
}
|
@ -1,29 +1,27 @@
|
||||
import { Task, TaskCategory, TaskID, getTaskWeightedMean, TaskCategoryID, getTaskStandardDeviation } from './task';
|
||||
import { Params, defaults, getTaskCategoryCost } from "./params";
|
||||
import { Params, defaults, getTaskCategoryCost, TaskCategoriesIndex } from "./params";
|
||||
import { Estimation } from '../hooks/useProjectEstimations';
|
||||
|
||||
export type ProjectID = string;
|
||||
export type ProjectID = number;
|
||||
|
||||
export interface Project {
|
||||
id: ProjectID
|
||||
label: string
|
||||
title: string
|
||||
description: string
|
||||
tasks: Tasks
|
||||
taskCategories: TaskCategory[]
|
||||
tasks: Task[]
|
||||
params: Params
|
||||
}
|
||||
|
||||
export interface Tasks {
|
||||
[id: string]: Task
|
||||
}
|
||||
|
||||
export function newProject(): Project {
|
||||
export function newProject(id: number|undefined = undefined): Project {
|
||||
return {
|
||||
id: "",
|
||||
label: "",
|
||||
id: id,
|
||||
title: "",
|
||||
description: "",
|
||||
tasks: {},
|
||||
tasks: [],
|
||||
taskCategories: [],
|
||||
params: {
|
||||
...defaults
|
||||
...defaults.params
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -36,7 +34,7 @@ export function getProjectWeightedMean(p : Project): number {
|
||||
}
|
||||
|
||||
export function getTaskCategoryWeightedMean(taskCategoryId: TaskCategoryID, p : Project): number {
|
||||
return Object.values(p.tasks).filter(t => t.category === taskCategoryId).reduce((sum: number, t: Task) => {
|
||||
return Object.values(p.tasks).filter(t => t.category && t.category.id === taskCategoryId).reduce((sum: number, t: Task) => {
|
||||
sum += getTaskWeightedMean(t);
|
||||
return sum;
|
||||
}, 0);
|
||||
@ -58,7 +56,7 @@ export function getTaskCategoriesMeanRepartition(project: Project): MeanRepartit
|
||||
|
||||
const repartition: MeanRepartition = {};
|
||||
|
||||
Object.values(project.params.taskCategories).forEach(tc => {
|
||||
Object.values(project.taskCategories).forEach(tc => {
|
||||
repartition[tc.id] = getTaskCategoryWeightedMean(tc.id, project) / projectMean;
|
||||
if (Number.isNaN(repartition[tc.id])) repartition[tc.id] = 0;
|
||||
});
|
||||
@ -74,34 +72,54 @@ export interface MinMaxCost {
|
||||
export interface Cost {
|
||||
totalCost: number
|
||||
totalTime: number
|
||||
details: { [taskCategoryId: string]: { time: number, cost: number } }
|
||||
details: CostDetails[]
|
||||
}
|
||||
|
||||
export interface CostDetails {
|
||||
time: number
|
||||
cost: number
|
||||
taskCategoryId: TaskCategoryID
|
||||
}
|
||||
|
||||
export function getMinMaxCosts(project: Project, estimation: Estimation): MinMaxCost {
|
||||
const max: Cost = {totalCost: 0, totalTime: 0, details: {}};
|
||||
const min: Cost = {totalCost: 0, totalTime: 0, details: {}};
|
||||
const max: Cost = {totalCost: 0, totalTime: 0, details: []};
|
||||
const min: Cost = {totalCost: 0, totalTime: 0, details: []};
|
||||
|
||||
const repartition = getTaskCategoriesMeanRepartition(project);
|
||||
|
||||
Object.values(project.params.taskCategories).forEach(tc => {
|
||||
Object.values(project.taskCategories).forEach(tc => {
|
||||
const cost = getTaskCategoryCost(tc);
|
||||
|
||||
const maxTime = Math.round((estimation.e + estimation.sd) * repartition[tc.id]);
|
||||
max.details[tc.id] = {
|
||||
time: maxTime,
|
||||
cost: Math.ceil(maxTime) * cost,
|
||||
};
|
||||
max.totalTime += max.details[tc.id].time;
|
||||
max.totalCost += max.details[tc.id].cost;
|
||||
let maxDetails = getDetailsForTaskCategory(max.details, tc.id);
|
||||
if (!maxDetails) {
|
||||
maxDetails = { taskCategoryId: tc.id, time: 0, cost: 0 };
|
||||
max.details.push(maxDetails);
|
||||
}
|
||||
|
||||
maxDetails.time = maxTime;
|
||||
maxDetails.cost = Math.ceil(maxTime) * cost;
|
||||
|
||||
max.totalTime += maxDetails.time;
|
||||
max.totalCost += maxDetails.cost;
|
||||
|
||||
const minTime = Math.round((estimation.e - estimation.sd) * repartition[tc.id]);
|
||||
min.details[tc.id] = {
|
||||
time: minTime,
|
||||
cost: Math.ceil(minTime) * cost,
|
||||
};
|
||||
min.totalTime += min.details[tc.id].time;
|
||||
min.totalCost += min.details[tc.id].cost;
|
||||
let minDetails = getDetailsForTaskCategory(min.details, tc.id);
|
||||
if (!minDetails) {
|
||||
minDetails = { taskCategoryId: tc.id, time: 0, cost: 0 };
|
||||
min.details.push(minDetails);
|
||||
}
|
||||
minDetails.time = minTime;
|
||||
minDetails.cost = Math.ceil(minTime) * cost;
|
||||
min.totalTime += minDetails.time;
|
||||
min.totalCost += minDetails.cost;
|
||||
});
|
||||
|
||||
return { max, min };
|
||||
}
|
||||
|
||||
function getDetailsForTaskCategory(details: CostDetails[], id: TaskCategoryID): CostDetails|void {
|
||||
for (let d, i = 0; (d = details[i]); ++i) {
|
||||
if (d.taskCategoryId === id) return d;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { defaults } from "./params";
|
||||
import { uuid } from "../util/uuid";
|
||||
|
||||
export type TaskID = string
|
||||
export type TaskID = number
|
||||
|
||||
export enum EstimationConfidence {
|
||||
Optimistic = "optimistic",
|
||||
@ -11,21 +12,21 @@ export enum EstimationConfidence {
|
||||
export interface Task {
|
||||
id: TaskID
|
||||
label: string
|
||||
category: TaskCategoryID
|
||||
category: TaskCategory
|
||||
estimations: { [confidence in EstimationConfidence]: number }
|
||||
}
|
||||
|
||||
export type TaskCategoryID = string
|
||||
export type TaskCategoryID = number
|
||||
|
||||
export interface TaskCategory {
|
||||
id: TaskCategoryID
|
||||
id: number
|
||||
label: string
|
||||
costPerTimeUnit: number
|
||||
}
|
||||
|
||||
export function newTask(label: string, category: TaskCategoryID): Task {
|
||||
export function newTask(label: string, category: TaskCategory): Task {
|
||||
return {
|
||||
id: '',
|
||||
id: null,
|
||||
label,
|
||||
category,
|
||||
estimations: {
|
||||
@ -38,8 +39,8 @@ export function newTask(label: string, category: TaskCategoryID): Task {
|
||||
|
||||
export function createTaskCategory(): TaskCategory {
|
||||
return {
|
||||
id: '',
|
||||
costPerTimeUnit: defaults.costPerTimeUnit,
|
||||
id: null,
|
||||
costPerTimeUnit: defaults.params.costPerTimeUnit,
|
||||
label: ""
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user