Compare commits

..

No commits in common. 'main' and 'module/tasks' have entirely different histories.

  1. 28
      company/src/main/java/de/srsoftware/umbrella/company/CompanyModule.java
  2. 1
      company/src/main/java/de/srsoftware/umbrella/company/Constants.java
  3. 34
      company/src/main/java/de/srsoftware/umbrella/company/SqliteDb.java
  4. 4
      company/src/main/java/de/srsoftware/umbrella/company/api/CompanyDb.java
  5. 2
      contact/src/main/java/de/srsoftware/umbrella/contact/ContactDb.java
  6. 10
      contact/src/main/java/de/srsoftware/umbrella/contact/ContactModule.java
  7. 6
      contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java
  8. 2
      core/src/main/java/de/srsoftware/umbrella/core/api/CompanyService.java
  9. 4
      core/src/main/java/de/srsoftware/umbrella/core/api/ContactService.java
  10. 33
      core/src/main/java/de/srsoftware/umbrella/core/model/Contact.java
  11. 20
      core/src/main/java/de/srsoftware/umbrella/core/model/Customer.java
  12. 1
      documents/src/main/java/de/srsoftware/umbrella/documents/Constants.java
  13. 27
      documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java
  14. 36
      frontend/src/Components/ContactSelector.svelte
  15. 21
      frontend/src/routes/document/Add.svelte
  16. 2
      frontend/src/routes/files/Index.svelte
  17. 31
      frontend/src/routes/task/Index.svelte
  18. 4
      frontend/src/urls.svelte.js
  19. 5
      stock/src/main/java/de/srsoftware/umbrella/stock/SqliteDb.java
  20. 1
      translations/src/main/resources/de.json
  21. 2
      translations/src/main/resources/en.json
  22. 9
      web/src/main/resources/web/css/bloodshed-color.css
  23. 4
      web/src/main/resources/web/css/bloodshed.css
  24. 9
      web/src/main/resources/web/css/default-color.css
  25. 6
      web/src/main/resources/web/css/default.css
  26. 13
      web/src/main/resources/web/css/winter-color.css
  27. 6
      web/src/main/resources/web/css/winter.css

28
company/src/main/java/de/srsoftware/umbrella/company/CompanyModule.java

@ -2,7 +2,6 @@
package de.srsoftware.umbrella.company; package de.srsoftware.umbrella.company;
import static de.srsoftware.umbrella.company.Constants.CONFIG_DATABASE; import static de.srsoftware.umbrella.company.Constants.CONFIG_DATABASE;
import static de.srsoftware.umbrella.company.Constants.NEXT_CUSTOMER_NUMBER;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.ModuleRegistry.*; import static de.srsoftware.umbrella.core.ModuleRegistry.*;
@ -74,12 +73,7 @@ public class CompanyModule extends BaseHandler implements CompanyService {
return switch (head){ return switch (head){
case LIST -> getCompanyList(user.get(),ex); case LIST -> getCompanyList(user.get(),ex);
case null, case null,
default -> { default -> super.doGet(path,ex);
try {
yield getCompany(user.get(), path, Long.parseLong(head), ex);
} catch (NumberFormatException ignored){}
yield super.doGet(path,ex);
}
}; };
} catch (UmbrellaException e) { } catch (UmbrellaException e) {
return send(ex,e); return send(ex,e);
@ -130,14 +124,6 @@ public class CompanyModule extends BaseHandler implements CompanyService {
return companyDb.load(companyId); return companyDb.load(companyId);
} }
private boolean getCompany(UmbrellaUser user, Path path, long companyId, HttpExchange ex) throws IOException {
return switch (path.pop()){
case NEXT_CUSTOMER_NUMBER -> getNextCustomerNumber(user, companyId, ex);
case null, default -> super.doGet(path,ex);
};
}
private boolean getCompanyList(UmbrellaUser user, HttpExchange ex) throws IOException, UmbrellaException { private boolean getCompanyList(UmbrellaUser user, HttpExchange ex) throws IOException, UmbrellaException {
var result = new HashMap<Long,Map<String,Object>>(); var result = new HashMap<Long,Map<String,Object>>();
for (var entry : listCompaniesOf(user).entrySet()) result.put(entry.getKey(),entry.getValue().toMap()); for (var entry : listCompaniesOf(user).entrySet()) result.put(entry.getKey(),entry.getValue().toMap());
@ -151,13 +137,6 @@ public class CompanyModule extends BaseHandler implements CompanyService {
return members; return members;
} }
private boolean getNextCustomerNumber(UmbrellaUser user, long companyId, HttpExchange ex) throws IOException {
var company = companyDb.load(companyId);
if (!membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name());
var nextCustomerNumber = companyDb.getNextCustomerNumber(companyId);
return sendContent(ex,nextCustomerNumber);
}
@Override @Override
public Map<Long,Company> listCompaniesOf(UmbrellaUser user) throws UmbrellaException { public Map<Long,Company> listCompaniesOf(UmbrellaUser user) throws UmbrellaException {
return loadMembers(companyDb.listCompaniesOf(user.id())); return loadMembers(companyDb.listCompaniesOf(user.id()));
@ -226,9 +205,4 @@ public class CompanyModule extends BaseHandler implements CompanyService {
var companies = companyDb.find(user.id(),keys); var companies = companyDb.find(user.id(),keys);
return sendContent(ex,mapValues(companies)); return sendContent(ex,mapValues(companies));
} }
@Override
public void saveNewCustomer(long companyId, String id) {
companyDb.saveNewCustomer(companyId, id);
}
} }

