Browse Source

implemented stock display from location tree to property list

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
module/stock.v2
Stephan Richter 3 weeks ago
parent
commit
a52df2b434
  1. 2
      core/src/main/java/de/srsoftware/umbrella/core/Constants.java
  2. 17
      core/src/main/java/de/srsoftware/umbrella/core/model/Item.java
  3. 26
      core/src/main/java/de/srsoftware/umbrella/core/model/Property.java
  4. 31
      frontend/src/routes/stock/Index.svelte
  5. 30
      frontend/src/routes/stock/ItemList.svelte
  6. 27
      frontend/src/routes/stock/ItemProps.svelte
  7. 2
      frontend/src/routes/stock/Locations.svelte
  8. 164
      stock/src/main/java/de/srsoftware/umbrella/stock/SqliteDb.java
  9. 7
      stock/src/main/java/de/srsoftware/umbrella/stock/StockDb.java
  10. 1
      translations/src/main/resources/de.json

2
core/src/main/java/de/srsoftware/umbrella/core/Constants.java

@ -150,8 +150,6 @@ public class Constants {
public static final String PROJECT_ID = "project_id"; public static final String PROJECT_ID = "project_id";
public static final String PROPERTIES = "properties"; public static final String PROPERTIES = "properties";
public static final String QUANTITY = "quantity";
public static final String RECEIVERS = "receivers"; public static final String RECEIVERS = "receivers";
public static final String REDIRECT = "redirect"; public static final String REDIRECT = "redirect";
public static final String RENDERED = "rendered"; public static final String RENDERED = "rendered";

17
core/src/main/java/de/srsoftware/umbrella/core/model/Item.java

@ -1,16 +1,15 @@
/* © SRSoftware 2025 */ /* © SRSoftware 2025 */
package de.srsoftware.umbrella.core.model; package de.srsoftware.umbrella.core.model;
import de.srsoftware.tools.Mappable; import static de.srsoftware.umbrella.core.Constants.*;
import de.srsoftware.tools.Mappable;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import static de.srsoftware.umbrella.core.Constants.*;
public class Item implements Mappable { public class Item implements Mappable {
private long id; private long id;
private Mappable owner; private Mappable owner;
@ -27,6 +26,10 @@ public class Item implements Mappable {
this.properties = new HashSet<>(); this.properties = new HashSet<>();
} }
public long id(){
return id;
}
public static Item of(ResultSet rs, Mappable owner, Location location) throws SQLException { public static Item of(ResultSet rs, Mappable owner, Location location) throws SQLException {
var id = rs.getLong(ID); var id = rs.getLong(ID);
var code = rs.getString(CODE); var code = rs.getString(CODE);
@ -34,6 +37,14 @@ public class Item implements Mappable {
return new Item(owner, id, location, code, name); return new Item(owner, id, location, code, name);
} }
public Mappable owner(){
return owner;
}
public Collection<Property> properties() {
return properties;
}
@Override @Override
public Map<String, Object> toMap() { public Map<String, Object> toMap() {
return Map.of( return Map.of(

26
core/src/main/java/de/srsoftware/umbrella/core/model/Property.java

@ -1,18 +1,33 @@
/* © SRSoftware 2025 */ /* © SRSoftware 2025 */
package de.srsoftware.umbrella.core.model; package de.srsoftware.umbrella.core.model;
import de.srsoftware.tools.Mappable; import static de.srsoftware.umbrella.core.Constants.*;
import de.srsoftware.tools.Mappable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map; import java.util.Map;
import static de.srsoftware.umbrella.core.Constants.*;
public class Property implements Mappable { public class Property implements Mappable {
long id; long id;
String name; String name;
Object value; Object value;
String unit; String unit;
String quantity;
public Property(long id, String name, Object value, String unit) {
this.id = id;
this.name = name;
this.value = value;
this.unit = unit;
}
public static Property of(ResultSet rs) throws SQLException {
var id = rs.getLong(ID);
var name = rs.getString(NAME);
var value = rs.getObject(VALUE);
var unit = rs.getString(UNIT);
return new Property(id, name, value, unit);
}
@Override @Override
public Map<String, Object> toMap() { public Map<String, Object> toMap() {
@ -20,8 +35,7 @@ public class Property implements Mappable {
ID, id, ID, id,
NAME, name, NAME, name,
VALUE, value, VALUE, value,
UNIT, unit, UNIT, unit
QUANTITY, quantity
); );
} }
} }

31
frontend/src/routes/stock/Index.svelte

@ -9,17 +9,19 @@
import ItemProps from './ItemProps.svelte'; import ItemProps from './ItemProps.svelte';
let items = $derived.by(loadItems);
let item = $state(null);
let location = $state(null);
let properties = $state(null);
let top_level = $state(null); let top_level = $state(null);
let selected = $state(null);
let items = $derived(loadItems(selected));
async function loadItems(loc){ async function loadItems(){
if (!loc) return null; if (!location) return null;
const url = api(`stock/items_at/${loc.id}`) const url = api(`stock/items_at/${location.id}`)
const res = await fetch(url,{credentials:'include'}); const res = await fetch(url,{credentials:'include'});
if (res.ok){ if (res.ok){
yikes(); yikes();
return loc.name; return res.json();
} else { } else {
error(res); error(res);
return null; return null;
@ -47,22 +49,23 @@
{#each top_level as realm,idx} {#each top_level as realm,idx}
<h3>{realm.name}</h3> <h3>{realm.name}</h3>
{#if realm.locations} {#if realm.locations}
<Locations locations={realm.locations} bind:selected /> <Locations locations={realm.locations} bind:selected={location} />
{/if} {/if}
{/each} {/each}
{/if} {/if}
</td> </td>
<td class="items"> <td class="items">
{#if selected} {#await items}
<h3>{selected.name}</h3> <span>loading…</span>
{:then data}
{#if location}
<h3>{location.name}</h3>
{/if} {/if}
{#if items} <ItemList items={data.sort((a,b) => a.code.localeCompare(b.code))} bind:selected={item} />
<pre>{JSON.stringify(items)}</pre> {/await}
{/if}
<ItemList />
</td> </td>
<td class="properties"> <td class="properties">
<ItemProps /> <ItemProps {item} />
</td> </td>
</tr> </tr>
</tbody> </tbody>

30
frontend/src/routes/stock/ItemList.svelte

@ -1,13 +1,23 @@
<script> <script>
import { t } from '../../translations.svelte'; import { t } from '../../translations.svelte';
let { items, selected = $bindable(null) } = $props();
</script> </script>
<ul> <table>
<li> <thead>
<a> <tr>
<span class="symbol"></span> {t('add_object',{object:'item'})} <th>{t('id')}</th>
</a> <th>{t('code')}</th>
</li> <th>{t('name')}</th>
<li>Item 1</li> </tr>
<li>Item 2</li> </thead>
<li>Item 3</li> <tbody>
</ul> {#each items as item}
<tr onclick={ev => selected = item}>
<td>{item.id}</td>
<td>{item.code}</td>
<td>{item.name}</td>
</tr>
{/each}
</tbody>
</table>

27
frontend/src/routes/stock/ItemProps.svelte

@ -1,5 +1,22 @@
<ul> <script>
<li>Prop 1</li> let { item } = $props();
<li>Prop 2</li> </script>
<li>Prop 3</li>
</ul> {#if item}
<h3>{item.name}</h3>
<table>
<tbody>
{#each item.properties as prop}
<tr>
<td>
{prop.name}
</td>
<td>
{prop.value}
{prop.unit}
</td>
</tr>
{/each}
</tbody>
</table>
{/if}

2
frontend/src/routes/stock/Locations.svelte

@ -26,7 +26,7 @@
<ul> <ul>
<li> <li>
<a> <a>
<span class="symbol"></span> {t('add_object',{object:'location'})} <span class="symbol"></span> {t('add_object',{object:t('location')})}
</a> </a>
</li> </li>
{#each locations as location} {#each locations as location}

164
stock/src/main/java/de/srsoftware/umbrella/stock/SqliteDb.java

@ -7,6 +7,8 @@ import static de.srsoftware.tools.jdbc.Condition.isNull;
import static de.srsoftware.tools.jdbc.Query.*; import static de.srsoftware.tools.jdbc.Query.*;
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.ModuleRegistry.companyService;
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
import static de.srsoftware.umbrella.stock.Constants.*; import static de.srsoftware.umbrella.stock.Constants.*;
import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.ERROR;
@ -15,12 +17,7 @@ import static java.text.MessageFormat.format;
import de.srsoftware.tools.Mappable; import de.srsoftware.tools.Mappable;
import de.srsoftware.umbrella.core.BaseDb; import de.srsoftware.umbrella.core.BaseDb;
import de.srsoftware.umbrella.core.ModuleRegistry; import de.srsoftware.umbrella.core.model.*;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Company;
import de.srsoftware.umbrella.core.model.Item;
import de.srsoftware.umbrella.core.model.Location;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@ -53,23 +50,6 @@ public class SqliteDb extends BaseDb implements StockDb {
super(connection); super(connection);
} }
@Override
protected int createTables() {
int currentVersion = createSettingsTable();
switch (currentVersion){
case 0:
createLocationsTable();
createItemsTable();
createPropertiesTable();
createItemPropsTable();
case 1:
dropTokenTable();
case 2:
transformTables();
}
return setCurrentVersion(3);
}
private void createIntermediateItemsTable() throws SQLException { // create intermediate table private void createIntermediateItemsTable() throws SQLException { // create intermediate table
var sql = "CREATE TABLE IF NOT EXISTS items_temp ({0} LONG NOT NULL, {1} LONG NOT NULL, {2} VARCHAR(255), {3} VARCHAR(255) NOT NULL, {4} LONG NOT NULL, PRIMARY KEY({0}, {1}))"; var sql = "CREATE TABLE IF NOT EXISTS items_temp ({0} LONG NOT NULL, {1} LONG NOT NULL, {2} VARCHAR(255), {3} VARCHAR(255) NOT NULL, {4} LONG NOT NULL, PRIMARY KEY({0}, {1}))";
sql = format(sql, OWNER, ID, CODE, NAME, LOCATION_ID); sql = format(sql, OWNER, ID, CODE, NAME, LOCATION_ID);
@ -128,6 +108,23 @@ public class SqliteDb extends BaseDb implements StockDb {
} }
} }
@Override
protected int createTables() {
int currentVersion = createSettingsTable();
switch (currentVersion){
case 0:
createLocationsTable();
createItemsTable();
createPropertiesTable();
createItemPropsTable();
case 1:
dropTokenTable();
case 2:
transformTables();
}
return setCurrentVersion(3);
}
private void dropTokenTable() { private void dropTokenTable() {
try { try {
db.prepareStatement("DROP TABLE IF EXISTS tokens").execute(); db.prepareStatement("DROP TABLE IF EXISTS tokens").execute();
@ -137,23 +134,57 @@ public class SqliteDb extends BaseDb implements StockDb {
} }
@Override @Override
public Collection<Item> listItems(long companyId) throws UmbrellaException { public Collection<Location> listChildLocations(long parentId) {
return List.of(); try {
var rs = select(ALL).from(TABLE_LOCATIONS).where(PARENT_LOCATION_ID,equal(parentId)).exec(db);
var list = new ArrayList<Location>();
while (rs.next()) list.add(Location.of(rs));
rs.close();
return list;
} catch (SQLException e){
throw databaseException("Failed to load child locations for {0}",parentId);
}
}
@Override
public Collection<Location> listCompanyLocations(Company company) {
try {
var rs = select(ALL).from(TABLE_LOCATIONS).where(OWNER,equal(-company.id())).where(PARENT_LOCATION_ID,isNull()).exec(db);
var list = new ArrayList<Location>();
while (rs.next()) list.add(Location.of(rs));
rs.close();
return list;
} catch (SQLException e){
throw databaseException("Failed to load locations for user {0}",company.name());
}
} }
@Override @Override
public Collection<Item> listItemsAt(long locationId) { public Collection<Item> listItemsAt(long locationId) {
try { try {
var location = loadLocation(locationId);
var rs = select(ALL).from(TABLE_ITEMS).where(LOCATION_ID,equal(locationId)).exec(db); var rs = select(ALL).from(TABLE_ITEMS).where(LOCATION_ID,equal(locationId)).exec(db);
var list = new ArrayList<Item>(); var list = new ArrayList<Item>();
var ownerMap = new HashMap<Long,Mappable>();
while (rs.next()) { while (rs.next()) {
var ownerId = rs.getLong(OWNER); var ownerId = rs.getLong(OWNER);
Mappable owner = ownerId < 0 ? ModuleRegistry.companyService().get(-ownerId) : ModuleRegistry.userService().loadUser(ownerId); var owner = ownerMap.get(ownerId);
var location = loadLocation(rs.getLong(LOCATION_ID)); if (owner == null) {
owner = ownerId < 0 ? companyService().get(-ownerId) : userService().loadUser(ownerId);
ownerMap.put(ownerId,owner);
}
list.add(Item.of(rs, owner, location)); list.add(Item.of(rs, owner, location));
} }
rs.close(); rs.close();
for (var item : list){
var owner = item.owner();
var ownerId = owner instanceof Company comp ? -comp.id() : (owner instanceof UmbrellaUser u ? u.id() : 0);
if (ownerId == 0) throw databaseException("Encountered unknown item owner of type {0}",owner.getClass().getSimpleName());
rs = select(ALL).from(TABLE_ITEM_PROPERTIES).leftJoin(PROPERTY_ID,TABLE_PROPERTIES,ID).where(OWNER,equal(ownerId)).where(ITEM_ID,equal(item.id())).exec(db);
while (rs.next()) item.properties().add(Property.of(rs));
rs.close();
}
return list; return list;
} catch (SQLException e){ } catch (SQLException e){
throw databaseException("Failed to load items at {0}",locationId); throw databaseException("Failed to load items at {0}",locationId);
@ -173,37 +204,6 @@ public class SqliteDb extends BaseDb implements StockDb {
} }
} }
@Override
public Collection<Location> listLocations(long companyId) {
return List.of();
}
@Override
public Collection<Location> listChildLocations(long parentId) {
try {
var rs = select(ALL).from(TABLE_LOCATIONS).where(PARENT_LOCATION_ID,equal(parentId)).exec(db);
var list = new ArrayList<Location>();
while (rs.next()) list.add(Location.of(rs));
rs.close();
return list;
} catch (SQLException e){
throw databaseException("Failed to load child locations for {0}",parentId);
}
}
@Override
public Collection<Location> listCompanyLocations(Company company) {
try {
var rs = select(ALL).from(TABLE_LOCATIONS).where(OWNER,equal(-company.id())).where(PARENT_LOCATION_ID,isNull()).exec(db);
var list = new ArrayList<Location>();
while (rs.next()) list.add(Location.of(rs));
rs.close();
return list;
} catch (SQLException e){
throw databaseException("Failed to load locations for user {0}",company.name());
}
}
@Override @Override
public Collection<Location> listUserLocations(UmbrellaUser user) { public Collection<Location> listUserLocations(UmbrellaUser user) {
try { try {
@ -217,29 +217,6 @@ public class SqliteDb extends BaseDb implements StockDb {
} }
} }
private void transformTables(){
try {
db.setAutoCommit(false);
createIntermediateLocationTable();
createIntermediateItemsTable();
createIntermediatePropsTable();
var oldLocationIdsToNew = transformLocations();
transformItems(oldLocationIdsToNew);
transformProperties();
replaceLocationsTable();
replaceItemsTable();
replaceItemPropsTable();
db.setAutoCommit(true);
} catch (Exception e) {
try {
db.rollback();
} catch (SQLException ignored) {
}
LOG.log(ERROR,"Failed to transform {0} table!",TABLE_LOCATIONS,e);
throw databaseException("Failed to transform {0} table!",TABLE_LOCATIONS);
}
}
private void replaceItemsTable() throws SQLException { private void replaceItemsTable() throws SQLException {
db.prepareStatement(format("DROP TABLE {0}",TABLE_ITEMS)).execute(); db.prepareStatement(format("DROP TABLE {0}",TABLE_ITEMS)).execute();
db.prepareStatement(format("ALTER TABLE {0} RENAME TO {1}","items_temp",TABLE_ITEMS)).execute(); db.prepareStatement(format("ALTER TABLE {0} RENAME TO {1}","items_temp",TABLE_ITEMS)).execute();
@ -335,4 +312,27 @@ public class SqliteDb extends BaseDb implements StockDb {
rs.close(); rs.close();
insert.execute(db).close(); insert.execute(db).close();
} }
private void transformTables(){
try {
db.setAutoCommit(false);
createIntermediateLocationTable();
createIntermediateItemsTable();
createIntermediatePropsTable();
var oldLocationIdsToNew = transformLocations();
transformItems(oldLocationIdsToNew);
transformProperties();
replaceLocationsTable();
replaceItemsTable();
replaceItemPropsTable();
db.setAutoCommit(true);
} catch (Exception e) {
try {
db.rollback();
} catch (SQLException ignored) {
}
LOG.log(ERROR,"Failed to transform {0} table!",TABLE_LOCATIONS,e);
throw databaseException("Failed to transform {0} table!",TABLE_LOCATIONS);
}
}
} }

7
stock/src/main/java/de/srsoftware/umbrella/stock/StockDb.java

@ -1,7 +1,6 @@
/* © SRSoftware 2025 */ /* © SRSoftware 2025 */
package de.srsoftware.umbrella.stock; package de.srsoftware.umbrella.stock;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Company; import de.srsoftware.umbrella.core.model.Company;
import de.srsoftware.umbrella.core.model.Item; import de.srsoftware.umbrella.core.model.Item;
import de.srsoftware.umbrella.core.model.Location; import de.srsoftware.umbrella.core.model.Location;
@ -11,12 +10,6 @@ import java.util.Collection;
public interface StockDb { public interface StockDb {
Collection<Location> listChildLocations(long parentId); Collection<Location> listChildLocations(long parentId);
Collection<Location> listCompanyLocations(Company company); Collection<Location> listCompanyLocations(Company company);
Collection<Item> listItems(long companyId) throws UmbrellaException;
Collection<Item> listItemsAt(long locationId); Collection<Item> listItemsAt(long locationId);
Collection<Location> listLocations(long companyId);
Collection<Location> listUserLocations(UmbrellaUser userId); Collection<Location> listUserLocations(UmbrellaUser userId);
} }

1
translations/src/main/resources/de.json

@ -130,6 +130,7 @@
"loading_object": "lade {object}…", "loading_object": "lade {object}…",
"local_court": "Amtsgericht", "local_court": "Amtsgericht",
"locality": "Ort", "locality": "Ort",
"location": "Ort",
"login" : "Anmeldung", "login" : "Anmeldung",
"login_services": "Login-Services", "login_services": "Login-Services",
"logout": "Abmelden", "logout": "Abmelden",

Loading…
Cancel
Save