[pricelist] fixed prices. first steps for volume

This commit is contained in:
Tim Gröger 2021-03-15 23:52:40 +01:00
parent f6951bdf0b
commit e4851bd178
3 changed files with 338 additions and 193 deletions

View File

@ -48,15 +48,36 @@
color="accent" color="accent"
round round
dense dense
@click="props.expand = !props.expand" @click="
props.expand = !props.expand;
console.log(volumes);
"
:icon="props.expand ? 'mdi-chevron-up' : 'mdi-chevron-down'" :icon="props.expand ? 'mdi-chevron-up' : 'mdi-chevron-down'"
v-if="volumes.row.cost_price_pro_volume == null"
/> />
</q-td> </q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props"> <q-td key="volume" :props="props">
<div v-if="col.name == 'min_prices'"> {{ props.row.volume }}L
<q-popup-edit
buttons
label-cancel="Abbrechen"
label-set="Speichern"
v-model="props.row.volume"
@save="updateVolume(props.row, volumes.row)"
>
<q-input
dense
filled
v-model.number="props.row.volume"
type="number"
suffix="L"
/>
</q-popup-edit>
</q-td>
<q-td key="min_prices" :props="props">
<div <div
v-for="(min_price, index) in col.value" v-for="(min_price, index) in props.row.min_prices"
:key="col.name + index" :key="`min_prices` + index"
class="row justify-between" class="row justify-between"
> >
<div class="col"> <div class="col">
@ -66,102 +87,14 @@
{{ parseFloat(min_price.price).toFixed(3) }} {{ parseFloat(min_price.price).toFixed(3) }}
</div> </div>
</div> </div>
</div> </q-td>
<div v-else-if="col.name == 'prices'"> <q-td key="prices" :props="props">
<q-table <price-table
dense
hide-header
:columns="column_prices" :columns="column_prices"
:data="col.value" :data="props.row.prices"
:row="props.row"
:visible-columns="visibleColumn" :visible-columns="visibleColumn"
flat
>
<template v-slot:body="prices_props">
<q-tr :props="prices_props">
<q-td
v-for="col in prices_props.cols"
:key="col.name"
:props="prices_props"
>
<div v-if="col.name == 'public'" class="full-width">
<q-toggle
v-model="col.value"
dense
@input="updatePrice(prices_props.row, props.row)"
/> />
</div>
<div v-else class="full-width">
{{ col.value }}
</div>
</q-td>
<q-td>
<q-btn
color="negative"
padding="xs"
round
size="xs"
icon="mdi-delete"
@click="deletePrice(prices_props.row, props.row)"
/>
</q-td>
</q-tr>
</template>
<template v-slot:bottom>
<div class="full-width row justify-end">
<q-btn size="xs" icon-right="add" color="positive" label="Preis hinzufügen">
<q-menu anchor="center middle" self="center middle">
<div class="row justify-around q-pa-sm">
<q-input
v-model.number="newPrice.price"
dense
filled
class="q-px-sm"
type="number"
label="Preis"
/>
<q-input
v-model="newPrice.description"
dense
filled
class="q-px-sm"
label="Beschreibung"
/>
<q-toggle
v-model="newPrice.public"
dense
class="q-px-sm"
label="Öffentlich"
/>
</div>
<div class="row justify-between q-pa-sm">
<q-btn label="Abbrechen" @click="cancelAddPrice" v-close-popup />
<q-btn
label="Speichern"
color="primary"
@click="addPrice(props.row)"
v-close-popup
/>
</div>
</q-menu>
</q-btn>
</div>
</template>
<template v-slot:no-data class="justify-end">
<div class="full-width row justify-end">
<q-btn
size="xs"
icon-right="add"
color="positive"
label="Preis hinzufügen"
@click="addPrice(col.value)"
/>
</div>
</template>
</q-table>
</div>
<div v-else>
{{ col.value }}
</div>
</q-td> </q-td>
</q-tr> </q-tr>
<q-tr v-show="props.expand" :props="props"> <q-tr v-show="props.expand" :props="props">
@ -221,9 +154,24 @@
icon-right="add" icon-right="add"
label="Abgabe hinzufügen" label="Abgabe hinzufügen"
size="xs" size="xs"
@click="addVolume(volumes.value)" v-if="volumes.row.cost_price_pro_volume"
>
<q-menu anchor="center middle" self="center middle">
<div class="row justify-around q-pa-sm">
<q-input dense label="Liter" type="number" v-model.number="newVolume.volume" />
</div>
<div class="row justify-between q-pa-sm">
<q-btn label="Abbrechen" @click="cancelAddVolume" v-close-popup />
<q-btn
label="Speichern"
color="primary"
@click="addVolume(volumes.row)"
v-close-popup
/> />
</div> </div>
</q-menu>
</q-btn>
</div>
</template> </template>
</q-table> </q-table>
</template> </template>
@ -235,8 +183,10 @@
import { defineComponent, onBeforeMount, ref, computed } from '@vue/composition-api'; import { defineComponent, onBeforeMount, ref, computed } from '@vue/composition-api';
import store, { calc_min_prices } from '../store/altStore'; import store, { calc_min_prices } from '../store/altStore';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import PriceTable from 'src/plugins/pricelist/components/CalculationTable/PriceTable.vue';
export default defineComponent({ export default defineComponent({
name: 'CalculationTable', name: 'CalculationTable',
components: { PriceTable },
setup(_, { root }) { setup(_, { root }) {
onBeforeMount(() => { onBeforeMount(() => {
store.actions.getDrinks(); store.actions.getDrinks();
@ -246,82 +196,82 @@ export default defineComponent({
{ {
name: 'name', name: 'name',
label: 'Getränkename', label: 'Getränkename',
field: 'name', field: 'name'
}, },
{ {
name: 'article_id', name: 'article_id',
label: 'Artikelnummer', label: 'Artikelnummer',
field: 'article_id', field: 'article_id'
}, },
{ {
name: 'drink_type', name: 'drink_type',
label: 'Kategorie', label: 'Kategorie',
field: 'type', field: 'type',
format: (val: FG.DrinkType) => `${val.name}`, format: (val: FG.DrinkType) => `${val.name}`
}, },
{ {
name: 'volume_package', name: 'volume_package',
label: 'Inhalt in l des Gebinde', label: 'Inhalt in l des Gebinde',
field: 'volume', field: 'volume'
}, },
{ {
name: 'package_size', name: 'package_size',
label: 'Gebindegröße', label: 'Gebindegröße',
field: 'package_size', field: 'package_size'
}, },
{ {
name: 'cost_price_package_netto', name: 'cost_price_package_netto',
label: 'Preis Netto/Gebinde', label: 'Preis Netto/Gebinde',
field: 'cost_price_package_netto', field: 'cost_price_package_netto',
format: (val: number | null) => (val ? `${val.toFixed(3)}` : ''), format: (val: number | null) => (val ? `${val.toFixed(3)}` : '')
}, },
{ {
name: 'cost_price_pro_volume', name: 'cost_price_pro_volume',
label: 'Preis mit 19%/Liter', label: 'Preis mit 19%/Liter',
field: 'cost_price_pro_volume', field: 'cost_price_pro_volume',
format: (val: number | null) => (val ? `${val.toFixed(3)}` : ''), format: (val: number | null) => (val ? `${val.toFixed(3)}` : '')
}, },
{ {
name: 'volumes', name: 'volumes',
label: 'Preiskalkulation', label: 'Preiskalkulation',
field: 'volumes', field: 'volumes'
}, }
]; ];
const column_calc = [ const column_calc = [
{ {
name: 'volume', name: 'volume',
label: 'Abgabe in l', label: 'Abgabe in l',
field: 'volume', field: 'volume',
format: (val: number) => `${val} L`, format: (val: number) => `${val} L`
}, },
{ {
name: 'min_prices', name: 'min_prices',
label: 'Minimal Preise', label: 'Minimal Preise',
field: 'min_prices', field: 'min_prices'
}, },
{ {
name: 'prices', name: 'prices',
label: 'Preise', label: 'Preise',
field: 'prices', field: 'prices'
}, }
]; ];
const column_prices = [ const column_prices = [
{ {
name: 'price', name: 'price',
label: 'Preis', label: 'Preis',
field: 'price', field: 'price',
format: (val: number) => `${val.toFixed(2)}`, format: (val: number) => `${val.toFixed(2)}`
}, },
{ {
name: 'description', name: 'description',
label: 'Beschreibung', label: 'Beschreibung',
field: 'description', field: 'description'
}, },
{ {
name: 'public', name: 'public',
label: 'Öffentlich', label: 'Öffentlich',
field: 'public', field: 'public'
}, }
]; ];
const visibleColumn = ref([ const visibleColumn = ref([
'name', 'name',
@ -333,56 +283,32 @@ export default defineComponent({
'prices', 'prices',
'price', 'price',
'description', 'description',
'public', 'public'
]); ]);
const emptyVolume = {
function deletePrice(row: FG.DrinkPrice) { id: -1,
console.log(row); volume: 0,
}
const emptyPrice = {
price: 0,
description: '',
public: true,
};
const newPrice = ref(emptyPrice);
function addPrice(volume: FG.DrinkPriceVolume) {
store.actions.setPrice({ ...newPrice.value }, volume);
cancelAddPrice();
}
function cancelAddPrice() {
setTimeout(() => {
addPrice.value = emptyPrice;
}, 200);
}
function updatePrice(price: FG.DrinkPrice, volume: FG.DrinkPriceVolume) {
store.actions.updatePrice(price, volume);
}
function deletePrice(price: FG.DrinkPrice, volume: FG.DrinkPriceVolume) {
console.log(price, volume);
store.actions.deletePrice(price, volume);
}
function addVolume(table: FG.DrinkPriceVolume[]) {
table.push({
id: v4(),
volume: null,
min_prices: [ min_prices: [
{ { percentage: 100, price: 0 },
percentage: 100, { percentage: 250, price: 0 },
price: 0, { percentage: 300, price: 0 }
},
{
percentage: 250,
price: 0,
},
{
percentage: 300,
price: 0,
},
], ],
prices: [], prices: [],
ingredients: [], ingredients: []
}); };
const newVolume = ref(emptyVolume);
function addVolume(drink: FG.Drink) {
store.actions.setVolume(newVolume.value, drink);
cancelAddVolume();
}
function cancelAddVolume() {
setTimeout(() => {
newVolume.value = emptyVolume;
}, 200);
}
function updateVolume(volume: FG.DrinkPriceVolume, drink: FG.Drink) {
console.log(volume);
store.actions.updateVolume(volume, drink);
} }
function addIngredient(ingredients: FG.Ingredient[]) { function addIngredient(ingredients: FG.Ingredient[]) {
@ -390,23 +316,20 @@ export default defineComponent({
} }
return { return {
drinks: computed({ get: () => store.state.drinks, set: (val) => console.log(val) }), drinks: computed(() => store.state.drinks),
columns, columns,
column_calc, column_calc,
column_prices, column_prices,
visibleColumn, visibleColumn,
deletePrice,
newPrice,
addPrice,
updatePrice,
deletePrice,
cancelAddPrice,
addVolume, addVolume,
cancelAddVolume,
newVolume,
updateVolume,
addIngredient, addIngredient,
calc_min_prices, calc_min_prices,
console, console
}; };
}, }
}); });
</script> </script>

View File

@ -0,0 +1,199 @@
<template>
<q-table
style="max-height: 130px"
dense
hide-header
:columns="columns"
:data="data"
:visible-columns="visibleColumns"
flat
virtual-scroll
:pagination.sync="pagination"
:rows-per-page-options="[0]"
>
<template v-slot:body="prices_props">
<q-tr :props="prices_props">
<q-td key="price" :props="prices_props">
{{ prices_props.row.price.toFixed(2) }}
<q-popup-edit
v-model="prices_props.row.price"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@save="updatePrice(prices_props.row, row)"
>
<q-input
v-model.number="prices_props.row.price"
type="number"
label="Preis"
dense
filled
autofocus
suffix="€"
/> </q-popup-edit
></q-td>
<q-td key="description" :props="prices_props">
{{ prices_props.row.description }}
<q-popup-edit
v-model="prices_props.row.description"
buttons
label="Beschreibung"
label-cancel="Abbrechen"
label-set="Speichern"
@save="updatePrice(prices_props.row, row)"
>
<q-input v-model="prices_props.row.description" dense autofocus filled />
</q-popup-edit>
</q-td>
<q-td key="public" :props="prices_props">
<q-toggle
v-model="prices_props.row.public"
dense
@input="updatePrice(prices_props.row, row)"
/>
</q-td>
<q-td>
<q-btn
color="negative"
padding="xs"
round
size="xs"
icon="mdi-delete"
@click="deletePrice(prices_props.row, row)"
/>
</q-td>
</q-tr>
</template>
<template v-slot:bottom>
<div class="full-width row justify-end">
<q-btn size="xs" icon-right="add" color="positive" label="Preis hinzufügen">
<q-menu anchor="center middle" self="center middle">
<div class="row justify-around q-pa-sm">
<q-input
v-model.number="newPrice.price"
dense
filled
class="q-px-sm"
type="number"
label="Preis"
suffix="€"
/>
<q-input
v-model="newPrice.description"
dense
filled
class="q-px-sm"
label="Beschreibung"
/>
<q-toggle v-model="newPrice.public" dense class="q-px-sm" label="Öffentlich" />
</div>
<div class="row justify-between q-pa-sm">
<q-btn label="Abbrechen" @click="cancelAddPrice" v-close-popup />
<q-btn label="Speichern" color="primary" @click="addPrice(row)" v-close-popup />
</div>
</q-menu>
</q-btn>
</div>
</template>
<template v-slot:no-data class="justify-end">
<div class="full-width row justify-end">
<q-btn size="xs" icon-right="add" color="positive" label="Preis hinzufügen">
<q-menu anchor="center middle" self="center middle">
<div class="row justify-around q-pa-sm">
<q-input
v-model.number="newPrice.price"
dense
filled
class="q-px-sm"
type="number"
label="Preis"
suffix="€"
/>
<q-input
v-model="newPrice.description"
dense
filled
class="q-px-sm"
label="Beschreibung"
/>
<q-toggle v-model="newPrice.public" dense class="q-px-sm" label="Öffentlich" />
</div>
<div class="row justify-between q-pa-sm">
<q-btn label="Abbrechen" @click="cancelAddPrice" v-close-popup />
<q-btn label="Speichern" color="primary" @click="addPrice(row)" v-close-popup />
</div>
</q-menu>
</q-btn>
</div>
</template>
</q-table>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from '@vue/composition-api';
import store from '../../store/altStore';
export default defineComponent({
name: 'PriceTable',
props: {
columns: {
type: Array,
required: true
},
data: {
type: Array,
required: true
},
row: {
type: Object as () => FG.DrinkPriceVolume,
required: true
},
visibleColumns: {
type: Array,
required: true
}
},
setup(props) {
const emptyPrice = {
id: -1,
price: 0,
description: '',
public: true
};
const newPrice = ref(emptyPrice);
function addPrice(volume: FG.DrinkPriceVolume) {
store.actions.setPrice({ ...newPrice.value }, volume);
cancelAddPrice();
}
function cancelAddPrice() {
setTimeout(() => {
newPrice.value = emptyPrice;
}, 200);
}
function updatePrice(price: FG.DrinkPrice, volume: FG.DrinkPriceVolume) {
console.log(price);
store.actions.updatePrice(price, volume);
}
function deletePrice(price: FG.DrinkPrice, volume: FG.DrinkPriceVolume) {
console.log(price, volume);
store.actions.deletePrice(price, volume);
}
const pagination = computed(() => {
rowsPerPage: props.data.length;
});
return {
newPrice,
addPrice,
cancelAddPrice,
updatePrice,
deletePrice,
pagination,
console
};
}
});
</script>
<style scoped></style>

View File

@ -6,7 +6,7 @@ import ExtraIngredient = FG.ExtraIngredient;
const state = reactive<{ drinks: FG.Drink[]; tags: FG.Tag[]; drinkTypes: FG.DrinkType[] }>({ const state = reactive<{ drinks: FG.Drink[]; tags: FG.Tag[]; drinkTypes: FG.DrinkType[] }>({
drinks: [], drinks: [],
tags: [], tags: [],
drinkTypes: [], drinkTypes: []
}); });
const actions = { const actions = {
@ -21,7 +21,7 @@ const actions = {
volume.min_prices = [ volume.min_prices = [
{ percentage: 100, price: (drink.cost_price_pro_volume || 0) * volume.volume * 1 }, { percentage: 100, price: (drink.cost_price_pro_volume || 0) * volume.volume * 1 },
{ percentage: 250, price: (drink.cost_price_pro_volume || 0) * volume.volume * 2.5 }, { percentage: 250, price: (drink.cost_price_pro_volume || 0) * volume.volume * 2.5 },
{ percentage: 300, price: (drink.cost_price_pro_volume || 0) * volume.volume * 3 }, { percentage: 300, price: (drink.cost_price_pro_volume || 0) * volume.volume * 3 }
]; ];
if (volume.ingredients.length > 0) { if (volume.ingredients.length > 0) {
calc_min_prices(volume); calc_min_prices(volume);
@ -30,16 +30,16 @@ const actions = {
}); });
}); });
}) })
.catch((err) => console.warn(err)); .catch(err => console.warn(err));
}, },
setPrice(price: FG.DrinkPrice, volume: FG.DrinkPriceVolume) { setPrice(price: FG.DrinkPrice, volume: FG.DrinkPriceVolume) {
axios axios
.post(`pricelist/prices/volumes/${volume.id}`, price) .post(`pricelist/volumes/${volume.id}/prices`, price)
.then((response: AxiosResponse<FG.DrinkPrice>) => { .then((response: AxiosResponse<FG.DrinkPrice>) => {
volume.prices.push(response.data); volume.prices.push(response.data);
this.sortPrices(volume); this.sortPrices(volume);
}) })
.catch((err) => console.warn(err)); .catch(err => console.warn(err));
}, },
sortPrices(volume: FG.DrinkPriceVolume) { sortPrices(volume: FG.DrinkPriceVolume) {
volume.prices.sort((a, b) => { volume.prices.sort((a, b) => {
@ -52,29 +52,52 @@ const actions = {
axios axios
.delete(`pricelist/prices/${price.id}`) .delete(`pricelist/prices/${price.id}`)
.then(() => { .then(() => {
const index = volume.prices.findIndex((a) => a.id == price.id); const index = volume.prices.findIndex(a => a.id == price.id);
if (index > -1) { if (index > -1) {
volume.prices.splice(index, 1); volume.prices.splice(index, 1);
} }
}) })
.catch((err) => console.warn(err)); .catch(err => console.warn(err));
}, },
updatePrice(price: FG.DrinkPrice, volume: FG.DrinkPriceVolume) { updatePrice(price: FG.DrinkPrice, volume: FG.DrinkPriceVolume) {
axios axios
.put(`pricelist/prices/${price.id}`, price) .put(`pricelist/prices/${price.id}`, price)
.then((response: AxiosResponse<FG.DrinkPrice>) => { .then((response: AxiosResponse<FG.DrinkPrice>) => {
const index = volume.prices.findIndex((a) => a.id === price.id); const index = volume.prices.findIndex(a => a.id === price.id);
if (index > -1) { if (index > -1) {
volume.prices[index] = response.data; //volume.prices[index] = response.data;
this.sortPrices(volume); this.sortPrices(volume);
} }
}) })
.catch((err) => console.log(err)); .catch(err => console.log(err));
}, },
setVolume(volume: FG.DrinkPriceVolume, drink: FG.Drink) {
axios
.post(`pricelist/drinks/${drink.id}/volumes`, volume)
.then((response: AxiosResponse<FG.DrinkPriceVolume>) => {
drink.volumes.push(response.data);
})
.catch(err => console.warn(err));
},
updateVolume(volume: FG.DrinkPriceVolume, drink: FG.Drink) {
axios
.put(`pricelist/volumes/${volume.id}`, volume)
.then(() => {
//calc_min_prices_by_drink(volume, drink);
})
.catch(err => console.warn(err));
}
}; };
const getters = {}; const getters = {};
function calc_min_prices_by_drink(volume: FG.DrinkPriceVolume, drink: FG.Drink) {
volume.min_prices.forEach(min_price => {
min_price.price =
((drink.cost_price_pro_volume || 0) * volume.volume * min_price.percentage) / 100;
});
}
function calc_min_prices(row: FG.DrinkPriceVolume) { function calc_min_prices(row: FG.DrinkPriceVolume) {
row.volume = 0; row.volume = 0;
let cost_price = 0; let cost_price = 0;
@ -87,7 +110,7 @@ function calc_min_prices(row: FG.DrinkPriceVolume) {
cost_price += ingredient.price; cost_price += ingredient.price;
} }
}); });
row.min_prices.forEach((min_price) => { row.min_prices.forEach(min_price => {
min_price.price = (cost_price * min_price.percentage) / 100; min_price.price = (cost_price * min_price.percentage) / 100;
}); });
} }
@ -96,5 +119,5 @@ export { calc_min_prices };
export default { export default {
state, state,
actions, actions,
getters, getters
}; };