[TASK] Inital State
This commit is contained in:
parent
0cef46b3b2
commit
42c50342ab
157
.ddev/config.yaml
Normal file
157
.ddev/config.yaml
Normal file
@ -0,0 +1,157 @@
|
||||
APIVersion: v1.12.0
|
||||
name: inventar
|
||||
type: php
|
||||
docroot: public
|
||||
php_version: "7.2"
|
||||
webserver_type: nginx-fpm
|
||||
router_http_port: "80"
|
||||
router_https_port: "443"
|
||||
xdebug_enabled: false
|
||||
additional_hostnames: []
|
||||
additional_fqdns: []
|
||||
nfs_mount_enabled: false
|
||||
provider: default
|
||||
use_dns_when_possible: true
|
||||
timezone: ""
|
||||
|
||||
|
||||
# This config.yaml was created with ddev version v1.12.0
|
||||
# webimage: drud/ddev-webserver:v1.12.1
|
||||
# dbimage: drud/ddev-dbserver-mariadb-10.2:v1.12.0
|
||||
# dbaimage: drud/phpmyadmin:v1.12.0
|
||||
# bgsyncimage: drud/ddev-bgsync:v1.12.0
|
||||
# However we do not recommend explicitly wiring these images into the
|
||||
# config.yaml as they may break future versions of ddev.
|
||||
# You can update this config.yaml using 'ddev config'.
|
||||
|
||||
# Key features of ddev's config.yaml:
|
||||
|
||||
# name: <projectname> # Name of the project, automatically provides
|
||||
# http://projectname.ddev.site and https://projectname.ddev.site
|
||||
|
||||
# type: <projecttype> # drupal6/7/8, backdrop, typo3, wordpress, php
|
||||
|
||||
# docroot: <relative_path> # Relative path to the directory containing index.php.
|
||||
|
||||
# php_version: "7.2" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4"
|
||||
|
||||
# You can explicitly specify the webimage, dbimage, dbaimage lines but this
|
||||
# is not recommended, as the images are often closely tied to ddev's' behavior,
|
||||
# so this can break upgrades.
|
||||
|
||||
# webimage: <docker_image> # nginx/php docker image.
|
||||
# dbimage: <docker_image> # mariadb docker image.
|
||||
# dbaimage: <docker_image>
|
||||
# bgsyncimage: <docker_image>
|
||||
|
||||
# mariadb_version and mysql_version
|
||||
# ddev can use many versions of mariadb and mysql
|
||||
# However these directives are mutually exclusive
|
||||
# mariadb_version: 10.2
|
||||
# mysql_version: 8.0
|
||||
|
||||
# router_http_port: <port> # Port to be used for http (defaults to port 80)
|
||||
# router_https_port: <port> # Port for https (defaults to 443)
|
||||
|
||||
# xdebug_enabled: false # Set to true to enable xdebug and "ddev start" or "ddev restart"
|
||||
# Note that for most people the commands
|
||||
# "ddev exec enable_xdebug" and "ddev exec disable_xdebug" work better,
|
||||
# as leaving xdebug enabled all the time is a big performance hit.
|
||||
|
||||
# webserver_type: nginx-fpm # Can be set to apache-fpm or apache-cgi as well
|
||||
|
||||
# timezone: Europe/Berlin
|
||||
# This is the timezone used in the containers and by PHP;
|
||||
# it can be set to any valid timezone,
|
||||
# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# For example Europe/Dublin or MST7MDT
|
||||
|
||||
# additional_hostnames:
|
||||
# - somename
|
||||
# - someothername
|
||||
# would provide http and https URLs for "somename.ddev.site"
|
||||
# and "someothername.ddev.site".
|
||||
|
||||
# additional_fqdns:
|
||||
# - example.com
|
||||
# - sub1.example.com
|
||||
# would provide http and https URLs for "example.com" and "sub1.example.com"
|
||||
# Please take care with this because it can cause great confusion.
|
||||
|
||||
# upload_dir: custom/upload/dir
|
||||
# would set the destination path for ddev import-files to custom/upload/dir.
|
||||
|
||||
# working_dir:
|
||||
# web: /var/www/html
|
||||
# db: /home
|
||||
# would set the default working directory for the web and db services.
|
||||
# These values specify the destination directory for ddev ssh and the
|
||||
# directory in which commands passed into ddev exec are run.
|
||||
|
||||
# omit_containers: ["dba", "ddev-ssh-agent"]
|
||||
# would omit the dba (phpMyAdmin) and ddev-ssh-agent containers. Currently
|
||||
# only those two containers can be omitted here.
|
||||
# Note that these containers can also be omitted globally in the
|
||||
# ~/.ddev/global_config.yaml or with the "ddev config global" command.
|
||||
|
||||
# nfs_mount_enabled: false
|
||||
# Great performance improvement but requires host configuration first.
|
||||
# See https://ddev.readthedocs.io/en/stable/users/performance/#using-nfs-to-mount-the-project-into-the-container
|
||||
|
||||
# webcache_enabled: false (deprecated)
|
||||
# Was only for macOS, but now deprecated.
|
||||
# See https://ddev.readthedocs.io/en/stable/users/performance/#webcache
|
||||
|
||||
# host_https_port: "59002"
|
||||
# The host port binding for https can be explicitly specified. It is
|
||||
# dynamic unless otherwise specified.
|
||||
# This is not used by most people, most people use the *router* instead
|
||||
# of the localhost port.
|
||||
|
||||
# host_webserver_port: "59001"
|
||||
# The host port binding for the ddev-webserver can be explicitly specified. It is
|
||||
# dynamic unless otherwise specified.
|
||||
# This is not used by most people, most people use the *router* instead
|
||||
# of the localhost port.
|
||||
|
||||
# host_db_port: "59002"
|
||||
# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic
|
||||
# unless explicitly specified.
|
||||
|
||||
# phpmyadmin_port: "1000"
|
||||
# The PHPMyAdmin port can be changed from the default 8036
|
||||
|
||||
# mailhog_port: "1001"
|
||||
# The MailHog port can be changed from the default 8025
|
||||
|
||||
# webimage_extra_packages: [php-yaml, php7.3-ldap]
|
||||
# Extra Debian packages that are needed in the webimage can be added here
|
||||
|
||||
# dbimage_extra_packages: [telnet,netcat]
|
||||
# Extra Debian packages that are needed in the dbimage can be added here
|
||||
|
||||
# use_dns_when_possible: true
|
||||
# If the host has internet access and the domain configured can
|
||||
# successfully be looked up, DNS will be used for hostname resolution
|
||||
# instead of editing /etc/hosts
|
||||
# Defaults to true
|
||||
|
||||
# project_tld: ddev.site
|
||||
# The top-level domain used for project URLs
|
||||
# The default "ddev.site" allows DNS lookup via a wildcard
|
||||
# If you prefer you can change this to "ddev.local" to preserve
|
||||
# pre-v1.9 behavior.
|
||||
|
||||
# ngrok_args: --subdomain mysite --auth username:pass
|
||||
# Provide extra flags to the "ngrok http" command, see
|
||||
# https://ngrok.com/docs#http or run "ngrok http -h"
|
||||
|
||||
# provider: default # Currently either "default" or "pantheon"
|
||||
#
|
||||
# Many ddev commands can be extended to run tasks before or after the
|
||||
# ddev command is executed, for example "post-start", "post-import-db",
|
||||
# "pre-composer", "post-composer"
|
||||
# See https://ddev.readthedocs.io/en/stable/users/extending-commands/ for more
|
||||
# information on the commands that can be extended and the tasks you can define
|
||||
# for them. Example:
|
||||
#hooks:
|
||||
6
.env.example
Normal file
6
.env.example
Normal file
@ -0,0 +1,6 @@
|
||||
DB_SERVER=localhost
|
||||
DB_NAME=inventur
|
||||
DB_USER=inventur
|
||||
DB_PASSWORD=inventur
|
||||
|
||||
NODE_ENV=production
|
||||
19
.eslintrc.json
Normal file
19
.eslintrc.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"root": true,
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"indent": ["error", 2]
|
||||
}
|
||||
}
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/vendor/
|
||||
/node_modules/
|
||||
.env
|
||||
|
||||
data/
|
||||
!data/*.example
|
||||
304
app/js/App.vue
Normal file
304
app/js/App.vue
Normal file
@ -0,0 +1,304 @@
|
||||
<template>
|
||||
<div>
|
||||
<NavigationBar
|
||||
:primary-color="primaryColor"
|
||||
@changed-tab="changeView"
|
||||
/>
|
||||
<div class="container">
|
||||
<div
|
||||
v-if="!ready"
|
||||
class="valign-wrapper"
|
||||
style="height: 90vh"
|
||||
>
|
||||
<div class="progress">
|
||||
<div class="indeterminate" />
|
||||
</div>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="ready && view == 'inventory'"
|
||||
id="inventory"
|
||||
class="col s12"
|
||||
>
|
||||
<InventoryTable
|
||||
ref="inventory"
|
||||
:articles="articles"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="ready && view == 'calc'"
|
||||
id="calc"
|
||||
class="col s12"
|
||||
>
|
||||
<Calculator :articles="articles" />
|
||||
</div>
|
||||
<div
|
||||
v-if="ready && view == 'article'"
|
||||
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>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<div
|
||||
ref="fab"
|
||||
class="fixed-action-btn"
|
||||
>
|
||||
<a
|
||||
class="btn-floating btn-large"
|
||||
:class="primaryColor"
|
||||
>
|
||||
<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="#"
|
||||
class="btn-floating tooltipped"
|
||||
data-position="left"
|
||||
data-tooltip="Artikel hinzufügen"
|
||||
@click="addArticle"
|
||||
>
|
||||
<i class="material-icons">add</i>
|
||||
</a>
|
||||
</li>
|
||||
<li v-show="view == 'inventory'">
|
||||
<a
|
||||
href="#"
|
||||
class="btn-floating tooltipped"
|
||||
data-position="left"
|
||||
data-tooltip="Inventur zurücksetzen"
|
||||
@click="$refs.inventory.$emit('reset-inventur')"
|
||||
>
|
||||
<i class="material-icons">clear_all</i>
|
||||
</a>
|
||||
</li>
|
||||
<li v-show="view == 'inventory'">
|
||||
<a
|
||||
href="#"
|
||||
class="btn-floating tooltipped"
|
||||
data-position="left"
|
||||
data-tooltip="Inventur exportieren"
|
||||
@click="$refs.inventory.$emit('export-inventur')"
|
||||
>
|
||||
<i class="material-icons">save</i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Calculator from './components/Calculator';
|
||||
import InventoryTable from './components/InventoryTable';
|
||||
import NavigationBar from './components/NavigationBar';
|
||||
import {Article, thawArticle} from './model/article';
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: { InventoryTable, NavigationBar, Calculator },
|
||||
data() {
|
||||
return {
|
||||
articles: [],
|
||||
bon: [],
|
||||
ready: false,
|
||||
caretPosition: 0,
|
||||
fab: null,
|
||||
view: "inventory",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
primaryColor() {
|
||||
switch (this.view) {
|
||||
case "inventory":
|
||||
return "teal";
|
||||
case "calc":
|
||||
return "orange";
|
||||
case "article":
|
||||
return "blue";
|
||||
default:
|
||||
return "red";
|
||||
}
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.loadArticles();
|
||||
},
|
||||
mounted() {
|
||||
var fab = this.$refs.fab;
|
||||
this.fab = this.$M.FloatingActionButton.init(fab, {});
|
||||
},
|
||||
methods: {
|
||||
changeView(tabId) {
|
||||
this.view = tabId;
|
||||
},
|
||||
addArticle: function() {
|
||||
this.articles.push(new Article());
|
||||
},
|
||||
storeArticles: function() {
|
||||
this.$http
|
||||
.post(
|
||||
"/api/artikel",
|
||||
JSON.stringify(this.articles)
|
||||
)
|
||||
.then(response => {
|
||||
this.$M.toast({ html: response.body });
|
||||
});
|
||||
},
|
||||
loadArticles: function() {
|
||||
var app = this;
|
||||
this.$http
|
||||
.get("api/artikel/theater")
|
||||
.then(response => {
|
||||
return response.data;
|
||||
})
|
||||
.then(json => {
|
||||
console.log(json);
|
||||
json.forEach(element => {
|
||||
app.articles.push(thawArticle(element));
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
this.$M.toast({ html: "Artikel wurden geladen." });
|
||||
})
|
||||
.then(() => {
|
||||
this.$M.updateTextFields();
|
||||
app.ready = true;
|
||||
});
|
||||
},
|
||||
exportInventur() {
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
124
app/js/components/Calculator.vue
Normal file
124
app/js/components/Calculator.vue
Normal file
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div class="row pin-top">
|
||||
<div class="col s12 m4 card darken-4 grey grey-text text-lighten-2">
|
||||
<table class="card-content">
|
||||
<tbody style="max-height:300px">
|
||||
<tr
|
||||
v-for="item in items"
|
||||
v-show="item.count > 0"
|
||||
:key="'bon_'+item.short"
|
||||
>
|
||||
<td class="right-align">
|
||||
{{ item.count }} ×
|
||||
</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td class="right-align">
|
||||
<TweenedNumber
|
||||
:wert="item.sum"
|
||||
:einheit="'€'"
|
||||
:precision="2"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="white-text">
|
||||
<th class="right-align">
|
||||
<TweenedNumber
|
||||
:wert="totalCount"
|
||||
/>
|
||||
</th>
|
||||
<th>
|
||||
Artikel
|
||||
</th>
|
||||
<th class="right-align">
|
||||
<TweenedNumber
|
||||
:wert="totalSum"
|
||||
:einheit="'€'"
|
||||
:precision="2"
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col s12 m8">
|
||||
<div class="row">
|
||||
<div
|
||||
v-for="a in items"
|
||||
:key="'bon_btn_'+a.short"
|
||||
class="col s3"
|
||||
>
|
||||
<button
|
||||
class="waves-effect waves-light btn-large btn-flat col s12"
|
||||
@click="a.count++;a.sum+=a.price;"
|
||||
>
|
||||
{{ a.short }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s3">
|
||||
<button
|
||||
class="waves-effect waves-light btn-large orange col s12"
|
||||
@click="resetBon"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TweenedNumber from './TweenedNumber';
|
||||
|
||||
export default {
|
||||
name: "Calculator",
|
||||
components: { TweenedNumber },
|
||||
props: {
|
||||
articles: {
|
||||
type: Array,
|
||||
default() { return []; }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
items: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalSum() {
|
||||
return this.items.reduce((total, item) => {
|
||||
return total + item.sum;
|
||||
}, 0);
|
||||
},
|
||||
totalCount() {
|
||||
return this.items.reduce((total, item) => {
|
||||
return total + item.count;
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.resetBon();
|
||||
},
|
||||
methods: {
|
||||
resetBon() {
|
||||
this.items = this.articles.map(a => {
|
||||
return {
|
||||
count: 0,
|
||||
name: a.name,
|
||||
short: a.short,
|
||||
price: a.portion.price,
|
||||
sum: 0,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
7
app/js/components/InventoryItem.css
Normal file
7
app/js/components/InventoryItem.css
Normal file
@ -0,0 +1,7 @@
|
||||
.inventory-item {
|
||||
transition: all .2s;
|
||||
}
|
||||
|
||||
.inventory-item>* {
|
||||
transition: all .2s .2s;
|
||||
}
|
||||
153
app/js/components/InventoryItem.vue
Normal file
153
app/js/components/InventoryItem.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<tr
|
||||
style="border: 0;"
|
||||
class="inventory-item"
|
||||
:class="{
|
||||
'teal lighten-5' : item.Sale > 0,
|
||||
'orange darken-2 white-text' : item.Sold %1 !=0,
|
||||
'red darken-2 white-text' : item.Sale < 0,
|
||||
}"
|
||||
>
|
||||
<th
|
||||
class="right-align"
|
||||
>
|
||||
{{ item.article.name }}
|
||||
</th>
|
||||
|
||||
<td
|
||||
v-for="prop in ['start','fetched','end','lost']"
|
||||
:key="item.article.short+'_'+prop"
|
||||
class="border-bottom center-align"
|
||||
:class="{active:classObject.active == prop}"
|
||||
contenteditable
|
||||
@keypress="restrictInput(prop, $event)"
|
||||
@focus="onFocus(prop, $event)"
|
||||
@blur="onBlur(prop, $event)"
|
||||
>
|
||||
{{ 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
|
||||
+ '%, transparent '
|
||||
+ item.Sold*100/total
|
||||
+'%,transparent 100%) 1'
|
||||
}"
|
||||
:wert="item.Sold"
|
||||
:einheit="item.article.PortionType"
|
||||
:precision="item.PortionPrecision"
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
class="right-align border-bottom"
|
||||
>
|
||||
<TweenedNumber
|
||||
:wert="item.Sale"
|
||||
:einheit="'€'"
|
||||
:precision="2"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TweenedNumber from './TweenedNumber';
|
||||
import './InventoryItem.css';
|
||||
|
||||
export default {
|
||||
name:"InventoryItem",
|
||||
components: {
|
||||
TweenedNumber,
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tweenedSold: 0,
|
||||
tweenedSale: 0,
|
||||
classObject: {active : null}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
SoldAnimated() {
|
||||
return this.tweenedSold.toFixed(this.item.PortionPrecision);
|
||||
},
|
||||
SaleAnimated() {
|
||||
return this.tweenedSale.toFixed(2);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
setCaretPosition(el){
|
||||
if (el != null) {
|
||||
var range = document.createRange();
|
||||
var sel = window.getSelection();
|
||||
range.setStart(el.firstChild, el.innerText.length);
|
||||
range.collapse(true);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
el.focus();
|
||||
}
|
||||
},
|
||||
restrictInput(prop, event){
|
||||
var x = event.key;
|
||||
console.log(prop + " inserting " + x)
|
||||
if (x === "Enter" ) {
|
||||
event.target.blur();
|
||||
}
|
||||
if (isNaN(x) && x != ',') {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
onFocus(property, event){
|
||||
this.classObject.active = property;
|
||||
var range = document.createRange();
|
||||
var sel = window.getSelection();
|
||||
range.selectNodeContents(event.target);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
},
|
||||
onBlur(property, event) {
|
||||
this.classObject.active = null;
|
||||
var value = event.target.innerText;
|
||||
value = value.replace(',', '.')
|
||||
console.log(property + " left value: " + value);
|
||||
if (!isNaN(value)) {
|
||||
this.item[property] = Number(value);
|
||||
}
|
||||
},
|
||||
onInput(property, event){
|
||||
if (event.inputType == "insertText") {
|
||||
var regex = /\d+\.?\d{0,2}/;
|
||||
if (event.data.match(regex)){
|
||||
var value = event.target.innerText;
|
||||
this.item[property] = Number(value);
|
||||
}
|
||||
}
|
||||
this.setCaretPosition(event.target);
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
25
app/js/components/InventoryTable.css
Normal file
25
app/js/components/InventoryTable.css
Normal file
@ -0,0 +1,25 @@
|
||||
table.condensed td,
|
||||
table.condensed th {
|
||||
padding: 5px 5px;
|
||||
}
|
||||
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
.border-right {
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.border-diagram {
|
||||
padding: 0;
|
||||
}
|
||||
.border-diagram>div {
|
||||
margin-bottom: -3px;
|
||||
border-radius: 0;
|
||||
border-bottom: 3px solid transparent;
|
||||
}
|
||||
|
||||
.active {
|
||||
border-color: rgb(38, 166, 154);
|
||||
border-width: 2px;
|
||||
}
|
||||
151
app/js/components/InventoryTable.vue
Normal file
151
app/js/components/InventoryTable.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<table class="table condensed highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="right-align">
|
||||
Artikel
|
||||
</th>
|
||||
<th class="center-align">
|
||||
Beginn
|
||||
</th>
|
||||
<th class="center-align">
|
||||
Zugang
|
||||
</th>
|
||||
<th class="center-align">
|
||||
Ende
|
||||
</th>
|
||||
<th class="center-align">
|
||||
Verlust
|
||||
</th>
|
||||
<th class="right-align">
|
||||
Verkauft
|
||||
</th>
|
||||
<th class="right-align">
|
||||
Summe
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<InventoryItem
|
||||
v-for="(item,index) in inventory"
|
||||
:key="'item-'+index"
|
||||
:item="item"
|
||||
:total="sold"
|
||||
/>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th
|
||||
colspan="6"
|
||||
class="right-align"
|
||||
>
|
||||
Gesamtsumme:
|
||||
</th>
|
||||
<td class="right-align">
|
||||
<TweenedNumber
|
||||
:wert="sales"
|
||||
:precision="2"
|
||||
:einheit="'€'"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InventoryItem from "./InventoryItem";
|
||||
import TweenedNumber from "./TweenedNumber";
|
||||
import { InventoryArticle } from '../model/inventory_article';
|
||||
import './InventoryTable.css';
|
||||
|
||||
export default {
|
||||
name: "InventoryTable",
|
||||
components: { InventoryItem, TweenedNumber },
|
||||
props: {
|
||||
articles: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inventory: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
meta() {
|
||||
return {
|
||||
datum: new Date().toString(),
|
||||
}
|
||||
},
|
||||
sales() {
|
||||
var total_sales = this.inventory.reduce(function(total, item) {
|
||||
return total + item.Sale;
|
||||
}, 0);
|
||||
return total_sales;
|
||||
},
|
||||
sold() {
|
||||
var total_sold = this.inventory.reduce((total, item) => {
|
||||
return total + item.Sold;
|
||||
},0);
|
||||
return total_sold;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$on('reset-inventur', this.resetInventory);
|
||||
this.$on('export-inventur', this.exportInventory);
|
||||
|
||||
this.resetInventory();
|
||||
},
|
||||
methods: {
|
||||
resetInventory() {
|
||||
this.inventory = this.articles.map(item => {
|
||||
return new InventoryArticle(item);
|
||||
});
|
||||
},
|
||||
getInventurBlob() {
|
||||
let inventur = this.inventory.map(T => {
|
||||
return {
|
||||
name: T.article.name,
|
||||
preis: T.article.portion.price,
|
||||
beginn: T.start,
|
||||
zugang: T.fetched,
|
||||
ende: T.end,
|
||||
verlust: T.lost,
|
||||
verkauft: T.Sold,
|
||||
umsatz: T.Sale
|
||||
};
|
||||
});
|
||||
const data = JSON.stringify({
|
||||
meta: this.meta,
|
||||
data: inventur
|
||||
});
|
||||
const blob = new Blob([data],{type: 'text/json' });
|
||||
return blob;
|
||||
},
|
||||
exportInventory() {
|
||||
var formData = new FormData();
|
||||
formData.append('file', this.getInventurBlob(), 'inventur.json');
|
||||
this.$http
|
||||
.post(
|
||||
"/api/inventur",
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
this.$M.toast({ html: response.body });
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
63
app/js/components/NavigationBar.vue
Normal file
63
app/js/components/NavigationBar.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<nav
|
||||
class="nav-extended"
|
||||
:class="primaryColor"
|
||||
>
|
||||
<div class="nav-wrapper container" />
|
||||
<div class="nav-content container">
|
||||
<ul class="tabs tabs-transparent">
|
||||
<li class="tab">
|
||||
<a
|
||||
class="active"
|
||||
@click.stop.prevent="changeTab('inventory', $event)"
|
||||
>Inventur</a>
|
||||
</li>
|
||||
<li class="tab">
|
||||
<a
|
||||
@click.stop.prevent="changeTab('article', $event)"
|
||||
>Artikelliste</a>
|
||||
</li>
|
||||
<li class="tab">
|
||||
<a
|
||||
@click.stop.prevent="changeTab('calc', $event)"
|
||||
>Rechner</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import M from 'materialize-css';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
primaryColor: {
|
||||
type: String,
|
||||
default: "red"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabs: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
var tabs = document.getElementsByClassName("tabs")[0];
|
||||
M.Tabs.init(tabs, {});
|
||||
this.tabs = M.Tabs.getInstance(tabs);
|
||||
},
|
||||
methods: {
|
||||
changeTab(tabId, event) {
|
||||
this.view = tabId;
|
||||
this.tabs._handleTabClick(event);
|
||||
this.tabs.updateTabIndicator();
|
||||
this.$emit("changed-tab", this.view);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
15
app/js/components/TweenedNumber.css
Normal file
15
app/js/components/TweenedNumber.css
Normal file
@ -0,0 +1,15 @@
|
||||
.tweened {
|
||||
display: flex;
|
||||
align-content: end;
|
||||
justify-content: end;
|
||||
}
|
||||
.tweened>div:first-child {
|
||||
text-align: right;
|
||||
min-width: 1.5rem;
|
||||
}
|
||||
.tweened>div:nth-child(0n+2) {
|
||||
min-width: 1.5rem;
|
||||
}
|
||||
.tweened>div:nth-child(0n+3) {
|
||||
min-width: 2rem;
|
||||
}
|
||||
63
app/js/components/TweenedNumber.vue
Normal file
63
app/js/components/TweenedNumber.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="tweened">
|
||||
<div>{{ intAnimated }}</div>
|
||||
<div v-if="precision>0">
|
||||
,{{ deciAnimated }}
|
||||
</div>
|
||||
<div v-else />
|
||||
<div>{{ einheit }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gsap from 'gsap';
|
||||
import './TweenedNumber.css';
|
||||
|
||||
export default {
|
||||
name: "TweenedNumber",
|
||||
props: {
|
||||
wert: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
einheit: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
precision: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
tweenedValue: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
valueAnimated() {
|
||||
return this.tweenedValue.toLocaleString(
|
||||
"de-DE",
|
||||
{
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: this.precision
|
||||
}
|
||||
);
|
||||
},
|
||||
intAnimated() {
|
||||
return this.valueAnimated.split(',')[0];
|
||||
},
|
||||
deciAnimated() {
|
||||
return this.valueAnimated.split(',')[1];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
wert: {
|
||||
handler(newValue) {
|
||||
gsap.to(this.$data, 0.5, {tweenedValue: newValue });
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
16
app/js/index.js
Normal file
16
app/js/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
import Vue from "vue";
|
||||
import App from "./App";
|
||||
import 'materialize-css/dist/css/materialize.min.css';
|
||||
import Axios from 'axios';
|
||||
import M from 'materialize-css';
|
||||
|
||||
Vue.prototype.$http = Axios;
|
||||
Vue.prototype.$M = M;
|
||||
|
||||
new Vue({
|
||||
delimiters: ["${", "}"],
|
||||
components: {
|
||||
App
|
||||
},
|
||||
template: "<App/>"
|
||||
}).$mount("#app");
|
||||
80
app/js/model/article.js
Normal file
80
app/js/model/article.js
Normal file
@ -0,0 +1,80 @@
|
||||
export class Article{
|
||||
constructor() {
|
||||
this.name = "";
|
||||
this.short = "";
|
||||
this.dimension = "";
|
||||
this.content = {
|
||||
size : 0,
|
||||
price: 0
|
||||
};
|
||||
this.portion = {
|
||||
size : 0,
|
||||
price : 0,
|
||||
type : ""
|
||||
};
|
||||
}
|
||||
get Name() {
|
||||
return this.name;
|
||||
}
|
||||
set Name(value) {
|
||||
this.name = value;
|
||||
}
|
||||
get Short() {
|
||||
return this.short;
|
||||
}
|
||||
set Short(value) {
|
||||
this.short = value;
|
||||
}
|
||||
get ContentSize() {
|
||||
return this.content.size;
|
||||
}
|
||||
set ContentSize(value) {
|
||||
this.content.size = value;
|
||||
}
|
||||
get Dimension() {
|
||||
return this.dimension;
|
||||
}
|
||||
set Dimension(value) {
|
||||
this.dimension = value;
|
||||
}
|
||||
get PortionSize() {
|
||||
return this.portion.size;
|
||||
}
|
||||
set PortionSize(value) {
|
||||
this.portion.size = value;
|
||||
}
|
||||
get PortionType() {
|
||||
return this.portion.type;
|
||||
}
|
||||
set PortionType(value) {
|
||||
this.portion.type = value;
|
||||
}
|
||||
get PortionPrice() {
|
||||
return this.portion.price;
|
||||
}
|
||||
set PortionPrice(value) {
|
||||
this.portion.price = value;
|
||||
}
|
||||
get ContentPrice() {
|
||||
return this.Portions * this.portion.price;
|
||||
}
|
||||
get Portions() {
|
||||
return this.content.size / (this.portion.size || 1);
|
||||
}
|
||||
}
|
||||
|
||||
Article.thaw = function (json) {
|
||||
var article = new Article();
|
||||
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;
|
||||
|
||||
return article;
|
||||
};
|
||||
|
||||
export const thawArticle = Article.thaw;
|
||||
72
app/js/model/inventory_article.js
Normal file
72
app/js/model/inventory_article.js
Normal file
@ -0,0 +1,72 @@
|
||||
export class InventoryArticle {
|
||||
constructor(article) {
|
||||
this.article = article;
|
||||
this.end = 0;
|
||||
this.start = 0;
|
||||
this.fetched = 0;
|
||||
this.lost = 0;
|
||||
}
|
||||
get StartPortions() {
|
||||
var countFull = Math.floor(this.start);
|
||||
var fullPortions = countFull * this.article.Portions;
|
||||
var rest = this.start - countFull;
|
||||
var restPortions = rest / this.article.PortionSize;
|
||||
|
||||
return fullPortions + restPortions;
|
||||
}
|
||||
get FetchedPortions() {
|
||||
var countFull = Math.floor(this.fetched);
|
||||
var fullPortions = countFull * this.article.Portions;
|
||||
var rest = this.fetched - countFull;
|
||||
var restPortions = rest / this.article.PortionSize;
|
||||
|
||||
return fullPortions + restPortions;
|
||||
}
|
||||
get EndPortions() {
|
||||
var countFull = Math.floor(this.end);
|
||||
var fullPortions = countFull * this.article.Portions;
|
||||
var rest = this.end - countFull;
|
||||
var restPortions = rest / this.article.PortionSize;
|
||||
|
||||
return fullPortions + restPortions;
|
||||
}
|
||||
get LostPortions() {
|
||||
var countFull = Math.floor(this.lost);
|
||||
var fullPortions = countFull * this.article.Portions;
|
||||
var rest = this.lost - countFull;
|
||||
var restPortions = rest / this.article.PortionSize;
|
||||
|
||||
return fullPortions + restPortions;
|
||||
}
|
||||
get Sold() {
|
||||
var adds = this.StartPortions + this.FetchedPortions;
|
||||
var subs = this.EndPortions + this.LostPortions;
|
||||
return adds - subs;
|
||||
}
|
||||
get Sale() {
|
||||
return this.Sold * this.article.PortionPrice;
|
||||
}
|
||||
get StepSize() {
|
||||
var singlePack = this.article.Portions == 1;
|
||||
return singlePack ? 1 : 0.05;
|
||||
}
|
||||
get PortionPrecision() {
|
||||
var singlePack = this.article.Portions == 1;
|
||||
return singlePack ? 0 : 2;
|
||||
}
|
||||
reset() {
|
||||
this.start = 0;
|
||||
this.fetched = 0;
|
||||
this.end = 0;
|
||||
this.lost = 0;
|
||||
}
|
||||
}
|
||||
|
||||
InventoryArticle.thaw = function (json) {
|
||||
this.name = json.name;
|
||||
this.start = json.start;
|
||||
this.fetched = json.fetched;
|
||||
this.end = json.end;
|
||||
this.lost = json.lost;
|
||||
};
|
||||
export const thawInventoryArticle = InventoryArticle.thaw;
|
||||
54
backend.php
54
backend.php
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
require "localconf.php";
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] != "POST") {
|
||||
echo "Sorry, ich spreche kein GET.";
|
||||
return;
|
||||
}
|
||||
if (empty($_POST) && empty(file_get_contents('php://input'))) {
|
||||
echo "Ich brauche INPUT";
|
||||
return;
|
||||
}
|
||||
|
||||
if ($_GET['controller'] == "Article"){
|
||||
if ($_GET['action'] == "store") {
|
||||
$json = file_get_contents('php://input');
|
||||
$file = fopen(__DIR__.'/data/articles.json','w');
|
||||
fwrite($file, $json);
|
||||
fclose($file);
|
||||
echo "Artikel wurden gespeichert.";
|
||||
}
|
||||
}
|
||||
|
||||
if ($_GET['controller'] == "Inventur"){
|
||||
if ($_GET['action'] == "export") {
|
||||
$json = file_get_contents('php://input');
|
||||
$list = json_decode($json, true);
|
||||
$fp = fopen('data/inventur_'.date('Y-m-d').'.csv', 'w');
|
||||
|
||||
fputcsv($fp,array_keys(flatten($list[0])));
|
||||
foreach ($list as $obj) {
|
||||
$fields = flatten($obj);
|
||||
//array_walk_recursive($obj, function($a)use (&$fields) { $fields[] = $a;});
|
||||
//print_r($fields);
|
||||
fputcsv($fp,$fields);
|
||||
}
|
||||
echo "Inventur wurde gespeichert.";
|
||||
fclose($fp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function flatten($array, $prefix = '') {
|
||||
$result = array();
|
||||
foreach($array as $key=>$value) {
|
||||
if(is_array($value)) {
|
||||
$result = $result + flatten($value, $prefix . $key . '_');
|
||||
}
|
||||
else {
|
||||
$result[$prefix . $key] = $value;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
38
composer.json
Normal file
38
composer.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "chrosey/inventar",
|
||||
"type": "project",
|
||||
"authors": [
|
||||
{
|
||||
"name": "chrosey",
|
||||
"email": "christian.seyfferth@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"slim/slim": "^4.3",
|
||||
"vlucas/phpdotenv": "^4.1",
|
||||
"catfan/medoo": "^1.7",
|
||||
"slim/psr7": "^0.6.0",
|
||||
"slim/php-view": "^2.2",
|
||||
"slim/twig-view": "^3.0",
|
||||
"php-di/php-di": "^6.0",
|
||||
"symfony/webpack-encore-bundle": "^1.7",
|
||||
"illuminate/database": "^6.10"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"scripts": {
|
||||
"csfix": "phpcbf",
|
||||
"cscheck": "phpcs",
|
||||
"phpstan": "phpstan analyse src/ -c phpstan.neon --level=7 --no-progress -vvv --memory-limit=1024M"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Chrosey\\Inventur\\":"src"
|
||||
}
|
||||
}
|
||||
}
|
||||
3563
composer.lock
generated
Normal file
3563
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
css/lib/materialize.min.css
vendored
13
css/lib/materialize.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
[{"name":"Edelpils","dimension":"l","content":{"size":0.33,"price":0},"portion":{"size":0.33,"price":3.5,"type":"Fl."}},{"name":"Schwarzbier","dimension":"l","content":{"size":0.33,"price":0},"portion":{"size":0.33,"price":3.5,"type":"Fl."}},{"name":"Schöfferhofer","dimension":"l","content":{"size":0.33,"price":0},"portion":{"size":0.33,"price":3.5,"type":"Fl."}},{"name":"Bitburger af.","dimension":"l","content":{"size":0.33,"price":0},"portion":{"size":0.33,"price":3.5,"type":"Fl."}},{"name":"Weiswein","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.2,"price":6,"type":"Gl."}},{"name":"Rotwein","dimension":"l","content":{"size":0.75,"price":0},"portion":{"size":0.2,"price":6.5,"type":"Gl."}},{"name":"Secco","dimension":"l","content":{"size":0.75,"price":0},"portion":{"size":0.1,"price":6,"type":"Gl."}},{"name":"B-Saft","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.2,"price":3.5,"type":"Gl."}},{"name":"K-Saft","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.2,"price":3.5,"type":"Gl."}},{"name":"O-Saft","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.2,"price":3.5,"type":"Gl."}},{"name":"G-Saft","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.2,"price":3.5,"type":"Gl."}},{"name":"Vita Cola","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.2,"price":2.5,"type":"Gl."}},{"name":"Vita Orange","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.2,"price":2.5,"type":"Gl."}},{"name":"Vita Zitrone","dimension":"l","content":{"size":1,"price":0},"portion":{"size":0.2,"price":2.5,"type":"Gl."}},{"name":"Tonic","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."}},{"name":"Bitter Lemon","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."}},{"name":"Ginger Ale","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."}},{"name":"Apfelschorle","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":3,"type":"Fl."}},{"name":"TWQ naturell","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":2.5,"type":"Fl."}},{"name":"TWQ medium","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":2.5,"type":"Fl."}},{"name":"TWQ classic","dimension":"l","content":{"size":0.25,"price":0},"portion":{"size":0.25,"price":2.5,"type":"Fl."}},{"name":"Kaffee","dimension":"Tasse","content":{"size":1,"price":0},"portion":{"size":1,"price":2.5,"type":"T"}},{"name":"Latte Macchiato","dimension":"Glas","content":{"size":1,"price":0},"portion":{"size":1,"price":3.5,"type":"Gl."}},{"name":"dopp. Esp.","dimension":"Tasse","content":{"size":1,"price":0},"portion":{"size":1,"price":3.5,"type":"T"}},{"name":"Brezel","dimension":"Stück","content":{"size":1,"price":0},"portion":{"size":1,"price":2.5,"type":"Stk."}},{"name":"Schokoriegel","dimension":"Stück","content":{"size":1,"price":0},"portion":{"size":1,"price":2,"type":"Stk."}}]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
188
index.html
188
index.html
@ -1,188 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta lang="de">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta name="theme-color" content="#004d40">
|
||||
<title>Inventur</title>
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/lib/materialize.min.css">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="icon" sizes="192x192" href="/favicons/android-chrome-192x192.png">
|
||||
<link rel="apple-touch-icon" href="/favicons/android-chrome-192x192.png">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<nav class="nav-extended teal" >
|
||||
<div class="nav-wrapper container">
|
||||
<a href="#" class="brand-logo">Inventur</a>
|
||||
</div>
|
||||
<div class="nav-content container">
|
||||
<ul class="tabs tabs-transparent">
|
||||
<li class="tab"><a href="#article" @click="view = 'article';">Artikelliste</a></li>
|
||||
<li class="tab"><a href="#inventory" class="active" @click="view = 'inventur';">Inventur</a></li>
|
||||
<li class="tab"><a href="#calc" @click="view = 'calc';">Rechner</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container">
|
||||
<div class="valign-wrapper" style="height: 90vh" v-if="!ready">
|
||||
<div class="progress">
|
||||
<div class="indeterminate"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s12" id="article" v-if="ready">
|
||||
<div v-for="(a, index) in articles" class="card">
|
||||
<div class="card-content row">
|
||||
<span class="card-title">{{ a.name }}</span>
|
||||
<div class="input-field inline col s8">
|
||||
<input v-model="a.name" placeholder="Artikelname" class="validate" :id="'a_name_'+index">
|
||||
<label :for="'a_name_'+index" class="active">Name</label>
|
||||
</div>
|
||||
<div class="input-field inline col s4">
|
||||
<input v-model="a.short" placeholder="Kurzname" class="validate" :id="'a_short_'+index">
|
||||
<label :for="'a_short_'+index" class="active">Kürzel</label>
|
||||
</div>
|
||||
<div class="input-field col s8">
|
||||
<input v-model.number="a.content.size" placeholder="Gesamtinhalt" type="number" class="validate" step="0.01" :id="'a_csize_'+index">
|
||||
<label :for="'a_csize_'+index" class="active">Gesamtinhalt</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<input v-model="a.dimension" placeholder="Dimension" class="validate" :id="'a_dim_'+index" 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 v-model.number="a.portion.size" placeholder="Gesamtinhalt" type="number" class="validate" step="0.01" :id="'a_psize_'+index" max="5">
|
||||
<label :for="'a_psize_'+index" class="active">Portionsinhalt</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<input v-model="a.portion.type" placeholder="Art" class="validate" :id="'a_ptype_'+index" max="5">
|
||||
<label :for="'a_ptype_'+index" class="active">Portionsbezeichnung</label>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<input v-model.number="a.portion.price" placeholder="Preis" type="number" step="0.01" class="validate" :id="'a_pprice_'+index">
|
||||
<label :for="'a_pprice_'+index" class="active">Portionspreis in €</label>
|
||||
</div>
|
||||
|
||||
<div class="col s12 right">
|
||||
<span class="right">Gesamtpreis {{ a.ContentPrice | currency }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a class="" href="#">Artikel löschen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s12" id="inventory" v-if="ready">
|
||||
<div v-for="(a, index) in inventory.ug" class="card hoverable">
|
||||
<div class="card-content row">
|
||||
<div class="col s12 m2">{{ a.article.name }}</div>
|
||||
<div class="input-field col s6 m2">
|
||||
<input v-model.number="a.start" placeholder="Anfang" title="Anfang" type="number" :step="a.StepSize" :id="'i_s_'+index">
|
||||
<label :for="'i_s_'+index" class="active">Beginn</label>
|
||||
</div>
|
||||
<div class="input-field col s6 m2 inline">
|
||||
<input v-model.number="a.fetched" placeholder="Zugang" title="Zugang" type="number" :step="a.StepSize" :id="'i_f_'+index">
|
||||
<label :for="'i_f_'+index" class="active">Zugang</label>
|
||||
</div>
|
||||
<div class="input-field col s6 m2">
|
||||
<input v-model.number="a.end" placeholder="Ende" title="Ende" type="number" :step="a.StepSize" :id="'i_e_'+index">
|
||||
<label :for="'i_e_'+index" class="active">Ende</label>
|
||||
</div>
|
||||
<div class="input-field col s6 m2">
|
||||
<input v-model.number="a.lost" placeholder="Verlust" title="Verlust" type="number" :step="a.StepSize" :id="'i_l_'+index">
|
||||
<label :for="'i_l_'+index" class="active">Verlust</label>
|
||||
</div>
|
||||
<div class="col m2 right right-align"> <b>{{a.article.portion.price | currency }} × {{ a.Sold | number(a.PortionPrecision) }} {{ a.article.PortionType }} <br/>= {{ a.Sale | currency }}</b></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card teal lighten-2 white-text">
|
||||
<div class="card-content">
|
||||
<h5>Gesamtsumme: {{ sales_ug | currency }}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s12" id="calc" v-if="ready">
|
||||
<div class="row pin-top">
|
||||
<div class="col s12 m4 card darken-4 grey grey-text text-lighten-2">
|
||||
<table class="card-content">
|
||||
<tr v-for="item in bonned(bon)">
|
||||
<td class="right">{{ item.count }} ×</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td class="right">{{ item.price * item.count | currency }}</td>
|
||||
</tr>
|
||||
<tr class="white-text">
|
||||
<th class="right">{{ bon_sum > 0 ?bon_sum: "" }}</th>
|
||||
<th>{{ bon_sum > 0 ? "Artikel": ""}}</th>
|
||||
<th class="right">{{ bon_price }}</th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col s12 m8">
|
||||
<div class="row">
|
||||
<div class="col s3" v-for="a in bon">
|
||||
<button class="waves-effect waves-light btn-large btn-flat col s12" @click="a.count++;">{{ a.short }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s3">
|
||||
<button class="waves-effect waves-light btn-large orange col s12" @click="resetBon">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed-action-btn">
|
||||
<a href="#" class="btn-floating btn-large" :class="[ view == 'article' ? 'orange' : (view == 'inventur' ? 'teal' : 'brown') ]">
|
||||
<i class="large material-icons">more</i>
|
||||
</a>
|
||||
<ul>
|
||||
<li v-if="view == 'article'">
|
||||
<a href="#" class="btn-floating tooltipped" data-position="left" @click="storeArticles" data-tooltip="Artikelliste speichern"><i class="material-icons">save</i></a>
|
||||
</li>
|
||||
<li v-if="view == 'article'">
|
||||
<a href="#" class="btn-floating tooltipped" data-position="left" @click="addArticle" data-tooltip="Artikel hinzufügen"><i class="material-icons">add</i></a>
|
||||
</li>
|
||||
<li v-if="view == 'inventur'">
|
||||
<a href="#" class="btn-floating tooltipped" data-position="left" @click="resetInventur" data-tooltip="Inventur zurücksetzen"><i class="material-icons">reset</i></a>
|
||||
</li>
|
||||
<li v-if="view == 'inventur'">
|
||||
<a href="#" class="btn-floating tooltipped" data-position="left" @click="exportInventur" data-tooltip="Inventur exportieren"><i class="material-icons">export</i></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="js/lib/jquery.min.js"></script>
|
||||
<script src="js/lib/materialize.min.js"></script>
|
||||
<script src="js/lib/moment-with-locales.min.js"></script>
|
||||
<script src="js/lib/accounting.min.js"></script>
|
||||
<script src="js/lib/vue-dev.js"></script>
|
||||
<script src="js/lib/vue-resource.min.js"></script>
|
||||
<script src="js/site.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/model/article.js"></script>
|
||||
<script src="js/model/inventory_article.js"></script>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('serviceWorker.js')
|
||||
.then(function(registration) {
|
||||
console.log("[ServiceWorker] registration successful with scope: ", registration.scope);
|
||||
}).catch(function(err){
|
||||
console.error("[ServiceWorker] registration failed: ", err);
|
||||
})
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
97
js/app.js
97
js/app.js
@ -1,97 +0,0 @@
|
||||
Vue.filter('currency', function(money){
|
||||
return accounting.formatMoney(money);
|
||||
});
|
||||
Vue.filter('number', function(number, precision = 2){
|
||||
|
||||
return accounting.formatNumber(number, precision);
|
||||
});
|
||||
|
||||
var app = new Vue({
|
||||
el: "#app",
|
||||
data : {
|
||||
articles : [],
|
||||
inventory : {
|
||||
ug : [],
|
||||
mob : [],
|
||||
stud : []
|
||||
},
|
||||
bon: [],
|
||||
view: 'inventur',
|
||||
ready: false,
|
||||
},
|
||||
computed: {
|
||||
sales_ug: function (){
|
||||
var total_sales = this.inventory.ug.reduce(function(total, item ) {
|
||||
return total + item.Sale;
|
||||
}, 0);
|
||||
return total_sales;
|
||||
},
|
||||
bon_price: function() {
|
||||
var total = this.bon.reduce(function(total, item) {
|
||||
return total + item.count * item.price;
|
||||
}, 0);
|
||||
return accounting.formatMoney(total);
|
||||
},
|
||||
bon_sum: function() {
|
||||
var total = this.bon.reduce(function(total, item) {
|
||||
return total + item.count;
|
||||
}, 0);
|
||||
return total;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addArticle: function() {
|
||||
this.articles.push(new Article());
|
||||
},
|
||||
storeArticles: function() {
|
||||
this.$http.post('./backend?controller=Article&action=store', JSON.stringify(this.articles))
|
||||
.then(response => {
|
||||
M.toast({html: response.body});
|
||||
})
|
||||
},
|
||||
loadArticles: function() {
|
||||
this.$http.get('./data/articles.json')
|
||||
.then(response => { return response.json();})
|
||||
.then(json => {
|
||||
json.forEach(element => {
|
||||
this.articles.push(Article.thaw(element));
|
||||
});
|
||||
}).then( x => {
|
||||
M.toast({ html: 'Artikel wurden geladen.'});
|
||||
}).then( x => {
|
||||
this.articles.forEach(a => {
|
||||
ia = new InventoryArticle();
|
||||
ia.article = a;
|
||||
this.inventory.ug.push(ia);
|
||||
this.bon.push({count: 0, name: a.name, short: a.short, price: a.portion.price});
|
||||
});
|
||||
}).then( x => {
|
||||
M.updateTextFields();
|
||||
this.ready = true;
|
||||
});
|
||||
},
|
||||
resetInventur: function() {
|
||||
|
||||
},
|
||||
exportInventur: function() {
|
||||
this.$http.post('./backend?controller=Inventur&action=export', JSON.stringify(this.inventory.ug))
|
||||
.then(response => {
|
||||
M.toast({html: response.body});
|
||||
})
|
||||
},
|
||||
resetBon: function(article) {
|
||||
this.bon.forEach(function (item){
|
||||
item.count = 0;
|
||||
});
|
||||
},
|
||||
bonned: function (items) {
|
||||
return items.filter(function (item) {
|
||||
return item.count > 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
created: function(){
|
||||
this.loadArticles();
|
||||
}
|
||||
|
||||
});
|
||||
4
js/lib/accounting.min.js
vendored
4
js/lib/accounting.min.js
vendored
@ -1,4 +0,0 @@
|
||||
/*!
|
||||
* accounting.js v0.4.2, copyright 2014 Open Exchange Rates, MIT license, http://openexchangerates.github.io/accounting.js
|
||||
*/
|
||||
(function(p,z){function q(a){return!!(""===a||a&&a.charCodeAt&&a.substr)}function m(a){return u?u(a):"[object Array]"===v.call(a)}function r(a){return"[object Object]"===v.call(a)}function s(a,b){var d,a=a||{},b=b||{};for(d in b)b.hasOwnProperty(d)&&null==a[d]&&(a[d]=b[d]);return a}function j(a,b,d){var c=[],e,h;if(!a)return c;if(w&&a.map===w)return a.map(b,d);for(e=0,h=a.length;e<h;e++)c[e]=b.call(d,a[e],e,a);return c}function n(a,b){a=Math.round(Math.abs(a));return isNaN(a)?b:a}function x(a){var b=c.settings.currency.format;"function"===typeof a&&(a=a());return q(a)&&a.match("%v")?{pos:a,neg:a.replace("-","").replace("%v","-%v"),zero:a}:!a||!a.pos||!a.pos.match("%v")?!q(b)?b:c.settings.currency.format={pos:b,neg:b.replace("%v","-%v"),zero:b}:a}var c={version:"0.4.1",settings:{currency:{symbol:"$",format:"%s%v",decimal:".",thousand:",",precision:2,grouping:3},number:{precision:0,grouping:3,thousand:",",decimal:"."}}},w=Array.prototype.map,u=Array.isArray,v=Object.prototype.toString,o=c.unformat=c.parse=function(a,b){if(m(a))return j(a,function(a){return o(a,b)});a=a||0;if("number"===typeof a)return a;var b=b||".",c=RegExp("[^0-9-"+b+"]",["g"]),c=parseFloat((""+a).replace(/\((.*)\)/,"-$1").replace(c,"").replace(b,"."));return!isNaN(c)?c:0},y=c.toFixed=function(a,b){var b=n(b,c.settings.number.precision),d=Math.pow(10,b);return(Math.round(c.unformat(a)*d)/d).toFixed(b)},t=c.formatNumber=c.format=function(a,b,d,i){if(m(a))return j(a,function(a){return t(a,b,d,i)});var a=o(a),e=s(r(b)?b:{precision:b,thousand:d,decimal:i},c.settings.number),h=n(e.precision),f=0>a?"-":"",g=parseInt(y(Math.abs(a||0),h),10)+"",l=3<g.length?g.length%3:0;return f+(l?g.substr(0,l)+e.thousand:"")+g.substr(l).replace(/(\d{3})(?=\d)/g,"$1"+e.thousand)+(h?e.decimal+y(Math.abs(a),h).split(".")[1]:"")},A=c.formatMoney=function(a,b,d,i,e,h){if(m(a))return j(a,function(a){return A(a,b,d,i,e,h)});var a=o(a),f=s(r(b)?b:{symbol:b,precision:d,thousand:i,decimal:e,format:h},c.settings.currency),g=x(f.format);return(0<a?g.pos:0>a?g.neg:g.zero).replace("%s",f.symbol).replace("%v",t(Math.abs(a),n(f.precision),f.thousand,f.decimal))};c.formatColumn=function(a,b,d,i,e,h){if(!a)return[];var f=s(r(b)?b:{symbol:b,precision:d,thousand:i,decimal:e,format:h},c.settings.currency),g=x(f.format),l=g.pos.indexOf("%s")<g.pos.indexOf("%v")?!0:!1,k=0,a=j(a,function(a){if(m(a))return c.formatColumn(a,f);a=o(a);a=(0<a?g.pos:0>a?g.neg:g.zero).replace("%s",f.symbol).replace("%v",t(Math.abs(a),n(f.precision),f.thousand,f.decimal));if(a.length>k)k=a.length;return a});return j(a,function(a){return q(a)&&a.length<k?l?a.replace(f.symbol,f.symbol+Array(k-a.length+1).join(" ")):Array(k-a.length+1).join(" ")+a:a})};if("undefined"!==typeof exports){if("undefined"!==typeof module&&module.exports)exports=module.exports=c;exports.accounting=c}else"function"===typeof define&&define.amd?define([],function(){return c}):(c.noConflict=function(a){return function(){p.accounting=a;c.noConflict=z;return c}}(p.accounting),p.accounting=c)})(this);
|
||||
4
js/lib/jquery.min.js
vendored
4
js/lib/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
6
js/lib/materialize.min.js
vendored
6
js/lib/materialize.min.js
vendored
File diff suppressed because one or more lines are too long
1216
js/lib/moment-with-locales.min.js
vendored
1216
js/lib/moment-with-locales.min.js
vendored
File diff suppressed because it is too large
Load Diff
10798
js/lib/vue-dev.js
10798
js/lib/vue-dev.js
File diff suppressed because it is too large
Load Diff
7
js/lib/vue-resource.min.js
vendored
7
js/lib/vue-resource.min.js
vendored
File diff suppressed because one or more lines are too long
6
js/lib/vue.min.js
vendored
6
js/lib/vue.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,79 +0,0 @@
|
||||
function Article() {
|
||||
this.name = "";
|
||||
this.short = "";
|
||||
this.dimension = "";
|
||||
this.content = {
|
||||
size : 0,
|
||||
price: 0
|
||||
};
|
||||
this.portion = {
|
||||
size : 0,
|
||||
price : 0,
|
||||
type : ""
|
||||
};
|
||||
}
|
||||
|
||||
Article.prototype = {
|
||||
get Name() {
|
||||
return this.name;
|
||||
},
|
||||
set Name(value) {
|
||||
this.name = value;
|
||||
},
|
||||
get Short() {
|
||||
return this.short;
|
||||
},
|
||||
set Short(value) {
|
||||
this.short = value;
|
||||
},
|
||||
get ContentSize() {
|
||||
return this.content.size;
|
||||
},
|
||||
set ContentSize(value) {
|
||||
this.content.size = value;
|
||||
},
|
||||
get Dimension() {
|
||||
return this.dimension;
|
||||
},
|
||||
set Dimension(value) {
|
||||
this.dimension = value;
|
||||
},
|
||||
get PortionSize() {
|
||||
return this.portion.size;
|
||||
},
|
||||
set PortionSize(value) {
|
||||
this.portion.size = value;
|
||||
},
|
||||
get PortionType() {
|
||||
return this.portion.type;
|
||||
},
|
||||
set PortionType(value) {
|
||||
this.portion.type = value;
|
||||
},
|
||||
get PortionPrice() {
|
||||
return this.portion.price;
|
||||
},
|
||||
set PortionPrice(value) {
|
||||
this.portion.price = value;
|
||||
},
|
||||
get ContentPrice() {
|
||||
return this.Portions * this.portion.price;
|
||||
},
|
||||
get Portions() {
|
||||
return this.content.size / (this.portion.size || 1);
|
||||
}
|
||||
};
|
||||
|
||||
Article.thaw = function (json) {
|
||||
var article = new Article();
|
||||
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;
|
||||
|
||||
return article;
|
||||
};
|
||||
@ -1,66 +0,0 @@
|
||||
function InventoryArticle(){
|
||||
this.article = new Article();
|
||||
this.end = 0,
|
||||
this.start = 0;
|
||||
this.fetched = 0;
|
||||
this.lost = 0;
|
||||
}
|
||||
|
||||
InventoryArticle.prototype = {
|
||||
get StartPortions() {
|
||||
countFull = Math.floor(this.start);
|
||||
fullPortions = countFull * this.article.Portions;
|
||||
rest = this.start - countFull;
|
||||
restPortions = rest / this.article.PortionSize;
|
||||
|
||||
return fullPortions + restPortions;
|
||||
},
|
||||
get FetchedPortions() {
|
||||
countFull = Math.floor(this.fetched);
|
||||
fullPortions = countFull * this.article.Portions;
|
||||
rest = this.fetched - countFull;
|
||||
restPortions = rest / this.article.PortionSize;
|
||||
|
||||
return fullPortions + restPortions;
|
||||
},
|
||||
get EndPortions() {
|
||||
countFull = Math.floor(this.end);
|
||||
fullPortions = countFull * this.article.Portions;
|
||||
rest = this.end - countFull;
|
||||
restPortions = rest / this.article.PortionSize;
|
||||
|
||||
return fullPortions + restPortions;
|
||||
},
|
||||
get LostPortions() {
|
||||
countFull = Math.floor(this.lost);
|
||||
fullPortions = countFull * this.article.Portions;
|
||||
rest = this.lost - countFull;
|
||||
restPortions = rest / this.article.PortionSize;
|
||||
|
||||
return fullPortions + restPortions;
|
||||
},
|
||||
get Sold() {
|
||||
adds = this.StartPortions + this.FetchedPortions;
|
||||
subs = this.EndPortions + this.LostPortions;
|
||||
return adds - subs;
|
||||
},
|
||||
get Sale() {
|
||||
return this.Sold * this.article.PortionPrice;
|
||||
},
|
||||
get StepSize() {
|
||||
singlePack = this.article.Portions == 1;
|
||||
return singlePack ? 1 : 0.05;
|
||||
},
|
||||
get PortionPrecision() {
|
||||
singlePack = this.article.Portions == 1;
|
||||
return singlePack ? 0 : 2;
|
||||
}
|
||||
};
|
||||
|
||||
InventoryArticle.thaw = function(json){
|
||||
this.name = json.name;
|
||||
this.start = json.start;
|
||||
this.fetched = json.fetched;
|
||||
this.end = json.end;
|
||||
this.lost = json.lost;
|
||||
};
|
||||
21
js/site.js
21
js/site.js
@ -1,21 +0,0 @@
|
||||
accounting.settings = {
|
||||
currency: {
|
||||
symbol: "€",
|
||||
format: "%v %s",
|
||||
decimal: ",",
|
||||
thousand: ".",
|
||||
precision: 2
|
||||
},
|
||||
number: {
|
||||
precision : 2, // default precision on numbers is 0
|
||||
thousand: ".",
|
||||
decimal : ","
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function(){
|
||||
$('.tabs').tabs();
|
||||
$('.fixed-action-btn').floatingActionButton();
|
||||
$('.tooltipped').tooltip();
|
||||
});
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*"
|
||||
]
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
$database = [
|
||||
'server' => 'localhost',
|
||||
'user' => 'inventur',
|
||||
'password' => '*inv2018#',
|
||||
'db' => 'inventur'
|
||||
];
|
||||
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
$database = [
|
||||
'server' => 'localhost',
|
||||
'user' => 'inventur',
|
||||
'password' => '*inv2018#',
|
||||
'db' => 'inventur'
|
||||
];
|
||||
9646
package-lock.json
generated
Normal file
9646
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
@ -1,11 +1,51 @@
|
||||
{
|
||||
"name": "inventur",
|
||||
"name": "inventar",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.html",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 0"
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"test": "tests"
|
||||
},
|
||||
"author": "chrosey",
|
||||
"license": "ISC"
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "encore production --progress",
|
||||
"watch": "encore dev --watch",
|
||||
"dev": "encore dev",
|
||||
"csfix": "eslint app/js --ext .js,.vue, --fix"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.chrosey.de/chrosey/inventur.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"core-js": "^3.6.1",
|
||||
"gsap": "^3.0.4",
|
||||
"materialize-css": "^1.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.7",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"@symfony/webpack-encore": "^0.28.2",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"css-loader": "^2.1.1",
|
||||
"dotenv": "^8.2.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-loader": "^3.0.3",
|
||||
"eslint-plugin-vue": "^6.1.2",
|
||||
"vue-loader": "^15.8.3",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack-notifier": "^1.8.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 0.5%",
|
||||
"last 2 versions",
|
||||
"Firefox ESR",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
|
||||
46
phpcs.xml.dist
Normal file
46
phpcs.xml.dist
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset>
|
||||
<arg name="basepath" value="."/>
|
||||
<arg name="extensions" value="php"/>
|
||||
<arg name="parallel" value="16"/>
|
||||
<arg name="colors"/>
|
||||
|
||||
<!-- Ignore warnings, show progress of the run and show sniff names -->
|
||||
<arg value="nps"/>
|
||||
|
||||
<!-- Directories to be checked -->
|
||||
<file>src</file>
|
||||
<file>tests</file>
|
||||
<file>public</file>
|
||||
|
||||
<exclude-pattern>tests/dependencies/*</exclude-pattern>
|
||||
|
||||
<!-- Include full Doctrine Coding Standard -->
|
||||
<rule ref="Doctrine">
|
||||
<exclude name="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix"/>
|
||||
<exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming.SuperfluousSuffix"/>
|
||||
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix"/>
|
||||
<exclude name="Squiz.Commenting.FunctionComment.InvalidNoReturn" />
|
||||
<exclude name="Generic.Formatting.MultipleStatementAlignment" />
|
||||
</rule>
|
||||
|
||||
<!-- Do not align assignments -->
|
||||
<rule ref="Generic.Formatting.MultipleStatementAlignment">
|
||||
<severity>0</severity>
|
||||
</rule>
|
||||
|
||||
<!-- Do not align comments -->
|
||||
<rule ref="Squiz.Commenting.FunctionComment.SpacingAfterParamName">
|
||||
<severity>0</severity>
|
||||
</rule>
|
||||
<rule ref="Squiz.Commenting.FunctionComment.SpacingAfterParamType">
|
||||
<severity>0</severity>
|
||||
</rule>
|
||||
|
||||
<!-- Require no space before colon in return types -->
|
||||
<rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHintSpacing">
|
||||
<properties>
|
||||
<property name="spacesCountBeforeColon" value="0"/>
|
||||
</properties>
|
||||
</rule>
|
||||
</ruleset>
|
||||
7
phpstan.neon
Normal file
7
phpstan.neon
Normal file
@ -0,0 +1,7 @@
|
||||
includes:
|
||||
- vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon
|
||||
- vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon
|
||||
parameters:
|
||||
excludes_analyse:
|
||||
- %currentWorkingDirectory%/src/Migrations/*.php
|
||||
- %currentWorkingDirectory%/src/Kernel.php
|
||||
49
public/build/app.css
Normal file
49
public/build/app.css
Normal file
@ -0,0 +1,49 @@
|
||||
.tweened {
|
||||
display: flex;
|
||||
align-content: end;
|
||||
justify-content: end;
|
||||
}
|
||||
.tweened>div:first-child {
|
||||
text-align: right;
|
||||
min-width: 1.5rem;
|
||||
}
|
||||
.tweened>div:nth-child(0n+2) {
|
||||
min-width: 1.5rem;
|
||||
}
|
||||
.tweened>div:nth-child(0n+3) {
|
||||
min-width: 2rem;
|
||||
}
|
||||
.inventory-item {
|
||||
transition: all .2s;
|
||||
}
|
||||
|
||||
.inventory-item>* {
|
||||
transition: all .2s .2s;
|
||||
}
|
||||
table.condensed td,
|
||||
table.condensed th {
|
||||
padding: 5px 5px;
|
||||
}
|
||||
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
.border-right {
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.border-diagram {
|
||||
padding: 0;
|
||||
}
|
||||
.border-diagram>div {
|
||||
margin-bottom: -3px;
|
||||
border-radius: 0;
|
||||
border-bottom: 3px solid transparent;
|
||||
}
|
||||
|
||||
.active {
|
||||
border-color: rgb(38, 166, 154);
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vVHdlZW5lZE51bWJlci5jc3MiLCJ3ZWJwYWNrOi8vL0ludmVudG9yeUl0ZW0uY3NzIiwid2VicGFjazovLy9JbnZlbnRvcnlUYWJsZS5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7SUFDSSxhQUFhO0lBQ2Isa0JBQWtCO0lBQ2xCLG9CQUFvQjtBQUN4QjtBQUNBO0lBQ0ksaUJBQWlCO0lBQ2pCLGlCQUFpQjtBQUNyQjtBQUNBO0lBQ0ksaUJBQWlCO0FBQ3JCO0FBQ0E7SUFDSSxlQUFlO0FBQ25CLEM7QUNkQTtJQUNJLG1CQUFtQjtBQUN2Qjs7QUFFQTtJQUNJLHVCQUF1QjtBQUMzQixDO0FDTkE7O0lBRUksZ0JBQWdCO0FBQ3BCOztBQUVBO0lBQ0ksNENBQTRDO0FBQ2hEO0FBQ0E7SUFDSSwyQ0FBMkM7QUFDL0M7O0FBRUE7SUFDSSxVQUFVO0FBQ2Q7QUFDQTtJQUNJLG1CQUFtQjtJQUNuQixnQkFBZ0I7SUFDaEIsb0NBQW9DO0FBQ3hDOztBQUVBO0lBQ0ksK0JBQStCO0lBQy9CLGlCQUFpQjtBQUNyQixDIiwiZmlsZSI6ImFwcC5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIudHdlZW5lZCB7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBhbGlnbi1jb250ZW50OiBlbmQ7XG4gICAganVzdGlmeS1jb250ZW50OiBlbmQ7XG59XG4udHdlZW5lZD5kaXY6Zmlyc3QtY2hpbGQge1xuICAgIHRleHQtYWxpZ246IHJpZ2h0O1xuICAgIG1pbi13aWR0aDogMS41cmVtO1xufVxuLnR3ZWVuZWQ+ZGl2Om50aC1jaGlsZCgwbisyKSB7XG4gICAgbWluLXdpZHRoOiAxLjVyZW07XG59XG4udHdlZW5lZD5kaXY6bnRoLWNoaWxkKDBuKzMpIHtcbiAgICBtaW4td2lkdGg6IDJyZW07XG59IiwiLmludmVudG9yeS1pdGVtIHtcbiAgICB0cmFuc2l0aW9uOiBhbGwgLjJzO1xufVxuXG4uaW52ZW50b3J5LWl0ZW0+KiB7XG4gICAgdHJhbnNpdGlvbjogYWxsIC4ycyAuMnM7XG59IiwidGFibGUuY29uZGVuc2VkIHRkLFxudGFibGUuY29uZGVuc2VkIHRoIHtcbiAgICBwYWRkaW5nOiA1cHggNXB4O1xufVxuXG4uYm9yZGVyLWJvdHRvbSB7XG4gICAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkIHJnYmEoMCwgMCwgMCwgMC4xMik7XG59XG4uYm9yZGVyLXJpZ2h0IHtcbiAgICBib3JkZXItcmlnaHQ6IDFweCBzb2xpZCByZ2JhKDAsIDAsIDAsIDAuMTIpO1xufVxuXG4uYm9yZGVyLWRpYWdyYW0ge1xuICAgIHBhZGRpbmc6IDA7XG59XG4uYm9yZGVyLWRpYWdyYW0+ZGl2IHtcbiAgICBtYXJnaW4tYm90dG9tOiAtM3B4OyBcbiAgICBib3JkZXItcmFkaXVzOiAwOyBcbiAgICBib3JkZXItYm90dG9tOiAzcHggc29saWQgdHJhbnNwYXJlbnQ7XG59XG5cbi5hY3RpdmUge1xuICAgIGJvcmRlci1jb2xvcjogcmdiKDM4LCAxNjYsIDE1NCk7XG4gICAgYm9yZGVyLXdpZHRoOiAycHg7XG59Il0sInNvdXJjZVJvb3QiOiIifQ==*/
|
||||
2874
public/build/app.js
Normal file
2874
public/build/app.js
Normal file
File diff suppressed because one or more lines are too long
14
public/build/entrypoints.json
Normal file
14
public/build/entrypoints.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"entrypoints": {
|
||||
"app": {
|
||||
"css": [
|
||||
"/build/vendors~app.css",
|
||||
"/build/app.css"
|
||||
],
|
||||
"js": [
|
||||
"/build/vendors~app.js",
|
||||
"/build/app.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
6
public/build/manifest.json
Normal file
6
public/build/manifest.json
Normal file
@ -0,0 +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"
|
||||
}
|
||||
16
public/build/vendors~app.css
Normal file
16
public/build/vendors~app.css
Normal file
File diff suppressed because one or more lines are too long
35319
public/build/vendors~app.js
Normal file
35319
public/build/vendors~app.js
Normal file
File diff suppressed because one or more lines are too long
6
public/index.php
Normal file
6
public/index.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$app = require_once __DIR__.'/../src/bootstrap.php';
|
||||
$app->run();
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
var APP_SHELL_VERSION = 2;
|
||||
/*
|
||||
var APP_SHELL_VERSION = 3;
|
||||
var APP_SHELL_CACHE = `inventur_shell-v${APP_SHELL_VERSION}`;
|
||||
var APP_SHELL_URLS = [
|
||||
'./',
|
||||
@ -8,7 +8,7 @@ var APP_SHELL_URLS = [
|
||||
'js/lib/accounting.min.js',
|
||||
'js/lib/moment-with-locales.min.js',
|
||||
'js/lib/jquery.min.js',
|
||||
'js/lib/vue-dev.js',
|
||||
'js/lib/vue.js',
|
||||
'js/lib/vue.min.js',
|
||||
'js/lib/vue-resource.min.js'
|
||||
];
|
||||
@ -48,3 +48,4 @@ self.addEventListener('fetch', function(event){
|
||||
);
|
||||
});
|
||||
|
||||
*/
|
||||
48
src/bootstrap.php
Normal file
48
src/bootstrap.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use DI\Container;
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Slim\Factory\AppFactory;
|
||||
use Slim\Views\Twig;
|
||||
use Slim\Views\TwigMiddleware;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
require __DIR__ . '/env.php';
|
||||
|
||||
$container = new Container();
|
||||
AppFactory::setContainer($container);
|
||||
|
||||
$container->set('view', static function () {
|
||||
return Twig::create(__DIR__ . '/../templates', []);
|
||||
});
|
||||
|
||||
$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'),
|
||||
]);
|
||||
|
||||
$capsule->setAsGlobal();
|
||||
$capsule->bootEloquent();
|
||||
|
||||
return $capsule;
|
||||
});
|
||||
|
||||
$container->set('upload_directory', __DIR__ . '/../data/uploads');
|
||||
|
||||
$app = AppFactory::create();
|
||||
|
||||
$app->add(TwigMiddleware::createFromContainer($app));
|
||||
|
||||
require 'routes.php';
|
||||
|
||||
return $app;
|
||||
3
src/container/database.php
Normal file
3
src/container/database.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
7
src/container/view.php
Normal file
7
src/container/view.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$container->set('view', static function () {
|
||||
return Twig::create(__DIR__ . '/../templates', []);
|
||||
});
|
||||
8
src/env.php
Normal file
8
src/env.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
|
||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
|
||||
$dotenv->load();
|
||||
70
src/routes.php
Normal file
70
src/routes.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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;
|
||||
|
||||
$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'];
|
||||
|
||||
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->post('/inventur', function (Request $request, Response $response, $args) {
|
||||
$directory = $this->get('upload_directory');
|
||||
|
||||
$uploadedFile = $request->getUploadedFiles()['file'];
|
||||
|
||||
if ($uploadedFile->getError() === UPLOAD_ERR_OK) {
|
||||
$filename = moveUploadedFile($directory, $uploadedFile);
|
||||
$response->getBody()->write('uploaded ' . $filename . '<br/>');
|
||||
}
|
||||
|
||||
return $response;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Move Uploaded File to Target Destination
|
||||
*
|
||||
* Create random hexName
|
||||
*
|
||||
* @param String $directory Target Direcotry
|
||||
* @param UploadedFile $uploadedFile the File that was uploaded
|
||||
**/
|
||||
function moveUploadedFile(string $directory, UploadedFile $uploadedFile): string
|
||||
{
|
||||
$extension = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION);
|
||||
$basename = bin2hex(random_bytes(8)); // see http://php.net/manual/en/function.random-bytes.php
|
||||
$filename = sprintf('%s.%0.8s', $basename, $extension);
|
||||
|
||||
$uploadedFile->moveTo($directory . DIRECTORY_SEPARATOR . $filename);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
21
templates/frontend.html
Normal file
21
templates/frontend.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta lang="de">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta name="theme-color" content="#004d40">
|
||||
<title>Inventur</title>
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link rel="stylesheet" href="build/vendors~app.css">
|
||||
<link rel="stylesheet" href="build/app.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/build/vendors~app.js"></script>
|
||||
<script src="/build/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
93
webpack.config.js
Normal file
93
webpack.config.js
Normal file
@ -0,0 +1,93 @@
|
||||
let Encore = require('@symfony/webpack-encore');
|
||||
|
||||
// Manually configure the runtime environment if not already configured yet by the "encore" command.
|
||||
// It's useful when you use tools that rely on webpack.config.js file.
|
||||
if (!Encore.isRuntimeEnvironmentConfigured()) {
|
||||
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
|
||||
}
|
||||
|
||||
Encore
|
||||
// directory where compiled assets will be stored
|
||||
.setOutputPath('public/build/')
|
||||
// public path used by the web server to access the output path
|
||||
.setPublicPath('/build')
|
||||
// only needed for CDN's or sub-directory deploy
|
||||
//.setManifestKeyPrefix('build/')
|
||||
|
||||
/*
|
||||
* ENTRY CONFIG
|
||||
*
|
||||
* Add 1 entry for each "page" of your app
|
||||
* (including one that's included on every page - e.g. "app")
|
||||
*
|
||||
* Each entry will result in one JavaScript file (e.g. app.js)
|
||||
* and one CSS file (e.g. app.css) if you JavaScript imports CSS.
|
||||
*/
|
||||
.addEntry('app', './app/js/index.js')
|
||||
//.addEntry('page1', './assets/js/page1.js')
|
||||
//.addEntry('page2', './assets/js/page2.js')
|
||||
|
||||
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
|
||||
.splitEntryChunks()
|
||||
|
||||
// will require an extra script tag for runtime.js
|
||||
// but, you probably want this, unless you're building a single-page app
|
||||
//.enableSingleRuntimeChunk()
|
||||
.disableSingleRuntimeChunk()
|
||||
|
||||
/*
|
||||
* FEATURE CONFIG
|
||||
*
|
||||
* Enable & configure other features below. For a full
|
||||
* list of features, see:
|
||||
* https://symfony.com/doc/current/frontend.html#adding-more-features
|
||||
*/
|
||||
.cleanupOutputBeforeBuild()
|
||||
.enableBuildNotifications()
|
||||
.enableSourceMaps(!Encore.isProduction())
|
||||
// enables hashed filenames (e.g. app.abc123.css)
|
||||
.enableVersioning(Encore.isProduction())
|
||||
|
||||
// enables @babel/preset-env polyfills
|
||||
.configureBabel((babelConfig) => {
|
||||
babelConfig.plugins.push('@babel/plugin-transform-runtime');
|
||||
}, {
|
||||
useBuiltIns: 'usage',
|
||||
corejs: 3
|
||||
})
|
||||
|
||||
// enables Vue.js support
|
||||
.enableVueLoader()
|
||||
|
||||
// enables Sass/SCSS support
|
||||
//.enableSassLoader()
|
||||
|
||||
// uncomment if you use TypeScript
|
||||
//.enableTypeScriptLoader()
|
||||
|
||||
// uncomment to get integrity="..." attributes on your script & link tags
|
||||
// requires WebpackEncoreBundle 1.4 or higher
|
||||
//.enableIntegrityHashes()
|
||||
|
||||
// uncomment if you're having problems with a jQuery plugin
|
||||
//.autoProvidejQuery()
|
||||
|
||||
// uncomment if you use API Platform Admin (composer req api-admin)
|
||||
//.enableReactPreset()
|
||||
//.addEntry('admin', './assets/js/admin.js')
|
||||
|
||||
// enable ESLint
|
||||
.addLoader({
|
||||
enforce: 'pre',
|
||||
test: /\.(js|vue)$/,
|
||||
loader: 'eslint-loader',
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
fix: true,
|
||||
emitError: true,
|
||||
emitWarning: true,
|
||||
},
|
||||
})
|
||||
;
|
||||
|
||||
module.exports = Encore.getWebpackConfig();
|
||||
Loading…
x
Reference in New Issue
Block a user