1
company/src/main/java/de/srsoftware/umbrella/company/Constants.java

@ -6,7 +6,6 @@ public class Constants {
public static final String CONFIG_DATABASE = "umbrella.modules.company.database"; public static final String CONFIG_DATABASE = "umbrella.modules.company.database";
public static final String DISTINCT = "DISTINCT *"; public static final String DISTINCT = "DISTINCT *";
public static final String NEXT_CUSTOMER_NUMBER = "next_customer_number";
public static final String TABLE_COMPANIES_USERS = "companies_users"; public static final String TABLE_COMPANIES_USERS = "companies_users";
public static final String TABLE_COMPANIES = "companies"; public static final String TABLE_COMPANIES = "companies";
} }

34
company/src/main/java/de/srsoftware/umbrella/company/SqliteDb.java

@ -11,7 +11,6 @@ import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Field.*; import static de.srsoftware.umbrella.core.Field.*;
import static de.srsoftware.umbrella.core.Field.COMPANY_ID; import static de.srsoftware.umbrella.core.Field.COMPANY_ID;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.unprocessable;
import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.ERROR;
import static java.text.MessageFormat.format; import static java.text.MessageFormat.format;
@ -22,7 +21,6 @@ import de.srsoftware.umbrella.core.model.Company;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*; import java.util.*;
import java.util.regex.Pattern;
import org.json.JSONObject; import org.json.JSONObject;
public class SqliteDb extends BaseDb implements CompanyDb { public class SqliteDb extends BaseDb implements CompanyDb {
@ -128,24 +126,6 @@ CREATE TABLE IF NOT EXISTS "companies" (
} }
} }
@Override
public String getNextCustomerNumber(long companyId) {
try {
var rs = select(LAST_CUSTOMER_NUMBER,CUSTOMER_NUMBER_PREFIX).from(TABLE_COMPANIES).where(ID,equal(companyId)).exec(db);
var last = 0L;
String prefix = null;
if (rs.next()){
last = rs.getLong(LAST_CUSTOMER_NUMBER);
prefix = rs.getString(CUSTOMER_NUMBER_PREFIX);
}
rs.close();
var next = last+1;
return prefix+next; // TODO: currently not taking growing number lengths into account, this should be resolved!
} catch (SQLException e) {
throw databaseException("Failed to load customer number settings for company {0}",companyId);
}
}
@Override @Override
public HashMap<Long, Company> find(long userId, Collection<String> keys) { public HashMap<Long, Company> find(long userId, Collection<String> keys) {
try { try {
@ -228,18 +208,4 @@ CREATE TABLE IF NOT EXISTS "companies" (
throw UmbrellaException.databaseException("Failed to save {0}…",company.name()); throw UmbrellaException.databaseException("Failed to save {0}…",company.name());
} }
} }
@Override
public void saveNewCustomer(long companyId, String id) {
var p = Pattern.compile("(?s)(.*?)(\\d+)$");
var m = p.matcher(id);
if (!m.matches()) throw unprocessable("{0} is not a valid customer id: it does not end with a number!");
String prefix = m.group(1); // Prefix before last number
long number = Long.parseLong(m.group(2)); // The last numeric part
try {
update(TABLE_COMPANIES).set(LAST_CUSTOMER_NUMBER,CUSTOMER_NUMBER_PREFIX).where(ID,equal(companyId)).prepare(db).apply(number,prefix).close();
} catch (SQLException e) {
throw databaseException("Failed to update customer number counter for company {0}", companyId);
}
}
} }

