From fd536abe11bc70d990ee31fc28fd44afa724d10c Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Sun, 30 Nov 2025 23:24:02 +0100 Subject: [PATCH] working on migration of items to stock db Signed-off-by: Stephan Richter --- build.gradle.kts | 2 +- .../umbrella/core/api/StockService.java | 2 +- .../srsoftware/umbrella/core/model/Item.java | 38 +++++++---- .../umbrella/core/model/Property.java | 22 +++++++ .../de/srsoftware/umbrella/stock/ItemDb.java | 63 ++++++++++++++++++ .../srsoftware/umbrella/stock/SqliteDb.java | 66 +++++++++++++++---- .../umbrella/stock/StockModule.java | 32 ++------- 7 files changed, 168 insertions(+), 57 deletions(-) create mode 100644 stock/src/main/java/de/srsoftware/umbrella/stock/ItemDb.java diff --git a/build.gradle.kts b/build.gradle.kts index a1a5076..ea11025 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,7 +41,7 @@ subprojects { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") implementation("de.srsoftware:configuration.api:1.0.2") - implementation("de.srsoftware:tools.jdbc:2.0.3") + implementation("de.srsoftware:tools.jdbc:2.0.4") implementation("de.srsoftware:tools.http:6.0.5") implementation("de.srsoftware:tools.mime:1.1.3") implementation("de.srsoftware:tools.logging:1.3.2") diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/StockService.java b/core/src/main/java/de/srsoftware/umbrella/core/api/StockService.java index bd3c448..c43d954 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/api/StockService.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/StockService.java @@ -8,7 +8,7 @@ import java.util.Collection; public interface StockService { /** * Das war mal die methode um zu checken, ob einer Firma noch Items zugewiesen sind. - * Diese Methode muss neu definiert werden, sobald der Stock-Service neu implementiert ist. + * TODO: Diese Methode muss neu definiert werden, sobald der Stock-Service neu implementiert ist. * @param company_id * @return */ 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 dccc4d5..7fbdc3e 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 @@ -13,19 +13,20 @@ import org.json.JSONObject; public class Item implements Mappable { private long id, ownerNumber; // id is the database key, number the owner-relative id private Owner owner; - private String code, name; + private String code, description, name; private Location location; private Collection properties; private Set dirtyFields = new HashSet<>(); - public Item(long id, Owner owner, long ownerNumber, Location location, String code, String name) { - this.id = id; - this.owner = owner; + public Item(long id, Owner owner, long ownerNumber, Location location, String code, String name, String description) { + this.id = id; + this.owner = owner; this.ownerNumber = ownerNumber; - this.location = location; - this.code = code; - this.name = name; - this.properties = new HashSet<>(); + this.location = location; + this.code = code; + this.name = name; + this.description = description; + this.properties = new HashSet<>(); } public Item clear() { @@ -37,6 +38,10 @@ public class Item implements Mappable { return code; } + public String description(){ + return description; + } + public boolean isDirty(){ return !dirtyFields.isEmpty(); } @@ -65,13 +70,14 @@ public class Item implements Mappable { } public static Item of(ResultSet rs) throws SQLException { - var id = rs.getLong(ID); - var owner = OwnerRef.of(rs); + var id = rs.getLong(ID); + var owner = OwnerRef.of(rs); var ownerNumber = rs.getLong(OWNER_NUMBER); - var location = Location.of(rs); - var code = rs.getString(CODE); - var name = rs.getString(NAME); - return new Item(id, owner, ownerNumber, location, code, name); + var location = Location.of(rs); + var code = rs.getString(CODE); + var name = rs.getString(NAME); + var description = rs.getString(DESCRIPTION); + return new Item(id, owner, ownerNumber, location, code, name, description); } public Owner owner(){ @@ -92,6 +98,9 @@ public class Item implements Mappable { case NAME: name = json.getString(field); break; + case DESCRIPTION: + description = json.getString(field); + break; default: known = false; } @@ -112,6 +121,7 @@ public class Item implements Mappable { map.put(LOCATION,location.toMap()); map.put(CODE,code); map.put(NAME,name); + map.put(DESCRIPTION,description); map.put(OWNER_NUMBER,ownerNumber); if (properties != null) map.put(PROPERTIES,properties.stream().map(Property::toMap).toList()); return map; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Property.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Property.java index 87f83bd..f0a6ad1 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Property.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Property.java @@ -2,6 +2,7 @@ package de.srsoftware.umbrella.core.model; import static de.srsoftware.umbrella.core.Constants.*; +import static java.text.MessageFormat.format; import de.srsoftware.tools.Mappable; import java.sql.ResultSet; @@ -22,6 +23,14 @@ public class Property implements Mappable { this.unit = unit; } + public long id(){ + return id; + } + + public String name(){ + return name; + } + public static Property of(ResultSet rs) throws SQLException { var id = rs.getLong(ID); var name = rs.getString(NAME); @@ -45,6 +54,19 @@ public class Property implements Mappable { return map; } + @Override + public String toString() { + return format("{0} ({1} = {2}{3})",getClass().getSimpleName(),name,value,unit==null?"":" "+unit); + } + + public String unit(){ + return unit; + } + + public Object value(){ + return value; + } + public Property value(Object newVal){ value = newVal; return this; diff --git a/stock/src/main/java/de/srsoftware/umbrella/stock/ItemDb.java b/stock/src/main/java/de/srsoftware/umbrella/stock/ItemDb.java new file mode 100644 index 0000000..3de337d --- /dev/null +++ b/stock/src/main/java/de/srsoftware/umbrella/stock/ItemDb.java @@ -0,0 +1,63 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.stock; + +import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; +import static de.srsoftware.tools.jdbc.Query.select; +import static de.srsoftware.umbrella.core.ConnectionProvider.connect; +import static de.srsoftware.umbrella.core.Constants.*; +import static de.srsoftware.umbrella.core.Field.COMPANY_ID; +import static de.srsoftware.umbrella.core.Field.UNIT_PRICE; +import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException; +import static de.srsoftware.umbrella.stock.Constants.TABLE_ITEMS; + +import de.srsoftware.umbrella.core.ModuleRegistry; +import de.srsoftware.umbrella.core.model.DbLocation; +import de.srsoftware.umbrella.core.model.Item; +import de.srsoftware.umbrella.core.model.Location; +import de.srsoftware.umbrella.core.model.Property; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; + +public class ItemDb { + private final System.Logger LOG = System.getLogger(getClass().getSimpleName()); + private final Connection db; + + public ItemDb(String dbFilePath){ + db = connect(dbFilePath); + } + + public void migrateTo(StockDb stockDb) { + try { + var companyLocations = new HashMap(); + var rs = select(ALL).from(TABLE_ITEMS).exec(db); + while (rs.next()){ + var id = rs.getLong(ID); + var companyId = rs.getLong(COMPANY_ID); + var code = rs.getString(CODE); + var name = rs.getString(NAME); + var description = rs.getString(DESCRIPTION); + var unit = rs.getString(UNIT); + var unitPrice = rs.getLong(UNIT_PRICE); + var tax = rs.getLong(TAX); + var company = ModuleRegistry.companyService().get(companyId); + var location = companyLocations.get(companyId); + if (location == null) { // TODO: ids currently do not get assigned + location = stockDb.save(new DbLocation(0,company,null,"virtual items",null)); + companyLocations.put(companyId,location); + } + var stockItem = new Item(0,company,0,location,code,name,description); + var props = stockItem.properties(); // TODO: saving props currently does not work + props.add(new Property(0,UNIT_PRICE,unitPrice/100d,company.currency())); + props.add(new Property(0,UNIT,unit,null)); + props.add(new Property(0,TAX,tax,"%")); + props.add(new Property(0,"legacy_id",id,null)); + stockDb.save(stockItem); + } + rs.close(); + } catch (SQLException e) { + throw databaseException("Failed to migrate items from itemDB to stockDB!"); + } + LOG.log(System.Logger.Level.WARNING,"migrateTo({0}) not implemented", stockDb); + } +} 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 90fd96c..0a1e9ce 100644 --- a/stock/src/main/java/de/srsoftware/umbrella/stock/SqliteDb.java +++ b/stock/src/main/java/de/srsoftware/umbrella/stock/SqliteDb.java @@ -11,8 +11,7 @@ import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.ModuleRegistry.noteService; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException; import static de.srsoftware.umbrella.stock.Constants.*; -import static java.lang.System.Logger.Level.ERROR; -import static java.lang.System.Logger.Level.WARNING; +import static java.lang.System.Logger.Level.*; import static java.text.MessageFormat.format; import de.srsoftware.tools.jdbc.Query; @@ -51,21 +50,21 @@ public class SqliteDb extends BaseDb implements StockDb { Long propertyId = null; if (rs.next()) propertyId = rs.getLong(1); rs.close(); - if (propertyId == null || propertyId == 0) throw databaseException("Failed to create new property {0} to DB",name); + if (propertyId == null || propertyId == 0) throw databaseException("Failed to create new property {0} in DB",name); insertInto(TABLE_ITEM_PROPERTIES,ITEM_ID,PROPERTY_ID,VALUE).values(itemId,propertyId,value).execute(db); return new Property(propertyId,name,value,unit); } catch (SQLException e) { - throw databaseException("Failed to create new property {0} to DB",name); + throw databaseException("Failed to create new property {0} in DB",name); } } - @Override - public Location delete(DbLocation location) { + private void createDescriptionColumn(){ try { - Query.delete().from(TABLE_LOCATIONS).where(ID,equal(location.id())).execute(db); - return location; - } catch (SQLException e){ - throw databaseException("Failed to delete \"{0}\"",location.name()); + var sql = "ALTER TABLE {0} ADD COLUMN {1} TEXT"; + sql = format(sql,TABLE_ITEMS,DESCRIPTION); + db.prepareStatement(sql).execute(); + } catch (SQLException e) { + throw databaseException("failed to create {0} column in {1} table!",DESCRIPTION,TABLE_ITEMS); } } @@ -165,8 +164,20 @@ public class SqliteDb extends BaseDb implements StockDb { dropTokenTable(); case 2: transformTables(); + case 3: + createDescriptionColumn(); + } + return setCurrentVersion(4); + } + + @Override + public Location delete(DbLocation location) { + try { + Query.delete().from(TABLE_LOCATIONS).where(ID,equal(location.id())).execute(db); + return location; + } catch (SQLException e){ + throw databaseException("Failed to delete \"{0}\"",location.name()); } - return setCurrentVersion(3); } private void dropTokenTable() { @@ -400,7 +411,6 @@ public class SqliteDb extends BaseDb implements StockDb { .execute(db).getGeneratedKeys(); if (rs.next()) item.id(rs.getLong(1)); rs.close(); - return item; } catch (SQLException e) { throw databaseException("Failed to save new item to database!"); } @@ -419,15 +429,43 @@ public class SqliteDb extends BaseDb implements StockDb { } else { pq.apply(item.code(),item.name(),item.location().id()); } - return item.clear(); + item.clear(); } catch (SQLException e){ throw databaseException("Failed to update item {0}",item.name()); } } + saveProperties(item); return item; } + private void saveProperties(Item item){ + var saved = new ArrayList(); + for (var property : item.properties()) { + saved.add(saveProperty(item, property)); + } + item.properties().clear(); + item.properties().addAll(saved); + } + + private Property saveProperty(Item item, Property property) { + Long propId = property.id(); + if (is0(propId)) { + LOG.log(DEBUG,"Saving new property {0}",property); + try { + var rs = select(ID).from(TABLE_PROPERTIES).where(NAME,equal(property.name())).where(UNIT,equal(property.unit())).exec(db); + if (rs.next()) { + propId = rs.getLong(1); + } + rs.close(); + } catch (SQLException e) { + throw databaseException("Failed to load property \"{}\"!",property.name()); + } + } + if (is0(propId)) return addNewProperty(item.id(), property.name(), property.value(), property.unit()); + return setProperty(item.id(),propId,property.value()); + } + @Override public Property setProperty(long itemId, long existingPropId, Object value) { try { @@ -435,7 +473,7 @@ public class SqliteDb extends BaseDb implements StockDb { var rs = select(ALL).from(TABLE_PROPERTIES).where(ID,equal(existingPropId)).exec(db); if (rs.next()) prop = Property.of(rs); rs.close(); - if (prop == null) throw databaseException("Failed to add new property to item {0}",itemId); + if (prop == null) throw databaseException("Failed to load property {0} for item {1}",existingPropId,itemId); if ("".equals(value)){ Query.delete().from(TABLE_ITEM_PROPERTIES).where(ITEM_ID,equal(itemId)).where(PROPERTY_ID,equal(existingPropId)).execute(db); } else { 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 095c2bd..aebf492 100644 --- a/stock/src/main/java/de/srsoftware/umbrella/stock/StockModule.java +++ b/stock/src/main/java/de/srsoftware/umbrella/stock/StockModule.java @@ -12,12 +12,10 @@ import static de.srsoftware.umbrella.core.Paths.LIST; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.stock.Constants.*; import static java.lang.System.Logger.Level.WARNING; -import static java.text.MessageFormat.format; 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; @@ -40,6 +38,8 @@ public class StockModule extends BaseHandler implements StockService { super(); var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE)); stockDb = new SqliteDb(connect(dbFile)); + Optional itemDbConfig = config.get("umbrella.modules.items.database"); + itemDbConfig.map(ItemDb::new).ifPresent(itemDb -> itemDb.migrateTo(stockDb)); ModuleRegistry.add(this); } @@ -309,13 +309,14 @@ public class StockModule extends BaseHandler implements StockService { private boolean postItem(UmbrellaUser user, HttpExchange ex) throws IOException { var json = json(ex); if (!json.has(NAME) || !(json.get(NAME) instanceof String name)) throw missingFieldException(NAME); + var description = json.has(DESCRIPTION) && json.get(DESCRIPTION) instanceof String d ? d : null; if (!json.has(CODE) || !(json.get(CODE) instanceof String code)) throw missingFieldException(CODE); if (!json.has(LOCATION) || !(json.get(LOCATION) instanceof JSONObject locationData)) throw missingFieldException(LOCATION); 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 {0}!",location); var number = stockDb.nextItemNumberFor(owner); - var newItem = new Item(0,owner,number,location,code,name); + var newItem = new Item(0,owner,number,location,code,name,description); return sendContent(ex,stockDb.save(newItem)); } @@ -380,32 +381,9 @@ public class StockModule extends BaseHandler implements StockService { return sendContent(ex,property); } - 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 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) { + // TODO return List.of(); } }