From f4e85c870c82506e86b60f0f5338d713c9659f80 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Tue, 10 Feb 2026 08:45:00 +0100 Subject: [PATCH 1/2] implemented cloning of stock items. NEXT: update of GUI via message bus Signed-off-by: Stephan Richter --- .../umbrella/core/constants/Path.java | 4 ++- .../umbrella/documents/DocumentApi.java | 2 +- frontend/src/routes/stock/ItemProps.svelte | 35 +++++++++++-------- .../umbrella/stock/StockModule.java | 20 ++++++++++- web/src/main/resources/web/css/default.css | 10 ++++++ 5 files changed, 54 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/de/srsoftware/umbrella/core/constants/Path.java b/core/src/main/java/de/srsoftware/umbrella/core/constants/Path.java index decc82f2..853be141 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/constants/Path.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/constants/Path.java @@ -5,8 +5,10 @@ public class Path { private Path(){}; public static final String ADD = "add"; - public static final String AVAILABLE = "available"; + public static final String AVAILABLE = "available"; + public static final String CSS = "css"; + public static final String CLONE = "clone"; public static final String COMMON_TEMPLATES = "common_templates"; public static final String COMPANY = "company"; public static final String CONNECTED = "connected"; 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 22e152da..2d1901b7 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java @@ -552,7 +552,7 @@ public class DocumentApi extends BaseHandler implements DocumentService { private boolean postToDocument(HttpExchange ex, de.srsoftware.tools.Path path, UmbrellaUser user, long docId) throws IOException, UmbrellaException { var head = path.pop(); return switch (head){ - case CLONE -> postCloneDoc(docId,ex,user); + case Path.CLONE -> postCloneDoc(docId,ex,user); case POSITION -> postDocumentPosition(docId,ex,user); case PATH_SEND -> sendDocument(ex,path,user,docId); case null, default -> super.doPost(path,ex); diff --git a/frontend/src/routes/stock/ItemProps.svelte b/frontend/src/routes/stock/ItemProps.svelte index a808eaf9..bc2077ba 100644 --- a/frontend/src/routes/stock/ItemProps.svelte +++ b/frontend/src/routes/stock/ItemProps.svelte @@ -1,7 +1,7 @@ {#if item} - patch('name',v)} /> -Code: patch('code',v)} /> + update('name',v)} /> +
{@html item.description.rendered}
+ + + + {#each item.properties.toSorted(byName) as prop}
{t('Code')}: + update('code',v)} /> +
diff --git a/stock/src/main/java/de/srsoftware/umbrella/stock/StockModule.java b/stock/src/main/java/de/srsoftware/umbrella/stock/StockModule.java index bd8d574d..2bf47ebe 100644 --- a/stock/src/main/java/de/srsoftware/umbrella/stock/StockModule.java +++ b/stock/src/main/java/de/srsoftware/umbrella/stock/StockModule.java @@ -27,6 +27,7 @@ import de.srsoftware.umbrella.core.api.Owner; import de.srsoftware.umbrella.core.api.StockService; import de.srsoftware.umbrella.core.constants.Field; import de.srsoftware.umbrella.core.constants.Path; +import de.srsoftware.umbrella.core.constants.Text; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.*; import de.srsoftware.umbrella.core.model.Location; @@ -190,6 +191,7 @@ public class StockModule extends BaseHandler implements StockService { if (user.isEmpty()) return unauthorized(ex); var head = path.pop(); return switch (head) { + case Path.CLONE -> postClone(user.get(),ex); case Path.ITEM -> postItem(user.get(), ex); case LIST -> postItemList(user.get(), path, ex); case Path.LOCATION -> postLocation(user.get(),ex); @@ -285,7 +287,7 @@ public class StockModule extends BaseHandler implements StockService { var json = json(ex); if (!(json.get(ID) instanceof Number id)) throw missingField(ID); json.remove(ID); - + LOG.log(WARNING,"Missing permission check in StockModule.patchItem()!"); var item = stockDb.loadItem(id.longValue()); item.patch(json); return sendContent(ex,stockDb.save(item)); @@ -336,6 +338,21 @@ public class StockModule extends BaseHandler implements StockService { return sendContent(ex,location); } + private boolean postClone(UmbrellaUser user, HttpExchange ex) throws IOException { + var json = json(ex); + if (!json.has(ID))throw missingField(ID); + if (!(json.get(ID) instanceof Number num)) throw invalidField(ID,Text.NUMBER); + long itemId = num.longValue(); + var item = stockDb.loadItem(itemId); + stockDb.loadProperties(item); + var location = item.location().resolve(); + var owner = location.owner().resolve(); + if (!assigned(owner,user)) throw forbidden("You are not allowed to add items to \"{location}\"!", Text.LOCATION,location.name()); + var newItem = new Item(0,owner,0,location,item.code(),item.name(),item.description()); + for (var property : item.properties()) newItem.properties().add(property); + return sendContent(ex,stockDb.save(newItem)); + } + private boolean postItem(UmbrellaUser user, HttpExchange ex) throws IOException { var json = json(ex); if (!json.has(NAME) || !(json.get(NAME) instanceof String name)) throw missingField(NAME); @@ -395,6 +412,7 @@ public class StockModule extends BaseHandler implements StockService { if (!(itemData.get(ID) instanceof Number itemId)) throw missingField(ID); if (!(json.get("add_prop") instanceof JSONObject propData)) throw missingField("add_prop"); if (!propData.has(VALUE)) throw missingField(VALUE); + LOG.log(WARNING,"Missing permission check in StockModule.postProperty()!"); var value = propData.get(VALUE); if (value == null) throw missingField(VALUE); diff --git a/web/src/main/resources/web/css/default.css b/web/src/main/resources/web/css/default.css index 5a402981..e4ca5d87 100644 --- a/web/src/main/resources/web/css/default.css +++ b/web/src/main/resources/web/css/default.css @@ -476,6 +476,16 @@ table{ bottom: 0; } +.properties{ + position: relative; +} + +.properties .clone{ + position: absolute; + right: 10px; + top: 40px; +} + .version > a{ padding: 5px; } From aaf33ffa8f13b73326fabf29919c36620dada6d0 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Tue, 10 Feb 2026 23:48:10 +0100 Subject: [PATCH 2/2] implemented GUI update on cloning items Signed-off-by: Stephan Richter --- .../umbrella/messagebus/MessageApi.java | 4 +- .../umbrella/messagebus/MessageBus.java | 2 +- .../umbrella/messagebus/events/ItemEvent.java | 49 +++++++++++++++++++ frontend/src/routes/stock/ItemList.svelte | 36 +++++++++++++- frontend/src/routes/stock/ItemProps.svelte | 2 +- stock/build.gradle.kts | 1 + .../umbrella/stock/StockModule.java | 16 ++++-- 7 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 bus/src/main/java/de/srsoftware/umbrella/messagebus/events/ItemEvent.java diff --git a/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageApi.java b/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageApi.java index 36b12119..9081fc56 100644 --- a/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageApi.java +++ b/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageApi.java @@ -49,11 +49,11 @@ public class MessageApi extends BaseHandler{ if (++counter > 300) counter = sendBeacon(addr,stream); } else { var event = eventQueue.removeFirst(); - //if (event.isIntendedFor(user.get())) { + if (event.isIntendedFor(user.get())) { LOG.log(DEBUG, "sending event to {0}", addr); sendEvent(stream, event); counter = 0; - //} + } } } LOG.log(INFO,"{0} disconnected from event stream.",addr); diff --git a/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageBus.java b/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageBus.java index 6b6964a7..2fd57df6 100644 --- a/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageBus.java +++ b/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageBus.java @@ -12,7 +12,7 @@ public class MessageBus { private MessageBus(){} - public void dispatch(Event event){ + public void dispatch(Event event){ new Thread(() -> { // TODO: use thread pool try { Thread.sleep(100); diff --git a/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/ItemEvent.java b/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/ItemEvent.java new file mode 100644 index 00000000..2bfbde2b --- /dev/null +++ b/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/ItemEvent.java @@ -0,0 +1,49 @@ +package de.srsoftware.umbrella.messagebus.events; + +import de.srsoftware.umbrella.core.ModuleRegistry; +import de.srsoftware.umbrella.core.api.Owner; +import de.srsoftware.umbrella.core.constants.Field; +import de.srsoftware.umbrella.core.model.*; + +import java.util.Collection; +import java.util.List; + +import static de.srsoftware.umbrella.core.constants.Field.*; +import static de.srsoftware.umbrella.core.model.Translatable.t; + +public class ItemEvent extends Event{ + public ItemEvent(UmbrellaUser initiator, String module, Item item, EventType type) { + super(initiator, module, item, type); + } + + @Override + public Collection audience() { + Owner owner = payload().location().resolve().owner().resolve(); + if (owner instanceof UmbrellaUser user) return List.of(user); + if (owner instanceof Company company) return ModuleRegistry.companyService().getMembers(company.id()); + return List.of(); + } + + @Override + public Translatable describe() { + return switch (eventType()){ + case CREATE -> describeCreate(); + case null, default -> null; + }; + } + + private Translatable describeCreate() { + var loc = payload().location().resolve().name(); + return t("{user} added \"{item}\" to \"{location}\"", USER,initiator().name(), ITEM, payload().name(), LOCATION, loc); + } + + + @Override + public Translatable subject() { + var loc = payload().location().resolve().name(); + return switch (eventType()){ + case CREATE -> t("A new item has been added to \"{location}\":",LOCATION,loc); + case null, default -> null; + }; + } +} diff --git a/frontend/src/routes/stock/ItemList.svelte b/frontend/src/routes/stock/ItemList.svelte index d11e729a..b7bd4e0b 100644 --- a/frontend/src/routes/stock/ItemList.svelte +++ b/frontend/src/routes/stock/ItemList.svelte @@ -1,11 +1,39 @@ diff --git a/frontend/src/routes/stock/ItemProps.svelte b/frontend/src/routes/stock/ItemProps.svelte index bc2077ba..29a2c968 100644 --- a/frontend/src/routes/stock/ItemProps.svelte +++ b/frontend/src/routes/stock/ItemProps.svelte @@ -69,7 +69,7 @@ {#if item} update('name',v)} /> - +
{@html item.description.rendered}
diff --git a/stock/build.gradle.kts b/stock/build.gradle.kts index fb8598b9..d40dbd45 100644 --- a/stock/build.gradle.kts +++ b/stock/build.gradle.kts @@ -1,6 +1,7 @@ description = "Umbrella : Stock" dependencies{ + implementation(project(":bus")) implementation(project(":core")) implementation("de.srsoftware:configuration.json:1.0.3") } \ No newline at end of file diff --git a/stock/src/main/java/de/srsoftware/umbrella/stock/StockModule.java b/stock/src/main/java/de/srsoftware/umbrella/stock/StockModule.java index 2bf47ebe..4990d640 100644 --- a/stock/src/main/java/de/srsoftware/umbrella/stock/StockModule.java +++ b/stock/src/main/java/de/srsoftware/umbrella/stock/StockModule.java @@ -14,6 +14,7 @@ import static de.srsoftware.umbrella.core.constants.Module.USER; import static de.srsoftware.umbrella.core.constants.Path.*; import static de.srsoftware.umbrella.core.constants.Path.PROPERTY; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; +import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus; import static de.srsoftware.umbrella.stock.Constants.*; import static java.lang.System.Logger.Level.WARNING; import static java.util.Comparator.comparing; @@ -26,13 +27,18 @@ import de.srsoftware.umbrella.core.*; import de.srsoftware.umbrella.core.api.Owner; import de.srsoftware.umbrella.core.api.StockService; import de.srsoftware.umbrella.core.constants.Field; +import de.srsoftware.umbrella.core.constants.Module; import de.srsoftware.umbrella.core.constants.Path; import de.srsoftware.umbrella.core.constants.Text; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.*; import de.srsoftware.umbrella.core.model.Location; + import java.io.IOException; import java.util.*; + +import de.srsoftware.umbrella.messagebus.events.Event; +import de.srsoftware.umbrella.messagebus.events.ItemEvent; import org.json.JSONObject; public class StockModule extends BaseHandler implements StockService { @@ -350,7 +356,9 @@ public class StockModule extends BaseHandler implements StockService { if (!assigned(owner,user)) throw forbidden("You are not allowed to add items to \"{location}\"!", Text.LOCATION,location.name()); var newItem = new Item(0,owner,0,location,item.code(),item.name(),item.description()); for (var property : item.properties()) newItem.properties().add(property); - return sendContent(ex,stockDb.save(newItem)); + newItem = stockDb.save(newItem); + messageBus().dispatch(new ItemEvent(user, Module.STOCK, newItem, Event.EventType.CREATE)); + return sendContent(ex,newItem); } private boolean postItem(UmbrellaUser user, HttpExchange ex) throws IOException { @@ -362,8 +370,10 @@ public class StockModule extends BaseHandler implements StockService { var location = stockDb.loadLocation(locationData.getLong(ID)); var owner = location.owner().resolve(); if (!assigned(owner,user)) throw forbidden("You are not allowed to add items to {location}!", Field.LOCATION,location); - var newItem = new Item(0,owner,0,location,code,name,description); - return sendContent(ex,stockDb.save(newItem)); + var newItem = stockDb.save(new Item(0,owner,0,location,code,name,description)); + messageBus().dispatch(new ItemEvent(user, Module.STOCK, newItem, Event.EventType.CREATE)); + return sendContent(ex,newItem); + } private boolean postItemList(UmbrellaUser user, de.srsoftware.tools.Path path, HttpExchange ex) throws IOException {