4
company/src/main/java/de/srsoftware/umbrella/company/api/CompanyDb.java

@ -18,13 +18,9 @@ public interface CompanyDb {
Collection<Long> getMembers(long companyId) throws UmbrellaException; Collection<Long> getMembers(long companyId) throws UmbrellaException;
String getNextCustomerNumber(long companyId);
Map<Long,Company> listCompaniesOf(long id) throws UmbrellaException; Map<Long,Company> listCompaniesOf(long id) throws UmbrellaException;
Company load(long companyId) throws UmbrellaException; Company load(long companyId) throws UmbrellaException;
Company save(Company company); Company save(Company company);
void saveNewCustomer(long companyId, String id);
} }

2
contact/src/main/java/de/srsoftware/umbrella/contact/ContactDb.java

@ -10,7 +10,7 @@ public interface ContactDb {
Map<Long,Contact> listContactsOf(long userId) throws UmbrellaException; Map<Long,Contact> listContactsOf(long userId) throws UmbrellaException;
Contact load(long contactId, long userId); Contact load(long id, long userId);
Contact save(Contact contact); Contact save(Contact contact);

10
contact/src/main/java/de/srsoftware/umbrella/contact/ContactModule.java

@ -114,11 +114,6 @@ public class ContactModule extends BaseHandler implements ContactService {
return sendContent(ex,mapValues(listContactsOf(user))); return sendContent(ex,mapValues(listContactsOf(user)));
} }
@Override
public Contact load(long userId, long contactId) {
return contactDb.load(contactId,userId);
}
@Override @Override
public Map<Long,Contact> listContactsOf(UmbrellaUser user) throws UmbrellaException { public Map<Long,Contact> listContactsOf(UmbrellaUser user) throws UmbrellaException {
return contactDb.listContactsOf(user.id()); return contactDb.listContactsOf(user.id());
@ -135,9 +130,4 @@ public class ContactModule extends BaseHandler implements ContactService {
contactDb.setOwner(user.id(),contact); contactDb.setOwner(user.id(),contact);
return sendContent(ex,contact); return sendContent(ex,contact);
} }
@Override
public Contact save(Contact contact) {
return contactDb.save(contact);
}
} }

6
contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java

