Re-implemented guessing of next document id.

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2025-11-24 23:24:31 +01:00
parent 6bb03f4e04
commit 2cd53b19cb
10 changed files with 72 additions and 135 deletions

View File

@@ -9,7 +9,6 @@ import java.util.regex.Pattern;
public class Constants {
private Constants(){}
public static final Pattern POST_CODE = compile("(.*\\w+.*)\n(.*\\d+.*)\n(\\d{5}) (\\w+)",DOTALL);
public static final String CLONE = "clone";
@@ -21,8 +20,6 @@ public class Constants {
public static final String ERROR_ADDRESS_MISSING = "{0} address does not contain street address / post code / city";
public static final String MOVE = "move";
public static final String PATH_ADD_ITEM = "add_item";
@@ -38,8 +35,6 @@ public class Constants {
public static final String POSITION = "position";
public static final String PROJECT_ID = "project_id";
public static final String TABLE_COMPANY_SETTINGS = "company_settings";
public static final String TABLE_CUSTOMER_SETTINGS = "company_customer_settings";
public static final String TABLE_DOCUMENTS = "documents";
public static final String TABLE_DOCUMENT_TYPES = "document_types";
@@ -47,4 +42,9 @@ public class Constants {
public static final String TABLE_PRICES = "customer_prices";
public static final String TABLE_TEMPLATES = "templates";
public static final String TEMPLATES = "templates";
public static final String TYPE_CONFIRMATION = "confirmation";
public static final String TYPE_INVOICE = "invoice";
public static final String TYPE_OFFER = "offer";
public static final String TYPE_REMINDER = "reminder";
}

View File

@@ -432,8 +432,7 @@ public class DocumentApi extends BaseHandler implements DocumentService {
Document doc = getDocument(docId, user).a;
var companySettings = db.getCompanySettings(doc.companyId(),docType);
var nextNumber = companySettings.nextDocId();
var nextNumber = db.nextDocId(user.language(),doc.companyId(),docType);
Document clone = new Document(
0,
@@ -469,19 +468,17 @@ public class DocumentApi extends BaseHandler implements DocumentService {
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,"unknwon",null);
String currency = company.currency();
String sep = company.decimalSeparator();
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();
var nextNumber = db.nextDocId(user.language(), companyId,type);
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,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,null,null,lastHead,lastFooter,currency,sep,sender,customer,new PositionList());
var saved = db.save(doc);
if (newCustomer) {
if (customerData.get(CONTACT_ID) instanceof Number contactId) {
@@ -491,7 +488,6 @@ public class DocumentApi extends BaseHandler implements DocumentService {
}
companyService().saveNewCustomer(companyId,customer.id());
}
db.step(companySettings);
return sendContent(ex,saved);
}

View File

@@ -27,8 +27,6 @@ public interface DocumentDb {
CustomerSettings getCustomerSettings(long companyId, Type docType, String customerId) throws UmbrellaException;
CompanySettings getCompanySettings(long companyId, Type docType) throws UmbrellaException;
Collection<Template> getCompanyTemplates(long l) throws UmbrellaException;
Type getType(int typeId) throws UmbrellaException;
@@ -39,12 +37,7 @@ public interface DocumentDb {
Document loadDoc(long docId) throws UmbrellaException;
/**
* decrement the document number
* @param companyId
* @param type
*/
void rollback(long companyId, Type type) throws UmbrellaException;
String nextDocId(String language, long companyId, Type type);
Document save(Document document) throws UmbrellaException;
@@ -52,11 +45,5 @@ public interface DocumentDb {
CustomerSettings save(long companyId, Type docType, String customerId, CustomerSettings settings) throws UmbrellaException;
/**
* increment the document number
* @param settings containing company id and document type
*/
void step(CompanySettings settings);
Pair<Integer> switchPositions(long docId, Pair<Integer> longPair) throws UmbrellaException;
}

View File

@@ -10,6 +10,7 @@ import static de.srsoftware.umbrella.core.Field.*;
import static de.srsoftware.umbrella.core.Field.COMPANY_ID;
import static de.srsoftware.umbrella.core.Field.TAX;
import static de.srsoftware.umbrella.core.Field.UNIT;
import static de.srsoftware.umbrella.core.ModuleRegistry.translator;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
import static de.srsoftware.umbrella.core.model.Document.DEFAULT_THOUSANDS_SEPARATOR;
import static de.srsoftware.umbrella.core.model.Document.State;
@@ -20,6 +21,7 @@ import static java.time.ZoneOffset.UTC;
import de.srsoftware.tools.Pair;
import de.srsoftware.tools.jdbc.Query;
import de.srsoftware.umbrella.core.BaseDb;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.*;
import de.srsoftware.umbrella.documents.model.*;
@@ -28,30 +30,33 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.util.*;
import java.util.regex.Pattern;
public class SqliteDb implements DocumentDb{
public class SqliteDb extends BaseDb implements DocumentDb{
private static final System.Logger LOG = System.getLogger(SqliteDb.class.getSimpleName());
private final Connection db;
private static final String DB_VERSION = "message_db_version";
private static final int INITIAL_DB_VERSION = 1;
private static final Pattern NUMBER_PATTERN = Pattern.compile("(\\D*)(\\d+)(.*)");
public SqliteDb(Connection conn){
db = conn;
init();
public SqliteDb(Connection connection) {
super(connection);
}
private int createTables() {
protected int createTables() {
int currentVersion = createSettingsTable();
switch (currentVersion) {
case 0:
createTableDocumentTypes();
createTableTemplates();
createTableDocuments();
createTablePositions();
createTableCustomerPrices();
createTableCompanySettings();
createTableCustomerSettings();
return createTableSettings();
}
return setCurrentVersion(1);
}
private void createTableCompanySettings() {
/*private void createTableCompanySettings() {
var sql = "CREATE TABLE IF NOT EXISTS {0} ({1} INT NOT NULL, {2} INT NOT NULL, {3} TEXT DEFAULT \"A\", {4} TEXT DEFAULT NULL, {5} INT NOT NULL DEFAULT 1, PRIMARY KEY ({1}, {2}))";
try {
var stmt = db.prepareStatement(format(sql,TABLE_COMPANY_SETTINGS, COMPANY_ID,DOC_TYPE_ID,TYPE_PREFIX,TYPE_SUFFIX,TYPE_NUMBER));
@@ -61,7 +66,7 @@ public class SqliteDb implements DocumentDb{
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_COMPANY_SETTINGS,e);
throw new RuntimeException(e);
}
}
}*/
private void createTableCustomerPrices() {
var sql = "CREATE TABLE IF NOT EXISTS {0} ({1} INT NOT NULL, {2} VARCHAR(255), {3} VARCHAR(50), {4} INTEGER)";
@@ -82,7 +87,7 @@ public class SqliteDb implements DocumentDb{
stmt.execute();
stmt.close();
} catch (SQLException e) {
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_COMPANY_SETTINGS,e);
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_CUSTOMER_SETTINGS,e);
throw new RuntimeException(e);
}
}
@@ -96,8 +101,8 @@ CREATE TABLE IF NOT EXISTS {0} (
{3} INT NOT NULL,
{4} TEXT NOT NULL,
{5} TIMESTAMP NOT NULL,
{6} INT NOT NULL NULL,
{7} INT NOT NULL,
{6} INT NOT NULL,
{7} INT,
{8} VARCHAR(100),
{9} TEXT,
{10} TEXT,
@@ -128,6 +133,12 @@ CREATE TABLE IF NOT EXISTS {0} (
var stmt = db.prepareStatement(format(createTable,TABLE_DOCUMENT_TYPES, ID,NEXT_TYPE, NAME));
stmt.execute();
stmt.close();
insertInto(TABLE_DOCUMENT_TYPES,ID,NEXT_TYPE,NAME)
.values(1,2,TYPE_OFFER)
.values(2,3,TYPE_CONFIRMATION)
.values(3,4,TYPE_INVOICE)
.values(4,4,TYPE_REMINDER)
.execute(db);
} catch (SQLException e) {
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_DOCUMENT_TYPES,e);
throw new RuntimeException(e);
@@ -241,22 +252,10 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
return pos;
}
@Override
public CompanySettings getCompanySettings(long companyId, Type docType) throws UmbrellaException {
try {
var rs = select(ALL).from(TABLE_COMPANY_SETTINGS).where(COMPANY_ID,equal(companyId)).where(DOC_TYPE_ID,equal(docType.id())).exec(db);
CompanySettings settings = null;
if (rs.next()) settings = CompanySettings.of(rs);
rs.close();
if (settings != null) return settings;
} catch (SQLException ignored) {
}
throw databaseException("Failed to load customer settings (company: {0}, document type: {1})",companyId, docType.name());
}
@Override
public Collection<Template> getCompanyTemplates(long companyId) throws UmbrellaException {
try {
var rs = select(ALL).from(TABLE_TEMPLATES).where(COMPANY_ID,equal(companyId)).exec(db);
var templates = new HashSet<Template>();
while (rs.next()) templates.add(Template.of(rs));
@@ -454,21 +453,28 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
}
@Override
public void rollback(long companyId, Type type) throws UmbrellaException {
public String nextDocId(String language, long companyId, Type type) {
try {
var settings = getCompanySettings(companyId,type);
var numbers = new HashSet<String>();
var rs = select(NUMBER).from(TABLE_DOCUMENTS).where(COMPANY_ID,equal(companyId)).exec(db);
while (rs.next()) numbers.add(rs.getString(NUMBER));
var rs = select(NUMBER).from(TABLE_DOCUMENTS).where(COMPANY_ID,equal(companyId)).where(TYPE_ID,equal(type.id())).sort(ID+" DESC").limit(1).exec(db);
String lastId = null;
if (rs.next()) lastId = rs.getString(1);
rs.close();
var previous = settings.previous();
while (previous.isPresent() && !numbers.contains(previous.get().nextDocId())) previous = previous.get().previous();
previous.ifPresent(this::step);
} catch (SQLException e){
// TODO
if (lastId == null) return translator().translate(language,type.name())+"-0001";
var numeric = NUMBER_PATTERN.matcher(lastId);
if (numeric.find()){
var prefix = numeric.group(1);
var digits = numeric.group(2);
var suffix = numeric.group(3);
var len = digits.length();
while (digits.startsWith("0")) digits = digits.substring(1);
var lid = Long.parseLong(digits)+1;
digits = ""+lid;
while (digits.length()<len) digits = "0"+digits;
return prefix+digits+suffix;
}
return lastId;
} catch (SQLException e) {
throw databaseException("Failed to read last document id");
}
}
@@ -493,8 +499,9 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
var timestamp = doc.date().atStartOfDay(UTC).toInstant().getEpochSecond();
var sender = doc.sender();
var custom = doc.customer();
var templateId = doc.template() == null ? null : doc.template().id();
var stmt = insertInto(TABLE_DOCUMENTS,TYPE_ID,COMPANY_ID, DATE, DELIVERY_DATE,FOOTER,HEAD, NUMBER, STATE, SENDER,TAX_NUMBER,BANK_ACCOUNT,COURT,CUSTOMER,CUSTOMER_EMAIL,CUSTOMER_NUMBER,CUSTOMER_TAX_NUMBER,TEMPLATE_ID,CURRENCY)
.values(doc.type().id(),doc.companyId(),timestamp,doc.delivery(),doc.footer(),doc.head(),doc.number(),doc.state().code(),sender.name(),sender.taxNumber(),sender.bankAccount(),sender.court(),custom.name(),custom.email(),custom.id(),custom.taxNumber(),doc.template().id(),doc.currency())
.values(doc.type().id(),doc.companyId(),timestamp,doc.delivery(),doc.footer(),doc.head(),doc.number(),doc.state().code(),sender.name(),sender.taxNumber(),sender.bankAccount(),sender.court(),custom.name(),custom.email(),custom.id(),custom.taxNumber(),templateId, doc.currency())
.execute(db);
var rs = stmt.getGeneratedKeys();
Long newId = null;
@@ -589,20 +596,6 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
}
}
@Override
public void step(CompanySettings settings) {
try {
update(TABLE_COMPANY_SETTINGS)
.set(TYPE_NUMBER)
.where(COMPANY_ID,equal(settings.companyId())).where(DOC_TYPE_ID,equal(settings.typeId()))
.prepare(db)
.apply(settings.typeNumber()+1)
.close();
} catch (SQLException e) {
LOG.log(WARNING,"Failed to increment doc number");
}
}
@Override
public Pair<Integer> switchPositions(long docId, Pair<Integer> pair) throws UmbrellaException {
try {
@@ -652,6 +645,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
var company = rs.getLong(COMPANY_ID);
var name = rs.getString(NAME);
var data = rs.getBytes(TEMPLATE);
if (id == 0) return new Template(0,company,"",null);
return new Template(id,company,name,data);
} catch (SQLException ignored){
return null;

View File

@@ -1,33 +0,0 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.documents.model;
import static de.srsoftware.tools.Optionals.emptyIfNull;
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.documents.Constants.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
public record CompanySettings(long companyId, long typeId, String typePrefix, String typeSuffix, long typeNumber) {
public String nextDocId(){
return typePrefix+typeNumber+typeSuffix;
}
public static CompanySettings of(ResultSet rs) throws SQLException {
var companyId = rs.getLong(COMPANY_ID);
var typeId = rs.getLong(DOC_TYPE_ID);
var typePrefix = emptyIfNull(rs.getString(TYPE_PREFIX));
var typeSuffix = emptyIfNull(rs.getString(TYPE_SUFFIX));
var typeNumber = rs.getLong(TYPE_NUMBER);
return new CompanySettings(companyId,typeId,typePrefix,typeSuffix,typeNumber);
}
public Optional<CompanySettings> previous(){
if (typeNumber<1) return Optional.empty();
return Optional.of(new CompanySettings(companyId,typeId,typePrefix,typeSuffix,typeNumber-1));
}
}

View File

@@ -45,8 +45,7 @@
description : description,
amount : 1,
unit : t('pieces'),
unit_price : unit_price,
tax : 0
unit_price : unit_price
});
}

View File

@@ -1,7 +1,7 @@
<script>
import {onMount} from 'svelte';
import {api} from '../../urls.svelte.js';
import {api, post} from '../../urls.svelte.js';
import {t} from '../../translations.svelte.js';
let {
@@ -16,11 +16,7 @@
async function loadTemplates(){
const url = api('document/templates');
var resp = await fetch(url,{
credentials : 'include',
method : 'POST',
body : JSON.stringify({company:company})
});
var resp = await post(url,{company:company});
if (resp.ok){
templates = await resp.json();
} else {

View File

@@ -2,7 +2,7 @@
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import {api} from '../../urls.svelte.js';
import { api, post } from '../../urls.svelte.js';
import { error, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte.js';
import { user } from '../../user.svelte.js';
@@ -29,11 +29,7 @@
async function addPosition(selected){
const url = api(`document/${doc.id}/position`);
const resp = await fetch(url,{
method : 'POST',
credentials : 'include',
body : JSON.stringify(selected)
});
const resp = await post(url,selected);
if (resp.ok){
doc.positions = await resp.json();
yikes();

View File

@@ -204,6 +204,7 @@
"permission_owner": "Besitzer",
"permission_read_only": "lesen",
"phone": "Telefon",
"pieces": "Stück",
"pos": "Pos",
"position": "Position",
"positions": "Positionen",

View File

@@ -204,6 +204,7 @@
"permission_owner": "owner",
"permission_read_only": "read-only",
"phone": "phone",
"pieces": "pieces",
"pos": "pos",
"position": "position",
"positions": "positions",