diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Item.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Item.java index 0522e93..3ffc7b6 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Item.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Item.java @@ -4,11 +4,14 @@ package de.srsoftware.umbrella.core.model; import static de.srsoftware.umbrella.core.Constants.*; import de.srsoftware.tools.Mappable; +import org.json.JSONObject; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.HashSet; import java.util.Map; +import java.util.Set; public class Item implements Mappable { private long id; @@ -16,6 +19,7 @@ public class Item implements Mappable { private String code, name; private Location location; private Collection properties; + private Set dirtyFields = new HashSet<>(); private Item(Mappable owner, long id, Location location, String code, String name) { this.owner = owner; @@ -26,6 +30,10 @@ public class Item implements Mappable { this.properties = new HashSet<>(); } + public boolean isDirty(){ + return !dirtyFields.isEmpty(); + } + public long id(){ return id; } @@ -62,4 +70,19 @@ public class Item implements Mappable { public String toString() { return name; } + + public Item patch(JSONObject json) { + for (var field : json.keySet()){ + var known = true; + switch (field) { + case NAME: + name = json.getString(field); + break; + default: + known = false; + } + if (known) dirtyFields.add(field); + } + return this; + } } diff --git a/frontend/src/routes/stock/ItemProps.svelte b/frontend/src/routes/stock/ItemProps.svelte index d95449f..070ba0f 100644 --- a/frontend/src/routes/stock/ItemProps.svelte +++ b/frontend/src/routes/stock/ItemProps.svelte @@ -1,4 +1,5 @@ {#if item} -

{item.name}

+ diff --git a/stock/src/main/java/de/srsoftware/umbrella/stock/SqliteDb.java b/stock/src/main/java/de/srsoftware/umbrella/stock/SqliteDb.java index 11466b5..859facb 100644 --- a/stock/src/main/java/de/srsoftware/umbrella/stock/SqliteDb.java +++ b/stock/src/main/java/de/srsoftware/umbrella/stock/SqliteDb.java @@ -222,6 +222,21 @@ public class SqliteDb extends BaseDb implements StockDb { } } + @Override + public Item loadItem(Mappable owner, long itemId) { + var ownerId = owner instanceof Company comp ? -comp.id() : (owner instanceof UmbrellaUser u ? u.id() : 0); + if (ownerId == 0) throw databaseException("Failed to load item: unknown owner type ({0})",owner.getClass().getSimpleName()); + try { + var rs = select(ALL).from(TABLE_ITEMS).where(OWNER,equal(ownerId)).where(ID,equal(itemId)).exec(db); + Item result = null; + if (rs.next()) result = Item.of(rs,owner,null); + rs.close(); + if (result != null) return result; + } catch (SQLException ignored) { + } + throw databaseException("Failed to load item"); + } + private Location loadLocation(long locationId) { try { var rs = select(ALL).from(TABLE_LOCATIONS).where(ID,equal(locationId)).exec(db); @@ -276,6 +291,12 @@ public class SqliteDb extends BaseDb implements StockDb { db.prepareStatement(format("ALTER TABLE {0} RENAME TO {1}","locations_temp",TABLE_LOCATIONS)).execute(); } + @Override + public Item save(Item item) { + LOG.log(ERROR,"StockDb.save(…) not implemented!"); + return item; + } + private void transformItems(Map oldLocationIdsToNew) throws SQLException { var rs = select(ALL).from(TABLE_ITEMS).exec(db); var insert = insertInto("items_temp",OWNER, ID, CODE, NAME, LOCATION_ID); diff --git a/stock/src/main/java/de/srsoftware/umbrella/stock/StockDb.java b/stock/src/main/java/de/srsoftware/umbrella/stock/StockDb.java index cc8d929..ed5aba9 100644 --- a/stock/src/main/java/de/srsoftware/umbrella/stock/StockDb.java +++ b/stock/src/main/java/de/srsoftware/umbrella/stock/StockDb.java @@ -1,6 +1,7 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.stock; +import de.srsoftware.tools.Mappable; import de.srsoftware.umbrella.core.model.*; import java.util.Collection; @@ -12,4 +13,7 @@ public interface StockDb { Collection listItemsAt(long locationId); Collection listProperties(); Collection listUserLocations(UmbrellaUser userId); + Item loadItem(Mappable owner, long itemId); + + Item save(Item item); } 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 5c1d87d..0f1f6d6 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 java.util.Comparator.comparing; import com.sun.net.httpserver.HttpExchange; import de.srsoftware.configuration.Configuration; +import de.srsoftware.tools.Mappable; import de.srsoftware.tools.Path; import de.srsoftware.tools.SessionToken; import de.srsoftware.umbrella.core.BaseHandler; @@ -62,6 +63,19 @@ public class StockModule extends BaseHandler implements StockService { } } + @Override + public boolean doPatch(Path path, HttpExchange ex) throws IOException { + addCors(ex); + try { + Optional token = SessionToken.from(ex).map(Token::of); + var user = userService().loadUser(token); + if (user.isEmpty()) return unauthorized(ex); + return patchItem(user.get(),ex); + } catch (UmbrellaException e){ + return send(ex,e); + } + } + @Override public boolean doPost(Path path, HttpExchange ex) throws IOException { addCors(ex); @@ -130,6 +144,31 @@ public class StockModule extends BaseHandler implements StockService { return sendContent(ex, result); } + private boolean patchItem(UmbrellaUser user, HttpExchange ex) throws IOException { + var json = json(ex); + if (!(json.get(ID) instanceof Number id)) throw missingFieldException(ID); + if (!(json.get(OWNER) instanceof JSONObject ownerRef)) throw missingFieldException(OWNER); + json.remove(ID); + json.remove(OWNER); + var owner = toOwner(ownerRef); + + var item = stockDb.loadItem(owner,id.longValue()); + item.patch(json); + return sendContent(ex,stockDb.save(item)); + } + + private Mappable toOwner(JSONObject owner) { + var keys = owner.keySet(); + if (keys.size() != 1) throw unprocessable("{0} expected to have only one child!",OWNER); + String key = new ArrayList<>(keys).getFirst(); + return switch (key) { + case COMPANY -> companyService().get(owner.getLong(key)); + case USER -> userService().loadUser(owner.getLong(key)); + default -> throw invalidFieldException(format("Single child of {0}", OWNER), format("either {0} or {1}", COMPANY, USER)); + }; + + } + private boolean postProperty(UmbrellaUser user, HttpExchange ex) throws IOException { var json = json(ex); if (!(json.get(FIELD_ITEM) instanceof JSONObject itemData)) throw missingFieldException(FIELD_ITEM); @@ -139,14 +178,7 @@ public class StockModule extends BaseHandler implements StockService { if (!propData.has(VALUE)) throw missingFieldException(VALUE); var value = propData.get(VALUE); if (value == null) throw missingFieldException(VALUE); - var keys = owner.keySet(); - if (keys.size() != 1) throw unprocessable("{0} expected to have only one child!",OWNER); - String key = new ArrayList<>(keys).getFirst(); - long ownerId = switch (key) { - case COMPANY -> -owner.getLong(key); - case USER -> owner.getLong(key); - default -> throw invalidFieldException(format("Single child of {0}", OWNER), format("either {0} or {1}", COMPANY, USER)); - }; + var ownerId = toOwnerId(owner); Property property = null; if (propData.get("existing_prop_id") instanceof Number existingPropId && existingPropId.longValue() != 0L){ @@ -160,6 +192,18 @@ public class StockModule extends BaseHandler implements StockService { return sendContent(ex,property); } + private long toOwnerId(JSONObject owner) { + var keys = owner.keySet(); + if (keys.size() != 1) throw unprocessable("{0} expected to have only one child!",OWNER); + String key = new ArrayList<>(keys).getFirst(); + return switch (key) { + case COMPANY -> -owner.getLong(key); + case USER -> owner.getLong(key); + default -> throw invalidFieldException(format("Single child of {0}", OWNER), format("either {0} or {1}", COMPANY, USER)); + }; + + } + @Override public Collection redefineMe(long company_id) { return List.of();