@ -87,14 +87,14 @@ public class SqliteDb extends BaseDb implements ContactDb{
} }
@Override @Override
public Contact load(long contactId, long userId) { public Contact load(long id, long userId) {
try { try {
var rs = select(ALL).from(TABLE_CONTACTS).leftJoin(ID,TABLE_CONTACTS_USERS,CONTACT_ID).where(USER_ID,equal(userId)).where(ID,equal(contactId)).exec(db); var rs = select(ALL).from(TABLE_CONTACTS).leftJoin(ID,TABLE_CONTACTS_USERS,CONTACT_ID).where(USER_ID,equal(userId)).where(ID,equal(id)).exec(db);
Contact contact = null; Contact contact = null;
if (rs.next()) contact = Contact.of(rs); if (rs.next()) contact = Contact.of(rs);
rs.close(); rs.close();
if (contact != null) return contact; if (contact != null) return contact;
throw notFound("Failed to load contact with id = {0}", contactId); throw notFound("Failed to load contact with id = {0}",id);
} catch (SQLException e) { } catch (SQLException e) {
throw databaseException("Failed to load contacts of user {0}",userId); throw databaseException("Failed to load contacts of user {0}",userId);
} }

2
core/src/main/java/de/srsoftware/umbrella/core/api/CompanyService.java

@ -15,6 +15,4 @@ public interface CompanyService {
Map<Long,Company> listCompaniesOf(UmbrellaUser user) throws UmbrellaException; Map<Long,Company> listCompaniesOf(UmbrellaUser user) throws UmbrellaException;
boolean membership(long companyId, long userId) throws UmbrellaException; boolean membership(long companyId, long userId) throws UmbrellaException;
void saveNewCustomer(long companyId, String id);
} }

4
core/src/main/java/de/srsoftware/umbrella/core/api/ContactService.java

@ -8,8 +8,4 @@ import java.util.Map;
public interface ContactService { public interface ContactService {
Map<Long, Contact> listContactsOf(UmbrellaUser user) throws UmbrellaException; Map<Long, Contact> listContactsOf(UmbrellaUser user) throws UmbrellaException;
Contact load(long userId, long contactId);
Contact save(Contact contact);
} }

33
core/src/main/java/de/srsoftware/umbrella/core/model/Contact.java

@ -3,7 +3,6 @@ package de.srsoftware.umbrella.core.model;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
import static java.text.MessageFormat.format;
import de.srsoftware.tools.Mappable; import de.srsoftware.tools.Mappable;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -11,21 +10,7 @@ import java.sql.SQLException;
import java.util.Map; import java.util.Map;
import org.json.JSONObject; import org.json.JSONObject;
public class Contact implements Mappable{ public record Contact(long id, String vcard) implements Mappable {
private final static String X_CUSTOMER_NUMBER = "X-CUSTOMER-NUMBER";
private long id;
private String vcard;
public Contact(long id, String vcard){
this.id = id;
this.vcard = vcard;
}
public long id(){
return id;
}
public static Contact of(ResultSet rs) throws SQLException { public static Contact of(ResultSet rs) throws SQLException {
return new Contact(rs.getLong(ID),rs.getString(DATA)); return new Contact(rs.getLong(ID),rs.getString(DATA));
} }
@ -36,24 +21,8 @@ public class Contact implements Mappable{
return new Contact(id,vcard.replace(from, to)); return new Contact(id,vcard.replace(from, to));
} }
public Contact setCustomerNumber(String customerNumber) {
if (vcard.contains(X_CUSTOMER_NUMBER)) {
var pattern = format("^{0}:.*",X_CUSTOMER_NUMBER);
var replacement = format("{0}:{1}",X_CUSTOMER_NUMBER,customerNumber);
vcard = vcard.replaceAll(pattern,replacement);
} else {
var index = vcard.lastIndexOf("\n");
vcard = format("{0}\r\n{1}:{2}{3}",vcard.substring(0,index),X_CUSTOMER_NUMBER,customerNumber,vcard.substring(index));
}
return this;
}
@Override @Override
public Map<String, Object> toMap() { public Map<String, Object> toMap() {
return Map.of(ID,id,VCARD,vcard); return Map.of(ID,id,VCARD,vcard);
} }
public String vcard(){
return vcard;
}
} }

20
core/src/main/java/de/srsoftware/umbrella/core/model/Customer.java

@ -1,14 +1,16 @@
/* © SRSoftware 2025 */ /* © SRSoftware 2025 */
package de.srsoftware.umbrella.core.model; package de.srsoftware.umbrella.core.model;
import static de.srsoftware.tools.Optionals.emptyIfNull;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Field.*; import static de.srsoftware.umbrella.core.Field.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
import de.srsoftware.tools.Mappable; import de.srsoftware.tools.Mappable;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import java.util.*; import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.json.JSONObject; import org.json.JSONObject;
public final class Customer implements Mappable { public final class Customer implements Mappable {
@ -71,7 +73,7 @@ public final class Customer implements Mappable {
if (!json.has(ID) || !(json.get(ID) instanceof String id)) throw missingFieldException(ID); if (!json.has(ID) || !(json.get(ID) instanceof String id)) throw missingFieldException(ID);
if (!json.has(NAME) || !(json.get(NAME) instanceof String name)) throw missingFieldException(NAME); if (!json.has(NAME) || !(json.get(NAME) instanceof String name)) throw missingFieldException(NAME);
if (!json.has(EMAIL) || !(json.get(EMAIL) instanceof String email)) throw missingFieldException(EMAIL); if (!json.has(EMAIL) || !(json.get(EMAIL) instanceof String email)) throw missingFieldException(EMAIL);
var taxId = json.has(TAX_ID) && json.get(TAX_ID) instanceof String tid ? tid : null; if (!json.has(TAX_ID) || !(json.get(TAX_ID) instanceof String taxId)) throw missingFieldException(TAX_ID);
var lang = json.has(LANGUAGE) && json.get(LANGUAGE) instanceof String l ? l : FALLBACK_LANG; var lang = json.has(LANGUAGE) && json.get(LANGUAGE) instanceof String l ? l : FALLBACK_LANG;
return new Customer(id,name,email,taxId,lang); return new Customer(id,name,email,taxId,lang);
} }
@ -102,11 +104,11 @@ public final class Customer implements Mappable {
@Override @Override
public Map<String, Object> toMap() { public Map<String, Object> toMap() {
var map = new HashMap<String,Object>(); return Map.of(
map.put("id", id); "id", id,
map.put("name", name); "name", name,
map.put("email", email); "email", email,
map.put("tax_id", emptyIfNull(taxNumber)); "tax_id", taxNumber
return map; );
} }
} }

