diff --git a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java index 53d73d7..e041e87 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java @@ -20,6 +20,7 @@ public class Constants { public static final String DESCRIPTION = "description"; public static final String DOMAIN = "domain"; public static final String DUE_DATE = "due_date"; + public static final String DURATION = "duration"; public static final String EMAIL = "email"; public static final String END_TIME = "end_time"; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java b/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java index 35b525f..25dfbb8 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java @@ -8,8 +8,9 @@ import java.util.Collection; public interface TaskService { CompanyService companyService(); Collection listCompanyTasks(long companyId) throws UmbrellaException; + Collection listProjectTasks(long projectId) throws UmbrellaException; + ProjectService projectService(); UserService userService(); - } diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java index 8ff9f4c..d8afb71 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java @@ -1,13 +1,14 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.core.model; -import de.srsoftware.tools.Mappable; - import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Util.dateTimeOf; +import static de.srsoftware.umbrella.core.Util.markdown; +import de.srsoftware.tools.Mappable; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.Duration; import java.time.LocalDateTime; import java.util.*; @@ -59,18 +60,15 @@ public class Time implements Mappable{ this.taskIds = taskIds; } + public long id(){ + return id; + } - @Override - public Map toMap() { - var map = new HashMap(); - map.put(ID,id); - map.put(USER_ID,userId); - map.put(SUBJECT,subject); - map.put(DESCRIPTION,description); - map.put(START_TIME,start); - map.put(END_TIME,end); - map.put(STATE,Map.of(STATUS_CODE,state.code,NAME,state.name())); - return map; + public boolean isClosed() { + return switch (state){ + case Complete, Cancelled -> true; + case null, default -> false; + }; } public static Time of(ResultSet rs) throws SQLException { @@ -90,4 +88,26 @@ public class Time implements Mappable{ new HashSet<>() ); } + + public LocalDateTime start(){ + return start; + } + + public Collection taskIds(){ + return taskIds; + } + + @Override + public Map toMap() { + var map = new HashMap(); + map.put(ID,id); + map.put(USER_ID,userId); + map.put(SUBJECT,subject); + map.put(DESCRIPTION,Map.of(SOURCE,description,RENDERED,markdown(description))); + map.put(START_TIME,start.toString().replace("T"," ")); + map.put(END_TIME,end.toString().replace("T"," ")); + map.put(STATE,Map.of(STATUS_CODE,state.code,NAME,state.name())); + map.put(DURATION,Duration.between(start,end).toMinutes()/60d); + return map; + } } diff --git a/documents/src/main/java/de/srsoftware/umbrella/documents/Constants.java b/documents/src/main/java/de/srsoftware/umbrella/documents/Constants.java index 4eff8e2..a1cfe7b 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/Constants.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/Constants.java @@ -79,6 +79,7 @@ public class Constants { public static final String PATH_POSITIONS = "positions"; public static final String PATH_SEND = "send"; public static final String PATH_TYPES = "types"; + public static final String POSITION = "position"; public static final String PROJECT_ID = "project_id"; public static final String STATES = "states"; diff --git a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java index 5b829c0..29b795d 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java @@ -141,7 +141,15 @@ public class DocumentApi extends BaseHandler { case LIST -> listCompaniesDocuments(ex,user.get(),token.orElse(null)); case TEMPLATES -> postTemplateList(ex,user.get()); case null -> postDocument(ex,user.get()); - default -> super.doPost(path,ex); + default -> { + var docId = 0L; + try { + docId = Long.parseLong(head); + } catch (NumberFormatException ignored) { + yield super.doPost(path,ex); + } + yield postToDocument(ex,path,user.get(),docId); + } }; } catch (UmbrellaException e) { return send(ex,e); @@ -247,6 +255,10 @@ public class DocumentApi extends BaseHandler { return sendContent(ex,saved.toMap()); } + private boolean postDocumentPosition(long docId, HttpExchange ex, UmbrellaUser user) throws IOException { + return notImplemented(ex,"postDocumentPosition",this); + } + private boolean postTemplateList(HttpExchange ex, UmbrellaUser user) throws UmbrellaException, IOException { var json = json(ex); if (!(json.has(COMPANY) && json.get(COMPANY) instanceof Number companyId)) throw missingFieldException(COMPANY); @@ -255,4 +267,13 @@ public class DocumentApi extends BaseHandler { var templates = db.getCompanyTemplates(companyId.longValue()); return sendContent(ex,templates.stream().map(Template::toMap)); } + + + private boolean postToDocument(HttpExchange ex, Path path, UmbrellaUser user, long docId) throws IOException { + var head = path.pop(); + return switch (head){ + case POSITION -> postDocumentPosition(docId,ex,user); + case null, default -> super.doPost(path,ex); + }; + } } diff --git a/frontend/src/routes/document/PositionSelector.svelte b/frontend/src/routes/document/PositionSelector.svelte index 5063f6d..2848529 100644 --- a/frontend/src/routes/document/PositionSelector.svelte +++ b/frontend/src/routes/document/PositionSelector.svelte @@ -6,20 +6,43 @@ let { close = () => {}, doc = $bindable({}), onSelect = (item) => {} } = $props(); - let select = $state(0); + let source = $state(0); - function estimateSelected(estimate){ - onSelect({task:estimate}); + function select(position){ close(); + onSelect(position); + } + + function estimateSelected(estimate){ + select({ + code:t('estimated_time'), + subject:estimate.name, + description:estimate.description.source, + amount:estimate.estimated_time, + unit:doc.currency+"/h" + }); } function itemSelected(item){ - onSelect({item:item}); - close(); + select({ + code:item.code, + subject:item.name, + description:item.description.source, + amount:1, + unit:item.unit, + unit_price:item.unit_price, + tax:item.tax + }); } function timeSelected(time){ - console.log({timeSelected:time}); + select({ + code:t('document.timetrack'), + title:time.subject, + description:time.description.source, + amount:time.duration, + unit:doc.currency+"/h" + }); } @@ -42,14 +65,14 @@
- - - + + + - {#if select == 0} + {#if source == 0} - {:else if select == 1} + {:else if source == 1} {:else} diff --git a/frontend/src/routes/document/TimeList.svelte b/frontend/src/routes/document/TimeList.svelte index 946d93a..cc93bd6 100644 --- a/frontend/src/routes/document/TimeList.svelte +++ b/frontend/src/routes/document/TimeList.svelte @@ -4,17 +4,33 @@ let { company_id, onSelect = (time) => {} } = $props(); + let projects = $state(null); let times = $state(null); let error = $state(null); - async function loadTimes(){ - const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/times/list`; + async function loadProjects(){ + const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project/list`; let data = { company_id: company_id }; const resp = await fetch(url,{ credentials:'include', method: 'POST', body: JSON.stringify(data) }); + if (resp.ok){ + projects = await resp.json(); + } else { + error = await resp.body(); + } + } + + async function loadTimes(projectId){ + const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/times/list`; + let data = { company_id: company_id, project_id: projectId }; + const resp = await fetch(url,{ + credentials:'include', + method: 'POST', + body: JSON.stringify(data) + }); if (resp.ok){ times = await resp.json(); } else { @@ -22,11 +38,24 @@ } } - onMount(loadTimes); + onMount(loadProjects);

Times

+ {#if projects} + {#each projects as project,idx1} + + {/each} + {/if} {#if times} + {#each times as time,idx2} +
onSelect(time)}> + {(time.duration).toFixed(3)} {t('hours')} + {time.subject} + {time.start_time} + {@html time.description.rendered} +
+ {/each} {/if}
\ No newline at end of file diff --git a/frontend/src/routes/document/View.svelte b/frontend/src/routes/document/View.svelte index c3ddb71..f73156e 100644 --- a/frontend/src/routes/document/View.svelte +++ b/frontend/src/routes/document/View.svelte @@ -10,7 +10,7 @@ import StateSelector from './StateSelector.svelte'; import TemplateSelector from './TemplateSelector.svelte'; let { id } = $props(); - let error = null; + let error = $state(null); let doc = $state(null); let position_select = $state(false); @@ -63,12 +63,17 @@ } } - function addPosition(selected){ - console.log(selected); - let newPos = {}; - if (selected.item) newPos['item']=selected.item.id; - if (selected.task) newPos['task']=selected.task.id; - console.log(JSON.stringify({newPos:newPos})); + async function addPosition(selected){ + const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/document/${doc.id}/position`; + const resp = await fetch(url,{ + method: 'POST', + credentials:'include', + body:JSON.stringify(selected) + }); + if (resp.ok){ + } else { + error = await resp.text(); + } } onMount(loadDoc); diff --git a/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java b/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java index 8097dd7..b7afd48 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java @@ -7,6 +7,7 @@ import static de.srsoftware.umbrella.core.Paths.LIST; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.forbidden; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE; +import static java.util.Comparator.comparing; import com.sun.net.httpserver.HttpExchange; import de.srsoftware.configuration.Configuration; @@ -61,7 +62,7 @@ public class ProjectModule extends BaseHandler implements ProjectService { } public Collection listProjects(long companyId, boolean includeClosed) throws UmbrellaException { - return projectDb.list(companyId, includeClosed); + return projectDb.list(companyId, includeClosed).stream().sorted(comparing(Project::name)).toList(); } private boolean listItems(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException { diff --git a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java index 6e356cb..71d79e9 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java @@ -93,6 +93,11 @@ public class TaskModule extends BaseHandler implements TaskService { return taskDb.listTasks(projectList.stream().map(Project::id).toList()); } + @Override + public Collection listProjectTasks(long projectId) throws UmbrellaException { + return taskDb.listTasks(List.of(projectId)); + } + private Map placeInTree(Task task, HashMap> tree, Map map) { var taskMap = task.toMap(); if (task.parentTaskId() != null){ diff --git a/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java b/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java index 85b5fc3..7dffc93 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java @@ -13,7 +13,6 @@ import java.sql.SQLException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.List; public class SqliteDb implements TimeDb { diff --git a/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java b/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java index 30b2187..8e0f919 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java @@ -4,7 +4,6 @@ package de.srsoftware.umbrella.time; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Time; import java.util.Collection; -import java.util.List; public interface TimeDb { Collection