flaschengeist-frontend/src/plugins/pricelist/components/CalculationTable.vue

840 lines
26 KiB
Vue

<template>
<q-table
v-model:pagination="pagination"
title="Kalkulationstabelle"
:columns="columns"
:data="drinks"
:visible-columns="visibleColumn"
:dense="$q.screen.lt.md"
row-key="id"
virtual-scroll
:rows-per-page-options="[0]"
>
<template #header="props">
<q-tr :props="props">
<q-th auto-width />
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template #top-right>
<div class="row justify-end q-gutter-sm">
<q-btn label="neues Getränk" color="positive" icon-right="add">
<q-menu anchor="center middle" self="center middle">
<div class="q-pa-sm">
<div class="q-table__title q-pa-sm">Neues Getränk</div>
<div class="row">
<q-input
v-model="newDrink.name"
class="col-sm-4 col-xs-6 q-pa-sm"
filled
label="Getränkname"
/>
<q-input
v-model="newDrink.article_id"
class="col-sm-4 col-xs-6 q-pa-sm"
filled
label="Artikelnummer"
/>
<q-select
v-model="newDrink.type"
class="col-sm-4 col-xs-6 q-pa-sm"
filled
label="Kategorie"
:options="drinkTypes"
option-label="name"
/>
<q-input
v-model.number="newDrink.volume"
class="col-sm-4 col-xs-6 q-pa-sm"
filled
label="Inhalt in L/Gebinde"
type="number"
/>
<q-input
v-model.number="newDrink.package_size"
class="col-sm-4 col-xs-6 q-pa-sm"
filled
label="Gebindegröße"
type="number"
/>
<q-input
v-model.number="newDrink.cost_price_package_netto"
class="col-sm-4 col-xs-6 q-pa-sm"
filled
label="Preis Netto/Gebinde"
type="number"
/>
<q-input
v-model="cost_price_pro_volume"
class="col-sm-4 col-xs-6 q-pa-sm"
filled
label="Preis mit 19%/Liter"
:disable="calc_price_pro_volume"
/>
</div>
<div class="row justify-between">
<q-btn v-close-popup label="Abbrechen" @click="cancelAddDrink" />
<q-btn v-close-popup label="Speichern" color="primary" @click="addDrink" />
</div>
</div>
</q-menu>
</q-btn>
<q-select
v-model="visibleColumn"
multiple
filled
dense
options-dense
display-value="Sichtbarkeit"
emit-value
map-options
:options="[...columns, ...column_calc, ...column_prices]"
option-value="name"
options-cover
/>
</div>
</template>
<template #body="drinks_props">
<q-tr :props="drinks_props">
<q-td auto-width>
<q-btn
v-if="drinks_props.row.volumes.length === 0"
size="xs"
color="negative"
round
dense
icon="mdi-delete"
class="q-mx-sm"
@click="deleteDrink(drinks_props.row)"
/>
</q-td>
<q-td key="name" :props="drinks_props">
{{ drinks_props.row.name }}
<q-popup-edit
v-model="drinks_props.row.name"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@save="updateDrink(drinks_props.row)"
>
<q-input v-model="drinks_props.row.name" filled dense autofocus clearable />
</q-popup-edit>
</q-td>
<q-td key="drink_type" :props="drinks_props">
{{ drinks_props.row.type.name }}
<q-popup-edit
v-model="drinks_props.row.type"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@save="updateDrink(drinks_props.row)"
>
<q-select
v-model="drinks_props.row.type"
:options="drinkTypes"
option-label="name"
filled
dense
autofocus
/>
</q-popup-edit>
</q-td>
<q-td key="article_id" :props="drinks_props">
{{ drinks_props.row.article_id || 'o.A.' }}
<q-popup-edit
v-model="drinks_props.row.article_id"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@save="updateDrink(drinks_props.row)"
>
<q-input v-model="drinks_props.row.article_id" filled dense autofocus clearable />
</q-popup-edit>
</q-td>
<q-td key="volume_package" :props="drinks_props">
{{ drinks_props.row.volume ? `${drinks_props.row.volume} L` : 'o.A.' }}
<q-popup-edit
v-if="
!drinks_props.row.volumes.some((volume) =>
volume.ingredients.some((ingredient) => ingredient.drink_ingredient)
)
"
v-model.number="drinks_props.row.volume"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@save="updateDrink(drinks_props.row)"
>
<q-input
v-model.number="drinks_props.row.volume"
filled
dense
autofocus
type="number"
clearable
step="0.01"
min="0"
suffix="L"
/>
</q-popup-edit>
</q-td>
<q-td key="package_size" :props="drinks_props">
{{ drinks_props.row.package_size || 'o.A.' }}
<q-popup-edit
v-if="
!drinks_props.row.volumes.some((volume) =>
volume.ingredients.some((ingredient) => ingredient.drink_ingredient)
)
"
v-model="drinks_props.row.package_size"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@save="updateDrink(drinks_props.row)"
>
<q-input
v-model.number="drinks_props.row.package_size"
filled
dense
autofocus
type="number"
min="0"
/>
</q-popup-edit>
</q-td>
<q-td key="cost_price_package_netto" :props="drinks_props">
{{
drinks_props.row.cost_price_package_netto
? `${drinks_props.row.cost_price_package_netto}€`
: 'o.A.'
}}
<q-popup-edit
v-if="
!drinks_props.row.volumes.some((volume) =>
volume.ingredients.some((ingredient) => ingredient.drink_ingredient)
)
"
v-model="drinks_props.row.cost_price_package_netto"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@save="updateDrink(drinks_props.row)"
>
<q-input
v-model.number="drinks_props.row.cost_price_package_netto"
filled
dense
autofocus
type="number"
step="0.01"
min="0"
suffix="€"
@change="console.log(drinks_props.row)"
/>
</q-popup-edit>
</q-td>
<q-td key="cost_price_pro_volume" :props="drinks_props">
{{
drinks_props.row.cost_price_pro_volume.value
? `${drinks_props.row.cost_price_pro_volume.value.toFixed(3)}€`
: 'o.A.'
}}
<q-popup-edit
v-if="
!(
!!drinks_props.row.cost_price_package_netto &&
!!drinks_props.row.volume &&
!!drinks_props.row.package_size
) &&
!drinks_props.row.volumes.some((volume) =>
volume.ingredients.some((ingredient) => ingredient.drink_ingredient)
)
"
v-model="drinks_props.row.cost_price_pro_volume.value"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@save="updateDrink(drinks_props.row)"
>
<q-input
v-model.number="drinks_props.row.cost_price_pro_volume.value"
filled
dense
autofocus
type="number"
min="0"
step="0.1"
suffix="€"
/>
</q-popup-edit>
</q-td>
<q-td key="volumes" :props="drinks_props">
<q-table
:columns="column_calc"
:data="drinks_props.row.volumes"
dense
:visible-columns="visibleColumn"
row-key="id"
flat
>
<template #header="props">
<q-tr :props="props">
<q-th auto-width />
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template #body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
v-if="!drinks_props.row.cost_price_pro_volume.value"
size="sm"
color="accent"
round
dense
:icon="props.expand ? 'mdi-chevron-up' : 'mdi-chevron-down'"
@click="props.expand = !props.expand"
/>
<q-btn
v-if="props.row.ingredients.length === 0 && props.row.prices.length === 0"
size="xs"
color="negative"
round
dense
icon="mdi-delete"
class="q-mx-sm"
@click="deleteVolume(props.row, drinks_props.row)"
/>
</q-td>
<q-td key="volume" :props="props">
{{ parseFloat(props.row.volume.value).toFixed(3) }}L
<q-popup-edit
v-if="drinks_props.row.cost_price_pro_volume"
v-model="props.row.volume.value"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@save="updateVolume(props.row, drinks_props.row)"
>
<q-input
v-model.number="props.row.volume.value"
dense
filled
type="number"
suffix="L"
/>
</q-popup-edit>
</q-td>
<q-td key="min_prices" :props="props">
<div
v-for="(min_price, index) in props.row.min_prices"
:key="`min_prices` + index"
class="row justify-between"
>
<div class="col">
<q-badge color="primary">{{ min_price.percentage }}%</q-badge>
</div>
<div class="col" style="text-align: end">
{{ min_price.price.value.toFixed(3) }}€
</div>
</div>
</q-td>
<q-td key="prices" :props="props">
<price-table
:columns="column_prices"
:data="props.row.prices"
:row="props.row"
:visible-columns="visibleColumn"
/>
</q-td>
</q-tr>
<q-tr v-show="props.expand" :props="props">
<q-td colspan="100%">
<ingredients :ingredients="props.row.ingredients" :volume="props.row" />
</q-td>
</q-tr>
</template>
<template #bottom>
<div class="full-width row justify-end">
<q-btn
v-if="drinks_props.row.cost_price_pro_volume"
color="positive"
icon-right="add"
label="Abgabe hinzufügen"
size="xs"
>
<q-menu anchor="center middle" self="center middle">
<div class="row justify-around q-pa-sm">
<q-input
v-model.number="newVolume.volume"
filled
dense
label="Liter"
type="number"
/>
</div>
<div class="row justify-between q-pa-sm">
<q-btn v-close-popup label="Abbrechen" @click="cancelAddVolume" />
<q-btn
v-close-popup
label="Speichern"
color="primary"
@click="addVolume(drinks_props.row)"
/>
</div>
</q-menu>
</q-btn>
</div>
</template>
<template #no-data>
<div class="full-width row justify-end">
<q-btn
v-if="drinks_props.row.cost_price_pro_volume"
color="positive"
icon-right="add"
label="Abgabe hinzufügen"
size="xs"
>
<q-menu anchor="center middle" self="center middle">
<div class="row justify-around q-pa-sm">
<q-input
v-model.number="newVolume.volume"
filled
dense
label="Liter"
type="number"
/>
</div>
<div class="row justify-between q-pa-sm">
<q-btn v-close-popup label="Abbrechen" @click="cancelAddVolume" />
<q-btn
v-close-popup
label="Speichern"
color="primary"
@click="addVolume(drinks_props.row)"
/>
</div>
</q-menu>
</q-btn>
</div>
</template>
</q-table>
</q-td>
</q-tr>
</template>
<template #body-cell-volumes="volumes">
<q-table
:columns="column_calc"
:data="volumes.value"
dense
:visible-columns="visibleColumn"
row-key="id"
flat
>
<template #header="props">
<q-tr :props="props">
<q-th auto-width />
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template #body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
v-if="volumes.row.cost_price_pro_volume == null"
size="sm"
color="accent"
round
dense
:icon="props.expand ? 'mdi-chevron-up' : 'mdi-chevron-down'"
@click="
props.expand = !props.expand;
console.log(volumes);
"
/>
<q-btn
v-if="props.row.ingredients.length === 0 && props.row.prices.length === 0"
size="xs"
color="negative"
round
dense
icon="mdi-delete"
class="q-mx-sm"
@click="deleteVolume(props.row, volumes.row)"
/>
</q-td>
<q-td key="volume" :props="props">
{{ parseFloat(props.row.volume.value).toFixed(3) }}L
<!--{{ props.row.volume }}-->
<q-popup-edit
v-if="volumes.row.cost_price_pro_volume"
v-model="props.row.volume.value"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@save="updateVolume(props.row, volumes.row)"
>
<q-input
v-model.number="props.row.volume.value"
dense
filled
type="number"
suffix="L"
/>
</q-popup-edit>
</q-td>
<q-td key="min_prices" :props="props">
<div
v-for="(min_price, index) in props.row.min_prices"
:key="`min_prices` + index"
class="row justify-between"
>
<div class="col">
<q-badge color="primary">{{ min_price.percentage }}%</q-badge>
</div>
<div class="col" style="text-align: end">
<!--{{ parseFloat(min_price.price).toFixed(3) }}€-->
{{ min_price.price.value.toFixed(3) }}€
</div>
</div>
</q-td>
<q-td key="prices" :props="props">
<price-table
:columns="column_prices"
:data="props.row.prices"
:row="props.row"
:visible-columns="visibleColumn"
/>
</q-td>
</q-tr>
<q-tr v-show="props.expand" :props="props">
<q-td colspan="100%">
<ingredients :ingredients="props.row.ingredients" :volume="props.row" />
</q-td>
</q-tr>
</template>
<template #bottom>
<div class="full-width row justify-end">
<q-btn
v-if="volumes.row.cost_price_pro_volume"
color="positive"
icon-right="add"
label="Abgabe hinzufügen"
size="xs"
>
<q-menu anchor="center middle" self="center middle">
<div class="row justify-around q-pa-sm">
<q-input
v-model.number="newVolume.volume"
filled
dense
label="Liter"
type="number"
min="0"
step="0.01"
suffix="L"
/>
</div>
<div class="row justify-between q-pa-sm">
<q-btn v-close-popup label="Abbrechen" @click="cancelAddVolume" />
<q-btn
v-close-popup
label="Speichern"
color="primary"
@click="addVolume(volumes.row)"
/>
</div>
</q-menu>
</q-btn>
</div>
</template>
<template #no-data>
<div class="full-width row justify-end">
<q-btn
v-if="volumes.row.cost_price_pro_volume"
color="positive"
icon-right="add"
label="Abgabe hinzufügen"
size="xs"
>
<q-menu anchor="center middle" self="center middle">
<div class="row justify-around q-pa-sm">
<q-input
v-model.number="newVolume.volume"
filled
dense
label="Liter"
type="number"
min="0"
step="0.01"
suffix="L"
/>
</div>
<div class="row justify-between q-pa-sm">
<q-btn v-close-popup label="Abbrechen" @click="cancelAddVolume" />
<q-btn
v-close-popup
label="Speichern"
color="primary"
@click="addVolume(volumes.row)"
/>
</div>
</q-menu>
</q-btn>
</div>
</template>
</q-table>
</template>
</q-table>
</template>
<script lang="ts">
import { defineComponent, onBeforeMount, ref, ComputedRef, computed } from 'vue';
import PriceTable from 'src/plugins/pricelist/components/CalculationTable/PriceTable.vue';
import Ingredients from 'src/plugins/pricelist/components/CalculationTable/Ingredients.vue';
import { StateInterface, useMainStore } from 'src/store';
import { Store } from 'vuex';
function sort(a: string | number, b: string | number) {
if (a > b) return 1;
if (b > a) return -1;
return 0;
}
export default defineComponent({
name: 'CalculationTable',
components: { PriceTable, Ingredients },
setup() {
const mainStore = useMainStore();
onBeforeMount(() => {
//store.actions.getPriceCalcColumn(user);
});
const user = mainStore.currentUser.userid;
const columns = [
{
name: 'name',
label: 'Getränkename',
field: 'name',
sortable: true,
sort,
},
{
name: 'article_id',
label: 'Artikelnummer',
field: 'article_id',
sortable: true,
sort,
},
{
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),
},
{
name: 'volume_package',
label: 'Inhalt in l des Gebinde',
field: 'volume',
sortable: true,
sort,
},
{
name: 'package_size',
label: 'Gebindegröße',
field: 'package_size',
sortable: true,
sort,
},
{
name: 'cost_price_package_netto',
label: 'Preis Netto/Gebinde',
field: 'cost_price_package_netto',
format: (val: number | null) => (val ? `${val.toFixed(3)}` : ''),
sortable: true,
sort,
},
{
name: 'cost_price_pro_volume',
label: 'Preis mit 19%/Liter',
field: 'cost_price_pro_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',
},
];
const column_calc = [
{
name: 'volume',
label: 'Abgabe in l',
field: 'volume',
format: (val: number) => `${val} L`,
},
{
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 visibleColumn = computed({
get: () => store.state.pricecalc_columns,
set: (val) => {
store.actions.updatePriceCalcColumn(user, val);
},
});
const emptyVolume: DrinkPriceVolume = {
id: -1,
_volume: 0,
volume: null,
min_prices: [
{ percentage: 100, price: null },
{ percentage: 250, price: null },
{ percentage: 300, price: null },
],
prices: [],
ingredients: [],
};
const newVolume = ref<DrinkPriceVolume>(emptyVolume);
function addVolume(drink: Drink) {
store.actions.setVolume(>newVolume.value, drink);
cancelAddVolume();
}
function cancelAddVolume() {
setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
newVolume.value = emptyVolume;
}, 200);
}
function updateVolume(volume: DrinkPriceVolume, drink: Drink) {
console.log(volume);
store.actions.updateVolume(volume, drink);
}
function deleteVolume(volume: FG.DrinkPriceVolume, drink: FG.Drink) {
store.actions.deleteVolume(volume, drink);
}
const pagination = computed(() => {
rowsPerPage: store.state.drinks.length;
});
const emptyDrink: FG.Drink = {
id: -1,
article_id: undefined,
package_size: undefined,
name: '',
volume: undefined,
cost_price_pro_volume: undefined,
cost_price_package_netto: undefined,
tags: [],
type: undefined,
volumes: [],
};
const newDrink = ref<FG.Drink>(emptyDrink);
const calc_price_pro_volume = computed(
() =>
!!newDrink.value.cost_price_package_netto &&
!!newDrink.value.volume &&
!!newDrink.value.package_size
);
const cost_price_pro_volume = computed({
get: () => {
if (calc_price_pro_volume.value) {
const retVal =
((newDrink.value.cost_price_package_netto || 0) /
((newDrink.value.volume || 0) * (newDrink.value.package_size || 0))) *
1.19;
newDrink.value.cost_price_pro_volume = Math.round(retVal * 1000) / 1000;
}
return newDrink.value.cost_price_pro_volume;
},
set: (val) => {
newDrink.value.cost_price_pro_volume = val;
},
});
const drinkTypes = computed(() => store.state.drinkTypes);
function addDrink() {
store.actions.setDrink(newDrink.value);
cancelAddDrink();
}
function cancelAddDrink() {
setTimeout(() => (newDrink.value = emptyDrink), 200);
}
function updateDrink(drink: Drink) {
store.actions.updateDrink(drink);
}
function deleteDrink(drink: Drink) {
store.actions.deleteDrink(drink);
}
return {
drinks: computed(() => store.state.drinks),
pagination,
columns,
column_calc,
column_prices,
visibleColumn,
addVolume,
cancelAddVolume,
newVolume,
updateVolume,
deleteVolume,
newDrink,
cost_price_pro_volume,
calc_price_pro_volume,
drinkTypes,
addDrink,
cancelAddDrink,
updateDrink,
deleteDrink,
console,
};*/
},
});
</script>
<style scoped></style>