Re-implemented guessing of next document id.
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -45,8 +45,7 @@
|
||||
description : description,
|
||||
amount : 1,
|
||||
unit : t('pieces'),
|
||||
unit_price : unit_price,
|
||||
tax : 0
|
||||
unit_price : unit_price
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
"permission_owner": "Besitzer",
|
||||
"permission_read_only": "lesen",
|
||||
"phone": "Telefon",
|
||||
"pieces": "Stück",
|
||||
"pos": "Pos",
|
||||
"position": "Position",
|
||||
"positions": "Positionen",
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
"permission_owner": "owner",
|
||||
"permission_read_only": "read-only",
|
||||
"phone": "phone",
|
||||
"pieces": "pieces",
|
||||
"pos": "pos",
|
||||
"position": "position",
|
||||
"positions": "positions",
|
||||
|
||||
Reference in New Issue
Block a user