2021-05-25 19:36:09 +00:00
|
|
|
<template>
|
|
|
|
<q-table
|
|
|
|
v-model:pagination="pagination"
|
|
|
|
title="Preistabelle"
|
|
|
|
:columns="columns"
|
|
|
|
:rows="drinks"
|
|
|
|
dense
|
|
|
|
:filter="search"
|
|
|
|
:filter-method="filter"
|
|
|
|
grid
|
|
|
|
:rows-per-page-options="[0]"
|
|
|
|
>
|
|
|
|
<template #top-right>
|
|
|
|
<div class="row justify-end q-gutter-sm">
|
|
|
|
<search-input v-model="search" :keys="search_keys" />
|
|
|
|
<slot></slot>
|
|
|
|
<q-btn v-if="!public && !nodetails" label="Aufpreise">
|
|
|
|
<q-menu anchor="center middle" self="center middle">
|
|
|
|
<min-price-setting />
|
|
|
|
</q-menu>
|
|
|
|
</q-btn>
|
|
|
|
<q-btn
|
|
|
|
v-if="!public && !nodetails && editable && hasPermission(PERMISSIONS.CREATE)"
|
|
|
|
color="primary"
|
|
|
|
round
|
|
|
|
icon="mdi-plus"
|
|
|
|
@click="newDrink"
|
|
|
|
>
|
|
|
|
<q-tooltip> Neues Getränk </q-tooltip>
|
|
|
|
</q-btn>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<template #item="props">
|
|
|
|
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4">
|
|
|
|
<q-card>
|
|
|
|
<q-img style="max-height: 256px" :src="image(props.row.uuid)">
|
|
|
|
<div
|
|
|
|
v-if="!public && !nodetails && editable"
|
|
|
|
class="absolute-top-right justify-end"
|
|
|
|
style="background-color: transparent"
|
|
|
|
>
|
|
|
|
<q-btn
|
|
|
|
round
|
|
|
|
icon="mdi-pencil"
|
|
|
|
style="background-color: rgba(0, 0, 0, 0.5)"
|
|
|
|
@click="editDrink = props.row"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="absolute-bottom-right justify-end">
|
|
|
|
<div class="text-subtitle1 text-right">
|
|
|
|
{{ props.row.name }}
|
|
|
|
</div>
|
|
|
|
<div class="text-caption text-right">
|
|
|
|
{{ props.row.type.name }}
|
|
|
|
</div>
|
|
|
|
</div>
|
2021-06-29 12:35:00 +00:00
|
|
|
<template #error>
|
2021-11-12 09:44:18 +00:00
|
|
|
<q-img class="bg-white" style="max-height: 256px" src="no-image.svg" />
|
2021-06-29 12:35:00 +00:00
|
|
|
<div
|
|
|
|
v-if="!public && !nodetails && editable"
|
|
|
|
class="absolute-top-right justify-end"
|
|
|
|
style="background-color: transparent"
|
|
|
|
>
|
|
|
|
<q-btn
|
|
|
|
round
|
|
|
|
icon="mdi-pencil"
|
|
|
|
style="background-color: rgba(0, 0, 0, 0.5)"
|
|
|
|
@click="editDrink = props.row"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="absolute-bottom-right justify-end">
|
|
|
|
<div class="text-subtitle1 text-right">
|
|
|
|
{{ props.row.name }}
|
|
|
|
</div>
|
|
|
|
<div class="text-caption text-right">
|
|
|
|
{{ props.row.type.name }}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
2021-05-25 19:36:09 +00:00
|
|
|
</q-img>
|
|
|
|
<q-card-section>
|
|
|
|
<q-badge
|
|
|
|
v-for="tag in props.row.tags"
|
|
|
|
:key="`${props.row.id}-${tag.id}`"
|
|
|
|
class="text-caption"
|
|
|
|
rounded
|
|
|
|
:style="`background-color: ${tag.color}`"
|
|
|
|
>
|
|
|
|
{{ tag.name }}
|
|
|
|
</q-badge>
|
|
|
|
</q-card-section>
|
|
|
|
<q-card-section v-if="!public && !nodetails">
|
|
|
|
<div class="fit row">
|
|
|
|
<q-input
|
|
|
|
v-if="props.row.article_id"
|
|
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
|
|
:model-value="props.row.article_id"
|
|
|
|
outlined
|
|
|
|
readonly
|
|
|
|
label="Artikelnummer"
|
|
|
|
dense
|
|
|
|
/>
|
|
|
|
<q-input
|
|
|
|
v-if="props.row.volume"
|
|
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
|
|
:model-value="props.row.volume"
|
|
|
|
outlined
|
|
|
|
readonly
|
|
|
|
label="Inhalt"
|
|
|
|
dense
|
|
|
|
suffix="L"
|
|
|
|
/>
|
|
|
|
<q-input
|
|
|
|
v-if="props.row.package_size"
|
|
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
|
|
:model-value="props.row.package_size"
|
|
|
|
outlined
|
|
|
|
readonly
|
|
|
|
label="Gebindegröße"
|
|
|
|
dense
|
|
|
|
/>
|
|
|
|
<q-input
|
|
|
|
v-if="props.row.cost_per_package"
|
|
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
|
|
:model-value="props.row.cost_per_package"
|
|
|
|
outlined
|
|
|
|
readonly
|
|
|
|
label="Preis Gebinde"
|
|
|
|
suffix="€"
|
|
|
|
dense
|
|
|
|
/>
|
|
|
|
<q-input
|
|
|
|
v-if="props.row.cost_per_volume"
|
|
|
|
class="col-xs-12 col-sm-6 q-pa-sm q-pb-lg"
|
|
|
|
:model-value="props.row.cost_per_volume"
|
|
|
|
outlined
|
|
|
|
readonly
|
|
|
|
label="Preis pro L"
|
|
|
|
hint="Inkl. 19% Mehrwertsteuer"
|
|
|
|
suffix="€"
|
|
|
|
dense
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</q-card-section>
|
|
|
|
<q-card-section v-if="props.row.volumes.length > 0 && notLoading">
|
|
|
|
<drink-price-volumes
|
|
|
|
:model-value="props.row.volumes"
|
|
|
|
:public="public"
|
|
|
|
:nodetails="nodetails"
|
|
|
|
/>
|
|
|
|
</q-card-section>
|
|
|
|
</q-card>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</q-table>
|
|
|
|
<q-dialog :model-value="editDrink !== undefined" persistent>
|
|
|
|
<drink-modify
|
|
|
|
:drink="editDrink"
|
|
|
|
@save="editing_drink"
|
|
|
|
@cancel="editDrink = undefined"
|
|
|
|
@delete="deleteDrink"
|
|
|
|
/>
|
|
|
|
</q-dialog>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
import DrinkPriceVolumes from '../components/CalculationTable/DrinkPriceVolumes.vue';
|
|
|
|
import { defineComponent, onBeforeMount, ComputedRef, computed, ref } from 'vue';
|
|
|
|
import { Drink, usePricelistStore, DrinkPriceVolume } from '../store';
|
|
|
|
import MinPriceSetting from '../components/MinPriceSetting.vue';
|
|
|
|
import { api, hasPermission } from '@flaschengeist/api';
|
|
|
|
import { filter, Search } from '../utils/filter';
|
|
|
|
import SearchInput from './SearchInput.vue';
|
|
|
|
import DrinkModify from './DrinkModify.vue';
|
|
|
|
import { DeleteObjects } from '../utils/utils';
|
|
|
|
import { PERMISSIONS } from '../permissions';
|
|
|
|
import { sort } from '../utils/sort';
|
|
|
|
import { Notify } from 'quasar';
|
|
|
|
|
|
|
|
export default defineComponent({
|
|
|
|
name: 'CalculationTable',
|
|
|
|
components: {
|
|
|
|
SearchInput,
|
|
|
|
MinPriceSetting,
|
|
|
|
DrinkPriceVolumes,
|
|
|
|
DrinkModify,
|
|
|
|
},
|
|
|
|
props: {
|
|
|
|
public: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
|
|
|
editable: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
|
|
|
nodetails: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
setup(props) {
|
|
|
|
const store = usePricelistStore();
|
|
|
|
|
|
|
|
onBeforeMount(() => {
|
|
|
|
void store.getDrinks();
|
|
|
|
});
|
|
|
|
|
|
|
|
const columns = [
|
|
|
|
{
|
|
|
|
name: 'picture',
|
|
|
|
label: 'Bild',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'name',
|
|
|
|
label: 'Name',
|
|
|
|
field: 'name',
|
|
|
|
sortable: true,
|
|
|
|
sort,
|
|
|
|
filterable: true,
|
|
|
|
public: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: 'drink_type',
|
|
|
|
label: 'Kategorie',
|
|
|
|
field: 'type',
|
|
|
|
format: (val: FG.DrinkType) => `${val.name}`,
|
|
|
|
sortable: true,
|
|
|
|
sort: (a: FG.DrinkType, b: FG.DrinkType) => sort(a.name, b.name),
|
|
|
|
filterable: true,
|
|
|
|
public: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'tags',
|
|
|
|
label: 'Tags',
|
|
|
|
field: 'tags',
|
|
|
|
format: (val: Array<FG.Tag>) => {
|
|
|
|
let retVal = '';
|
|
|
|
val.forEach((tag, index) => {
|
|
|
|
if (index > 0) {
|
|
|
|
retVal += ', ';
|
|
|
|
}
|
|
|
|
retVal += tag.name;
|
|
|
|
});
|
|
|
|
return retVal;
|
|
|
|
},
|
|
|
|
filterable: true,
|
|
|
|
public: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'article_id',
|
|
|
|
label: 'Artikelnummer',
|
|
|
|
field: 'article_id',
|
|
|
|
sortable: true,
|
|
|
|
sort,
|
|
|
|
filterable: true,
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'volume_package',
|
|
|
|
label: 'Inhalt in l des Gebinde',
|
|
|
|
field: 'volume',
|
|
|
|
sortable: true,
|
|
|
|
sort,
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'package_size',
|
|
|
|
label: 'Gebindegröße',
|
|
|
|
field: 'package_size',
|
|
|
|
sortable: true,
|
|
|
|
sort,
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'cost_per_package',
|
|
|
|
label: 'Preis Netto/Gebinde',
|
|
|
|
field: 'cost_per_package',
|
|
|
|
format: (val: number | null) => (val ? `${val.toFixed(3)}€` : ''),
|
|
|
|
sortable: true,
|
|
|
|
sort,
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'cost_per_volume',
|
|
|
|
label: 'Preis mit 19%/Liter',
|
|
|
|
field: 'cost_per_volume',
|
|
|
|
format: (val: number | null) => (val ? `${val.toFixed(3)}€` : ''),
|
|
|
|
sortable: true,
|
|
|
|
sort: (a: ComputedRef, b: ComputedRef) => sort(a.value, b.value),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'volumes',
|
|
|
|
label: 'Preiskalkulation',
|
|
|
|
field: 'volumes',
|
|
|
|
format: (val: Array<DrinkPriceVolume>) => {
|
|
|
|
let retVal = '';
|
|
|
|
val.forEach((val, index) => {
|
|
|
|
if (index > 0) {
|
|
|
|
retVal += ', ';
|
|
|
|
}
|
|
|
|
retVal += val.id;
|
|
|
|
});
|
|
|
|
return retVal;
|
|
|
|
},
|
|
|
|
sortable: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'receipt',
|
|
|
|
label: 'Bauanleitung',
|
|
|
|
field: 'receipt',
|
|
|
|
format: (val: Array<string>) => {
|
|
|
|
let retVal = '';
|
|
|
|
val.forEach((value, index) => {
|
|
|
|
if (index > 0) {
|
|
|
|
retVal += ', ';
|
|
|
|
}
|
|
|
|
retVal += value;
|
|
|
|
});
|
|
|
|
return retVal;
|
|
|
|
},
|
|
|
|
filterable: true,
|
|
|
|
sortable: false,
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
const column_calc = [
|
|
|
|
{
|
|
|
|
name: 'volume',
|
|
|
|
label: 'Abgabe in l',
|
|
|
|
field: 'volume',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'min_prices',
|
|
|
|
label: 'Minimal Preise',
|
|
|
|
field: 'min_prices',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'prices',
|
|
|
|
label: 'Preise',
|
|
|
|
field: 'prices',
|
|
|
|
},
|
|
|
|
];
|
|
|
|
const column_prices = [
|
|
|
|
{
|
|
|
|
name: 'price',
|
|
|
|
label: 'Preis',
|
|
|
|
field: 'price',
|
|
|
|
format: (val: number) => `${val.toFixed(2)}€`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'description',
|
|
|
|
label: 'Beschreibung',
|
|
|
|
field: 'description',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'public',
|
|
|
|
label: 'Öffentlich',
|
|
|
|
field: 'public',
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
const search_keys = computed(() =>
|
|
|
|
columns.filter(
|
|
|
|
(column) => column.filterable && (props.public || props.nodetails ? column.public : true)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
const pagination = ref({
|
|
|
|
sortBy: 'name',
|
|
|
|
descending: false,
|
|
|
|
rowsPerPage: store.drinks.length,
|
|
|
|
});
|
|
|
|
|
|
|
|
const drinkTypes = computed(() => store.drinkTypes);
|
|
|
|
|
|
|
|
function updateDrink(drink: Drink) {
|
|
|
|
void store.updateDrink(drink);
|
|
|
|
}
|
|
|
|
|
|
|
|
function deleteDrink() {
|
|
|
|
if (editDrink.value) {
|
|
|
|
store.deleteDrink(editDrink.value);
|
|
|
|
}
|
|
|
|
editDrink.value = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const showNewDrink = ref(false);
|
|
|
|
|
|
|
|
const drinkPic = ref<File>();
|
|
|
|
|
|
|
|
function onPictureRejected() {
|
|
|
|
Notify.create({
|
|
|
|
group: false,
|
|
|
|
type: 'negative',
|
|
|
|
message: 'Datei zu groß oder keine gültige Bilddatei.',
|
|
|
|
timeout: 10000,
|
|
|
|
progress: true,
|
|
|
|
actions: [{ icon: 'mdi-close', color: 'white' }],
|
|
|
|
});
|
|
|
|
drinkPic.value = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function savePicture(drinkPic: File) {
|
|
|
|
if (editDrink.value) {
|
|
|
|
await store.upload_drink_picture(editDrink.value, drinkPic).catch((response: Response) => {
|
|
|
|
if (response && response.status == 400) {
|
|
|
|
onPictureRejected();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function deletePicture() {
|
|
|
|
if (editDrink.value) {
|
|
|
|
await store.delete_drink_picture(editDrink.value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const search = ref<Search>({
|
|
|
|
value: '',
|
|
|
|
key: '',
|
|
|
|
label: '',
|
|
|
|
});
|
|
|
|
|
|
|
|
const emptyDrink: Drink = {
|
|
|
|
id: -1,
|
|
|
|
article_id: undefined,
|
|
|
|
package_size: undefined,
|
|
|
|
name: '',
|
|
|
|
volume: undefined,
|
|
|
|
cost_per_volume: undefined,
|
|
|
|
cost_per_package: undefined,
|
|
|
|
tags: [],
|
|
|
|
type: undefined,
|
|
|
|
volumes: [],
|
|
|
|
uuid: '',
|
|
|
|
};
|
|
|
|
|
|
|
|
function newDrink() {
|
|
|
|
editDrink.value = Object.assign({}, emptyDrink);
|
|
|
|
}
|
|
|
|
|
|
|
|
const editDrink = ref<Drink>();
|
|
|
|
|
|
|
|
async function editing_drink(
|
|
|
|
drink: Drink,
|
|
|
|
toDeleteObjects: DeleteObjects,
|
|
|
|
drinkPic: File | undefined,
|
|
|
|
deletePic: boolean
|
|
|
|
) {
|
|
|
|
notLoading.value = false;
|
|
|
|
for (const ingredient of toDeleteObjects.ingredients) {
|
|
|
|
await store.deleteIngredient(ingredient);
|
|
|
|
}
|
|
|
|
for (const price of toDeleteObjects.prices) {
|
|
|
|
await store.deletePrice(price);
|
|
|
|
}
|
|
|
|
for (const volume of toDeleteObjects.volumes) {
|
|
|
|
await store.deleteVolume(volume, drink);
|
|
|
|
}
|
|
|
|
if (drink.id > 0) {
|
|
|
|
await store.updateDrink(drink);
|
|
|
|
} else {
|
|
|
|
const _drink = await store.setDrink(drink);
|
|
|
|
if (editDrink.value) {
|
|
|
|
editDrink.value.id = _drink.id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (deletePic) {
|
|
|
|
await deletePicture();
|
|
|
|
}
|
|
|
|
if (drinkPic instanceof File) {
|
|
|
|
await savePicture(drinkPic);
|
|
|
|
}
|
|
|
|
editDrink.value = undefined;
|
|
|
|
notLoading.value = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function get_volumes(drink_id: number) {
|
|
|
|
return store.drinks.find((a) => a.id === drink_id)?.volumes;
|
|
|
|
}
|
|
|
|
|
|
|
|
const notLoading = ref(true);
|
|
|
|
|
|
|
|
const imageloading = ref<Array<{ id: number; loading: boolean }>>([]);
|
|
|
|
function getImageLoading(id: number) {
|
|
|
|
const loading = imageloading.value.find((a) => a.id === id);
|
|
|
|
if (loading) {
|
|
|
|
return loading.loading;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function image(uuid: string | undefined) {
|
|
|
|
if (uuid) {
|
2021-11-12 09:44:18 +00:00
|
|
|
return `${api.defaults.baseURL || ''}/pricelist/picture/${uuid}?size=256`;
|
2021-05-25 19:36:09 +00:00
|
|
|
}
|
|
|
|
return 'no-image.svg';
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
drinks: computed(() => store.drinks),
|
|
|
|
pagination,
|
|
|
|
columns,
|
|
|
|
column_calc,
|
|
|
|
column_prices,
|
|
|
|
drinkTypes,
|
|
|
|
updateDrink,
|
|
|
|
deleteDrink,
|
|
|
|
showNewDrink,
|
|
|
|
drinkPic,
|
|
|
|
savePicture,
|
|
|
|
deletePicture,
|
|
|
|
search,
|
|
|
|
filter,
|
|
|
|
search_keys,
|
|
|
|
tags: computed(() => store.tags),
|
|
|
|
editDrink,
|
|
|
|
editing_drink,
|
|
|
|
get_volumes,
|
|
|
|
notLoading,
|
|
|
|
getImageLoading,
|
|
|
|
newDrink,
|
|
|
|
hasPermission,
|
|
|
|
PERMISSIONS,
|
|
|
|
image,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped></style>
|