1
documents/src/main/java/de/srsoftware/umbrella/documents/Constants.java

@ -15,7 +15,6 @@ public class Constants {
public static final String CLONE = "clone"; public static final String CLONE = "clone";
public static final String CONFIG_DATABASE = "umbrella.modules.document.database"; public static final String CONFIG_DATABASE = "umbrella.modules.document.database";
public static final String CONFIG_TEMPLATES = "umbrella.modules.document.templates"; public static final String CONFIG_TEMPLATES = "umbrella.modules.document.templates";
public static final String CONTACT_ID = "contact_id";
public static final String CONTACTS = "contacts"; public static final String CONTACTS = "contacts";
public static final String CUSTOMERS = "customers"; public static final String CUSTOMERS = "customers";

27
documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java

@ -460,37 +460,28 @@ public class DocumentApi extends BaseHandler implements DocumentService {
private boolean postDocument(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException { private boolean postDocument(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
var json = json(ex); var json = json(ex);
if (!(json.has(SENDER) && json.get(SENDER) instanceof JSONObject senderData)) throw missingFieldException(SENDER); if (!(json.has(SENDER) && json.get(SENDER) instanceof JSONObject senderData)) throw missingFieldException(SENDER);
if (!senderData.has(COMPANY) || !(senderData.get(COMPANY) instanceof Number rawCompId)) throw missingFieldException(COMPANY); if (!senderData.has(COMPANY) || !(senderData.get(COMPANY) instanceof Number companyId)) throw missingFieldException(COMPANY);
var companyId = rawCompId.longValue();
var company = companyService().get(companyId); var company = companyService().get(companyId.longValue());
if (!companyService().membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company); if (!companyService().membership(companyId.longValue(),user.id())) throw forbidden("You are mot a member of company {0}",company);
if (!json.has(CUSTOMER) || !(json.get(CUSTOMER) instanceof JSONObject customerData)) throw missingFieldException(CUSTOMER); if (!json.has(CUSTOMER) || !(json.get(CUSTOMER) instanceof JSONObject customerData)) throw missingFieldException(CUSTOMER);
if (!json.has(TYPE) || !(json.get(TYPE) instanceof Number docTypeId)) throw missingFieldException(TYPE); if (!json.has(TYPE) || !(json.get(TYPE) instanceof Number docTypeId)) throw missingFieldException(TYPE);
var type = db.getType(docTypeId.intValue()); var type = db.getType(docTypeId.intValue());
var customer = Customer.of(customerData); var customer = Customer.of(customerData);
Template template = new Template(6,companyId,"unknwon",null); Template template = new Template(6,companyId.longValue(),"unknwon",null);
String currency = company.currency(); String currency = company.currency();
String sep = company.decimalSeparator(); String sep = company.decimalSeparator();
var settings = db.getCustomerSettings(companyId,type,customer.id()); var settings = db.getCustomerSettings(companyId.longValue(),type,customer.id());
var newCustomer = settings == null; if (settings == null) settings = CustomerSettings.empty();
if (newCustomer) settings = CustomerSettings.empty(); var companySettings = db.getCompanySettings(companyId.longValue(),type);
var companySettings = db.getCompanySettings(companyId,type);
var nextNumber = companySettings.nextDocId(); var nextNumber = companySettings.nextDocId();
String lastHead = settings.header(); String lastHead = settings.header();
String lastFooter = settings.footer(); String lastFooter = settings.footer();
var sender = Sender.of(senderData); var sender = Sender.of(senderData);
LOG.log(DEBUG,json.toString(2)); LOG.log(DEBUG,json.toString(2));
var doc = new Document(0,companyId,nextNumber,type, LocalDate.now(), NEW,template,null,lastHead,lastFooter,currency,sep,sender,customer,new PositionList()); var doc = new Document(0,companyId.longValue(),nextNumber,type, LocalDate.now(), NEW,template,null,lastHead,lastFooter,currency,sep,sender,customer,new PositionList());
var saved = db.save(doc); var saved = db.save(doc);
if (newCustomer) {
if (customerData.get(CONTACT_ID) instanceof Number contactId) {
var contacts = contactService();
var contact = contacts.load(user.id(), contactId.longValue());
contacts.save(contact.setCustomerNumber(customer.id()));
}
companyService().saveNewCustomer(companyId,customer.id());
}
db.step(companySettings); db.step(companySettings);
return sendContent(ex,saved); return sendContent(ex,saved);
} }

36
frontend/src/Components/ContactSelector.svelte

@ -26,25 +26,23 @@
contact.FN = fn(contact.vcard); contact.FN = fn(contact.vcard);
contact.ORG = org(contact.vcard); contact.ORG = org(contact.vcard);
const extras = contact.vcard.match(/^X-.*:.+/gm); const extras = contact.vcard.match(/^X-.*:.+/gm);
if (extras) { for (let ex of extras){
for (let ex of extras){ ex = extra(ex);
ex = extra(ex); switch (ex.name){
switch (ex.name){ case 'CUSTOMER-NUMBER':
case 'CUSTOMER-NUMBER': contact.customer_number = ex.value;
contact.customer_number = ex.value; break;
break; case 'TAX-NUMBER':
case 'TAX-NUMBER': contact.tax_id = ex.value;
contact.tax_id = ex.value; break;
break; case 'BANK-ACCOUNT':
case 'BANK-ACCOUNT': contact.bank_account = ex.value;
contact.bank_account = ex.value; break;
break; case 'COURT':
case 'COURT': contact.local_court = ex.value;
contact.local_court = ex.value; break;
break; default:
default: console.log(ex);
console.log(ex);
}
} }
} }
delete contact.vcard; delete contact.vcard;

21
frontend/src/routes/document/Add.svelte

@ -2,7 +2,7 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router'; import { useTinyRouter } from 'svelte-tiny-router';
import { api, get } from '../../urls.svelte'; import { api } from '../../urls.svelte';
import { error, yikes } from '../../warn.svelte'; import { error, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte'; import { t } from '../../translations.svelte';
@ -67,21 +67,10 @@
if (contact.ADR.street) addr += contact.ADR.street+"\n"; if (contact.ADR.street) addr += contact.ADR.street+"\n";
if (contact.ADR.locality) addr += contact.ADR.post_code + " "+ contact.ADR.locality + "\n"; if (contact.ADR.locality) addr += contact.ADR.post_code + " "+ contact.ADR.locality + "\n";
if (contact.ADR.county) addr += contact.ADR.country+"\n"; if (contact.ADR.county) addr += contact.ADR.country+"\n";
document.customer.name = addr; document.customer.name = addr;
document.customer.tax_id = contact.tax_id; document.customer.tax_id = contact.tax_id;
document.customer.id = contact.customer_number; document.customer.id = contact.customer_number;
document.customer.email = contact.EMAIL; document.customer.email = contact.EMAIL;
document.customer.contact_id = contact.id;
if (!document.customer.id) requestNewCustomerId();
}
async function requestNewCustomerId(){
var url = api(`company/${document.sender.company}/next_customer_number`);
var res = await get(url);
yikes();
document.customer.id = await res.text();
if (res.ok){
} else error(res);
} }
async function submit(){ async function submit(){

2
frontend/src/routes/files/Index.svelte

@ -6,7 +6,7 @@
import { t } from '../../translations.svelte'; import { t } from '../../translations.svelte';
import { user } from '../../user.svelte'; import { user } from '../../user.svelte';
const image_extensions = ['jpg','jpeg','gif','png','svg','webp']; const image_extensions = ['jpg','jpeg','gif','png','svg'];
const router = useTinyRouter(); const router = useTinyRouter();
let children = $state({}); let children = $state({});
let new_dir = $state(null); let new_dir = $state(null);

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

@ -6,9 +6,6 @@
import { error, yikes } from '../../warn.svelte'; import { error, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte.js'; import { t } from '../../translations.svelte.js';
let filter = $state(null);
let lower_filter = $derived(filter.toLowerCase());
let inverted_filter = $state(false);
let projects = $state({}); let projects = $state({});
let router = useTinyRouter(); let router = useTinyRouter();
let tasks = $state(null); let tasks = $state(null);
@ -47,19 +44,6 @@
router.navigate(`/task/${tid}/edit`); router.navigate(`/task/${tid}/edit`);
} }
function filterApplies(task){
if (!filter) return !inverted_filter;
if (task.name.toLowerCase().includes(lower_filter)) return !inverted_filter;
if (task.description.source.toLowerCase().includes(lower_filter)) return !inverted_filter;
if (projects[task.project_id].name.toLowerCase().includes(lower_filter)) return !inverted_filter;
if (task.parent_task_id){
const parent = map[task.parent_task_id];
if (parent && parent.name.toLowerCase().includes(lower_filter)) return !inverted_filter;
}
return inverted_filter;
}
function go(module, id){ function go(module, id){
router.navigate(`/${module}/${id}/view`); router.navigate(`/${module}/${id}/view`);
} }
@ -119,17 +103,6 @@
<fieldset> <fieldset>
<legend>{loading ? t('loading_object',{object:t('task_list')}) : t('task_list')}</legend> <legend>{loading ? t('loading_object',{object:t('task_list')}) : t('task_list')}</legend>
<div class="filter">
<label>
{t('filter')}: <input type="text" bind:value={filter} >
</label>
{#if filter}
<label>
<input type="checkbox" bind:checked={inverted_filter} />
{t('invert_filter')}
</label>
{/if}
</div>
{#if tasks} {#if tasks}
<table> <table>
<thead> <thead>
@ -144,7 +117,7 @@
</thead> </thead>
<tbody> <tbody>
{#each tasks as task,idx} {#each tasks as task,idx}
{#if task.status > 10 && task.status < 60 && !task.no_index && projects[task.project_id]?.status < 60 && !hidden[task.id] && filterApplies(task)} {#if task.status > 10 && task.status < 60 && !task.no_index && projects[task.project_id]?.status < 60 && !hidden[task.id]}
<tr> <tr>
<td onclick={() => go('task',task.id)}>{task.name}</td> <td onclick={() => go('task',task.id)}>{task.name}</td>
<td> <td>
@ -165,7 +138,7 @@
<td> <td>
<button class="symbol" onclick={() => edit(task.id)} title={t('edit')} ></button> <button class="symbol" onclick={() => edit(task.id)} title={t('edit')} ></button>
<button class="symbol" onclick={() => postpone(idx)} title={t('postpone')} ></button> <button class="symbol" onclick={() => postpone(idx)} title={t('postpone')} ></button>
<button class="symbol" onclick={() => open(idx)} title={t('state_open')}></button> <button class="symbol" onclick={() => open(idx)} title={t('open')}></button>
<button class="symbol" onclick={() => start(idx)} title={t('start')} ></button> <button class="symbol" onclick={() => start(idx)} title={t('start')} ></button>
<button class="symbol" onclick={() => complete(idx)} title={t('complete')} ></button> <button class="symbol" onclick={() => complete(idx)} title={t('complete')} ></button>
<button class="symbol" onclick={() => abort(idx)} title={t('abort')} ></button> <button class="symbol" onclick={() => abort(idx)} title={t('abort')} ></button>

4
frontend/src/urls.svelte.js

@ -4,10 +4,6 @@ export function api(rel_path){
return `${location.protocol}//${location.host.replace('5173','8080')}/api/${rel_path}`; return `${location.protocol}//${location.host.replace('5173','8080')}/api/${rel_path}`;
} }
export function get(url){
return fetch(url,{ credentials:'include' });
}
export function drop(url){ export function drop(url){
return fetch(url,{ return fetch(url,{
credentials:'include', credentials:'include',

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

@ -1,7 +1,6 @@
/* © SRSoftware 2025 */ /* © SRSoftware 2025 */
package de.srsoftware.umbrella.stock; package de.srsoftware.umbrella.stock;
import static de.srsoftware.tools.Optionals.is0;
import static de.srsoftware.tools.Optionals.nullIfEmpty; import static de.srsoftware.tools.Optionals.nullIfEmpty;
import static de.srsoftware.tools.jdbc.Condition.equal; import static de.srsoftware.tools.jdbc.Condition.equal;
import static de.srsoftware.tools.jdbc.Condition.isNull; import static de.srsoftware.tools.jdbc.Condition.isNull;
@ -303,11 +302,11 @@ public class SqliteDb extends BaseDb implements StockDb {
@Override @Override
public DbLocation save(DbLocation location) { public DbLocation save(DbLocation location) {
var parentId = is0(location.parent()) ? null : location.parent(); var parentId = location.parent() == 0 ? null : location.parent();
if (location.id() == 0) { // new location if (location.id() == 0) { // new location
try { try {
var rs = insertInto(TABLE_LOCATIONS,OWNER,PARENT_LOCATION_ID,NAME,DESCRIPTION) var rs = insertInto(TABLE_LOCATIONS,OWNER,PARENT_LOCATION_ID,NAME,DESCRIPTION)
.values(location.owner().dbCode(),parentId,location.name(),location.description()) .values(location.owner().dbCode(),location.parent() == 0 ? null : parentId,location.name(),location.description())
.execute(db).getGeneratedKeys(); .execute(db).getGeneratedKeys();
long id = 0; long id = 0;
if (rs.next()) id = rs.getLong(1); if (rs.next()) id = rs.getLong(1);

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

@ -119,7 +119,6 @@
"impersonate": "zu Nutzer wechseln", "impersonate": "zu Nutzer wechseln",
"IMPERSONATE": "Nutzer wechseln", "IMPERSONATE": "Nutzer wechseln",
"index_page": "Aufgabenübersicht", "index_page": "Aufgabenübersicht",
"invert_filter": "Filter umkehren",
"invoice": "Rechnung", "invoice": "Rechnung",
"item": "Artikel", "item": "Artikel",
"items": "Artikel", "items": "Artikel",

2
translations/src/main/resources/en.json

@ -119,9 +119,7 @@
"impersonate": "impersonate", "impersonate": "impersonate",
"IMPERSONATE": "impersonate", "IMPERSONATE": "impersonate",
"index_page": "task overview", "index_page": "task overview",
"invert_filter": "Filter umkehren",
"invoice": "invoice", "invoice": "invoice",
"item": "Item",
"items": "items", "items": "items",
"join_objects" : "join {objects}", "join_objects" : "join {objects}",

9
web/src/main/resources/web/css/bloodshed-color.css

@ -55,15 +55,6 @@ tr:hover a{
color: black; color: black;
} }
tr:hover .taglist .tag {
border-color: black;
}
tr:hover .taglist .tag button {
background: darkred;
color: black;
}
.archive{ .archive{
background: red; background: red;
color: black; color: black;

4
web/src/main/resources/web/css/bloodshed.css

@ -53,10 +53,6 @@ footer {
margin: 5px; margin: 5px;
} }
img {
max-width: 100%;
}
nav { nav {
position: sticky; position: sticky;
z-index: 100; z-index: 100;

9
web/src/main/resources/web/css/default-color.css

@ -54,15 +54,6 @@ tr:hover a{
color: black; color: black;
} }
tr:hover .taglist .tag {
border-color: black;
}
tr:hover .taglist .tag button {
background: orange;
color: black;
}
.archive{ .archive{
background: black; background: black;
} }

6
web/src/main/resources/web/css/default.css

@ -41,7 +41,7 @@ fieldset[tabindex="0"]{
overflow: hidden; overflow: hidden;
} }
fieldset[tabindex="0"]:hover{ fieldset[tabindex="0"]:focus-within{
max-height: unset; max-height: unset;
} }
@ -53,10 +53,6 @@ footer {
margin: 5px; margin: 5px;
} }
img {
max-width: 100%;
}
nav { nav {
position: sticky; position: sticky;
z-index: 100; z-index: 100;

13
web/src/main/resources/web/css/winter-color.css

@ -45,19 +45,6 @@ tr:hover{
background: #afffff; background: #afffff;
} }
tr:hover a{
color: black;
}
tr:hover .taglist .tag {
border-color: black;
}
tr:hover .taglist .tag button {
//background: orange;
color: black;
}
.archive{ .archive{
background: white; background: white;
} }

6
web/src/main/resources/web/css/winter.css

@ -41,7 +41,7 @@ fieldset[tabindex="0"]{
overflow: hidden; overflow: hidden;
} }
fieldset[tabindex="0"]:hover{ fieldset[tabindex="0"]:focus-within{
max-height: unset; max-height: unset;
} }
@ -53,10 +53,6 @@ footer {
margin: 5px; margin: 5px;
} }
img {
max-width: 100%;
}
nav { nav {
position: sticky; position: sticky;
z-index: 100; z-index: 100;

Loading…
Cancel
Save