update logic
This commit is contained in:
parent
20490ef9b6
commit
8d18e5838e
@ -1,6 +0,0 @@
|
||||
DB_SERVER=localhost
|
||||
DB_NAME=inventur
|
||||
DB_USER=inventur
|
||||
DB_PASSWORD=inventur
|
||||
|
||||
NODE_ENV=production
|
||||
198
app/js/App.vue
198
app/js/App.vue
@ -23,6 +23,8 @@
|
||||
<InventoryTable
|
||||
ref="inventory"
|
||||
:articles="articles"
|
||||
:groups="groups"
|
||||
:dimensions="dimensions"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@ -37,120 +39,11 @@
|
||||
id="article"
|
||||
class="col s12"
|
||||
>
|
||||
<div
|
||||
v-for="(a, index) in articles"
|
||||
:key="'article_'+index"
|
||||
class="card"
|
||||
>
|
||||
<div class="card-content row">
|
||||
<span class="card-title">{{ a.name }}</span>
|
||||
<div class="input-field inline col s8">
|
||||
<input
|
||||
:id="'a_name_'+index"
|
||||
v-model="a.name"
|
||||
placeholder="Artikelname"
|
||||
class="validate"
|
||||
>
|
||||
<label
|
||||
:for="'a_name_'+index"
|
||||
class="active"
|
||||
>Name</label>
|
||||
</div>
|
||||
<div class="input-field inline col s4">
|
||||
<input
|
||||
:id="'a_short_'+index"
|
||||
v-model="a.short"
|
||||
placeholder="Kurzname"
|
||||
class="validate"
|
||||
>
|
||||
<label
|
||||
:for="'a_short_'+index"
|
||||
class="active"
|
||||
>Kürzel</label>
|
||||
</div>
|
||||
<div class="input-field col s8">
|
||||
<input
|
||||
:id="'a_csize_'+index"
|
||||
v-model.number="a.content.size"
|
||||
placeholder="Gesamtinhalt"
|
||||
type="number"
|
||||
class="validate"
|
||||
step="0.01"
|
||||
>
|
||||
<label
|
||||
:for="'a_csize_'+index"
|
||||
class="active"
|
||||
>Gesamtinhalt</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<input
|
||||
:id="'a_dim_'+index"
|
||||
v-model="a.dimension"
|
||||
placeholder="Dimension"
|
||||
class="validate"
|
||||
max="5"
|
||||
>
|
||||
<label
|
||||
:for="'a_dim_'+index"
|
||||
class="active"
|
||||
>Dimension</label>
|
||||
<span class="helper-text">z.B. Liter(l), Stück(Stk.)</span>
|
||||
</div>
|
||||
|
||||
<div class="input-field col s4">
|
||||
<input
|
||||
:id="'a_psize_'+index"
|
||||
v-model.number="a.portion.size"
|
||||
placeholder="Gesamtinhalt"
|
||||
type="number"
|
||||
class="validate"
|
||||
step="0.01"
|
||||
max="5"
|
||||
>
|
||||
<label
|
||||
:for="'a_psize_'+index"
|
||||
class="active"
|
||||
>Portionsinhalt</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<input
|
||||
:id="'a_ptype_'+index"
|
||||
v-model="a.portion.type"
|
||||
placeholder="Art"
|
||||
class="validate"
|
||||
max="5"
|
||||
>
|
||||
<label
|
||||
:for="'a_ptype_'+index"
|
||||
class="active"
|
||||
>Portionsbezeichnung</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<input
|
||||
:id="'a_pprice_'+index"
|
||||
v-model.number="a.portion.price"
|
||||
placeholder="Preis"
|
||||
type="number"
|
||||
step="0.01"
|
||||
class="validate"
|
||||
>
|
||||
<label
|
||||
:for="'a_pprice_'+index"
|
||||
class="active"
|
||||
>Portionspreis in €</label>
|
||||
</div>
|
||||
|
||||
<div class="col s12 right">
|
||||
<span class="right">Gesamtpreis {{ a.ContentPrice }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a
|
||||
class
|
||||
href="#"
|
||||
>Artikel löschen</a>
|
||||
</div>
|
||||
</div>
|
||||
<ArticlesView
|
||||
:articles="articles"
|
||||
:groups="groups"
|
||||
:dimensions="dimensions"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
@ -165,17 +58,6 @@
|
||||
<i class="large material-icons">more_vert</i>
|
||||
</a>
|
||||
<ul>
|
||||
<li v-show="view == 'article'">
|
||||
<a
|
||||
href="#"
|
||||
class="btn-floating tooltipped"
|
||||
data-position="left"
|
||||
data-tooltip="Artikelliste speichern"
|
||||
@click="storeArticles"
|
||||
>
|
||||
<i class="material-icons">save</i>
|
||||
</a>
|
||||
</li>
|
||||
<li v-show="view == 'article'">
|
||||
<a
|
||||
href="#"
|
||||
@ -217,20 +99,23 @@
|
||||
<script>
|
||||
import Calculator from './components/Calculator';
|
||||
import InventoryTable from './components/InventoryTable';
|
||||
import ArticlesView from './components/ArticlesView';
|
||||
import NavigationBar from './components/NavigationBar';
|
||||
import {Article, thawArticle} from './model/article';
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: { InventoryTable, NavigationBar, Calculator },
|
||||
components: { InventoryTable, NavigationBar, Calculator, ArticlesView },
|
||||
data() {
|
||||
return {
|
||||
articles: [],
|
||||
dimensions: [],
|
||||
groups: [],
|
||||
bon: [],
|
||||
ready: false,
|
||||
caretPosition: 0,
|
||||
fab: null,
|
||||
view: "inventory",
|
||||
view: "article",
|
||||
location: 'fairwertbar'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -245,9 +130,14 @@ export default {
|
||||
default:
|
||||
return "red";
|
||||
}
|
||||
},
|
||||
ready() {
|
||||
return this.dimensions.length && this.groups.length;
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.loadDimensions();
|
||||
this.loadGroups();
|
||||
this.loadArticles();
|
||||
},
|
||||
mounted() {
|
||||
@ -261,35 +151,59 @@ export default {
|
||||
addArticle: function() {
|
||||
this.articles.push(new Article());
|
||||
},
|
||||
storeArticles: function() {
|
||||
loadDimensions: function() {
|
||||
var app = this;
|
||||
this.$http
|
||||
.post(
|
||||
"/api/artikel",
|
||||
JSON.stringify(this.articles)
|
||||
)
|
||||
.get("api/dimension")
|
||||
.then(response => {
|
||||
this.$M.toast({ html: response.body });
|
||||
return response.data;
|
||||
})
|
||||
.then(json => {
|
||||
if (json != null) {
|
||||
json.forEach(element => {
|
||||
app.dimensions.push(element);
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.$M.toast({ html: "Dimensionen wurden geladen." });
|
||||
});
|
||||
},
|
||||
loadGroups: function() {
|
||||
var app = this;
|
||||
this.$http
|
||||
.get("api/group")
|
||||
.then(response => {
|
||||
return response.data;
|
||||
})
|
||||
.then(json => {
|
||||
if (json != null) {
|
||||
json.forEach(element => {
|
||||
app.groups.push(element);
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.$M.toast({ html: "Artikelgruppen wurden geladen." });
|
||||
});
|
||||
},
|
||||
loadArticles: function() {
|
||||
var app = this;
|
||||
this.$http
|
||||
.get("api/artikel/theater")
|
||||
.get("api/artikel")
|
||||
.then(response => {
|
||||
return response.data;
|
||||
})
|
||||
.then(json => {
|
||||
console.log(json);
|
||||
json.forEach(element => {
|
||||
app.articles.push(thawArticle(element));
|
||||
});
|
||||
if (json != null) {
|
||||
json.forEach(element => {
|
||||
app.articles.push(thawArticle(element));
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.$M.toast({ html: "Artikel wurden geladen." });
|
||||
})
|
||||
.then(() => {
|
||||
this.$M.updateTextFields();
|
||||
app.ready = true;
|
||||
});
|
||||
},
|
||||
exportInventur() {
|
||||
|
||||
315
app/js/components/ArticleCard.vue
Normal file
315
app/js/components/ArticleCard.vue
Normal file
@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<div
|
||||
class="card"
|
||||
>
|
||||
<div class="card-content">
|
||||
<span class="card-title">
|
||||
{{ article.name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-tabs">
|
||||
<ul class="tabs tabs-fixed-width">
|
||||
<li class="tab">
|
||||
<a
|
||||
:href="'#' + identifier + '_info'"
|
||||
class="active"
|
||||
>Info</a>
|
||||
</li>
|
||||
<li class="tab">
|
||||
<a :href="'#' + identifier + '_variants'">Varianten</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<div
|
||||
:id="identifier + '_info'"
|
||||
class="row"
|
||||
>
|
||||
<div class="input-field inline col s8">
|
||||
<input
|
||||
:id="'a_name_' + article.id"
|
||||
v-model="article.name"
|
||||
placeholder="Artikelname"
|
||||
class="validate"
|
||||
>
|
||||
<label
|
||||
:for="'a_name_' + article.id"
|
||||
class="active"
|
||||
>Name</label>
|
||||
</div>
|
||||
<div class="input-field inline col s4">
|
||||
<input
|
||||
:id="'a_short_' + article.id"
|
||||
v-model="article.short"
|
||||
placeholder="Kurzname"
|
||||
class="validate"
|
||||
>
|
||||
<label
|
||||
:for="'a_short_' + article.id"
|
||||
class="active"
|
||||
>Kürzel</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<materialize-select
|
||||
:id="'a_group_' + article.id"
|
||||
v-model.number="article.group"
|
||||
:value="article.group"
|
||||
placeholder="Artikelgruppe"
|
||||
>
|
||||
<option
|
||||
v-for="group in groups"
|
||||
:key="group.id"
|
||||
:value="group.id"
|
||||
>
|
||||
{{ group.name }}
|
||||
</option>
|
||||
</materialize-select>
|
||||
<label :for="'a_group_' + article.id">Artikelgruppe</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<input
|
||||
:id="'a_csize_' + article.id"
|
||||
v-model.number="article.content.size"
|
||||
placeholder="Gesamtinhalt"
|
||||
type="number"
|
||||
class="validate"
|
||||
step="0.01"
|
||||
>
|
||||
<label
|
||||
:for="'a_csize_' + article.id"
|
||||
class="active"
|
||||
>Gesamtinhalt</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<materialize-select
|
||||
:id="'a_dim_' + article.id"
|
||||
v-model.number="article.dimension"
|
||||
:value="article.dimension"
|
||||
placeholder="Dimension"
|
||||
>
|
||||
<option
|
||||
v-for="dim in dimensions"
|
||||
:key="dim.id"
|
||||
:value="dim.id"
|
||||
>
|
||||
{{ dim.name }}
|
||||
</option>
|
||||
</materialize-select>
|
||||
<label :for="'a_dim_' + article.id">Dimension</label>
|
||||
</div>
|
||||
|
||||
<div class="input-field col s4">
|
||||
<input
|
||||
:id="'a_psize_' + article.id"
|
||||
v-model.number="article.portion.size"
|
||||
placeholder="Gesamtinhalt"
|
||||
type="number"
|
||||
class="validate"
|
||||
step="0.01"
|
||||
max="5"
|
||||
>
|
||||
<label
|
||||
:for="'a_psize_' + article.id"
|
||||
class="active"
|
||||
>Portionsinhalt</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<MaterializeSelect
|
||||
:id="'a_ptype_' + article.id"
|
||||
v-model.number="article.portion.type"
|
||||
:value="article.portion.type"
|
||||
placeholder="Art"
|
||||
>
|
||||
<option
|
||||
v-for="dim in dimensions"
|
||||
:key="dim.id"
|
||||
:value="dim.id"
|
||||
>
|
||||
{{ dim.name }}
|
||||
</option>
|
||||
</MaterializeSelect>
|
||||
<label :for="'a_ptype_' + article.id">Portionsbezeichnung</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<input
|
||||
:id="'a_pprice_' + article.id"
|
||||
v-model.number="article.portion.price"
|
||||
placeholder="Preis"
|
||||
type="number"
|
||||
step="0.01"
|
||||
class="validate"
|
||||
>
|
||||
<label
|
||||
:for="'a_pprice_' + article.id"
|
||||
class="active"
|
||||
>Portionspreis in €</label>
|
||||
</div>
|
||||
|
||||
<div class="col s12 right">
|
||||
<span
|
||||
class="right"
|
||||
>Gesamtpreis
|
||||
<TweenedNumber
|
||||
:wert="article.ContentPrice"
|
||||
:einheit="'€'"
|
||||
:precision="2"
|
||||
/></span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:id="identifier + '_variants'"
|
||||
class="row"
|
||||
>
|
||||
<div class="col s12">
|
||||
<ArticleVariant
|
||||
v-for="(variant, index) in variants"
|
||||
:key="'article_' + article.id + '_variant_' + index"
|
||||
:identifier="'article_' + article.id + '_variant_' + index"
|
||||
:variant="variant"
|
||||
:article="article"
|
||||
/>
|
||||
|
||||
<a
|
||||
href="#"
|
||||
@click="addVariant(article)"
|
||||
>Variante hinzufügen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a
|
||||
href="#"
|
||||
@click="deleteArticle(article)"
|
||||
>Artikel löschen</a>
|
||||
<a
|
||||
v-if="changed"
|
||||
href="#"
|
||||
@click="storeArticle(article)"
|
||||
>Artikel speichern</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Article, thawArticle } from '../model/article';
|
||||
import MaterializeSelect from "./MaterializeSelect";
|
||||
import TweenedNumber from "./TweenedNumber";
|
||||
import ArticleVariant from "./ArticleVariant";
|
||||
import { thawVariant, Variant } from '../model/variant';
|
||||
|
||||
export default {
|
||||
name: "Article",
|
||||
components: {MaterializeSelect, TweenedNumber, ArticleVariant},
|
||||
props: {
|
||||
a: {
|
||||
type: Object,
|
||||
default() { return new Article(); }
|
||||
},
|
||||
dimensions: {
|
||||
type: Array,
|
||||
default() { return []; }
|
||||
},
|
||||
groups: {
|
||||
type: Array,
|
||||
default() { return []; }
|
||||
},
|
||||
identifier: {
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
changed: false,
|
||||
article: null,
|
||||
original: null,
|
||||
variants: [],
|
||||
tabs: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
watch: {
|
||||
article: {
|
||||
handler(newValue, oldVal) {
|
||||
this.changed = JSON.stringify(newValue) != this.original;
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.article = this.a;
|
||||
this.original = JSON.stringify(this.a);
|
||||
this.loadVariants(this.article);
|
||||
},
|
||||
mounted() {
|
||||
var tabs = this.$el.getElementsByClassName("tabs")[0];
|
||||
M.Tabs.init(tabs, {});
|
||||
this.tabs = M.Tabs.getInstance(tabs);
|
||||
this.$M.updateTextFields();
|
||||
},
|
||||
methods: {
|
||||
storeArticle: function(article) {
|
||||
this.$http
|
||||
.post(
|
||||
"/api/artikel" + (article.id > 0 ? '/' + article.id : ''),
|
||||
JSON.stringify(article),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
this.$M.toast({ html: response.data + " Artikel aktualisiert." });
|
||||
this.article = thawArticle(response.data);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$M.toast({ html: response });
|
||||
});
|
||||
},
|
||||
deleteArticle: function(article) {
|
||||
this.$http
|
||||
.delete(
|
||||
"/api/artikel/" + article.id,
|
||||
)
|
||||
.then(response => {
|
||||
this.$emit('delete-article', article);
|
||||
this.$M.toast({ html: response.data + " Artikel gelöscht." });
|
||||
})
|
||||
.catch(error => {
|
||||
this.$M.toast({ html: response });
|
||||
});
|
||||
},
|
||||
addVariant(article) {
|
||||
var variant = new Variant();
|
||||
variant.ArticleId = article.id;
|
||||
this.variants.push(variant);
|
||||
},
|
||||
loadVariants: function(article) {
|
||||
this.$http
|
||||
.get(
|
||||
"/api/artikel/" + article.id + '/varianten',
|
||||
)
|
||||
.then(response => {
|
||||
return response.data;
|
||||
})
|
||||
.then(json => {
|
||||
if (json != null) {
|
||||
json.forEach(element => {
|
||||
this.variants.push(thawVariant(element));
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.$M.updateTextFields();
|
||||
})
|
||||
.catch(error => {
|
||||
this.$M.toast({ html: error });
|
||||
});
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
100
app/js/components/ArticleVariant.vue
Normal file
100
app/js/components/ArticleVariant.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="input-field col s4">
|
||||
<input
|
||||
:id="identifier + '_name'"
|
||||
v-model="variant.name"
|
||||
placeholder="Variantenbezeichnung"
|
||||
type="text"
|
||||
class="validate"
|
||||
>
|
||||
<label
|
||||
:for="identifier + '_name'"
|
||||
class="active"
|
||||
>Variantenbezeichnung</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<input
|
||||
:id="identifier + '_price'"
|
||||
v-model.number="variant.price"
|
||||
placeholder="Preis"
|
||||
type="number"
|
||||
class="validate"
|
||||
step="0.01"
|
||||
>
|
||||
<label
|
||||
:for="identifier + '_price'"
|
||||
class="active"
|
||||
>Preis</label>
|
||||
</div>
|
||||
<div class="input-field col s2">
|
||||
<input
|
||||
:value="article.PortionPrice + variant.Price"
|
||||
class="validate"
|
||||
disabled
|
||||
>
|
||||
</div>
|
||||
<div class="input-field col s2">
|
||||
<a
|
||||
href="#"
|
||||
@click="storeVariant(variant)"
|
||||
>Variante speichern</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Article } from '../model/article';
|
||||
import { thawVariant, Variant } from '../model/variant'
|
||||
export default {
|
||||
name: 'Variant',
|
||||
props: {
|
||||
variant: {
|
||||
type: Object,
|
||||
default() { return new Variant(); }
|
||||
},
|
||||
article: {
|
||||
type: Object,
|
||||
default() { return new Article(); }
|
||||
},
|
||||
identifier: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
storeVariant: function(variant) {
|
||||
variant.ArticleId = this.article.id;
|
||||
this.$http
|
||||
.post(
|
||||
"/api/varianten" + (variant.id > 0 ? '/' + variant.id : ''),
|
||||
JSON.stringify(variant),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
this.$M.toast({ html: response.data + " Variante aktualisiert." });
|
||||
this.variant = thawVariant(response.data);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$M.toast({ html: error });
|
||||
});
|
||||
},
|
||||
deleteVariant: function(variant) {
|
||||
this.$http
|
||||
.delete(
|
||||
"/api/varianten/" + variant.id,
|
||||
)
|
||||
.then(response => {
|
||||
this.$emit('delete-variant', variant);
|
||||
this.$M.toast({ html: response.data + " Varianten gelöscht." });
|
||||
})
|
||||
.catch(error => {
|
||||
this.$M.toast({ html: response });
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
51
app/js/components/ArticlesView.vue
Normal file
51
app/js/components/ArticlesView.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div>
|
||||
<ArticleCard
|
||||
v-for="(a, index) in articles"
|
||||
:key="'article_' + index"
|
||||
:identifier="'article_' + index"
|
||||
:a="a"
|
||||
:dimensions="dimensions"
|
||||
:groups="groups"
|
||||
@delete-article="removeFromList(a)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArticleCard from "./ArticleCard";
|
||||
|
||||
export default {
|
||||
name: "ArticlesView",
|
||||
components: {ArticleCard},
|
||||
props: {
|
||||
articles: {
|
||||
type: Array,
|
||||
default() { return []; }
|
||||
},
|
||||
groups: {
|
||||
type: Array,
|
||||
default() { return []; }
|
||||
},
|
||||
dimensions: {
|
||||
type: Array,
|
||||
default() { return []; }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
removeFromList(article) {
|
||||
this.articles.splice(this.articles.indexOf(article),1);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -2,7 +2,7 @@
|
||||
<tr
|
||||
style="border: 0;"
|
||||
class="inventory-item"
|
||||
:class="{
|
||||
:class="{
|
||||
'teal lighten-5' : item.Sale > 0,
|
||||
'orange darken-2 white-text' : item.Sold %1 !=0,
|
||||
'red darken-2 white-text' : item.Sale < 0,
|
||||
@ -26,20 +26,20 @@
|
||||
>
|
||||
{{ item[prop] }}
|
||||
</td>
|
||||
|
||||
|
||||
<td
|
||||
class="right-align border-bottom border-diagram"
|
||||
>
|
||||
<TweenedNumber
|
||||
:style="{
|
||||
'border-image': 'linear-gradient(to right , rgba(0,150,136,.01) 0%, rgba(0,150,136,.3) '
|
||||
+ item.Sold*100/total
|
||||
:style="{
|
||||
'border-image': 'linear-gradient(to right , rgba(0,150,136,.01) 0%, rgba(0,150,136,.3) '
|
||||
+ item.Sold*100/total
|
||||
+ '%, transparent '
|
||||
+ item.Sold*100/total
|
||||
+'%,transparent 100%) 1'
|
||||
}"
|
||||
:wert="item.Sold"
|
||||
:einheit="item.article.PortionType"
|
||||
:einheit="Dimension.short"
|
||||
:precision="item.PortionPrecision"
|
||||
/>
|
||||
</td>
|
||||
@ -74,6 +74,12 @@ export default {
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
dimensions: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@ -89,10 +95,13 @@ export default {
|
||||
},
|
||||
SaleAnimated() {
|
||||
return this.tweenedSale.toFixed(2);
|
||||
},
|
||||
Dimension() {
|
||||
return this.dimensions.find(element => element.id == this.item.article.PortionType)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
|
||||
},
|
||||
methods: {
|
||||
setCaretPosition(el){
|
||||
@ -139,15 +148,15 @@ export default {
|
||||
if (event.data.match(regex)){
|
||||
var value = event.target.innerText;
|
||||
this.item[property] = Number(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setCaretPosition(event.target);
|
||||
this.setCaretPosition(event.target);
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
<InventoryItem
|
||||
v-for="(item,index) in inventory"
|
||||
:key="'item-'+index"
|
||||
:dimensions="dimensions"
|
||||
:item="item"
|
||||
:total="sold"
|
||||
/>
|
||||
@ -68,6 +69,18 @@ export default {
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
groups: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
dimensions: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@ -148,4 +161,4 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
</style>
|
||||
|
||||
75
app/js/components/MaterializeSelect.vue
Normal file
75
app/js/components/MaterializeSelect.vue
Normal file
@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<select :value="value">
|
||||
<option
|
||||
value=""
|
||||
disabled
|
||||
selected
|
||||
>
|
||||
Bitte wählen
|
||||
</option>
|
||||
<slot />
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MaterializeSelect",
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @description Component local variables
|
||||
* @return {Object} data
|
||||
* @return {undefined|FormSelect} data.instance
|
||||
*/
|
||||
data() {
|
||||
return {
|
||||
instance: undefined,
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value() {
|
||||
this.instance.destroy();
|
||||
|
||||
this.$nextTick(() => (this.instance = this.initializeSelect()));
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.instance = this.initializeSelect();
|
||||
|
||||
this.$el.addEventListener("change", this.changeHandler);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
this.$el.removeEventListener("change", this.changeHandler);
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* @description Initialize a new Materialize select component
|
||||
* @param {Object} options
|
||||
* @return {FormSelect}
|
||||
* @see https://materializecss.com/select.html#options
|
||||
*/
|
||||
initializeSelect(options = {}) {
|
||||
return M.FormSelect.init(this.$el, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* @description Send the proper input event to the parents components
|
||||
* @param {Event} event
|
||||
* @param {HTMLSelectElement} target
|
||||
* @see https://developer.mozilla.org/fr/docs/Web/API/Event/target
|
||||
*/
|
||||
changeHandler({ target }) {
|
||||
this.$emit("input", target.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -8,12 +8,13 @@
|
||||
<ul class="tabs tabs-transparent">
|
||||
<li class="tab">
|
||||
<a
|
||||
class="active"
|
||||
@click.stop.prevent="changeTab('inventory', $event)"
|
||||
>Inventur</a>
|
||||
</li>
|
||||
<li class="tab">
|
||||
<a
|
||||
class="active"
|
||||
TweenedNumber
|
||||
@click.stop.prevent="changeTab('article', $event)"
|
||||
>Artikelliste</a>
|
||||
</li>
|
||||
@ -39,7 +40,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabs: []
|
||||
tabs: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -60,4 +61,4 @@ export default {
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<div class="tweened">
|
||||
<div>{{ intAnimated }}</div>
|
||||
<div v-if="precision>0">
|
||||
<span class="tweened">
|
||||
{{ intAnimated }}
|
||||
<span v-if="precision>0">
|
||||
,{{ deciAnimated }}
|
||||
</div>
|
||||
<div v-else />
|
||||
<div>{{ einheit }}</div>
|
||||
</div>
|
||||
</span>
|
||||
{{ einheit }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -31,15 +30,15 @@ export default {
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
tweenedValue: 0,
|
||||
tweenedValue: this.wert,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
valueAnimated() {
|
||||
return this.tweenedValue.toLocaleString(
|
||||
"de-DE",
|
||||
{
|
||||
maximumFractionDigits: 2,
|
||||
"de-DE",
|
||||
{
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: this.precision
|
||||
}
|
||||
);
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
export class Article{
|
||||
constructor() {
|
||||
this.id = 0;
|
||||
this.name = "";
|
||||
this.short = "";
|
||||
this.dimension = "";
|
||||
this.dimension = 0;
|
||||
this.group = 0;
|
||||
this.content = {
|
||||
size : 0,
|
||||
price: 0
|
||||
@ -10,7 +12,7 @@ export class Article{
|
||||
this.portion = {
|
||||
size : 0,
|
||||
price : 0,
|
||||
type : ""
|
||||
type : 0
|
||||
};
|
||||
}
|
||||
get Name() {
|
||||
@ -55,6 +57,12 @@ export class Article{
|
||||
set PortionPrice(value) {
|
||||
this.portion.price = value;
|
||||
}
|
||||
get Group() {
|
||||
return this.group;
|
||||
}
|
||||
set Group(value) {
|
||||
this.group = value;
|
||||
}
|
||||
get ContentPrice() {
|
||||
return this.Portions * this.portion.price;
|
||||
}
|
||||
@ -68,13 +76,15 @@ Article.thaw = function (json) {
|
||||
article.id = json.id;
|
||||
article.Name = json.name;
|
||||
article.Short = json.short;
|
||||
article.Dimension = json.dimension;
|
||||
article.ContentSize = json.content.size;
|
||||
article.PortionPrice = json.portion.price;
|
||||
article.PortionSize = json.portion.size;
|
||||
article.PortionType = json.portion.type;
|
||||
article.Dimension = json.content_dimension;
|
||||
article.ContentSize = json.content_size;
|
||||
article.PortionPrice = json.portion_price;
|
||||
article.PortionSize = json.portion_size;
|
||||
article.PortionType = json.portion_dimension;
|
||||
article.Group = json.group_id;
|
||||
article.variants = json.variants;
|
||||
|
||||
return article;
|
||||
};
|
||||
|
||||
export const thawArticle = Article.thaw;
|
||||
export const thawArticle = Article.thaw;
|
||||
|
||||
38
app/js/model/variant.js
Normal file
38
app/js/model/variant.js
Normal file
@ -0,0 +1,38 @@
|
||||
export class Variant{
|
||||
constructor() {
|
||||
this.id = 0;
|
||||
this.name = "";
|
||||
this.article_id = 0;
|
||||
this.price = 0;
|
||||
}
|
||||
get Name() {
|
||||
return this.name;
|
||||
}
|
||||
set Name(value) {
|
||||
this.name = value;
|
||||
}
|
||||
get ArticleId() {
|
||||
return this.article_id;
|
||||
}
|
||||
set ArticleId(value) {
|
||||
this.article_id = value;
|
||||
}
|
||||
get Price() {
|
||||
return this.price;
|
||||
}
|
||||
set Price(value) {
|
||||
this.price = value;
|
||||
}
|
||||
}
|
||||
|
||||
Variant.thaw = function (json) {
|
||||
var variant = new Variant();
|
||||
variant.id = json.id;
|
||||
variant.Name = json.name;
|
||||
variant.ArticleId = json.article_id;
|
||||
variant.Price = json.price;
|
||||
|
||||
return variant;
|
||||
};
|
||||
|
||||
export const thawVariant = Variant.thaw;
|
||||
@ -9,24 +9,18 @@
|
||||
],
|
||||
"require": {
|
||||
"slim/slim": "^4.3",
|
||||
"vlucas/phpdotenv": "^4.1",
|
||||
"catfan/medoo": "^1.7",
|
||||
"slim/psr7": "^0.6.0",
|
||||
"slim/php-view": "^2.2",
|
||||
"vlucas/phpdotenv": "^5.3",
|
||||
"slim/psr7": "^1.4",
|
||||
"slim/twig-view": "^3.0",
|
||||
"php-di/php-di": "^6.0",
|
||||
"symfony/webpack-encore-bundle": "^1.7",
|
||||
"illuminate/database": "^6.10",
|
||||
"illuminate/database": "^7.3",
|
||||
"phpoffice/phpspreadsheet": "^1.10",
|
||||
"monolog/monolog": "^2.0",
|
||||
"illuminate/filesystem": "^6.12"
|
||||
"illuminate/filesystem": "^7.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"doctrine/coding-standard": "^7.0",
|
||||
"thecodingmachine/phpstan-strict-rules": "^0.12.0",
|
||||
"thecodingmachine/safe": "^1.0",
|
||||
"thecodingmachine/phpstan-safe-rule": "@dev"
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"scripts": {
|
||||
"csfix": "phpcbf",
|
||||
@ -37,5 +31,9 @@
|
||||
"psr-4": {
|
||||
"Chrosey\\Inventur\\":"src"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/tinker": "^2.6",
|
||||
"symfony/var-dumper": "^5.3"
|
||||
}
|
||||
}
|
||||
|
||||
3535
composer.lock
generated
3535
composer.lock
generated
File diff suppressed because it is too large
Load Diff
63
database/migrations/2021_07_10_000000_add_bons.php
Normal file
63
database/migrations/2021_07_10_000000_add_bons.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
|
||||
class AddBons extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$builder = Capsule::schema();
|
||||
|
||||
$builder->table('articles', function($table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
$builder->create(
|
||||
'bons',
|
||||
function ($table) {
|
||||
$table->increments('id');
|
||||
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->useCurrent();
|
||||
}
|
||||
);
|
||||
|
||||
$builder->create(
|
||||
'bonitems',
|
||||
function ($table) {
|
||||
$table->increments('id');
|
||||
|
||||
$table->unsignedInteger('bon_id');
|
||||
$table->foreign('bon_id')->references('id')->on('bons');
|
||||
|
||||
$table->unsignedInteger('article_id');
|
||||
$table->foreign('article_id')->references('id')->on('articles');
|
||||
|
||||
$table->unsignedInteger('count');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
$builder = Capsule::schema();
|
||||
|
||||
$builder->table('articles', function($table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
|
||||
$builder->drop('bons');
|
||||
$builder->drop('bonitems');
|
||||
}
|
||||
}
|
||||
37
database/migrations/2021_07_10_000000_add_subarticles.php
Normal file
37
database/migrations/2021_07_10_000000_add_subarticles.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
|
||||
class AddSubarticles extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$builder = Capsule::schema();
|
||||
|
||||
$builder->table('articles', function($table) {
|
||||
|
||||
$table->unsignedInteger('main_article_id')->nullable();
|
||||
$table->foreign('main_article_id')->references('id')->on('articles');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
$builder = Capsule::schema();
|
||||
|
||||
$builder->table('articles', function($table) {
|
||||
$table->dropColum('main_article_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
45
database/migrations/2021_07_10_000000_article_variants.php
Normal file
45
database/migrations/2021_07_10_000000_article_variants.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
|
||||
class ArticleVariants extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$builder = Capsule::schema();
|
||||
|
||||
$builder->create(
|
||||
'variants',
|
||||
function ($table) {
|
||||
$table->increments('id');
|
||||
|
||||
$table->unsignedInteger('article_id');
|
||||
$table->foreign('article_id')->references('id')->on('articles');
|
||||
|
||||
$table->string('name');
|
||||
$table->string('short');
|
||||
$table->float('price', 8, 2);
|
||||
|
||||
$table->timestamps();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
$builder = Capsule::schema();
|
||||
|
||||
$builder->drop('variants');
|
||||
}
|
||||
}
|
||||
@ -1 +1 @@
|
||||
[{"name":"Edelpils","short":"Pils","dimension":"l","content":{"size":0.33000000000000002,"price":0},"portion":{"size":0.33000000000000002,"price":3.5,"type":"Fl."},"group":"Bier"},{"name":"Schwarzbier","short":"Sb","dimension":"l","content":{"size":0.33000000000000002,"price":0},"portion":{"size":0.33000000000000002,"price":3.5,"type":"Fl."},"group":"Bier"},{"name":"Sch\u00f6fferhofer","short":"Sch\u00f6","dimension":"l","content":{"size":0.33000000000000002,"price":0},"portion":{"size":0.33000000000000002,"price":3.5,"type":"Fl."},"group":"Bier"},{"name":"Bitburger af.","short":"0%","dimension":"l","content":{"size":0.33000000000000002,"price":0},"portion":{"size":0.33000000000000002,"price":3.5,"type":"Fl."},"group":"Bier"},{"name":"Kellerbier","short":"Keller","dimension":"l","content":{"size":0.33000000000000002,"price":0},"portion":{"size":0.33000000000000002,"price":3.5,"type":"Fl."},"group":"Bier"},{"name":"Weiswein","short":"WW","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":6,"type":"Gl."},"group":"Wein und Sekt"},{"name":"Ros\u00e9wein","short":"Ros\u00e9","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":6.5,"type":"Gl."},"group":"Wein und Sekt"},{"name":"Rotwein","short":"RW","dimension":"l","content":{"size":0.75,"price":0},"portion":{"size":0.20000000000000001,"price":6.5,"type":"Gl."},"group":"Wein und Sekt"},{"name":"Secco","short":"Sekt","dimension":"l","content":{"size":0.75,"price":0},"portion":{"size":0.10000000000000001,"price":6,"type":"Gl."},"group":"Wein und Sekt"},{"name":"B-Saft","short":"BS","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":3.5,"type":"Gl."},"group":"afG offen"},{"name":"K-Saft","short":"KS","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":3.5,"type":"Gl."},"group":"afG offen"},{"name":"O-Saft","short":"OS","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":3.5,"type":"Gl."},"group":"afG offen"},{"name":"M-Saft","short":"MS","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":3.5,"type":"Gl."},"group":"afG offen"},{"name":"Vita Cola","short":"Co","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":2.5,"type":"Gl."},"group":"afG offen"},{"name":"Vita Orange","short":"Fa","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":2.5,"type":"Gl."},"group":"afG offen"},{"name":"Vita Zitrone","short":"Spr","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":2.5,"type":"Gl."},"group":"afG offen"},{"name":"Tonic","short":"To","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."},"group":"afG Gastro"},{"name":"Bitter Lemon","short":"BL","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."},"group":"afG Gastro"},{"name":"Ginger Ale","short":"GA","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."},"group":"afG Gastro"},{"name":"Apfelschorle","short":"AS","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."},"group":"afG Gastro"},{"name":"Cola light","short":"Clight","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."},"group":"afG Gastro"},{"name":"TWQ naturell","short":"W-","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":2.5,"type":"Fl."},"group":"afG Gastro"},{"name":"TWQ medium","short":"W","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":2.5,"type":"Fl."},"group":"afG Gastro"},{"name":"TWQ classic","short":"W+","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":2.5,"type":"Fl."},"group":"afG Gastro"},{"name":"Kaffee","short":"TK","dimension":"T","content":{"size":1,"price":0},"portion":{"size":1,"price":2.5,"type":"T"},"group":"Hei\u00dfgetr\u00e4nke"},{"name":"Latte Macchiato","short":"LM","dimension":"Gl","content":{"size":1,"price":0},"portion":{"size":1,"price":3.5,"type":"Gl."},"group":"Hei\u00dfgetr\u00e4nke"},{"name":"dopp. Esp.","short":"dEsp","dimension":"T","content":{"size":1,"price":0},"portion":{"size":1,"price":3.5,"type":"T"},"group":"Hei\u00dfgetr\u00e4nke"},{"name":"Brezel","short":"Br","dimension":"stk","content":{"size":1,"price":0},"portion":{"size":1,"price":2.5,"type":"Stk."},"group":"Fingerfood"},{"name":"Schokoriegel","short":"Schoko","dimension":"stk","content":{"size":1,"price":0},"portion":{"size":1,"price":2,"type":"Stk."},"group":"Fingerfood"},{"name":"Duplo","short":"Duplo","dimension":"stk","content":{"size":1,"price":0},"portion":{"size":1,"price":1,"type":"Stk."},"group":"Fingerfood"}]
|
||||
[{"name":"Edelpils","short":"Pils","dimension":"l","content":{"size":0.33000000000000002,"price":0},"portion":{"size":0.33000000000000002,"price":3.5,"type":"Fl."}},{"name":"Schwarzbier","short":"Sb","dimension":"l","content":{"size":0.33000000000000002,"price":0},"portion":{"size":0.33000000000000002,"price":3.5,"type":"Fl."}},{"name":"Sch\u00f6fferhofer","short":"Sch\u00f6","dimension":"l","content":{"size":0.33000000000000002,"price":0},"portion":{"size":0.33000000000000002,"price":3.5,"type":"Fl."}},{"name":"Bitburger af.","short":"0%","dimension":"l","content":{"size":0.33000000000000002,"price":0},"portion":{"size":0.33000000000000002,"price":3.5,"type":"Fl."}},{"name":"Weiswein","short":"WW","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":6,"type":"Gl."}},{"name":"Rotwein","short":"RW","dimension":"l","content":{"size":0.75,"price":0},"portion":{"size":0.20000000000000001,"price":6.5,"type":"Gl."}},{"name":"Secco","short":"Sekt","dimension":"l","content":{"size":0.75,"price":0},"portion":{"size":0.10000000000000001,"price":6,"type":"Gl."}},{"name":"B-Saft","short":"BS","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":3.5,"type":"Gl."}},{"name":"K-Saft","short":"KS","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":3.5,"type":"Gl."}},{"name":"O-Saft","short":"OS","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":3.5,"type":"Gl."}},{"name":"G-Saft","short":"GS","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":3.5,"type":"Gl."}},{"name":"Vita Cola","short":"Co","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":2.5,"type":"Gl."}},{"name":"Vita Orange","short":"Fa","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":2.5,"type":"Gl."}},{"name":"Vita Zitrone","short":"Spr","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.20000000000000001,"price":2.5,"type":"Gl."}},{"name":"Tonic","short":"To","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."}},{"name":"Bitter Lemon","short":"BL","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."}},{"name":"Ginger Ale","short":"GA","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."}},{"name":"Apfelschorle","short":"AS","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."}},{"name":"TWQ naturell","short":"W-","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":2.5,"type":"Fl."}},{"name":"TWQ medium","short":"W","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":2.5,"type":"Fl."}},{"name":"TWQ classic","short":"W+","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":2.5,"type":"Fl."}},{"name":"Kaffee","short":"TK","dimension":"Tasse","content":{"size":1,"price":0},"portion":{"size":1,"price":2.5,"type":"T"}},{"name":"Latte Macchiato","short":"LM","dimension":"Glas","content":{"size":1,"price":0},"portion":{"size":1,"price":3.5,"type":"Gl."}},{"name":"dopp. Esp.","short":"dEsp","dimension":"Tasse","content":{"size":1,"price":0},"portion":{"size":1,"price":3.5,"type":"T"}},{"name":"Brezel","short":"Br","dimension":"St\u00fcck","content":{"size":1,"price":0},"portion":{"size":1,"price":2.5,"type":"Stk."}},{"name":"Schokoriegel","short":"Schoko","dimension":"St\u00fcck","content":{"size":1,"price":0},"portion":{"size":1,"price":2,"type":"Stk."}}]
|
||||
9
env.example
Normal file
9
env.example
Normal file
@ -0,0 +1,9 @@
|
||||
DB_HOST=db
|
||||
DB_NAME=db
|
||||
DB_USER=db
|
||||
DB_PASSWORD=db
|
||||
DB_DRIVER=mysql
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_COLLATION=utf8mb4_general_ci
|
||||
|
||||
NODE_ENV=production
|
||||
3275
package-lock.json
generated
3275
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
2325
public/build/app.js
2325
public/build/app.js
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"build/app.css": "/build/app.css",
|
||||
"build/app.js": "/build/app.js",
|
||||
"build/vendors~app.css": "/build/vendors~app.css",
|
||||
"build/vendors~app.js": "/build/vendors~app.js"
|
||||
"build/vendors~app.js": "/build/vendors~app.js",
|
||||
"build/app.css": "/build/app.css"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
83
src/Controller/ArticleController.php
Normal file
83
src/Controller/ArticleController.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Chrosey\Inventur\Controller;
|
||||
|
||||
use Chrosey\Inventur\Models\Article;
|
||||
use Chrosey\Inventur\Models\Variant;
|
||||
use Slim\Psr7\Request;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class ArticleController extends BaseController
|
||||
{
|
||||
public function create(Request $request, Response $response, $args)
|
||||
{
|
||||
$artikel = dump($request->getParsedBody());
|
||||
|
||||
$article = new Article([
|
||||
'name' => $artikel['name'],
|
||||
'short' => $artikel['short'],
|
||||
'content_size' => $artikel['content']['size'],
|
||||
'content_dimension' => $artikel['dimension'],
|
||||
'portion_size' => $artikel['portion']['size'],
|
||||
'portion_price' => $artikel['portion']['price'],
|
||||
'portion_dimension' => $artikel['portion']['type'],
|
||||
'group_id' => $artikel['group'],
|
||||
]);
|
||||
|
||||
$article->save();
|
||||
$response->getBody()->write(\json_encode($article));
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
public function update(Request $request, Response $response, $args)
|
||||
{
|
||||
$artikel = dump($request->getParsedBody());
|
||||
|
||||
$table = $this->db->table('articles');
|
||||
$updatedRowsCount = $table
|
||||
->where('id','=', $artikel['id'])
|
||||
->update([
|
||||
'name' => $artikel['name'],
|
||||
'short' => $artikel['short'],
|
||||
'content_size' => $artikel['content']['size'],
|
||||
'content_dimension' => $artikel['dimension'],
|
||||
'portion_size' => $artikel['portion']['size'],
|
||||
'portion_price' => $artikel['portion']['price'],
|
||||
'portion_dimension' => $artikel['portion']['type'],
|
||||
'group_id' => $artikel['group'],
|
||||
]);
|
||||
|
||||
$response->getBody()->write(\json_encode($updatedRowsCount));
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
public function delete(Request $request, Response $response, $args)
|
||||
{
|
||||
$affectedRows = Article::where('id', $args['id'])->delete();
|
||||
|
||||
$response->getBody()->write(\json_encode($affectedRows));
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
public function list(Request $request, Response $response, $args)
|
||||
{
|
||||
$articles = Article::all();
|
||||
|
||||
foreach ($articles as $article) {
|
||||
$variants = Variant::where('article_id', $article->id)->get();
|
||||
$article->variants = $variants;
|
||||
}
|
||||
|
||||
$response->getBody()->write(\json_encode($articles));
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
public function show()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
42
src/Controller/ArticleGroupController.php
Normal file
42
src/Controller/ArticleGroupController.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Chrosey\Inventur\Controller;
|
||||
|
||||
use Slim\Psr7\Request;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class ArticleGroupController extends BaseController
|
||||
{
|
||||
public function create(Request $request, Response $response, $args)
|
||||
{
|
||||
$dimension = dump($request->getParsedBody());
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function list(Request $request, Response $response, $args)
|
||||
{
|
||||
$table = $this->db->table('groups');
|
||||
|
||||
|
||||
$items = $table->get();
|
||||
dump($items);
|
||||
|
||||
$response->getBody()->write(\json_encode($items));
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
public function show()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@ -2,12 +2,28 @@
|
||||
|
||||
namespace Chrosey\Inventur\Controller;
|
||||
|
||||
use Illuminate\Database\Capsule\Manager;
|
||||
use Monolog\Logger;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class BaseController
|
||||
{
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Database
|
||||
*
|
||||
* @var Manager
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
42
src/Controller/DimensionController.php
Normal file
42
src/Controller/DimensionController.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Chrosey\Inventur\Controller;
|
||||
|
||||
use Slim\Psr7\Request;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class DimensionController extends BaseController
|
||||
{
|
||||
public function create(Request $request, Response $response, $args)
|
||||
{
|
||||
$dimension = dump($request->getParsedBody());
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function list(Request $request, Response $response, $args)
|
||||
{
|
||||
$table = $this->db->table('dimensions');
|
||||
|
||||
|
||||
$dimensions = $table->get();
|
||||
dump($dimensions);
|
||||
|
||||
$response->getBody()->write(\json_encode($dimensions));
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
public function show()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ use Illuminate\Database\Migrations\DatabaseMigrationRepository;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
||||
class DatabaseController extends BaseController
|
||||
class MigrationController extends BaseController
|
||||
{
|
||||
|
||||
function migrate($request, $response, $args)
|
||||
63
src/Controller/VariantController.php
Normal file
63
src/Controller/VariantController.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Chrosey\Inventur\Controller;
|
||||
|
||||
use Chrosey\Inventur\Models\Variant;
|
||||
use Slim\Psr7\Request;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class VariantController extends BaseController
|
||||
{
|
||||
|
||||
|
||||
public function list(Request $request, Response $response, $args)
|
||||
{
|
||||
$items = Variant::where('article_id', $args['id'])->get();
|
||||
dump($items);
|
||||
|
||||
$response->getBody()->write(\json_encode($items));
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
public function show()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function create(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = dump($request->getParsedBody());
|
||||
|
||||
$variant = new Variant([
|
||||
'name' => $data['name'],
|
||||
'short' => $data['short'] ?: $data['name'],
|
||||
'price' => $data['price'],
|
||||
'article_id' => $data['article_id'],
|
||||
]);
|
||||
|
||||
$variant->save();
|
||||
$response->getBody()->write(\json_encode($variant));
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
public function update(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = dump($request->getParsedBody());
|
||||
|
||||
$table = $this->db->table('variants');
|
||||
$updatedRowsCount = $table
|
||||
->where('id','=', $data['id'])
|
||||
->update([
|
||||
'name' => $data['name'],
|
||||
'short' => $data['short'] ?: $data['name'],
|
||||
'price' => $data['price'],
|
||||
'article_id' => $data['article_id'],
|
||||
]);
|
||||
|
||||
$response->getBody()->write(\json_encode($updatedRowsCount));
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
}
|
||||
25
src/Middleware/JsonBodyParserMiddleware.php
Normal file
25
src/Middleware/JsonBodyParserMiddleware.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Chrosey\Inventur\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
|
||||
class JsonBodyParserMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, RequestHandler $handler): Response
|
||||
{
|
||||
$contentType = $request->getHeaderLine('Content-Type');
|
||||
|
||||
if (strstr($contentType, 'application/json')) {
|
||||
$contents = json_decode(file_get_contents('php://input'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$request = $request->withParsedBody($contents);
|
||||
}
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
20
src/Models/Article.php
Normal file
20
src/Models/Article.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Chrosey\Inventur\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Article extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = ['name',
|
||||
'short',
|
||||
'content_size',
|
||||
'content_dimension',
|
||||
'portion_size',
|
||||
'portion_price',
|
||||
'portion_dimension',
|
||||
'group_id'];
|
||||
}
|
||||
15
src/Models/Variant.php
Normal file
15
src/Models/Variant.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Chrosey\Inventur\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Variant extends Model
|
||||
{
|
||||
|
||||
protected $fillable = ['name',
|
||||
'short',
|
||||
'price',
|
||||
'article_id'];
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Chrosey\Inventur\Middleware\JsonBodyParserMiddleware;
|
||||
use DI\Container;
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
@ -9,6 +10,13 @@ use Monolog\Logger;
|
||||
use Slim\Factory\AppFactory;
|
||||
use Slim\Views\Twig;
|
||||
use Slim\Views\TwigMiddleware;
|
||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||
use Symfony\Component\VarDumper\Dumper\CliDumper;
|
||||
use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider;
|
||||
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
|
||||
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
|
||||
use Symfony\Component\VarDumper\Dumper\ServerDumper;
|
||||
use Symfony\Component\VarDumper\VarDumper;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
require __DIR__ . '/env.php';
|
||||
@ -23,14 +31,14 @@ $container->set('view', static function () {
|
||||
$container->set('db', static function () {
|
||||
$capsule = new Capsule();
|
||||
$capsule->addConnection([
|
||||
'driver' => getenv('DB_DRIVER'),
|
||||
'host' => getenv('DB_HOST'),
|
||||
'database' => getenv('DB_NAME'),
|
||||
'username' => getenv('DB_USER'),
|
||||
'password' => getenv('DB_PASSWORD'),
|
||||
'charset' => getenv('DB_CHARSET'),
|
||||
'collation' => getenv('DB_COLLATION'),
|
||||
'prefix' => getenv('DB_PREFIX'),
|
||||
'driver' => $_ENV['DB_DRIVER'] ?: 'mysql',
|
||||
'host' => $_ENV['DB_HOST'] ?: 'localhost',
|
||||
'database' => $_ENV['DB_NAME'] ?: 'inventar',
|
||||
'username' => $_ENV['DB_USER'] ?: 'dbuser',
|
||||
'password' => $_ENV['DB_PASSWORD'] ?: 'password',
|
||||
'charset' => $_ENV['DB_CHARSET'] ?: 'utf8',
|
||||
'collation' => $_ENV['DB_COLLATION'] ?: 'utf8_general_ci',
|
||||
'prefix' => $_ENV['DB_PREFIX'] ?: '',
|
||||
]);
|
||||
|
||||
$capsule->setAsGlobal();
|
||||
@ -49,10 +57,36 @@ $container->set('log', static function() {
|
||||
$container->set('upload_directory', __DIR__ . '/../data/uploads');
|
||||
$container->set('data_directory', __DIR__ . '/../data/');
|
||||
|
||||
|
||||
$app = AppFactory::create();
|
||||
|
||||
$app->addMiddleware(new JsonBodyParserMiddleware());
|
||||
$app->add(TwigMiddleware::createFromContainer($app));
|
||||
|
||||
$cloner = new VarCloner();
|
||||
$fallbackDumper = in_array(PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper() : new HtmlDumper();
|
||||
$dumper = new ServerDumper('tcp://127.0.0.1:9912', $fallbackDumper, [
|
||||
'cli' => new CliContextProvider(),
|
||||
'source' => new SourceContextProvider(),
|
||||
]);
|
||||
|
||||
VarDumper::setHandler(function ($var) use ($cloner, $dumper) {
|
||||
$dumper->dump($cloner->cloneVar($var));
|
||||
});
|
||||
|
||||
/**
|
||||
* Add Error Middleware
|
||||
*
|
||||
* @param bool $displayErrorDetails -> Should be set to false in production
|
||||
* @param bool $logErrors -> Parameter is passed to the default ErrorHandler
|
||||
* @param bool $logErrorDetails -> Display error details in error log
|
||||
* @param LoggerInterface|null $logger -> Optional PSR-3 Logger
|
||||
*
|
||||
* Note: This middleware should be added last. It will not handle any exceptions/errors
|
||||
* for middleware added after it.
|
||||
*/
|
||||
$errorMiddleware = $app->addErrorMiddleware(true, true, true, $container->get('log'));
|
||||
|
||||
require 'routes.php';
|
||||
|
||||
return $app;
|
||||
|
||||
@ -2,41 +2,33 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Chrosey\Inventur\Controller\DatabaseController;
|
||||
use Chrosey\Inventur\Controller\ArticleController;
|
||||
use Chrosey\Inventur\Controller\ArticleGroupController;
|
||||
use Chrosey\Inventur\Controller\MigrationController;
|
||||
use Chrosey\Inventur\Controller\DimensionController;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Slim\Psr7\Stream;
|
||||
use Slim\Psr7\UploadedFile;
|
||||
use Slim\Routing\RouteCollectorProxy;
|
||||
use Chrosey\Inventur\Controller\SpreadsheetController;
|
||||
use Chrosey\Inventur\Controller\VariantController;
|
||||
|
||||
$app->get('/', function (Request $request, Response $response, $args) {
|
||||
return $this->get('view')->render($response, 'frontend.html', []);
|
||||
})->setName('frontend');
|
||||
|
||||
$app->group('/api', function (RouteCollectorProxy $group): void {
|
||||
$group->post('/artikel', function (Request $request, Response $response, $args) {
|
||||
$directory = $this->get('upload_directory');
|
||||
$uploadedFile = $request->getUploadedFiles()['articles'];
|
||||
$group->post('/artikel', ArticleController::class . ':create')->setName('article.create');
|
||||
$group->post('/artikel/{id}', ArticleController::class . ':update')->setName('article.update');
|
||||
$group->delete('/artikel/{id}', ArticleController::class . ':delete')->setName('article.delete');
|
||||
$group->get('/artikel/{id}/varianten', VariantController::class . ':list')->setName('article.list.variants');
|
||||
$group->post('/varianten', VariantController::class . ':create')->setName('variant.create');
|
||||
$group->post('/varianten/{id}', VariantController::class . ':update')->setName('variant.update');
|
||||
$group->delete('/varianten/{id}', VariantController::class . ':list')->setName('variant.delete');
|
||||
$group->get('/artikel[/{location}]', ArticleController::class . ':list')->setName('article.list');
|
||||
|
||||
if ($uploadedFile->getError() === UPLOAD_ERR_OK) {
|
||||
$filename = moveUploadedFile($directory, $uploadedFile);
|
||||
$response->write('uploaded ' . $filename . '<br/>');
|
||||
}
|
||||
|
||||
return $response;
|
||||
});
|
||||
|
||||
$group->get('/artikel/theater', function (Request $request, Response $response, $args) {
|
||||
$file = __DIR__ . '/../data/articles.theater.json';
|
||||
$fh = fopen($file, 'rb');
|
||||
|
||||
$stream = new Stream($fh);
|
||||
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withBody($stream);
|
||||
});
|
||||
$group->get('/dimension', DimensionController::class . ':list')->setName('dimension.list');
|
||||
$group->get('/group', ArticleGroupController::class . ':list')->setName('group.list');
|
||||
|
||||
$group->post('/inventur', function (Request $request, Response $response, $args) {
|
||||
$directory = $this->get('upload_directory');
|
||||
@ -55,9 +47,9 @@ $app->group('/api', function (RouteCollectorProxy $group): void {
|
||||
$group->get('/excel/download', SpreadsheetController::class . ':download');
|
||||
});
|
||||
|
||||
$app->get('/migrate', DatabaseController::class . ':migrate')->setName('migrate');
|
||||
$app->get('/migrate', MigrationController::class . ':migrate')->setName('migrate');
|
||||
|
||||
$app->get('/seed', DatabaseController::class . ':seed')->setName('seed');
|
||||
$app->get('/seed', MigrationController::class . ':seed')->setName('seed');
|
||||
|
||||
/**
|
||||
* Move Uploaded File to Target Destination
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user