Compare commits

..

No commits in common. "develop" and "v1.0.0-alpha.1" have entirely different histories.

23 changed files with 173 additions and 574 deletions

View File

@ -17,11 +17,11 @@ module.exports = {
project: resolve(__dirname, './tsconfig.json'), project: resolve(__dirname, './tsconfig.json'),
tsconfigRootDir: __dirname, tsconfigRootDir: __dirname,
ecmaVersion: 2019, // Allows for the parsing of modern ECMAScript features ecmaVersion: 2019, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports sourceType: 'module' // Allows for the use of imports
}, },
env: { env: {
browser: true, browser: true
}, },
// Rules order is important, please avoid shuffling them // Rules order is important, please avoid shuffling them
@ -44,7 +44,7 @@ module.exports = {
// https://github.com/prettier/eslint-config-prettier#installation // https://github.com/prettier/eslint-config-prettier#installation
// usage with Prettier, provided by 'eslint-config-prettier'. // usage with Prettier, provided by 'eslint-config-prettier'.
'plugin:prettier/recommended', 'prettier', //'plugin:prettier/recommended'
], ],
plugins: [ plugins: [
@ -54,6 +54,10 @@ module.exports = {
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file
// required to lint *.vue files // required to lint *.vue files
'vue', 'vue',
// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
// Prettier has not been included as plugin to avoid performance impact
// add it as an extension for your IDE
], ],
// add your custom rules here // add your custom rules here
@ -61,12 +65,11 @@ module.exports = {
'prefer-promise-reject-errors': 'off', 'prefer-promise-reject-errors': 'off',
// TypeScript // TypeScript
quotes: ['error', 'single', { avoidEscape: true }], quotes: ['warn', 'single', { avoidEscape: true }],
'@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off',
'vue/multi-word-component-names': 'off',
// allow debugger during development only // allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}, }
}; }

1
.gitignore vendored
View File

@ -2,5 +2,4 @@ node_modules
yarn-error.log yarn-error.log
# No need for sharing this # No need for sharing this
yarn.lock yarn.lock
.idea

View File

@ -1 +0,0 @@
.woodpecker/

View File

@ -1,14 +0,0 @@
pipeline:
deploy:
when:
event: tag
tag: v*
image: node:lts-alpine
commands:
- echo "//registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN" > .npmrc
- yarn publish --non-interactive
secrets: [ node_auth_token ]
depends_on:
- lint

View File

@ -1,9 +0,0 @@
pipeline:
lint:
when:
branch: [main, develop]
image: node:lts-alpine
commands:
- yarn install
- yarn lint

View File

