Files
Umbrella/frontend/src/routes/document/View.svelte
Stephan Richter ccb84995cb document db no longer storing complete template information in separate table:
- dropped table templates
- altered table documents: template_id (ref into templates) → template (name of template)
- templates are now picked up by the document registry

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2025-11-25 10:21:36 +01:00

268 lines
9.5 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
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';
import LineEditor from '../../Components/LineEditor.svelte';
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
import MultilineEditor from '../../Components/MultilineEditor.svelte';
import Notes from '../notes/RelatedNotes.svelte';
import PositionList from './PositionList.svelte';
import PositionSelector from './PositionSelector.svelte';
import StateSelector from './StateSelector.svelte';
import Tags from '../tags/TagList.svelte';
import TemplateSelector from './TemplateSelector.svelte';
import TypeSelector from './TypeSelector.svelte';
let doc = $state(null);
let editable = $derived(doc.state == 1);
let { id } = $props();
let pdfDisabled = $state(false);
let position_select = $state(false);
const router = useTinyRouter();
let sndDisabled = $state(false);
async function addPosition(selected){
const url = api(`document/${doc.id}/position`);
const resp = await post(url,selected);
if (resp.ok){
doc.positions = await resp.json();
yikes();
} else {
error(resp);
}
}
async function changeState(newVal){
let success = false;
if (doc.state == 1 || confirm(t('confirm_state'))){
success = await update('state',newVal);
}
if (success) {
doc.state = newVal;
} else {
const dummy = doc.state;
doc.state = null; // we need to alter in between,
doc.state = dummy; // otherwise the state will not be re-set
}
}
async function createSuccessorDoc(type){
const url = api(`document/${id}/clone`);
const res = await fetch(url,{
credentials :'include',
method : 'POST',
body : type
});
if (res.ok) {
yikes();
let json = await res.json();
router.navigate(`/document/${json.id}/view`);
loadDoc();
} else {
error(res);
}
}
async function loadDoc(){
const url = api(`document/${id}`);
const resp = await fetch(url,{credentials:'include'});
if (resp.ok){
doc = await resp.json();
yikes();
} else {
error(resp);
}
}
async function render(ev){
pdfDisabled = true;
const url = api(`document/${doc.id}/pdf`);
const resp = await fetch(url,{credentials:'include'});
if (resp.ok){
yikes();
const blob = await resp.blob();
const parts = resp.headers.get("Content-disposition").split(";");
const filename = parts[1].split('=')[1].split('"').join('');
var fileLink = document.createElement('a');
fileLink.href = URL.createObjectURL(blob);
fileLink.download = filename;
fileLink.click();
} else {
error(resp);
}
pdfDisabled = false;
}
async function update(path,newValue){
const parts = path.split('.');
if (parts.length<1) return false;
let data = newValue;
while (parts.length > 0){
const inner = data;
data = {};
data[parts.pop()] = inner;
}
try {
const url = api(`document/${doc.id}`);
const resp = await fetch(url,{
credentials : 'include',
method : 'PATCH',
body : JSON.stringify(data)
});
return resp.ok;
} catch (err){
return false;
}
}
onMount(loadDoc);
</script>
<svelte:head>
<title>Umbrella {t('document')} {doc?.number}</title>
</svelte:head>
{#if doc}
<fieldset class="customer">
<legend>{t('customer')}</legend>
<table>
<tbody>
<tr>
<td colspan="2">
<MultilineEditor bind:value={doc.customer.name} editable={editable} onSet={(val) => update('customer.name',val)} />
</td>
</tr>
<tr>
<th>{t('customer_id')}:</th>
<td>
<LineEditor bind:value={doc.customer.id} editable={editable} onSet={(val) => update('customer.id',val)} />
</td>
</tr>
<tr>
<th>{t('tax_id')}:</th>
<td>
<LineEditor bind:value={doc.customer.tax_id} editable={editable} onSet={(val) => update('customer.tax_id',val)} />
</td>
</tr>
<tr>
<th>{t('email')}:</th>
<td>
<LineEditor bind:value={doc.customer.email} editable={editable} onSet={(val) => update('customer.email',val)} />
</td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset class="sender">
<legend>{t('sender')}</legend>
<table>
<tbody>
<tr>
<td colspan="2">
<MultilineEditor bind:value={doc.sender.name} editable={editable} onSet={(val) => update('sender.name',val)} />
</td>
</tr>
<tr>
<th>{t('local_court')}:</th>
<td>
<LineEditor bind:value={doc.sender.court} editable={editable} onSet={(val) => update('sender.court',val)} />
</td>
</tr>
<tr>
<th>{t('tax_id')}:</th>
<td>
<LineEditor bind:value={doc.sender.tax_id} editable={editable} onSet={(val) => update('sender.tax_id',val)} />
</td>
</tr>
<tr>
<th>{t('bank_account')}:</th>
<td>
<MultilineEditor bind:value={doc.sender.bank_account} editable={editable} onSet={(val) => update('sender.bank_account',val)} />
</td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset class="invoice_data">
<legend>{t('type_'+doc.type)}</legend>
<table>
<tbody>
<tr>
<th>{t('number')}:</th>
<td><LineEditor bind:value={doc.number} editable={editable} onSet={(val) => update('number',val)} /></td>
</tr>
<tr>
<th>{t('state')}:</th>
<StateSelector selected={doc.state} onchange={changeState} onSet={(val) => update('state',val)} />
</tr>
<tr>
<th>{t('date')}:</th>
<LineEditor bind:value={doc.date} editable={editable} onSet={(val) => update('date',val)} />
</tr>
<tr>
<th>{t('delivery_date')}:</th>
<LineEditor bind:value={doc.delivery} editable={editable} onSet={(val) => update('delivery',val)} />
</tr>
<tr>
<th>{t('template')}:</th>
<td>
{#if editable}
<TemplateSelector company={doc.company.id} bind:value={doc.template} onchange={() => update('template_id',doc.template)} />
{:else}
{doc.template.name}
{/if}
</td>
</tr>
<tr>
<th>{t('create_new_object',{object:t('succeeding_document')})}:</th>
<td>
<TypeSelector caption={t('choose_type')} onSelect={createSuccessorDoc} />
</td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset class="clear">
<legend>{t('head')}</legend>
<MarkdownEditor bind:value={doc.head} editable={editable} onSet={(val) => update('head',val)} />
</fieldset>
<fieldset>
<legend>
{t('positions')}
{#if editable}
<button onclick={() => position_select = true}>{t('add_object',{object:t('position')})}</button>
{/if}
</legend>
<PositionList bind:document={doc} submit={update} />
</fieldset>
<fieldset>
<legend>{t('footer')}</legend>
<MarkdownEditor bind:value={doc.footer} editable={editable} onSet={(val) => update('footer',val)} />
</fieldset>
<fieldset>
<legend>{t('tags')}</legend>
<Tags module="document" {id} user_list={[+user.id]} />
</fieldset>
<fieldset>
<legend>{t('actions')}</legend>
<button onclick={render} disabled={pdfDisabled}>{t('create_pdf')}</button>
<button onclick={() => router.navigate(`/document/${doc.id}/send`)} >{t('send_document')}</button>
</fieldset>
{/if}
<div class="notes">
<h3>{t('notes')}</h3>
<Notes module="document" entity_id={id} />
</div>
{#if position_select}
<PositionSelector close={() => position_select=false} {doc} onSelect={addPosition} />
{/if}