implemented
- assigning new customer numbers to contacts that don`t have one - updating customer number counter in company table Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
package de.srsoftware.umbrella.company;
|
||||
|
||||
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.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.ModuleRegistry.*;
|
||||
@@ -73,7 +74,12 @@ public class CompanyModule extends BaseHandler implements CompanyService {
|
||||
return switch (head){
|
||||
case LIST -> getCompanyList(user.get(),ex);
|
||||
case null,
|
||||
default -> super.doGet(path,ex);
|
||||
default -> {
|
||||
try {
|
||||
yield getCompany(user.get(), path, Long.parseLong(head), ex);
|
||||
} catch (NumberFormatException ignored){}
|
||||
yield super.doGet(path,ex);
|
||||
}
|
||||
};
|
||||
} catch (UmbrellaException e) {
|
||||
return send(ex,e);
|
||||
@@ -124,6 +130,14 @@ public class CompanyModule extends BaseHandler implements CompanyService {
|
||||
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 {
|
||||
var result = new HashMap<Long,Map<String,Object>>();
|
||||
for (var entry : listCompaniesOf(user).entrySet()) result.put(entry.getKey(),entry.getValue().toMap());
|
||||
@@ -137,6 +151,13 @@ public class CompanyModule extends BaseHandler implements CompanyService {
|
||||
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
|
||||
public Map<Long,Company> listCompaniesOf(UmbrellaUser user) throws UmbrellaException {
|
||||
return loadMembers(companyDb.listCompaniesOf(user.id()));
|
||||
@@ -205,4 +226,9 @@ public class CompanyModule extends BaseHandler implements CompanyService {
|
||||
var companies = companyDb.find(user.id(),keys);
|
||||
return sendContent(ex,mapValues(companies));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNewCustomer(long companyId, String id) {
|
||||
companyDb.saveNewCustomer(companyId, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ public class Constants {
|
||||
|
||||
public static final String CONFIG_DATABASE = "umbrella.modules.company.database";
|
||||
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 = "companies";
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import static de.srsoftware.umbrella.core.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.Field.*;
|
||||
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.unprocessable;
|
||||
import static java.lang.System.Logger.Level.ERROR;
|
||||
import static java.text.MessageFormat.format;
|
||||
|
||||
@@ -21,6 +22,7 @@ import de.srsoftware.umbrella.core.model.Company;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class SqliteDb extends BaseDb implements CompanyDb {
|
||||
@@ -126,6 +128,24 @@ 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
|
||||
public HashMap<Long, Company> find(long userId, Collection<String> keys) {
|
||||
try {
|
||||
@@ -208,4 +228,18 @@ CREATE TABLE IF NOT EXISTS "companies" (
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,13 @@ public interface CompanyDb {
|
||||
|
||||
Collection<Long> getMembers(long companyId) throws UmbrellaException;
|
||||
|
||||
String getNextCustomerNumber(long companyId);
|
||||
|
||||
Map<Long,Company> listCompaniesOf(long id) throws UmbrellaException;
|
||||
|
||||
Company load(long companyId) throws UmbrellaException;
|
||||
|
||||
Company save(Company company);
|
||||
|
||||
void saveNewCustomer(long companyId, String id);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ public interface ContactDb {
|
||||
|
||||
Map<Long,Contact> listContactsOf(long userId) throws UmbrellaException;
|
||||
|
||||
Contact load(long id, long userId);
|
||||
Contact load(long contactId, long userId);
|
||||
|
||||
Contact save(Contact contact);
|
||||
|
||||
|
||||
@@ -114,6 +114,11 @@ public class ContactModule extends BaseHandler implements ContactService {
|
||||
return sendContent(ex,mapValues(listContactsOf(user)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact load(long userId, long contactId) {
|
||||
return contactDb.load(contactId,userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long,Contact> listContactsOf(UmbrellaUser user) throws UmbrellaException {
|
||||
return contactDb.listContactsOf(user.id());
|
||||
@@ -130,4 +135,9 @@ public class ContactModule extends BaseHandler implements ContactService {
|
||||
contactDb.setOwner(user.id(),contact);
|
||||
return sendContent(ex,contact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact save(Contact contact) {
|
||||
return contactDb.save(contact);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,14 +87,14 @@ public class SqliteDb extends BaseDb implements ContactDb{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact load(long id, long userId) {
|
||||
public Contact load(long contactId, long userId) {
|
||||
try {
|
||||
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);
|
||||
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);
|
||||
Contact contact = null;
|
||||
if (rs.next()) contact = Contact.of(rs);
|
||||
rs.close();
|
||||
if (contact != null) return contact;
|
||||
throw notFound("Failed to load contact with id = {0}",id);
|
||||
throw notFound("Failed to load contact with id = {0}", contactId);
|
||||
} catch (SQLException e) {
|
||||
throw databaseException("Failed to load contacts of user {0}",userId);
|
||||
}
|
||||
|
||||
@@ -15,4 +15,6 @@ public interface CompanyService {
|
||||
Map<Long,Company> listCompaniesOf(UmbrellaUser user) throws UmbrellaException;
|
||||
|
||||
boolean membership(long companyId, long userId) throws UmbrellaException;
|
||||
|
||||
void saveNewCustomer(long companyId, String id);
|
||||
}
|
||||
|
||||
@@ -8,4 +8,8 @@ import java.util.Map;
|
||||
|
||||
public interface ContactService {
|
||||
Map<Long, Contact> listContactsOf(UmbrellaUser user) throws UmbrellaException;
|
||||
|
||||
Contact load(long userId, long contactId);
|
||||
|
||||
Contact save(Contact contact);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.srsoftware.umbrella.core.model;
|
||||
|
||||
import static de.srsoftware.umbrella.core.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
||||
import static java.text.MessageFormat.format;
|
||||
|
||||
import de.srsoftware.tools.Mappable;
|
||||
import java.sql.ResultSet;
|
||||
@@ -10,7 +11,21 @@ import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public record Contact(long id, String vcard) implements Mappable {
|
||||
public class Contact 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 {
|
||||
return new Contact(rs.getLong(ID),rs.getString(DATA));
|
||||
}
|
||||
@@ -21,8 +36,24 @@ public record Contact(long id, String vcard) implements Mappable {
|
||||
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
|
||||
public Map<String, Object> toMap() {
|
||||
return Map.of(ID,id,VCARD,vcard);
|
||||
}
|
||||
|
||||
public String vcard(){
|
||||
return vcard;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class Constants {
|
||||
public static final String CLONE = "clone";
|
||||
public static final String CONFIG_DATABASE = "umbrella.modules.document.database";
|
||||
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 CUSTOMERS = "customers";
|
||||
|
||||
|
||||
@@ -460,28 +460,37 @@ public class DocumentApi extends BaseHandler implements DocumentService {
|
||||
private boolean postDocument(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
|
||||
var json = json(ex);
|
||||
if (!(json.has(SENDER) && json.get(SENDER) instanceof JSONObject senderData)) throw missingFieldException(SENDER);
|
||||
if (!senderData.has(COMPANY) || !(senderData.get(COMPANY) instanceof Number companyId)) throw missingFieldException(COMPANY);
|
||||
|
||||
var company = companyService().get(companyId.longValue());
|
||||
if (!companyService().membership(companyId.longValue(),user.id())) throw forbidden("You are mot a member of company {0}",company);
|
||||
if (!senderData.has(COMPANY) || !(senderData.get(COMPANY) instanceof Number rawCompId)) throw missingFieldException(COMPANY);
|
||||
var companyId = rawCompId.longValue();
|
||||
var company = companyService().get(companyId);
|
||||
if (!companyService().membership(companyId,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(TYPE) || !(json.get(TYPE) instanceof Number docTypeId)) throw missingFieldException(TYPE);
|
||||
var type = db.getType(docTypeId.intValue());
|
||||
var customer = Customer.of(customerData);
|
||||
Template template = new Template(6,companyId.longValue(),"unknwon",null);
|
||||
Template template = new Template(6,companyId,"unknwon",null);
|
||||
String currency = company.currency();
|
||||
String sep = company.decimalSeparator();
|
||||
var settings = db.getCustomerSettings(companyId.longValue(),type,customer.id());
|
||||
if (settings == null) settings = CustomerSettings.empty();
|
||||
var companySettings = db.getCompanySettings(companyId.longValue(),type);
|
||||
var settings = db.getCustomerSettings(companyId,type,customer.id());
|
||||
var newCustomer = settings == null;
|
||||
if (newCustomer) settings = CustomerSettings.empty();
|
||||
var companySettings = db.getCompanySettings(companyId,type);
|
||||
var nextNumber = companySettings.nextDocId();
|
||||
String lastHead = settings.header();
|
||||
String lastFooter = settings.footer();
|
||||
var sender = Sender.of(senderData);
|
||||
LOG.log(DEBUG,json.toString(2));
|
||||
var doc = new Document(0,companyId.longValue(),nextNumber,type, LocalDate.now(), NEW,template,null,lastHead,lastFooter,currency,sep,sender,customer,new PositionList());
|
||||
var doc = new Document(0,companyId,nextNumber,type, LocalDate.now(), NEW,template,null,lastHead,lastFooter,currency,sep,sender,customer,new PositionList());
|
||||
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);
|
||||
return sendContent(ex,saved);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
|
||||
import { api } from '../../urls.svelte';
|
||||
import { api, get } from '../../urls.svelte';
|
||||
import { error, yikes } from '../../warn.svelte';
|
||||
import { t } from '../../translations.svelte';
|
||||
|
||||
@@ -67,10 +67,21 @@
|
||||
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.county) addr += contact.ADR.country+"\n";
|
||||
document.customer.name = addr;
|
||||
document.customer.tax_id = contact.tax_id;
|
||||
document.customer.id = contact.customer_number;
|
||||
document.customer.email = contact.EMAIL;
|
||||
document.customer.name = addr;
|
||||
document.customer.tax_id = contact.tax_id;
|
||||
document.customer.id = contact.customer_number;
|
||||
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(){
|
||||
|
||||
@@ -4,6 +4,10 @@ export function 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){
|
||||
return fetch(url,{
|
||||
credentials:'include',
|
||||
|
||||
Reference in New Issue
Block a user