Compare commits
11 Commits
module/tas
...
feature/it
| Author | SHA1 | Date | |
|---|---|---|---|
| d6b56ac127 | |||
| a3bcc66b73 | |||
| 90528cfcac | |||
| fd536abe11 | |||
| d6b5d243c1 | |||
| 8f82ca87b4 | |||
| 600b0f2cf4 | |||
| 59f864d16f | |||
| 261a93bcc0 | |||
| 60c447a967 | |||
| b0550db5c2 |
@@ -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.2")
|
||||
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")
|
||||
|
||||
@@ -127,6 +127,7 @@ public class Constants {
|
||||
public static final String TAG_COLORS = "tag_colors";
|
||||
public static final String TASK_IDS = "task_ids";
|
||||
public static final String TAX = "tax";
|
||||
public static final String TAX_RATE = "tax_rate";
|
||||
public static final String TEMPLATE = "template";
|
||||
public static final String TEXT = "text";
|
||||
public static final String THOUSANDS_SEPARATOR = "thousands_separator";
|
||||
|
||||
@@ -20,6 +20,7 @@ public class Paths {
|
||||
public static final String STARTED = "started";
|
||||
public static final String STOP = "stop";
|
||||
public static final String SUBMIT = "submit";
|
||||
public static final String TAGGED = "tagged";
|
||||
public static final String TOKEN = "token";
|
||||
public static final String VIEW = "view";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -4,11 +4,14 @@ package de.srsoftware.umbrella.core.api;
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface TagService {
|
||||
void deleteEntity(String task, long taskId);
|
||||
|
||||
Map<String, List<Long>> getTagUses(UmbrellaUser user, String tag);
|
||||
|
||||
Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package de.srsoftware.umbrella.core.model;
|
||||
|
||||
import static de.srsoftware.umbrella.core.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.Util.mapMarkdown;
|
||||
|
||||
import de.srsoftware.tools.Mappable;
|
||||
import de.srsoftware.umbrella.core.api.Owner;
|
||||
@@ -13,19 +14,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<Property> properties;
|
||||
private Set<String> 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 +39,10 @@ public class Item implements Mappable {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String description(){
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean isDirty(){
|
||||
return !dirtyFields.isEmpty();
|
||||
}
|
||||
@@ -65,13 +71,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(){
|
||||
@@ -82,6 +89,10 @@ public class Item implements Mappable {
|
||||
return ownerNumber;
|
||||
}
|
||||
|
||||
public void ownerNumber(long newVal) {
|
||||
ownerNumber = newVal;
|
||||
}
|
||||
|
||||
public Item patch(JSONObject json) {
|
||||
for (var field : json.keySet()){
|
||||
var known = true;
|
||||
@@ -92,6 +103,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 +126,7 @@ public class Item implements Mappable {
|
||||
map.put(LOCATION,location.toMap());
|
||||
map.put(CODE,code);
|
||||
map.put(NAME,name);
|
||||
map.put(DESCRIPTION,mapMarkdown(description));
|
||||
map.put(OWNER_NUMBER,ownerNumber);
|
||||
if (properties != null) map.put(PROPERTIES,properties.stream().map(Property::toMap).toList());
|
||||
return map;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import Companies from "./routes/company/Index.svelte";
|
||||
import ContactList from "./routes/contact/Index.svelte";
|
||||
import DocList from "./routes/document/List.svelte";
|
||||
import EasyList from "./routes/task/EasyList.svelte";
|
||||
import EditService from "./routes/user/EditService.svelte";
|
||||
import EditUser from "./routes/user/EditUser.svelte";
|
||||
import FileIndex from "./routes/files/Index.svelte";
|
||||
@@ -104,6 +105,7 @@
|
||||
<Route path="/tags" component={TagList} />
|
||||
<Route path="/tags/use/:tag" component={TagUses} />
|
||||
<Route path="/task" component={TaskList} />
|
||||
<Route path="/tags/easylist/:tag" component={EasyList} />
|
||||
<Route path="/task/:parent_task_id/add_subtask" component={AddTask} />
|
||||
<Route path="/task/:id/edit" component={ViewTask} />
|
||||
<Route path="/task/:id/view" component={ViewTask} />
|
||||
|
||||
@@ -30,23 +30,33 @@
|
||||
|
||||
function itemSelected(item){
|
||||
let unit_price = null;
|
||||
let unit = t('pieces');
|
||||
let description = '';
|
||||
let tax = null;
|
||||
for (let prop of item.properties) {
|
||||
if (prop.name.toLowerCase().indexOf(t('price').toLowerCase())>-1){
|
||||
unit_price = 100*prop.value.replace(',','.');
|
||||
let lowerName = prop.name.toLowerCase();
|
||||
if (lowerName.indexOf(t('price').toLowerCase())>-1){
|
||||
unit_price = 100*String(prop.value).replace(',','.');
|
||||
} else if (lowerName.indexOf(t('unit').toLowerCase())>-1){
|
||||
unit = prop.value;
|
||||
} else if (lowerName.indexOf(t('tax_rate').toLowerCase())>-1 && prop.unit=='%'){
|
||||
tax = prop.value;
|
||||
} else {
|
||||
description += `* ${prop.name}: ${prop.value}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
select({
|
||||
var data = {
|
||||
item_code : item.code,
|
||||
title : item.name,
|
||||
description : description,
|
||||
amount : 1,
|
||||
unit : t('pieces'),
|
||||
unit : unit,
|
||||
unit_price : unit_price
|
||||
});
|
||||
};
|
||||
if (tax) data['tax'] = tax;
|
||||
|
||||
select(data);
|
||||
}
|
||||
|
||||
function timeSelected(time){
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
{#if item}
|
||||
<LineEditor type="h3" editable={true} value={item.name} onSet={v => patch('name',v)} />
|
||||
Code: <LineEditor type="span" editable={true} value={item.code} onSet={v => patch('code',v)} />
|
||||
<div>
|
||||
{@html item.description.rendered}
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
{#each item.properties.toSorted(byName) as prop}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte';
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
|
||||
import { api } from '../../urls.svelte.js';
|
||||
import { error, yikes } from '../../warn.svelte';
|
||||
@@ -8,6 +9,7 @@
|
||||
import Reference from './Reference.svelte';
|
||||
|
||||
let { tag } = $props();
|
||||
let router = useTinyRouter();
|
||||
let uses = $state(null);
|
||||
|
||||
async function loadUses(){
|
||||
@@ -21,11 +23,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
function goEasy(){
|
||||
router.navigate(`/tags/easylist/${tag}`);
|
||||
}
|
||||
|
||||
onMount(loadUses);
|
||||
</script>
|
||||
<fieldset>
|
||||
<legend>{t('tag_uses',{tag:tag})}</legend>
|
||||
<legend>
|
||||
{t('tag_uses',{tag:tag})}
|
||||
<button onclick={goEasy}>{t('easy_list')}</button>
|
||||
</legend>
|
||||
{#if uses}
|
||||
{#each Object.entries(uses) as [module,ids]}
|
||||
<h2>{t(module.endsWith('s') ? module : `${module}s`)}</h2>
|
||||
|
||||
171
frontend/src/routes/task/EasyList.svelte
Normal file
171
frontend/src/routes/task/EasyList.svelte
Normal file
@@ -0,0 +1,171 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
import { api, get, patch } from '../../urls.svelte';
|
||||
import { error, yikes } from '../../warn.svelte';
|
||||
import { t } from '../../translations.svelte';
|
||||
|
||||
import Detail from './EasyListDetail.svelte';
|
||||
|
||||
let { tag } = $props();
|
||||
let filter = $state(null);
|
||||
let highlight = $state(null);
|
||||
let search = $derived(filter ? filter.toLowerCase() : null);
|
||||
//let input;
|
||||
let tasks = $state(null);
|
||||
let router = useTinyRouter();
|
||||
let sorted = $derived(tasks ? Object.values(tasks).filter(noNoIndex).sort(byName) : null);
|
||||
|
||||
let start = 0;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
function byName(a,b){
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
function extend(e,task){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
highlight = task;
|
||||
return false;
|
||||
}
|
||||
|
||||
function getTask(evt){
|
||||
var link = evt.target;
|
||||
var id = link.getAttribute('task_id');
|
||||
return tasks[id];
|
||||
}
|
||||
|
||||
function goTag(e,newTag){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
router.navigate(`/tags/easylist/${newTag}`);
|
||||
tag = newTag;
|
||||
load();
|
||||
}
|
||||
|
||||
function ignore(evt){
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
async function load(){
|
||||
const url = api(`task/tagged/${tag}`);
|
||||
const res = await get(url);
|
||||
if (res.ok){
|
||||
yikes();
|
||||
tasks = await res.json();
|
||||
//input.focus();
|
||||
} else error(res);
|
||||
}
|
||||
|
||||
function match(task){
|
||||
if (!search) return true;
|
||||
if (task.name.toLowerCase().includes(search)) return true;
|
||||
if (task.tags){
|
||||
for (let tag of task.tags){
|
||||
if (tag.toLowerCase().includes(search)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function measured(evt,duration,d){
|
||||
if (d > 100) return;
|
||||
if (duration < 500){
|
||||
onclick(evt);
|
||||
} else {
|
||||
oncontextmenu(evt);
|
||||
}
|
||||
}
|
||||
|
||||
function noNoIndex(task){
|
||||
return !task.no_index;
|
||||
}
|
||||
|
||||
function onclick(evt) {
|
||||
ignore(evt);
|
||||
let task = getTask(evt);
|
||||
if (task.status <= 20) { // open
|
||||
update(task,60);
|
||||
} else update(task,20);
|
||||
return false;
|
||||
}
|
||||
|
||||
function oncontextmenu(evt) {
|
||||
ignore(evt);
|
||||
highlight = getTask(evt);
|
||||
return false;
|
||||
}
|
||||
|
||||
function ontouchstart(evt){
|
||||
ignore(evt);
|
||||
start = evt.timeStamp;
|
||||
x = evt.touches[0].clientX;
|
||||
y = evt.touches[0].clientY;
|
||||
return false;
|
||||
}
|
||||
|
||||
function ontouchend(evt){
|
||||
ignore(evt);
|
||||
let d = Math.abs(x - evt.changedTouches[0].clientX) + Math.abs(y - evt.changedTouches[0].clientY);
|
||||
measured(evt, evt.timeStamp - start, d);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
async function update(task,newState){
|
||||
highlight = null;
|
||||
const url = api(`task/${task.id}`);
|
||||
const res = await patch(url,{status:newState});
|
||||
if (res.ok){
|
||||
task.status = newState;
|
||||
yikes();
|
||||
// filter = null; // not sure what is better, resetting or keeping
|
||||
} else error(res);
|
||||
}
|
||||
|
||||
onMount(load);
|
||||
</script>
|
||||
|
||||
<h2>{t('tasks_for_tag',{tag:decodeURI(tag)})}</h2>
|
||||
|
||||
<div class="easylist">
|
||||
<fieldset class="open">
|
||||
<legend>{t('state_open')}</legend>
|
||||
{#if sorted}
|
||||
{#each sorted as task}
|
||||
{#if task.status == 20 && match(task)}
|
||||
<div href={`/task/${task.id}/view`} title={task.description.source} task_id={task.id} {onclick} {oncontextmenu} {ontouchstart} {ontouchend} onmousedown={ontouchstart} onmouseup={ontouchend} >
|
||||
{task.name}
|
||||
</div>
|
||||
{#if highlight == task}
|
||||
<Detail task={task} goTag={goTag} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="closed">
|
||||
<legend>{t('state_complete')}</legend>
|
||||
{#if sorted}
|
||||
{#each sorted as task}
|
||||
{#if task.status > 20 && match(task)}
|
||||
<div href={`/task/${task.id}/view`} title={task.description.source} task_id={task.id} {onclick} {oncontextmenu} {ontouchstart} {ontouchend} onmousedown={ontouchstart} onmouseup={ontouchend} >
|
||||
{task.name}
|
||||
</div>
|
||||
{#if highlight == task}
|
||||
<Detail task={task} goTag={goTag} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</fieldset>
|
||||
|
||||
<div class="filter">
|
||||
{t('filter')}: <input type="text" bind:value={filter} /> <!-- bind:this={input} -->
|
||||
</div>
|
||||
</div>
|
||||
22
frontend/src/routes/task/EasyListDetail.svelte
Normal file
22
frontend/src/routes/task/EasyListDetail.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<script>
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
import { t } from '../../translations.svelte';
|
||||
|
||||
let { goTag, task } = $props();
|
||||
let router = useTinyRouter();
|
||||
|
||||
function onclick(){
|
||||
router.navigate(`/task/${task.id}/edit`);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<button class="edit" {onclick}>{t('edit')}</button>
|
||||
{@html task.description.rendered}
|
||||
{#if task.tags}
|
||||
{t('other_tags')}:<br/>
|
||||
{#each task.tags as tag}
|
||||
<button onclick={e => goTag(e,tag)}>{tag}</button>
|
||||
{/each}
|
||||
<hr />
|
||||
{/if}
|
||||
@@ -2,4 +2,5 @@ description = "Umbrella : Stock"
|
||||
|
||||
dependencies{
|
||||
implementation(project(":core"))
|
||||
implementation("de.srsoftware:configuration.json:1.0.3")
|
||||
}
|
||||
@@ -8,6 +8,7 @@ public class Constants {
|
||||
|
||||
public static final String BELOW = "below";
|
||||
public static final String CONFIG_DATABASE = "umbrella.modules.stock.database";
|
||||
public static final String CONFIG_ITEM_DB = "umbrella.modules.items.database";
|
||||
public static final String ITEM = "item";
|
||||
public static final String ITEM_ID = "item_id";
|
||||
public static final String ITEMS = "items";
|
||||
|
||||
86
stock/src/main/java/de/srsoftware/umbrella/stock/ItemDb.java
Normal file
86
stock/src/main/java/de/srsoftware/umbrella/stock/ItemDb.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/* © 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.ModuleRegistry.companyService;
|
||||
import static de.srsoftware.umbrella.core.ModuleRegistry.translator;
|
||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
|
||||
import static de.srsoftware.umbrella.stock.Constants.TABLE_ITEMS;
|
||||
|
||||
import de.srsoftware.tools.Tuple;
|
||||
import de.srsoftware.umbrella.core.ModuleRegistry;
|
||||
import de.srsoftware.umbrella.core.model.*;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Comparator;
|
||||
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<Long,Location>();
|
||||
var companyInfo = new HashMap<Long, Tuple<Company,String>>(); // map from companyId → (company, language)
|
||||
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 tuple = companyInfo.get(companyId);
|
||||
String lang = null;
|
||||
Company company;
|
||||
if (tuple == null){
|
||||
company = companyService().get(companyId);
|
||||
for (var member : companyService().getMembers(companyId)){
|
||||
lang = member.language();
|
||||
if (lang != null){
|
||||
tuple = Tuple.of(company,lang);
|
||||
companyInfo.put(companyId,tuple);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
company = tuple.a;
|
||||
lang = tuple.b;
|
||||
}
|
||||
var location = companyLocations.get(companyId);
|
||||
if (location == null) {
|
||||
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();
|
||||
var keyUnitPrice = translator().translate(lang,UNIT_PRICE);
|
||||
var keyUnit = translator().translate(lang,UNIT);
|
||||
var keyTax = translator().translate(lang,TAX_RATE);
|
||||
var keyLegacyId = translator().translate(lang,"legacy_id");
|
||||
props.add(new Property(0,keyUnitPrice,unitPrice/100d,company.currency()));
|
||||
props.add(new Property(0,keyUnit,unit,null));
|
||||
props.add(new Property(0,keyTax,tax,"%"));
|
||||
props.add(new Property(0,keyLegacyId,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);
|
||||
}
|
||||
}
|
||||
@@ -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,22 @@ 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);
|
||||
insertInto(TABLE_ITEM_PROPERTIES,ITEM_ID,PROPERTY_ID,VALUE).values(itemId,propertyId,value).execute(db);
|
||||
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).close();
|
||||
db.setAutoCommit(true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public class SqliteDb extends BaseDb implements StockDb {
|
||||
|
||||
private void createPropertiesTable() {
|
||||
try {
|
||||
var sql = "CREATE TABLE IF NOT EXISTS {0} ( {1} LONG PRIMARY KEY, {2} VARCHAR(255) NOT NULL, {3} INT NOT NULL, {4} VARCHAR(255))";
|
||||
var sql = "CREATE TABLE IF NOT EXISTS {0} ( {1} INTEGER PRIMARY KEY, {2} VARCHAR(255) NOT NULL, {3} INT NOT NULL, {4} VARCHAR(255))";
|
||||
sql = format(sql, TABLE_PROPERTIES, ID, NAME, TYPE, UNIT);
|
||||
db.prepareStatement(sql).execute();
|
||||
} catch (SQLException e) {
|
||||
@@ -165,8 +165,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() {
|
||||
@@ -394,13 +406,13 @@ public class SqliteDb extends BaseDb implements StockDb {
|
||||
@Override
|
||||
public Item save(Item item) {
|
||||
if (item.id() == 0){
|
||||
var number = nextItemNumberFor(item.location().resolve().owner());
|
||||
try {
|
||||
var rs = insertInto(TABLE_ITEMS, OWNER, OWNER_NUMBER, CODE, NAME, LOCATION_ID)
|
||||
.values(item.owner().dbCode(), item.ownerNumber(), item.code(), item.name(), item.location().id())
|
||||
var rs = insertInto(TABLE_ITEMS, OWNER, OWNER_NUMBER, CODE, NAME, DESCRIPTION, LOCATION_ID)
|
||||
.values(item.owner().dbCode(), number, item.code(), item.name(), item.description(), item.location().id())
|
||||
.execute(db).getGeneratedKeys();
|
||||
if (rs.next()) item.id(rs.getLong(1));
|
||||
if (rs.next()) item.id(rs.getLong(1)).ownerNumber(number);
|
||||
rs.close();
|
||||
return item;
|
||||
} catch (SQLException e) {
|
||||
throw databaseException("Failed to save new item to database!");
|
||||
}
|
||||
@@ -409,25 +421,53 @@ public class SqliteDb extends BaseDb implements StockDb {
|
||||
var location = item.location();
|
||||
var query = update(TABLE_ITEMS).where(ID, equal(item.id()));
|
||||
if (location == null) {
|
||||
query.set(CODE,NAME);
|
||||
query.set(CODE,NAME,DESCRIPTION);
|
||||
} else {
|
||||
query.set(CODE,NAME,LOCATION_ID);
|
||||
query.set(CODE,NAME,DESCRIPTION,LOCATION_ID);
|
||||
}
|
||||
var pq = query.prepare(db);
|
||||
if (location == null) {
|
||||
pq.apply(item.code(),item.name());
|
||||
pq.apply(item.code(),item.name(),item.description()).close();
|
||||
} else {
|
||||
pq.apply(item.code(),item.name(),item.location().id());
|
||||
pq.apply(item.code(),item.name(),item.description(),item.location().id()).close();
|
||||
}
|
||||
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<Property>();
|
||||
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,11 +475,11 @@ 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 {
|
||||
replaceInto(TABLE_ITEM_PROPERTIES,ITEM_ID,PROPERTY_ID,VALUE).values(itemId,existingPropId,value).execute(db);
|
||||
replaceInto(TABLE_ITEM_PROPERTIES,ITEM_ID,PROPERTY_ID,VALUE).values(itemId,existingPropId,value).execute(db).close();
|
||||
}
|
||||
return prop.value(value);
|
||||
} catch (SQLException e) {
|
||||
|
||||
@@ -9,16 +9,14 @@ import static de.srsoftware.umbrella.core.Field.ITEM;
|
||||
import static de.srsoftware.umbrella.core.ModuleRegistry.companyService;
|
||||
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
|
||||
import static de.srsoftware.umbrella.core.Paths.LIST;
|
||||
import static de.srsoftware.umbrella.core.Util.mapValues;
|
||||
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.configuration.JsonConfig;
|
||||
import de.srsoftware.tools.Path;
|
||||
import de.srsoftware.tools.SessionToken;
|
||||
import de.srsoftware.umbrella.core.BaseHandler;
|
||||
@@ -41,6 +39,16 @@ public class StockModule extends BaseHandler implements StockService {
|
||||
super();
|
||||
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
|
||||
stockDb = new SqliteDb(connect(dbFile));
|
||||
Optional<String> itemDbConfig = config.get(CONFIG_ITEM_DB);
|
||||
itemDbConfig.map(ItemDb::new).ifPresent(itemDb -> itemDb.migrateTo(stockDb));
|
||||
if (itemDbConfig.isPresent()){
|
||||
try {
|
||||
config.drop(CONFIG_ITEM_DB);
|
||||
if (config instanceof JsonConfig jsonConfig) jsonConfig.save();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
ModuleRegistry.add(this);
|
||||
}
|
||||
|
||||
@@ -310,13 +318,13 @@ 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,0,location,code,name,description);
|
||||
return sendContent(ex,stockDb.save(newItem));
|
||||
}
|
||||
|
||||
@@ -381,32 +389,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<Object> redefineMe(long company_id) {
|
||||
// TODO
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,10 +204,10 @@ CREATE TABLE IF NOT EXISTS {0} (
|
||||
public String delete(long userId, String module, long entityId, String tag) {
|
||||
try {
|
||||
Query.delete().from(TABLE_TAGS)
|
||||
.where(TAG,equal(tag)).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,equal(userId))
|
||||
.where(TAG,iEqual(tag)).where(MODULE,iEqual(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,equal(userId))
|
||||
.execute(db);
|
||||
Query.delete().from(TABLE_TAGS)
|
||||
.where(TAG,equal(tag)).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,isNull())
|
||||
.where(TAG,iEqual(tag)).where(MODULE,iEqual(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,isNull())
|
||||
.execute(db);
|
||||
return tag;
|
||||
} catch (SQLException e){
|
||||
@@ -219,7 +219,7 @@ CREATE TABLE IF NOT EXISTS {0} (
|
||||
public void deleteEntity(String module, long entityId) {
|
||||
try {
|
||||
Query.delete().from(TABLE_TAGS)
|
||||
.where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId))
|
||||
.where(MODULE,iEqual(module)).where(ENTITY_ID,equal(entityId))
|
||||
.execute(db);
|
||||
} catch (SQLException e){
|
||||
throw new UmbrellaException("Failed to save tags ({0} {1})",module,entityId);
|
||||
@@ -229,7 +229,7 @@ CREATE TABLE IF NOT EXISTS {0} (
|
||||
@Override
|
||||
public Map<String, List<Long>> getUses(String tag, long userId) {
|
||||
try {
|
||||
var rs = select(ALL).from(TABLE_TAGS).where(TAG,equal(tag)).where(USER_ID,equal(userId)).exec(db);
|
||||
var rs = select(ALL).from(TABLE_TAGS).where(TAG,iEqual(tag)).where(USER_ID,equal(userId)).exec(db);
|
||||
var result = new HashMap<String,List<Long>>();
|
||||
while (rs.next()){
|
||||
var module = rs.getString(MODULE);
|
||||
@@ -237,7 +237,7 @@ CREATE TABLE IF NOT EXISTS {0} (
|
||||
result.computeIfAbsent(module, k -> new ArrayList<>()).add(entityId);
|
||||
}
|
||||
rs.close();
|
||||
rs = select(ALL).from(TABLE_TAGS).where(TAG,equal(tag)).where(USER_ID,isNull()).exec(db);
|
||||
rs = select(ALL).from(TABLE_TAGS).where(TAG,iEqual(tag)).where(USER_ID,isNull()).exec(db);
|
||||
while (rs.next()){
|
||||
var module = rs.getString(MODULE);
|
||||
var entityId = rs.getLong(ENTITY_ID);
|
||||
@@ -256,12 +256,12 @@ CREATE TABLE IF NOT EXISTS {0} (
|
||||
var tags = new HashSet<String>();
|
||||
|
||||
// load tags assigned to user
|
||||
var rs = select(TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,equal(userId)).exec(db);
|
||||
var rs = select(TAG).from(TABLE_TAGS).where(MODULE,iEqual(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,equal(userId)).exec(db);
|
||||
while (rs.next()) tags.add(rs.getString(1));
|
||||
rs.close();
|
||||
|
||||
// load tags assigned to no user
|
||||
rs = select(TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,isNull()).exec(db);
|
||||
rs = select(TAG).from(TABLE_TAGS).where(MODULE,iEqual(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,isNull()).exec(db);
|
||||
while (rs.next()) tags.add(rs.getString(1));
|
||||
rs.close();
|
||||
return tags;
|
||||
@@ -294,12 +294,12 @@ CREATE TABLE IF NOT EXISTS {0} (
|
||||
var tags = new HashMap<Long,HashSet<String>>();
|
||||
|
||||
// load tags assigned to user
|
||||
var rs = select(ENTITY_ID,TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,in(entityIds.toArray())).where(USER_ID,equal(userId)).exec(db);
|
||||
var rs = select(ENTITY_ID,TAG).from(TABLE_TAGS).where(MODULE,iEqual(module)).where(ENTITY_ID,in(entityIds.toArray())).where(USER_ID,equal(userId)).exec(db);
|
||||
while (rs.next()) tags.computeIfAbsent(rs.getLong(ENTITY_ID), k -> new HashSet<>()).add(rs.getString(TAG));
|
||||
rs.close();
|
||||
|
||||
// load tags assigned to no user
|
||||
rs = select(ENTITY_ID,TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,in(entityIds.toArray())).where(USER_ID,isNull()).exec(db);
|
||||
rs = select(ENTITY_ID,TAG).from(TABLE_TAGS).where(MODULE,iEqual(module)).where(ENTITY_ID,in(entityIds.toArray())).where(USER_ID,isNull()).exec(db);
|
||||
while (rs.next()) tags.computeIfAbsent(rs.getLong(ENTITY_ID), k -> new HashSet<>()).add(rs.getString(TAG));
|
||||
rs.close();
|
||||
return tags;
|
||||
@@ -328,7 +328,7 @@ CREATE TABLE IF NOT EXISTS {0} (
|
||||
@Override
|
||||
public void updateId(String module, Object oldId, Object newId) {
|
||||
try {
|
||||
update(TABLE_TAGS).set(ENTITY_ID).where(MODULE,equal(module)).where(ENTITY_ID,equal(oldId)).prepare(db).apply(newId).close();
|
||||
update(TABLE_TAGS).set(ENTITY_ID).where(MODULE,iEqual(module)).where(ENTITY_ID,equal(oldId)).prepare(db).apply(newId).close();
|
||||
LOG.log(DEBUG,"Updated tag @ {0}.{1} → {0}.{2}",module,oldId,newId);
|
||||
} catch (SQLException e) {
|
||||
throw databaseException("Failed to update {0}.{1} → {0}.{2}",module,oldId,newId);
|
||||
|
||||
@@ -112,8 +112,12 @@ public class TagModule extends BaseHandler implements TagService {
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, List<Long>> getTagUses(UmbrellaUser user, String tag){
|
||||
return tagDb.getUses(tag,user.id());
|
||||
}
|
||||
|
||||
private boolean getTagUses(HttpExchange ex, String tag, UmbrellaUser user) throws IOException {
|
||||
return sendContent(ex,tagDb.getUses(tag,user.id()));
|
||||
return sendContent(ex,getTagUses(user,tag));
|
||||
}
|
||||
|
||||
public Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.task;
|
||||
|
||||
import static de.srsoftware.tools.Optionals.is0;
|
||||
import static de.srsoftware.tools.Optionals.isSet;
|
||||
import static de.srsoftware.tools.Optionals.*;
|
||||
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
||||
import static de.srsoftware.umbrella.core.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.ModuleRegistry.*;
|
||||
@@ -15,6 +14,8 @@ import static de.srsoftware.umbrella.core.model.Permission.OWNER;
|
||||
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
|
||||
import static de.srsoftware.umbrella.task.Constants.*;
|
||||
import static java.lang.System.Logger.Level.WARNING;
|
||||
import static java.net.URLDecoder.decode;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import de.srsoftware.configuration.Configuration;
|
||||
@@ -92,6 +93,7 @@ public class TaskModule extends BaseHandler implements TaskService {
|
||||
var head = path.pop();
|
||||
return switch (head) {
|
||||
case PERMISSIONS -> getPermissionList(ex);
|
||||
case TAGGED -> getTaggedTasks(path, user.get(), ex);
|
||||
case null -> getUserTasks(user.get(), ex);
|
||||
default -> {
|
||||
var taskId = Long.parseLong(head);
|
||||
@@ -179,6 +181,19 @@ public class TaskModule extends BaseHandler implements TaskService {
|
||||
return sendContent(ex, map);
|
||||
}
|
||||
|
||||
private boolean getTaggedTasks(Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||
var tag = decode(path.toString(), UTF_8);
|
||||
var tags = tagService().getTagUses(user,tag);
|
||||
var taskIds = nullable(tags.get(TASK)).orElseGet(ArrayList::new);
|
||||
var tasks = mapValues(taskDb.load(taskIds));
|
||||
var taskTags = tagService().getTags(TASK,taskIds,user);
|
||||
for (var entry : tasks.entrySet()){
|
||||
var list = taskTags.get(entry.getKey());
|
||||
entry.getValue().put(TAGS,list==null?List.of():list);
|
||||
}
|
||||
return sendContent(ex, tasks);
|
||||
}
|
||||
|
||||
private boolean getTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
|
||||
var task = loadMembers(taskDb.load(taskId));
|
||||
if (!task.hasMember(user)) throw forbidden("You are not a member of {0}",task.name());
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"due_date": "Fälligkeitsdatum",
|
||||
"duration": "Dauer",
|
||||
|
||||
"easy_list": "Easy List",
|
||||
"edit": "Bearbeiten",
|
||||
"edit_object" : "{object} bearbeiten",
|
||||
"editing": "Nutzer {0} bearbeiten",
|
||||
@@ -190,6 +191,7 @@
|
||||
"oidc_Login" : "Anmeldung mit OIDC",
|
||||
"old_password": "altes Passwort",
|
||||
"organization": "Organisation",
|
||||
"other_tags": "andere Tags",
|
||||
|
||||
"page": "Seite",
|
||||
"parent_task": "übergeordnete Aufgabe",
|
||||
@@ -277,6 +279,7 @@
|
||||
"task": "Aufgabe",
|
||||
"task_list": "Aufgabenliste",
|
||||
"tasks": "Aufgaben",
|
||||
"tasks_for_tag": "Aufgaben mit Tag „{tag}“",
|
||||
"tax_id": "Steuernummer",
|
||||
"TAX-NUMBER": "Steuernummer",
|
||||
"tax_rate": "Steuersatz",
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"drag_n_drop": "drag & drop",
|
||||
"duration": "duration",
|
||||
|
||||
"easy_list": "Easy List",
|
||||
"edit": "edit",
|
||||
"edit_object" : "edit {object}",
|
||||
"editing": "edit user {0}",
|
||||
@@ -190,6 +191,7 @@
|
||||
"oidc_Login" : "Login via OIDC",
|
||||
"old_password": "old password",
|
||||
"organization": "organization",
|
||||
"other_tags": "other tags",
|
||||
|
||||
"page": "page",
|
||||
"parent_task": "parent task",
|
||||
@@ -277,6 +279,7 @@
|
||||
"task": "task",
|
||||
"task_list": "task list",
|
||||
"tasks": "tasks",
|
||||
"tasks_for_tag": "tasks with tag „{tag}“",
|
||||
"tax_id": "tax ID",
|
||||
"TAX-NUMBER": "tax ID",
|
||||
"tax_rate": "tax rate",
|
||||
|
||||
@@ -295,3 +295,16 @@ tr:hover .taglist .tag button {
|
||||
.vcard span.inactive{
|
||||
color: #222200;
|
||||
}
|
||||
|
||||
.easylist > fieldset > div {
|
||||
border-color: orange;
|
||||
color: orange;
|
||||
}
|
||||
.easylist fieldset {
|
||||
border-color: red;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.easylist .filter{
|
||||
background: black;
|
||||
}
|
||||
@@ -407,6 +407,25 @@ a.wikilink{
|
||||
grid-column-end: span 2;
|
||||
}
|
||||
|
||||
.easylist > fieldset > div {
|
||||
display: block;
|
||||
border: 1px solid;
|
||||
margin: 7px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
.easylist .filter{
|
||||
position: sticky;
|
||||
bottom: 22px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.easylist .edit{
|
||||
float: right;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.grid2{
|
||||
display: grid;
|
||||
@@ -428,6 +447,14 @@ a.wikilink{
|
||||
width: calc(100% - 10px);
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.easylist > fieldset > div {
|
||||
font-size: 25px;
|
||||
padding: 10px;
|
||||
}
|
||||
.easylist input{
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset.vcard{
|
||||
@@ -473,4 +500,4 @@ fieldset.vcard{
|
||||
margin: 0 6px;
|
||||
white-space: nowrap;
|
||||
display: inline flow-root;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,3 +285,16 @@ tr:hover .taglist .tag button {
|
||||
.vcard span.inactive{
|
||||
color: #222200;
|
||||
}
|
||||
|
||||
.easylist > fieldset > div {
|
||||
border-color: orange;
|
||||
color: orange;
|
||||
}
|
||||
.easylist fieldset {
|
||||
border-color: #ff7726;
|
||||
color: #ff7726;
|
||||
}
|
||||
|
||||
.easylist .filter{
|
||||
background: black;
|
||||
}
|
||||
@@ -485,6 +485,25 @@ a.wikilink{
|
||||
grid-column-end: span 2;
|
||||
}
|
||||
|
||||
.easylist > fieldset > div {
|
||||
display: block;
|
||||
border: 1px solid;
|
||||
margin: 7px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
.easylist .filter{
|
||||
position: sticky;
|
||||
bottom: 22px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.easylist .edit{
|
||||
float: right;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.grid2{
|
||||
display: grid;
|
||||
@@ -506,6 +525,14 @@ a.wikilink{
|
||||
width: calc(100% - 10px);
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.easylist > fieldset > div {
|
||||
font-size: 25px;
|
||||
padding: 10px;
|
||||
}
|
||||
.easylist input{
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset.vcard{
|
||||
@@ -551,4 +578,4 @@ fieldset.vcard{
|
||||
margin: 0 6px;
|
||||
white-space: nowrap;
|
||||
display: inline flow-root;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,3 +273,13 @@ tr:hover .taglist .tag button {
|
||||
.vcard span.inactive{
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.easylist > fieldset > div {
|
||||
border-color: blue;
|
||||
color: blue;
|
||||
background: #dfe4ff;
|
||||
}
|
||||
.easylist fieldset {
|
||||
border-color: blue;
|
||||
color: blue;
|
||||
}
|
||||
@@ -407,6 +407,25 @@ a.wikilink{
|
||||
grid-column-end: span 2;
|
||||
}
|
||||
|
||||
.easylist > fieldset > div {
|
||||
display: block;
|
||||
border: 1px solid;
|
||||
margin: 7px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
.easylist .filter{
|
||||
position: sticky;
|
||||
bottom: 22px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.easylist .edit{
|
||||
float: right;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.grid2{
|
||||
display: grid;
|
||||
@@ -428,6 +447,14 @@ a.wikilink{
|
||||
width: calc(100% - 10px);
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.easylist > fieldset > div {
|
||||
font-size: 25px;
|
||||
padding: 10px;
|
||||
}
|
||||
.easylist input{
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset.vcard{
|
||||
@@ -473,4 +500,4 @@ fieldset.vcard{
|
||||
margin: 0 6px;
|
||||
white-space: nowrap;
|
||||
display: inline flow-root;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user