implemented moving of document positions
This commit is contained in:
@@ -68,6 +68,7 @@ public class Constants {
|
|||||||
public static final String FIELD_TYPE_SUFFIX = "type_suffix";
|
public static final String FIELD_TYPE_SUFFIX = "type_suffix";
|
||||||
public static final String FIELD_UNIT = "unit";
|
public static final String FIELD_UNIT = "unit";
|
||||||
public static final String FIELD_UNIT_PRICE = "unit_price";
|
public static final String FIELD_UNIT_PRICE = "unit_price";
|
||||||
|
public static final String MOVE = "move";
|
||||||
|
|
||||||
public static final String PATH_ADD_ITEM = "add_item";
|
public static final String PATH_ADD_ITEM = "add_item";
|
||||||
public static final String PATH_ADD_TASK = "add_task";
|
public static final String PATH_ADD_TASK = "add_task";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
package de.srsoftware.umbrella.documents;
|
package de.srsoftware.umbrella.documents;
|
||||||
|
|
||||||
import static de.srsoftware.tools.MimeType.MIME_FORM_URL;
|
import static de.srsoftware.tools.MimeType.MIME_FORM_URL;
|
||||||
|
import static de.srsoftware.tools.Optionals.isSet;
|
||||||
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.Paths.LIST;
|
import static de.srsoftware.umbrella.core.Paths.LIST;
|
||||||
@@ -18,6 +19,7 @@ import static java.util.stream.Collectors.toMap;
|
|||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import de.srsoftware.configuration.Configuration;
|
import de.srsoftware.configuration.Configuration;
|
||||||
|
import de.srsoftware.tools.Pair;
|
||||||
import de.srsoftware.tools.Path;
|
import de.srsoftware.tools.Path;
|
||||||
import de.srsoftware.tools.SessionToken;
|
import de.srsoftware.tools.SessionToken;
|
||||||
import de.srsoftware.umbrella.core.BaseHandler;
|
import de.srsoftware.umbrella.core.BaseHandler;
|
||||||
@@ -123,7 +125,12 @@ public class DocumentApi extends BaseHandler {
|
|||||||
if (user.isEmpty()) return unauthorized(ex);
|
if (user.isEmpty()) return unauthorized(ex);
|
||||||
var head = path.pop();
|
var head = path.pop();
|
||||||
var docId = Long.parseLong(head);
|
var docId = Long.parseLong(head);
|
||||||
return patchDocument(docId,user.get(),ex);
|
head = path.pop();
|
||||||
|
return switch (head){
|
||||||
|
case POSITION -> patchDocumentPosition(docId,user.get(),ex);
|
||||||
|
case null -> patchDocument(docId,user.get(),ex);
|
||||||
|
default -> super.doPatch(path,ex);
|
||||||
|
};
|
||||||
} catch (NumberFormatException n){
|
} catch (NumberFormatException n){
|
||||||
return sendContent(ex,HTTP_UNPROCESSABLE,"Invalid document id");
|
return sendContent(ex,HTTP_UNPROCESSABLE,"Invalid document id");
|
||||||
} catch (UmbrellaException e) {
|
} catch (UmbrellaException e) {
|
||||||
@@ -219,16 +226,35 @@ public class DocumentApi extends BaseHandler {
|
|||||||
var doc = db.loadDoc(docId);
|
var doc = db.loadDoc(docId);
|
||||||
var companyId = doc.companyId();
|
var companyId = doc.companyId();
|
||||||
if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",doc.companyId());
|
if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",doc.companyId());
|
||||||
var json = json(ex);
|
doc.patch(json(ex));
|
||||||
for (var key : json.keySet()){
|
|
||||||
var value = json.get(key);
|
|
||||||
LOG.log(WARNING,"{0} : {1}",key,value);
|
|
||||||
}
|
|
||||||
doc.patch(json);
|
|
||||||
db.save(doc);
|
db.save(doc);
|
||||||
return ok(ex);
|
return ok(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean send(HttpExchange ex,PositionList positions) throws IOException {
|
||||||
|
return sendContent(ex,positions.entrySet().stream().collect(toMap(Map.Entry::getKey,entry -> entry.getValue().renderToMap())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean patchDocumentPosition(long docId, UmbrellaUser user, HttpExchange ex) throws UmbrellaException, IOException {
|
||||||
|
var doc = db.loadDoc(docId);
|
||||||
|
var companyId = doc.companyId();
|
||||||
|
if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",doc.companyId());
|
||||||
|
if (doc.state() != NEW) throw forbidden("Document has already been send and is write-protected!");
|
||||||
|
var json = json(ex);
|
||||||
|
var step = json.has(MOVE) && json.get(MOVE) instanceof Number num ? num.intValue() : 0;
|
||||||
|
Integer number = json.has(POSITION) && json.get(POSITION) instanceof Number num ? num.intValue() : null;
|
||||||
|
if (isSet(number) && step != 0) {
|
||||||
|
var pos2 = number+step;
|
||||||
|
if (number >0 && pos2>0 && number <=doc.positions().size() && pos2<=doc.positions().size()){
|
||||||
|
db.switchPositions(docId,new Pair<>(number,pos2));
|
||||||
|
doc = db.loadDoc(docId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return send(ex,db.save(doc).positions());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
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);
|
||||||
@@ -254,7 +280,7 @@ public class DocumentApi extends BaseHandler {
|
|||||||
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.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);
|
||||||
db.step(companySettings);
|
db.step(companySettings);
|
||||||
return sendContent(ex,saved.toMap());
|
return sendContent(ex,saved);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean postDocumentPosition(long docId, HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
|
private boolean postDocumentPosition(long docId, HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
|
||||||
@@ -275,7 +301,7 @@ public class DocumentApi extends BaseHandler {
|
|||||||
var pos = new Position(doc.positions().size()+1,itemCode,amount.doubleValue(),unit,title,description,unitPrice.longValue(),tax,timeId,false);
|
var pos = new Position(doc.positions().size()+1,itemCode,amount.doubleValue(),unit,title,description,unitPrice.longValue(),tax,timeId,false);
|
||||||
doc.positions().add(pos);
|
doc.positions().add(pos);
|
||||||
|
|
||||||
return sendContent(ex,db.save(doc).positions().entrySet().stream().collect(toMap(Map.Entry::getKey,entry -> entry.getValue().renderToMap())));
|
return sendContent(ex,db.save(doc).positions());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean postTemplateList(HttpExchange ex, UmbrellaUser user) throws UmbrellaException, IOException {
|
private boolean postTemplateList(HttpExchange ex, UmbrellaUser user) throws UmbrellaException, IOException {
|
||||||
|
|||||||
@@ -5,11 +5,30 @@
|
|||||||
import LineEditor from '../../Components/LineEditor.svelte';
|
import LineEditor from '../../Components/LineEditor.svelte';
|
||||||
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
|
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
|
||||||
import PriceEditor from '../../Components/PriceEditor.svelte';
|
import PriceEditor from '../../Components/PriceEditor.svelte';
|
||||||
var { currency, editable, pos = $bindable(null), submit = (key,newVal) => {} } = $props();
|
var { currency, editable, pos = $bindable(null), submit = (key,newVal) => {}, movePos = (number,step) => {} } = $props();
|
||||||
let prefix = `pos.${pos.number}`
|
let prefix = `pos.${pos.number}`
|
||||||
|
|
||||||
|
function moveup(){
|
||||||
|
movePos(pos.number,-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function movedown(){
|
||||||
|
movePos(pos.number,1);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.move{
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{#if pos}
|
{#if pos}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td class="move">
|
||||||
|
{#if editable && pos.number>1}
|
||||||
|
<span onclick={moveup}>⏫</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
<td>{pos.number}</td>
|
<td>{pos.number}</td>
|
||||||
<td class="item">
|
<td class="item">
|
||||||
<LineEditor bind:value={pos.item} editable={editable} onSet={(val) => submit(`${prefix}.item`,val)} />
|
<LineEditor bind:value={pos.item} editable={editable} onSet={(val) => submit(`${prefix}.item`,val)} />
|
||||||
@@ -33,7 +52,11 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="move">⏫<br/>⏬</td>
|
<td class="move">
|
||||||
|
{#if editable}
|
||||||
|
<span onclick={movedown}>⏬</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
<td colspan="6" class="description">
|
<td colspan="6" class="description">
|
||||||
<MarkdownEditor bind:value={pos.description} editable={editable} onSet={(val) => submit(`${prefix}.description`,val)} />
|
<MarkdownEditor bind:value={pos.description} editable={editable} onSet={(val) => submit(`${prefix}.description`,val)} />
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -4,15 +4,33 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { t } from '../../translations.svelte.js';
|
import { t } from '../../translations.svelte.js';
|
||||||
|
|
||||||
var { document = $bindable(null), submit = (key,newVal) => {} } = $props();
|
var { document = $bindable(null), submit = (key,newVal) => {}, error = $bindable(null) } = $props();
|
||||||
|
|
||||||
let editable = $derived(document.state == 1);
|
let editable = $derived(document.state == 1);
|
||||||
|
|
||||||
|
async function movePos(number,step){
|
||||||
|
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/document/${document.id}/position`;
|
||||||
|
const resp = await fetch(url,{
|
||||||
|
method: 'PATCH',
|
||||||
|
credentials:'include',
|
||||||
|
body:JSON.stringify({position:number,move:step})
|
||||||
|
});
|
||||||
|
if (resp.ok){
|
||||||
|
let json = await resp.json();
|
||||||
|
document.positions = {};
|
||||||
|
setTimeout(() => document.positions = json,10)
|
||||||
|
error = null;
|
||||||
|
} else {
|
||||||
|
error = await resp.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if document.positions}
|
{#if document.positions}
|
||||||
<table class="positions">
|
<table class="positions">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>{t('document.pos')}</th>
|
<th>{t('document.pos')}</th>
|
||||||
<th>{t('document.code')}</th>
|
<th>{t('document.code')}</th>
|
||||||
<th>{t('document.title_or_desc')}</th>
|
<th>{t('document.title_or_desc')}</th>
|
||||||
@@ -25,9 +43,10 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each Object.entries(document.positions) as [id,pos]}
|
{#each Object.entries(document.positions) as [id,pos]}
|
||||||
<Position currency={document.currency} bind:pos={document.positions[id]} editable={editable} {submit} />
|
<Position currency={document.currency} bind:pos={document.positions[id]} editable={editable} {submit} {movePos} />
|
||||||
{/each}
|
{/each}
|
||||||
<tr class="sums">
|
<tr class="sums">
|
||||||
|
<td></td>
|
||||||
<td colspan="2"></td>
|
<td colspan="2"></td>
|
||||||
<td>{t('document.net_sum')}</td>
|
<td>{t('document.net_sum')}</td>
|
||||||
<td>{document.net_sum/100} {document.currency}</td>
|
<td>{document.net_sum/100} {document.currency}</td>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
const resp = await fetch(url,{credentials:'include'});
|
const resp = await fetch(url,{credentials:'include'});
|
||||||
if (resp.ok){
|
if (resp.ok){
|
||||||
doc = await resp.json();
|
doc = await resp.json();
|
||||||
|
error = null;
|
||||||
} else {
|
} else {
|
||||||
error = await resp.text();
|
error = await resp.text();
|
||||||
}
|
}
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
});
|
});
|
||||||
if (resp.ok){
|
if (resp.ok){
|
||||||
doc.positions = await resp.json();
|
doc.positions = await resp.json();
|
||||||
|
error = null;
|
||||||
} else {
|
} else {
|
||||||
error = await resp.text();
|
error = await resp.text();
|
||||||
}
|
}
|
||||||
@@ -189,7 +191,7 @@
|
|||||||
<button onclick={() => position_select = true}>{t('document.add_position')}</button>
|
<button onclick={() => position_select = true}>{t('document.add_position')}</button>
|
||||||
{/if}
|
{/if}
|
||||||
</legend>
|
</legend>
|
||||||
<PositionList bind:document={doc} {submit} />
|
<PositionList bind:document={doc} {submit} bind:error={error} />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t('document.footer')}</legend>
|
<legend>{t('document.footer')}</legend>
|
||||||
|
|||||||
Reference in New Issue
Block a user