@ -1,5 +1,4 @@
# Flaschengeist `pricelist` fontend-plugin # Flaschengeist `pricelist` fontend-plugin
![status-badge](https://ci.os-sc.org/api/badges/Flaschengeist/flaschengeist-pricelist/status.svg)
This package provides the [Flaschengeist](https://flaschengeist.dev/Flaschengeist/flaschengeist) frontend for the pricelist plugin (build and manage drinks, show pricelist and calculate prices). This package provides the [Flaschengeist](https://flaschengeist.dev/Flaschengeist/flaschengeist) frontend for the pricelist plugin (build and manage drinks, show pricelist and calculate prices).

View File

@ -1,6 +1,6 @@
{ {
"license": "MIT", "license": "MIT",
"version": "1.0.0-alpha.5", "version": "1.0.0-alpha.1",
"name": "@flaschengeist/pricelist", "name": "@flaschengeist/pricelist",
"author": "Tim Gröger <flaschengeist@wu5.de>", "author": "Tim Gröger <flaschengeist@wu5.de>",
"homepage": "https://flaschengeist.dev/Flaschengeist", "homepage": "https://flaschengeist.dev/Flaschengeist",
@ -15,34 +15,34 @@
"main": "src/index.ts", "main": "src/index.ts",
"types": "src/api.d.ts", "types": "src/api.d.ts",
"scripts": { "scripts": {
"format": "prettier --config ./package.json --write '{,!(node_modules)/**/}*.{ts,vue,js}'", "format": "prettier --config ./package.json --write '{,!(node_modules)/**/}*.ts'",
"lint": "eslint --ext .js,.ts,.vue ./src" "lint": "eslint --ext .js,.ts,.vue ./src"
}, },
"dependencies": { "dependencies": {
"vuedraggable": "^4.1.0" "vuedraggable": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {
"@flaschengeist/types": "^1.0.0-alpha.10", "@flaschengeist/types": "^1.0.0-alpha.1",
"@quasar/app": "^3.2.4", "@quasar/app": "^3.0.0-beta.26",
"@typescript-eslint/eslint-plugin": "^5.5.0", "@typescript-eslint/eslint-plugin": "^4.24.0",
"@typescript-eslint/parser": "^5.5.0", "@typescript-eslint/parser": "^4.24.0",
"eslint": "^8.2.0", "axios": "^0.21.1",
"eslint": "^7.26.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-vue": "^7.9.0",
"eslint-plugin-vue": "^8.1.1", "pinia": "^2.0.0-alpha.18",
"pinia": "^2.0.6", "prettier": "^2.3.0",
"prettier": "^2.5.1", "quasar": "^2.0.0-beta.18",
"quasar": "^2.3.3", "typescript": "^4.2.4"
"typescript": "^4.5.2"
}, },
"peerDependencies": { "peerDependencies": {
"@flaschengeist/api": "^1.0.0-alpha.7", "@flaschengeist/api": "^1.0.0-alpha.1",
"@flaschengeist/users": "^1.0.0-alpha.3" "@flaschengeist/users": "^1.0.0-alpha.1"
}, },
"prettier": { "prettier": {
"singleQuote": true, "singleQuote": true,
"semi": true, "semi": true,
"printWidth": 100, "printWidth": 120,
"arrowParens": "always" "arrowParens": "always"
} }
} }

6
src/api.d.ts vendored
View File

@ -7,7 +7,7 @@ declare namespace FG {
volume?: number; volume?: number;
cost_per_volume?: number; cost_per_volume?: number;
cost_per_package?: number; cost_per_package?: number;
has_image: boolean; uuid: string;
receipt?: Array<string>; receipt?: Array<string>;
tags?: Array<Tag>; tags?: Array<Tag>;
type?: DrinkType; type?: DrinkType;
@ -17,19 +17,15 @@ declare namespace FG {
id: number; id: number;
volume: number; volume: number;
ingredient_id: number; ingredient_id: number;
cost_per_volume: number;
name: string;
} }
interface DrinkPrice { interface DrinkPrice {
id: number; id: number;
price: number; price: number;
volume?: DrinkPriceVolume;
public: boolean; public: boolean;
description?: string; description?: string;
} }
interface DrinkPriceVolume { interface DrinkPriceVolume {
id: number; id: number;
drink?: Drink;
volume: number; volume: number;
min_prices: Array<MinPrices>; min_prices: Array<MinPrices>;
prices: Array<DrinkPrice>; prices: Array<DrinkPrice>;

View File

@ -5,8 +5,7 @@
<div v-if="ingredient.drink_ingredient"> <div v-if="ingredient.drink_ingredient">
<div class="full-width row q-gutter-sm q-py-sm"> <div class="full-width row q-gutter-sm q-py-sm">
<div class="col"> <div class="col">
<!--{{ name(ingredient.drink_ingredient?.ingredient_id) }}--> {{ name(ingredient.drink_ingredient?.ingredient_id) }}
{{ ingredient.drink_ingredient?.name }}
</div> </div>
<div class="col"> <div class="col">
{{ {{

View File

@ -5,20 +5,15 @@
:columns="columns" :columns="columns"
:rows="drinks" :rows="drinks"
dense dense
row-key="id"
grid
:loading="loading"
:filter="search" :filter="search"
@request="onRequest"
>
<!--
:filter-method="filter" :filter-method="filter"
--> grid
:rows-per-page-options="[0]"
>
<template #top-right> <template #top-right>
<div class="row justify-end q-gutter-sm"> <div class="row justify-end q-gutter-sm">
<search-input v-model="search" :keys="search_keys" /> <search-input v-model="search" :keys="search_keys" />
<slot></slot> <slot></slot>
<q-toggle v-model="showPic" icon="mdi-camera-burst" />
<q-btn v-if="!public && !nodetails" label="Aufpreise"> <q-btn v-if="!public && !nodetails" label="Aufpreise">
<q-menu anchor="center middle" self="center middle"> <q-menu anchor="center middle" self="center middle">
<min-price-setting /> <min-price-setting />
@ -38,62 +33,7 @@
<template #item="props"> <template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4"> <div class="q-pa-xs col-xs-12 col-sm-6 col-md-4">
<q-card> <q-card>
<q-img <q-img style="max-height: 256px" :src="image(props.row.uuid)">
v-if="showPic && props.row.has_image"
style="max-height: 256px"
:src="image(props.row.id)"
fit="contain"
>
<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 #error>
<q-img class="bg-white" style="max-height: 256px" src="no-image.svg" />
<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>
</q-img>
<q-img
v-if="showPic && !props.row.has_image"
class="bg-white"
style="max-height: 256px"
src="no-image.svg"
>
<div <div
v-if="!public && !nodetails && editable" v-if="!public && !nodetails && editable"
class="absolute-top-right justify-end" class="absolute-top-right justify-end"
@ -115,19 +55,6 @@
</div> </div>
</div> </div>
</q-img> </q-img>
<q-card-section v-if="!showPic">
<div class="row">
<div class="col">
<div class="text-h4 text-center">
{{ props.row.name }}
</div>
<div class="text-caption text-center">
{{ props.row.type.name }}
</div>
</div>
<q-btn rounded icon="mdi-pencil" @click="editDrink = props.row" />
</div>
</q-card-section>
<q-card-section> <q-card-section>
<q-badge <q-badge
v-for="tag in props.row.tags" v-for="tag in props.row.tags"
@ -223,8 +150,8 @@ import { filter, Search } from '../utils/filter';
import SearchInput from './SearchInput.vue'; import SearchInput from './SearchInput.vue';
import DrinkModify from './DrinkModify.vue'; import DrinkModify from './DrinkModify.vue';
import { DeleteObjects } from '../utils/utils'; import { DeleteObjects } from '../utils/utils';
import { setNewImage, getNewImage } from '../utils/image';
import { PERMISSIONS } from '../permissions'; import { PERMISSIONS } from '../permissions';
import { sort } from '../utils/sort';
import { Notify } from 'quasar'; import { Notify } from 'quasar';
export default defineComponent({ export default defineComponent({
@ -253,11 +180,7 @@ export default defineComponent({
const store = usePricelistStore(); const store = usePricelistStore();
onBeforeMount(() => { onBeforeMount(() => {
//void store.getDrinks(); void store.getDrinks();
void onRequest({
pagination: pagination.value,
filter: undefined,
});
}); });
const columns = [ const columns = [
@ -269,6 +192,8 @@ export default defineComponent({
name: 'name', name: 'name',
label: 'Name', label: 'Name',
field: 'name', field: 'name',
sortable: true,
sort,
filterable: true, filterable: true,
public: true, public: true,
}, },
@ -278,6 +203,8 @@ export default defineComponent({
label: 'Kategorie', label: 'Kategorie',
field: 'type', field: 'type',
format: (val: FG.DrinkType) => `${val.name}`, format: (val: FG.DrinkType) => `${val.name}`,
sortable: true,
sort: (a: FG.DrinkType, b: FG.DrinkType) => sort(a.name, b.name),
filterable: true, filterable: true,
public: true, public: true,
}, },
@ -303,6 +230,7 @@ export default defineComponent({
label: 'Artikelnummer', label: 'Artikelnummer',
field: 'article_id', field: 'article_id',
sortable: true, sortable: true,
sort,
filterable: true, filterable: true,
public: false, public: false,
}, },
@ -311,6 +239,7 @@ export default defineComponent({
label: 'Inhalt in l des Gebinde', label: 'Inhalt in l des Gebinde',
field: 'volume', field: 'volume',
sortable: true, sortable: true,
sort,
public: false, public: false,
}, },
{ {
@ -318,6 +247,7 @@ export default defineComponent({
label: 'Gebindegröße', label: 'Gebindegröße',
field: 'package_size', field: 'package_size',
sortable: true, sortable: true,
sort,
public: false, public: false,
}, },
{ {
@ -326,6 +256,7 @@ export default defineComponent({
field: 'cost_per_package', field: 'cost_per_package',
format: (val: number | null) => (val ? `${val.toFixed(3)}` : ''), format: (val: number | null) => (val ? `${val.toFixed(3)}` : ''),
sortable: true, sortable: true,
sort,
public: false, public: false,
}, },
{ {
@ -334,6 +265,7 @@ export default defineComponent({
field: 'cost_per_volume', field: 'cost_per_volume',
format: (val: number | null) => (val ? `${val.toFixed(3)}` : ''), format: (val: number | null) => (val ? `${val.toFixed(3)}` : ''),
sortable: true, sortable: true,
sort: (a: ComputedRef, b: ComputedRef) => sort(a.value, b.value),
}, },
{ {
name: 'volumes', name: 'volumes',
@ -365,7 +297,7 @@ export default defineComponent({
}); });
return retVal; return retVal;
}, },
filterable: false, filterable: true,
sortable: false, sortable: false,
public: false, public: false,
}, },
@ -415,47 +347,9 @@ export default defineComponent({
const pagination = ref({ const pagination = ref({
sortBy: 'name', sortBy: 'name',
descending: false, descending: false,
page: 1, rowsPerPage: store.drinks.length,
rowsPerPage: 10,
rowsNumber: 10,
}); });
interface PaginationInterface {
sortBy: string;
descending: boolean;
page: number;
rowsPerPage: number;
rowsNumber: number;
}
const loading = ref(false);
async function onRequest(props: { pagination: PaginationInterface; filter?: Search }) {
const { page, rowsPerPage, sortBy, descending } = props.pagination;
loading.value = true;
console.log('search_keys', search_keys);
const fetchCount = rowsPerPage === 0 ? pagination.value.rowsNumber : rowsPerPage;
const startRow = (page - 1) * rowsPerPage;
try {
const result = await store.getDrinks({
offset: startRow,
limit: fetchCount,
descending,
search_name: props.filter?.value,
search_key: props.filter?.key,
});
pagination.value.page = page;
pagination.value.rowsPerPage = rowsPerPage;
pagination.value.sortBy = sortBy;
pagination.value.descending = descending;
console.log('result', result.count);
if (result.count) pagination.value.rowsNumber = result.count;
} catch (error) {
console.warn(error);
}
loading.value = false;
}
const drinkTypes = computed(() => store.drinkTypes); const drinkTypes = computed(() => store.drinkTypes);
function updateDrink(drink: Drink) { function updateDrink(drink: Drink) {
@ -467,7 +361,6 @@ export default defineComponent({
store.deleteDrink(editDrink.value); store.deleteDrink(editDrink.value);
} }
editDrink.value = undefined; editDrink.value = undefined;
void onRequest({ pagination: pagination.value, filter: search.value });
} }
const showNewDrink = ref(false); const showNewDrink = ref(false);
@ -519,7 +412,7 @@ export default defineComponent({
tags: [], tags: [],
type: undefined, type: undefined,
volumes: [], volumes: [],
has_image: false, uuid: '',
}; };
function newDrink() { function newDrink() {
@ -544,9 +437,6 @@ export default defineComponent({
for (const volume of toDeleteObjects.volumes) { for (const volume of toDeleteObjects.volumes) {
await store.deleteVolume(volume, drink); await store.deleteVolume(volume, drink);
} }
if (deletePic) {
await deletePicture();
}
if (drink.id > 0) { if (drink.id > 0) {
await store.updateDrink(drink); await store.updateDrink(drink);
} else { } else {
@ -555,15 +445,14 @@ export default defineComponent({
editDrink.value.id = _drink.id; editDrink.value.id = _drink.id;
} }
} }
if (drinkPic instanceof File && drinkPic.name) { if (deletePic) {
await deletePicture();
}
if (drinkPic instanceof File) {
await savePicture(drinkPic); await savePicture(drinkPic);
if (drink.id > 0) {
setNewImage(drink.id);
}
} }
editDrink.value = undefined; editDrink.value = undefined;
notLoading.value = true; notLoading.value = true;
void onRequest({ pagination: pagination.value, filter: search.value });
} }
function get_volumes(drink_id: number) { function get_volumes(drink_id: number) {
@ -572,21 +461,22 @@ export default defineComponent({
const notLoading = ref(true); const notLoading = ref(true);
function image(id: number | undefined) { const imageloading = ref<Array<{ id: number; loading: boolean }>>([]);
if (id) { function getImageLoading(id: number) {
const _newImage = getNewImage(id); const loading = imageloading.value.find((a) => a.id === id);
if (_newImage) { if (loading) {
return `${ return loading.loading;
api.defaults.baseURL || '' }
}/pricelist/drinks/${id}/picture?thumbnail?t=${_newImage.lastModified.toString()}`; return false;
} }
return `${api.defaults.baseURL || ''}/pricelist/drinks/${id}/picture?thumbnail`;
function image(uuid: string | undefined) {
if (uuid) {
return `${api.defaults.baseURL||''}/pricelist/picture/${uuid}?size=256`;
} }
return 'no-image.svg'; return 'no-image.svg';
} }
const showPic = ref(true);
return { return {
drinks: computed(() => store.drinks), drinks: computed(() => store.drinks),
pagination, pagination,
@ -608,13 +498,11 @@ export default defineComponent({
editing_drink, editing_drink,
get_volumes, get_volumes,
notLoading, notLoading,
getImageLoading,
newDrink, newDrink,
hasPermission, hasPermission,
PERMISSIONS, PERMISSIONS,
image, image,
loading,
onRequest,
showPic,
}; };
}, },
}); });

View File

@ -42,7 +42,7 @@
</div> </div>
</div> </div>
<div class="q-pa-sm"> <div class="q-pa-sm">
<div v-for="(price, index) in filterPublic(volume.prices)" :key="price.id"> <div v-for="(price, index) in volume.prices" :key="price.id">
<div class="fit row justify-around q-py-sm"> <div class="fit row justify-around q-py-sm">
<div <div
v-if="!editable || !hasPermission(PERMISSIONS.EDIT_PRICE)" v-if="!editable || !hasPermission(PERMISSIONS.EDIT_PRICE)"
@ -93,7 +93,7 @@
</q-btn> </q-btn>
</div> </div>
</div> </div>
<q-separator v-if="index < filterPublic(volume.prices).length - 1" /> <q-separator v-if="index < volume.prices.length - 1" />
</div> </div>
<div <div
v-if="!public && !nodetails && isUnderMinPrice" v-if="!public && !nodetails && isUnderMinPrice"
@ -294,13 +294,6 @@ export default defineComponent({
emit('update:modelValue', volumes.value); emit('update:modelValue', volumes.value);
} }
function filterPublic(prices: Array<FG.DrinkPrice>) {
if (props.public) {
return prices.filter((price) => price.public);
}
return prices;
}
const isUnderMinPrice = computed(() => { const isUnderMinPrice = computed(() => {
if (volumes.value) { if (volumes.value) {
const this_volume = volumes.value.find((a) => a.id === volume.value); const this_volume = volumes.value.find((a) => a.id === volume.value);
@ -335,7 +328,6 @@ export default defineComponent({
isUnderMinPrice, isUnderMinPrice,
hasPermission, hasPermission,
PERMISSIONS, PERMISSIONS,
filterPublic,
}; };
}, },
}); });

View File

@ -9,8 +9,7 @@
<div v-if="ingredient.drink_ingredient" class="col"> <div v-if="ingredient.drink_ingredient" class="col">
<div class="full-width row justify-evenly q-py-xs"> <div class="full-width row justify-evenly q-py-xs">
<div class="col"> <div class="col">
<!--{{ get_drink_ingredient_name(ingredient.drink_ingredient.ingredient_id) }}--> {{ get_drink_ingredient_name(ingredient.drink_ingredient.ingredient_id) }}
{{ ingredient.drink_ingredient.name }}
<q-popup-edit <q-popup-edit
v-if="editable" v-if="editable"
v-slot="scope" v-slot="scope"
@ -26,8 +25,6 @@
label="Getränk" label="Getränk"
filled filled
dense dense
use-input
@filter="filter_drinks"
:options="drinks" :options="drinks"
option-label="name" option-label="name"
option-value="id" option-value="id"
@ -81,8 +78,6 @@
v-model="ingredient.extra_ingredient" v-model="ingredient.extra_ingredient"
filled filled
dense dense
use-input
@filter="filter_extra_ingredients"
:options="extra_ingredients" :options="extra_ingredients"
option-label="name" option-label="name"
/> />
@ -112,10 +107,8 @@
v-model="newIngredient" v-model="newIngredient"
filled filled
dense dense
use-input
label="Zutat" label="Zutat"
:options="[...drinks, ...extra_ingredients]" :options="[...drinks, ...extra_ingredients]"
@filter="filter"
option-label="name" option-label="name"
/> />
</div> </div>
@ -162,7 +155,7 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, PropType, ref, onBeforeMount, unref } from 'vue'; import { computed, defineComponent, PropType, ref, onBeforeMount, unref } from 'vue';
import { usePricelistStore, Drink } from '../../store'; import { usePricelistStore } from '../../store';
import { clone } from '../../utils/utils'; import { clone } from '../../utils/utils';
export default defineComponent({ export default defineComponent({
@ -203,8 +196,6 @@ export default defineComponent({
id: -1, id: -1,
ingredient_id: newIngredient.value.id, ingredient_id: newIngredient.value.id,
volume: newIngredientVolume.value, volume: newIngredientVolume.value,
cost_per_volume: <number>(<FG.Drink>newIngredient.value).cost_per_volume,
name: newIngredient.value.name,
}, },
extra_ingredient: undefined, extra_ingredient: undefined,
}; };
@ -243,44 +234,13 @@ export default defineComponent({
emit('update:modelValue', unref(edit_ingredients)); emit('update:modelValue', unref(edit_ingredients));
update(); update();
} }
const drinks = ref<Array<Drink>>([]); const drinks = computed(() =>
const _extra_ingredients = computed(() => store.extraIngredients); store.drinks.filter((drink) => {
const extra_ingredients = ref(_extra_ingredients.value); console.log('computed drinks', drink.name, drink.cost_per_volume);
return drink.cost_per_volume;
async function filter_drinks(val: string, update: (a: () => void) => void) { })
let result = <Array<Drink>>[]; );
if (val === '') { const extra_ingredients = computed(() => store.extraIngredients);
result = await store.getDrinks_no_store({ limit: 5, ingredient: true });
} else {
result = await store.getDrinks_no_store({
limit: 5,
search_name: val,
search_key: 'name',
ingredient: true,
});
}
update(() => {
drinks.value = result;
});
}
function filter_extra_ingredients(val: string, update: (a: () => void) => void) {
if (val === '') {
update(() => {
extra_ingredients.value = _extra_ingredients.value;
});
} else {
update(() => {
extra_ingredients.value = _extra_ingredients.value.filter((ingredient) => {
return ingredient.name.toLowerCase().includes(val.toLowerCase());
});
});
}
}
async function filter(val: string, update: () => void) {
await filter_drinks(val, update);
filter_extra_ingredients(val, update);
}
function get_drink_ingredient_name(id: number) { function get_drink_ingredient_name(id: number) {
return store.drinks.find((a) => a.id === id)?.name; return store.drinks.find((a) => a.id === id)?.name;
@ -303,9 +263,6 @@ export default defineComponent({
deleteIngredient, deleteIngredient,
get_drink_ingredient_name, get_drink_ingredient_name,
edit_ingredients, edit_ingredients,
filter,
filter_drinks,
filter_extra_ingredients,
}; };
}, },
}); });

View File

@ -24,28 +24,26 @@
</div> </div>
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
<q-img :src="image" style="max-height: 256px" fit="contain" />
<div class="full-width row"> <div class="full-width row">
<div class="col-10 q-pa-sm"> <div class="col-10 q-pa-sm">
<q-file <q-file
v-model="drinkPic" v-model="drinkPic"
filled filled
:clearable="drinkPic.name != ''" clearable
dense dense
accept=".jpg, .jpeg, .png, image/*"
@update:model-value="imagePreview" @update:model-value="imagePreview"
@clear="clear" @clear="imgsrc = undefined"
> >
<template #file> <template #prepend>
<q-img :src="image" style="max-height: 256px" fit="contain" /> <q-icon name="mdi-image" />
</template> </template>
</q-file> </q-file>
</div> </div>
<div class="column justify-center"> <div class="col-2 q-pa-sm text-right">
<div class="col-2 q-pa-sm"> <q-btn round icon="mdi-delete" color="negative" size="sm" @click="delete_pic">
<q-btn round icon="mdi-delete" color="negative" size="sm" @click="delete_pic"> <q-tooltip> Bild entfernen </q-tooltip>
<q-tooltip> Bild entfernen </q-tooltip> </q-btn>
</q-btn>
</div>
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
@ -140,15 +138,13 @@
</q-card-section> </q-card-section>
<q-card-actions class="justify-around"> <q-card-actions class="justify-around">
<q-btn label="Abbrechen" @click="cancel" /> <q-btn label="Abbrechen" @click="cancel" />
<!--<q-btn v-if="can_delete" label="Löschen" color="negative" @click="delete_drink" />--> <q-btn v-if="can_delete" label="Löschen" color="negative" @click="delete_drink" />
<q-btn label="Löschen" color="negative" @click="delete_drink" />
<q-btn label="Speichern" color="primary" @click="save" /> <q-btn label="Speichern" color="primary" @click="save" />
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</template> </template>
<script lang="ts"> <script lang="ts">
import { clone, calc_min_prices, DeleteObjects, calc_cost_per_volume } from '../utils/utils'; import { clone, calc_min_prices, DeleteObjects, calc_cost_per_volume } from '../utils/utils';
import { getNewImage } from '../utils/image';
import { defineComponent, PropType, ref, onBeforeMount, computed } from 'vue'; import { defineComponent, PropType, ref, onBeforeMount, computed } from 'vue';
import DrinkPriceVolumes from './CalculationTable/DrinkPriceVolumes.vue'; import DrinkPriceVolumes from './CalculationTable/DrinkPriceVolumes.vue';
import BuildManual from '../components/CalculationTable/BuildManual.vue'; import BuildManual from '../components/CalculationTable/BuildManual.vue';
@ -178,6 +174,7 @@ export default defineComponent({
setup(props, { emit }) { setup(props, { emit }) {
onBeforeMount(() => { onBeforeMount(() => {
//edit_drink.value = <Drink>JSON.parse(JSON.stringify(props.drink)); //edit_drink.value = <Drink>JSON.parse(JSON.stringify(props.drink));
edit_drink.value = clone(props.drink);
edit_volumes.value = clone(props.drink.volumes); edit_volumes.value = clone(props.drink.volumes);
}); });
@ -191,12 +188,10 @@ export default defineComponent({
ingredients: [], ingredients: [],
}); });
const edit_drink = ref<Drink>(clone(props.drink)); const edit_drink = ref<Drink>();
const edit_volumes = ref<Array<DrinkPriceVolume>>([]); const edit_volumes = ref<Array<DrinkPriceVolume>>([]);
function save() { function save() {
(<Drink>edit_drink.value).volumes = edit_volumes.value; (<Drink>edit_drink.value).volumes = edit_volumes.value;
edit_drink.value.cost_per_volume = calc_cost_per_volume(edit_drink.value);
edit_drink.value._cost_per_volume = edit_drink.value.cost_per_volume;
emit('save', <Drink>edit_drink.value, toDeleteObjects.value, drinkPic.value, deletePic.value); emit('save', <Drink>edit_drink.value, toDeleteObjects.value, drinkPic.value, deletePic.value);
} }
@ -243,7 +238,7 @@ export default defineComponent({
edit_drink.value?.receipt?.splice(event, 1); edit_drink.value?.receipt?.splice(event, 1);
} }
const drinkPic = ref<File>(new File([], '', {})); const drinkPic = ref();
const imgsrc = ref(); const imgsrc = ref();
const deletePic = ref(false); const deletePic = ref(false);
@ -251,9 +246,9 @@ export default defineComponent({
function delete_pic() { function delete_pic() {
deletePic.value = true; deletePic.value = true;
imgsrc.value = undefined; imgsrc.value = undefined;
drinkPic.value = new File([], '', {}); drinkPic.value = undefined;
if (edit_drink.value) { if (edit_drink.value) {
edit_drink.value.has_image = false; edit_drink.value.uuid = '';
} }
} }
@ -270,23 +265,14 @@ export default defineComponent({
} }
const image = computed(() => { const image = computed(() => {
console.log(imgsrc.value, deletePic.value, edit_drink.value); if (deletePic.value) {
return 'no-image.svg';
}
if (imgsrc.value) { if (imgsrc.value) {
return <string>imgsrc.value; return <string>imgsrc.value;
} }
if (deletePic.value && !imgsrc.value) { if (edit_drink.value?.uuid) {
return 'no-image.svg'; return `${api.defaults.baseURL||''}/pricelist/picture/${edit_drink.value.uuid}?size=256`;
}
if (edit_drink.value?.has_image) {
const _image = getNewImage(edit_drink.value?.id);
if (_image) {
return `${api.defaults.baseURL || ''}/pricelist/drinks/${
edit_drink.value.id
}/picture?thumbnail?t=${_image.lastModified.toString()}`;
}
return `${api.defaults.baseURL || ''}/pricelist/drinks/${
edit_drink.value.id
}/picture?thumbnail`;
} }
return 'no-image.svg'; return 'no-image.svg';
}); });
@ -335,11 +321,6 @@ export default defineComponent({
edit_volumes.value?.some((a) => a.ingredients.length > 0) edit_volumes.value?.some((a) => a.ingredients.length > 0)
); );
function clear(val: File) {
drinkPic.value = new File([], '', {});
imgsrc.value = undefined;
}
return { return {
edit_drink, edit_drink,
save, save,
@ -366,7 +347,6 @@ export default defineComponent({
hasIngredients, hasIngredients,
hasPermission, hasPermission,
PERMISSIONS, PERMISSIONS,
clear,
}; };
}, },
}); });

View File

@ -5,13 +5,11 @@
:rows="drinks" :rows="drinks"
:visible-columns="visibleColumns" :visible-columns="visibleColumns"
:filter="search" :filter="search"
:filter-method="filter"
dense dense
v-model:pagination="pagination" :pagination="pagination"
:fullscreen="fullscreen" :fullscreen="fullscreen"
@request="onRequest"
> >
<!--
:filter-method="filter"-->
<template #top-right> <template #top-right>
<div class="row justify-end q-gutter-sm"> <div class="row justify-end q-gutter-sm">
<search-input v-model="search" :keys="options" /> <search-input v-model="search" :keys="options" />
@ -29,7 +27,7 @@
options-cover options-cover
/> />
<q-btn round icon="mdi-backburger"> <q-btn round icon="mdi-backburger">
<q-tooltip anchor="top middle" self="bottom middle"> Reihenfolge ändern </q-tooltip> <q-tooltip anchor='top middle' self='bottom middle'> Reihenfolge ändern </q-tooltip>
<q-menu anchor="bottom middle" self="top middle"> <q-menu anchor="bottom middle" self="top middle">
<drag v-model="order" class="q-list" ghost-class="ghost" group="people" item-key="id"> <drag v-model="order" class="q-list" ghost-class="ghost" group="people" item-key="id">
<template #item="{ element }"> <template #item="{ element }">
@ -82,7 +80,7 @@ import SearchInput from '../components/SearchInput.vue';
import { usePricelistStore, Order } from '../store'; import { usePricelistStore, Order } from '../store';
import { useMainStore } from '@flaschengeist/api'; import { useMainStore } from '@flaschengeist/api';
import { Search, filter } from '../utils/filter'; import { Search, filter } from '../utils/filter';
import draggableComponent from 'vuedraggable'; import drag from 'vuedraggable';
interface Row { interface Row {
name: string; name: string;
@ -96,7 +94,7 @@ interface Row {
export default defineComponent({ export default defineComponent({
name: 'Pricelist', name: 'Pricelist',
components: { SearchInput, drag: draggableComponent }, components: { SearchInput, drag: <ComponentPublicInstance>drag },
props: { props: {
public: { public: {
type: Boolean, type: Boolean,
@ -111,11 +109,11 @@ export default defineComponent({
if (!props.public) { if (!props.public) {
user.value = useMainStore().currentUser.userid; user.value = useMainStore().currentUser.userid;
void store.getPriceListColumnOrder(user.value); void store.getPriceListColumnOrder(user.value);
void store.getDrinks();
void store.getPriceCalcColumn(user.value); void store.getPriceCalcColumn(user.value);
} else { } else {
user.value = ''; user.value = '';
} }
void onRequest({ pagination: pagination.value });
}); });
const _order = ref<Array<Order>>([ const _order = ref<Array<Order>>([
@ -172,9 +170,7 @@ export default defineComponent({
{ {
name: 'name', name: 'name',
label: 'Name', label: 'Name',
//field: 'name', field: 'name',
field: 'volume',
format: (val: FG.DrinkPriceVolume) => <string>val.drink?.name,
sortable: true, sortable: true,
filterable: true, filterable: true,
align: 'left', align: 'left',
@ -182,23 +178,21 @@ export default defineComponent({
{ {
name: 'type', name: 'type',
label: 'Kategorie', label: 'Kategorie',
//field: 'type', field: 'type',
field: 'volume',
sortable: true, sortable: true,
filterable: true, filterable: true,
format: (val: FG.DrinkPriceVolume) => <string>val.drink?.type?.name, format: (val: FG.DrinkType) => val.name,
}, },
{ {
name: 'tags', name: 'tags',
label: 'Tags', label: 'Tags',
//field: 'tags', field: 'tags',
field: 'volume',
filterable: true, filterable: true,
format: (val: FG.DrinkPriceVolume) => { format: (val: Array<FG.Tag>) => {
let retVal = ''; let retVal = '';
val.drink?.tags?.forEach((tag, index) => { val.forEach((tag, index) => {
if (index >= (<Array<FG.Tag>>val.drink?.tags).length - 1 && index > 0) { if (index >= val.length - 1 && index > 0) {
retVal += ', '; retVal += ', ';
} }
retVal += tag.name; retVal += tag.name;
@ -209,11 +203,10 @@ export default defineComponent({
{ {
name: 'volume', name: 'volume',
label: 'Inhalt', label: 'Inhalt',
//field: 'volume',
field: 'volume', field: 'volume',
filterable: true, filterable: true,
sortable: true, sortable: true,
format: (val: FG.DrinkPriceVolume) => `${val.volume.toFixed(3)}L`, format: (val: number) => `${val.toFixed(3)}L`,
}, },
{ {
name: 'price', name: 'price',
@ -304,48 +297,9 @@ export default defineComponent({
label: '', label: '',
}); });
interface PaginationInterface {
sortBy: string;
descending: boolean;
page: number;
rowsPerPage: number;
rowsNumber: number;
}
async function onRequest(props: { pagination: PaginationInterface; filter?: Search }) {
const { page, rowsPerPage, sortBy, descending } = props.pagination;
loading.value = true;
//console.log('search_keys', search_keys);
const fetchCount = rowsPerPage === 0 ? pagination.value.rowsNumber : rowsPerPage;
const startRow = (page - 1) * rowsPerPage;
console.log('descending', descending);
try {
const result = await store.getPricelist({
offset: startRow,
limit: fetchCount,
descending: descending ? true : undefined,
search_name: props.filter?.value,
search_key: props.filter?.key,
sortBy: sortBy,
});
pagination.value.page = page;
pagination.value.rowsPerPage = rowsPerPage;
pagination.value.sortBy = sortBy;
pagination.value.descending = descending;
console.log('result', result);
if (result) pagination.value.rowsNumber = result;
} catch (error) {
console.warn(error);
}
loading.value = false;
}
const loading = ref(false);
const pagination = ref({ const pagination = ref({
sortBy: 'name', sortBy: 'name',
rowsPerPage: 10, rowsPerPage: 10,
rowsNumber: 10,
descending: false,
page: 1,
}); });
const fullscreen = ref(false); const fullscreen = ref(false);
@ -360,8 +314,6 @@ export default defineComponent({
filter, filter,
pagination, pagination,
fullscreen, fullscreen,
loading,
onRequest,
}; };
}, },
}); });

View File

@ -1,12 +1,13 @@
import { innerRoutes, outerRoutes } from './routes'; import { innerRoutes, outerRoutes } from './routes';
import { FG_Plugin } from '@flaschengeist/types'; import { FG_Plugin } from '@flaschengeist/typings';
const plugin: FG_Plugin.Plugin = { const plugin: FG_Plugin.Plugin = {
id: 'pricelist', id: 'pricelist',
name: 'Pricelist', name: 'Pricelist',
innerRoutes, innerRoutes,
outerRoutes, outerRoutes,
requiredModules: [['pricelist']], requiredModules: [],
requiredBackendModules: ['pricelist'],
version: '0.0.1', version: '0.0.1',
widgets: [], widgets: [],
}; };

View File

@ -39,7 +39,7 @@ export default defineComponent({
void store.get_min_prices().finally(() => { void store.get_min_prices().finally(() => {
volume.value.min_prices = calc_min_prices(volume.value, undefined, store.min_prices); volume.value.min_prices = calc_min_prices(volume.value, undefined, store.min_prices);
}); });
void store.getDrinks({ limit: 10 }); void store.getDrinks();
void store.getDrinkTypes(); void store.getDrinkTypes();
void store.getExtraIngredients(); void store.getExtraIngredients();
}); });

View File

@ -25,7 +25,7 @@ export default defineComponent({
const mainStore = useMainStore(); const mainStore = useMainStore();
onBeforeMount(() => { onBeforeMount(() => {
void store.getDrinks({ limit: 10 }); void store.getDrinks();
void store.getPriceListView(mainStore.currentUser.userid); void store.getPriceListView(mainStore.currentUser.userid);
}); });

View File

@ -1,17 +1,14 @@
<template> <template>
<q-table <q-table
v-model:pagination="pagination"
grid grid
title="Rezepte" title="Rezepte"
:rows="drinks" :rows="drinks"
row-key="id" row-key="id"
hide-header hide-header
@request="onRequest"
:columns="options"
:filter="search" :filter="search"
:filter-method="filter"
:columns="options"
> >
<!--:filter="search"
:filter-method="filter"-->
<template #top-right> <template #top-right>
<search-input v-model="search" :keys="search_keys" /> <search-input v-model="search" :keys="search_keys" />
</template> </template>
@ -21,7 +18,7 @@
<q-img <q-img
style="max-height: 256px" style="max-height: 256px"
loading="lazy" loading="lazy"
:src="image(props.row.id)" :src="image(props.row.uuid)"
placeholder-src="no-image.svg" placeholder-src="no-image.svg"
> >
<div class="absolute-bottom-right justify-end"> <div class="absolute-bottom-right justify-end">
@ -71,16 +68,12 @@ export default defineComponent({
setup() { setup() {
const store = usePricelistStore(); const store = usePricelistStore();
onBeforeMount(() => { onBeforeMount(() => {
void onRequest({ void store.getDrinks();
pagination: pagination.value,
});
}); });
const drinks = computed( const drinks = computed(() =>
() => store.drinks.filter((drink) => {
//store.drinks.filter((drink) => { return drink.volumes.some((volume) => volume.ingredients.length > 0);
// return drink.volumes.some((volume) => volume.ingredients.length > 0); })
//})
store.drinks
); );
const columns_drinks = [ const columns_drinks = [
@ -88,7 +81,6 @@ export default defineComponent({
name: 'picture', name: 'picture',
label: 'Bild', label: 'Bild',
align: 'center', align: 'center',
filterable: false,
}, },
{ {
name: 'name', name: 'name',
@ -128,7 +120,6 @@ export default defineComponent({
label: 'Preise', label: 'Preise',
field: 'volumes', field: 'volumes',
align: 'center', align: 'center',
filterable: false,
}, },
]; ];
const columns_volumes = [ const columns_volumes = [
@ -164,53 +155,12 @@ export default defineComponent({
const search = ref<Search>({ value: '', key: '', label: '' }); const search = ref<Search>({ value: '', key: '', label: '' });
const search_keys = computed(() => columns_drinks.filter((column) => column.filterable)); const search_keys = computed(() => columns_drinks.filter((column) => column.filterable));
function image(id: number) { function image(uuid: string | undefined) {
if (id) { if (uuid) {
return `${api.defaults.baseURL || ''}/pricelist/drinks/${id}/picture?thumbnail`; return `${api.defaults.baseURL||''}/pricelist/picture/${uuid}?size=256`;
} }
return 'no-image.svg'; return 'no-image.svg';
} }
const loading = ref(false);
const pagination = ref({
sortBy: 'name',
descending: false,
page: 1,
rowsPerPage: 10,
rowsNumber: 10,
});
async function onRequest(props: { pagination: PaginationInterface; filter?: Search }) {
const { page, rowsPerPage, sortBy, descending } = props.pagination;
loading.value = true;
console.log('search_keys', search_keys);
const fetchCount = rowsPerPage === 0 ? pagination.value.rowsNumber : rowsPerPage;
const startRow = (page - 1) * rowsPerPage;
try {
const result = await store.getDrinks({
offset: startRow,
limit: fetchCount,
descending,
search_name: props.filter?.value,
search_key: props.filter?.key,
receipt: true,
});
pagination.value.page = page;
pagination.value.rowsPerPage = rowsPerPage;
pagination.value.sortBy = sortBy;
pagination.value.descending = descending;
if (result.count) pagination.value.rowsNumber = result.count;
} catch (error) {
//..
}
loading.value = false;
}
interface PaginationInterface {
sortBy: string;
descending: boolean;
page: number;
rowsPerPage: number;
rowsNumber: number;
}
return { return {
drinks, drinks,
options: [...columns_drinks, ...columns_volumes, ...columns_prices], options: [...columns_drinks, ...columns_volumes, ...columns_prices],
@ -218,9 +168,6 @@ export default defineComponent({
filter, filter,
search_keys, search_keys,
image, image,
onRequest,
loading,
pagination,
}; };
}, },
}); });

View File

@ -77,7 +77,7 @@ export default defineComponent({
.catch((err) => console.log(err)); .catch((err) => console.log(err));
void store.getTags(); void store.getTags();
void store.getDrinkTypes(); void store.getDrinkTypes();
void store.getDrinks({ limit: 10 }); void store.getDrinks();
void store.get_min_prices(); void store.get_min_prices();
}); });

View File

@ -1,7 +1,6 @@
import { api } from '@flaschengeist/api'; import { api } from '@flaschengeist/api';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { calc_volume, calc_cost_per_volume, calc_all_min_prices } from './utils/utils'; import { calc_volume, calc_cost_per_volume, calc_all_min_prices } from './utils/utils';
import Ingredient = FG.Ingredient;
interface DrinkPriceVolume extends Omit<FG.DrinkPriceVolume, 'volume'> { interface DrinkPriceVolume extends Omit<FG.DrinkPriceVolume, 'volume'> {
_volume: number; _volume: number;
@ -14,6 +13,16 @@ interface Drink extends Omit<Omit<FG.Drink, 'cost_per_volume'>, 'volumes'> {
_cost_per_volume?: number; _cost_per_volume?: number;
} }
interface Pricelist {
name: string;
type: FG.DrinkType;
tags: Array<FG.Tag>;
volume: number;
price: number;
public: boolean;
description: string;
}
class DrinkPriceVolume implements DrinkPriceVolume { class DrinkPriceVolume implements DrinkPriceVolume {
constructor({ id, volume, prices, ingredients }: FG.DrinkPriceVolume) { constructor({ id, volume, prices, ingredients }: FG.DrinkPriceVolume) {
this.id = id; this.id = id;
@ -36,7 +45,7 @@ class Drink {
cost_per_package, cost_per_package,
tags, tags,
type, type,
has_image, uuid,
receipt, receipt,
}: FG.Drink) { }: FG.Drink) {
this.id = id; this.id = id;
@ -50,7 +59,7 @@ class Drink {
this.tags = tags; this.tags = tags;
this.type = type; this.type = type;
this.volumes = []; this.volumes = [];
this.has_image = has_image; this.uuid = uuid;
this.receipt = receipt || []; this.receipt = receipt || [];
} }
} }
@ -66,7 +75,6 @@ export const usePricelistStore = defineStore({
state: () => ({ state: () => ({
drinkTypes: [] as Array<FG.DrinkType>, drinkTypes: [] as Array<FG.DrinkType>,
drinks: [] as Array<Drink>, drinks: [] as Array<Drink>,
pricelist: [] as Array<FG.DrinkPrice>,
extraIngredients: [] as Array<FG.ExtraIngredient>, extraIngredients: [] as Array<FG.ExtraIngredient>,
min_prices: [] as Array<number>, min_prices: [] as Array<number>,
tags: [] as Array<FG.Tag>, tags: [] as Array<FG.Tag>,
@ -98,16 +106,11 @@ export const usePricelistStore = defineStore({
if (itm.length > 0) itm[0].name = drinkType.name; if (itm.length > 0) itm[0].name = drinkType.name;
}, },
async getExtraIngredients() { async getExtraIngredients() {
const { data } = await api.get<Array<FG.ExtraIngredient>>( const { data } = await api.get<Array<FG.ExtraIngredient>>('pricelist/ingredients/extraIngredients');
'pricelist/ingredients/extraIngredients'
);
this.extraIngredients = data; this.extraIngredients = data;
}, },
async setExtraIngredient(ingredient: FG.ExtraIngredient) { async setExtraIngredient(ingredient: FG.ExtraIngredient) {
const { data } = await api.post<FG.ExtraIngredient>( const { data } = await api.post<FG.ExtraIngredient>('pricelist/ingredients/extraIngredients', ingredient);
'pricelist/ingredients/extraIngredients',
ingredient
);
this.extraIngredients.push(data); this.extraIngredients.push(data);
}, },
async updateExtraIngredient(ingredient: FG.ExtraIngredient) { async updateExtraIngredient(ingredient: FG.ExtraIngredient) {
@ -129,24 +132,10 @@ export const usePricelistStore = defineStore({
this.extraIngredients.splice(index, 1); this.extraIngredients.splice(index, 1);
} }
}, },
async getDrinks(filter: { async getDrinks() {
limit?: number; const { data } = await api.get<Array<FG.Drink>>('pricelist/drinks');
offset?: number;
descending?: boolean;
search_name?: string;
search_key?: string;
receipt?: boolean;
}) {
if (!filter) filter = { limit: 10 };
console.log('filter_api', filter);
const { data } = await api.get<{ drinks: Array<FG.Drink>; count: number }>(
'pricelist/drinks',
{
params: filter,
}
);
this.drinks = []; this.drinks = [];
data.drinks.forEach((drink) => { data.forEach((drink) => {
const _drink = new Drink(drink); const _drink = new Drink(drink);
drink.volumes.forEach((volume) => { drink.volumes.forEach((volume) => {
const _volume = new DrinkPriceVolume(volume); const _volume = new DrinkPriceVolume(volume);
@ -155,7 +144,6 @@ export const usePricelistStore = defineStore({
this.drinks.push(_drink); this.drinks.push(_drink);
}); });
calc_all_min_prices(this.drinks, this.min_prices); calc_all_min_prices(this.drinks, this.min_prices);
return data;
}, },
sortPrices(volume: DrinkPriceVolume) { sortPrices(volume: DrinkPriceVolume) {
volume.prices.sort((a, b) => { volume.prices.sort((a, b) => {
@ -164,54 +152,6 @@ export const usePricelistStore = defineStore({
return 0; return 0;
}); });
}, },
async getDrinks_no_store(filter: {
limit?: number;
offset?: number;
descending?: boolean;
search_name?: string;
search_key?: string;
ingredient?: boolean;
}) {
if (!filter) filter = { limit: 10 };
console.log('filter_api', filter);
const { data } = await api.get<{ drinks: Array<FG.Drink>; count: number }>(
'pricelist/drinks',
{
params: filter,
}
);
const drinks: Array<Drink> = [];
data.drinks.forEach((drink) => {
const _drink = new Drink(drink);
drink.volumes.forEach((volume) => {
const _volume = new DrinkPriceVolume(volume);
_drink.volumes.push(_volume);
});
drinks.push(_drink);
});
calc_all_min_prices(drinks, this.min_prices);
return drinks;
},
async getPricelist(filter: {
limit?: number;
offset?: number;
descending?: boolean;
search_name?: string;
search_key?: string;
sortBy?: string;
}) {
const { data } = await api.get<{ pricelist: Array<FG.DrinkPrice>; count: number }>(
'pricelist/list',
{
params: filter,
}
);
this.pricelist = [];
console.log(data);
this.pricelist = data.pricelist;
console.log(this.pricelist);
return data.count;
},
async deletePrice(price: FG.DrinkPrice) { async deletePrice(price: FG.DrinkPrice) {
await api.delete(`pricelist/prices/${price.id}`); await api.delete(`pricelist/prices/${price.id}`);
}, },
@ -251,23 +191,6 @@ export const usePricelistStore = defineStore({
}); });
this.drinks[index] = _drink; this.drinks[index] = _drink;
} }
if (!!drink.cost_per_volume) {
this.drinks.forEach((_drink: Drink) => {
_drink.volumes.forEach((_volume: DrinkPriceVolume) => {
_volume.ingredients.forEach((_ingredient: Ingredient) => {
if (
_ingredient.drink_ingredient &&
_ingredient.drink_ingredient.ingredient_id === drink.id
) {
if (drink.cost_per_volume != null) {
_ingredient.drink_ingredient.cost_per_volume = drink.cost_per_volume;
}
_ingredient.drink_ingredient.name = drink.name;
}
});
});
});
}
calc_all_min_prices(this.drinks, this.min_prices); calc_all_min_prices(this.drinks, this.min_prices);
}, },
deleteDrink(drink: Drink) { deleteDrink(drink: Drink) {
@ -299,12 +222,12 @@ export const usePricelistStore = defineStore({
}); });
const _drink = this.drinks.find((a) => a.id === drink.id); const _drink = this.drinks.find((a) => a.id === drink.id);
if (_drink) { if (_drink) {
_drink.has_image = data.has_image; _drink.uuid = data.uuid;
} }
}, },
async delete_drink_picture(drink: Drink) { async delete_drink_picture(drink: Drink) {
await api.delete(`pricelist/drinks/${drink.id}/picture`); await api.delete(`pricelist/drinks/${drink.id}/picture`);
drink.has_image = false; drink.uuid = '';
}, },
async getTags() { async getTags() {
const { data } = await api.get<Array<FG.Tag>>('/pricelist/tags'); const { data } = await api.get<Array<FG.Tag>>('/pricelist/tags');
@ -345,9 +268,7 @@ export const usePricelistStore = defineStore({
this.pricelist_view = data; this.pricelist_view = data;
}, },
async getPriceListColumnOrder(userid: string) { async getPriceListColumnOrder(userid: string) {
const { data } = await api.get<Array<Order>>( const { data } = await api.get<Array<Order>>(`pricelist/users/${userid}/pricecalc_columns_order`);
`pricelist/users/${userid}/pricecalc_columns_order`
);
this.pricelist_columns_order = data; this.pricelist_columns_order = data;
}, },
async updatePriceListColumnOrder(userid: string, data: Array<Order>) { async updatePriceListColumnOrder(userid: string, data: Array<Order>) {
@ -355,7 +276,27 @@ export const usePricelistStore = defineStore({
this.pricelist_columns_order = data; this.pricelist_columns_order = data;
}, },
}, },
getters: {}, getters: {
pricelist() {
const retVal: Array<Pricelist> = [];
this.drinks.forEach((drink) => {
drink.volumes.forEach((volume) => {
volume.prices.forEach((price) => {
retVal.push({
name: drink.name,
type: <FG.DrinkType>drink.type,
tags: <Array<FG.Tag>>drink.tags,
volume: <number>volume.volume,
price: price.price,
public: price.public,
description: <string>price.description,
});
});
});
});
return retVal;
},
},
}); });
export { DrinkPriceVolume, Drink, Order }; export { DrinkPriceVolume, Drink, Order };

View File

@ -1,11 +1,6 @@
import { Drink } from '../store'; import { Drink } from '../store';
function filter( function filter(rows: Array<Drink>, terms: Search, cols: Array<Col>, cellValue: { (col: Col, row: Drink): string }) {
rows: Array<Drink>,
terms: Search,
cols: Array<Col>,
cellValue: { (col: Col, row: Drink): string }
) {
if (terms.value) { if (terms.value) {
return rows.filter((row) => { return rows.filter((row) => {
if (!terms.key || terms.key === '') { if (!terms.key || terms.key === '') {

View File

@ -1,20 +0,0 @@
import { ref } from 'vue';
const newImage = ref<Array<{ id: number; lastModified: Date }>>([]);
export function getNewImage(id: number) {
const image = newImage.value.find((a) => a.id === id);
if (image) {
return image;
}
return null;
}
export function setNewImage(id: number) {
const image = newImage.value.find((a) => a.id === id);
if (image) {
image.lastModified = new Date();
} else {
newImage.value.push({ id, lastModified: new Date() });
}
}

View File

@ -17,8 +17,7 @@ function calc_volume(volume: DrinkPriceVolume) {
function calc_cost_per_volume(drink: Drink) { function calc_cost_per_volume(drink: Drink) {
let retVal = drink._cost_per_volume; let retVal = drink._cost_per_volume;
if (!!drink.volume && !!drink.package_size && !!drink.cost_per_package) { if (!!drink.volume && !!drink.package_size && !!drink.cost_per_package) {
retVal = retVal = ((drink.cost_per_package || 0) / ((drink.volume || 0) * (drink.package_size || 0))) * 1.19;
((drink.cost_per_package || 0) / ((drink.volume || 0) * (drink.package_size || 0))) * 1.19;
} }
return retVal ? Math.round(retVal * 1000) / 1000 : retVal; return retVal ? Math.round(retVal * 1000) / 1000 : retVal;
@ -37,9 +36,8 @@ function helper(volume: DrinkPriceVolume, min_price: number) {
let extraIngredientPrice = 0; let extraIngredientPrice = 0;
volume.ingredients.forEach((ingredient) => { volume.ingredients.forEach((ingredient) => {
if (ingredient.drink_ingredient) { if (ingredient.drink_ingredient) {
//const _drink = usePricelistStore().drinks.find((a) => a.id === ingredient.drink_ingredient?.ingredient_id); const _drink = usePricelistStore().drinks.find((a) => a.id === ingredient.drink_ingredient?.ingredient_id);
//retVal += ingredient.drink_ingredient.volume * <number>(<unknown>_drink?.cost_per_volume); retVal += ingredient.drink_ingredient.volume * <number>(<unknown>_drink?.cost_per_volume);
retVal += ingredient.drink_ingredient.volume * ingredient.drink_ingredient.cost_per_volume;
} }
if (ingredient.extra_ingredient) { if (ingredient.extra_ingredient) {
extraIngredientPrice += ingredient.extra_ingredient.price; extraIngredientPrice += ingredient.extra_ingredient.price;
@ -48,11 +46,7 @@ function helper(volume: DrinkPriceVolume, min_price: number) {
return (retVal * min_price) / 100 + extraIngredientPrice; return (retVal * min_price) / 100 + extraIngredientPrice;
} }
function calc_min_prices( function calc_min_prices(volume: DrinkPriceVolume, cost_per_volume: number | undefined, min_prices: Array<number>) {
volume: DrinkPriceVolume,
cost_per_volume: number | undefined,
min_prices: Array<number>
) {
const retVal: Array<FG.MinPrices> = []; const retVal: Array<FG.MinPrices> = [];
volume.min_prices = []; volume.min_prices = [];
if (min_prices) { if (min_prices) {