Add very basic mobile app support for Theo
much more to come yet, but should be enough to launch a demo
This commit is contained in:
		
							parent
							
								
									774221420c
								
							
						
					
					
						commit
						8c738d3ee8
					
				
					 27 changed files with 12577 additions and 1 deletions
				
			
		
							
								
								
									
										3
									
								
								mobile/.browserslistrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								mobile/.browserslistrc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | > 1% | ||||||
|  | last 2 versions | ||||||
|  | not dead | ||||||
							
								
								
									
										29
									
								
								mobile/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								mobile/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | .DS_Store | ||||||
|  | node_modules | ||||||
|  | /dist | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # local env files | ||||||
|  | .env.local | ||||||
|  | .env.*.local | ||||||
|  | 
 | ||||||
|  | # Log files | ||||||
|  | npm-debug.log* | ||||||
|  | yarn-debug.log* | ||||||
|  | yarn-error.log* | ||||||
|  | pnpm-debug.log* | ||||||
|  | 
 | ||||||
|  | # Editor directories and files | ||||||
|  | .idea | ||||||
|  | .vscode | ||||||
|  | *.suo | ||||||
|  | *.ntvs* | ||||||
|  | *.njsproj | ||||||
|  | *.sln | ||||||
|  | *.sw? | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # custom entries follow | ||||||
|  | 
 | ||||||
|  | vue.config.js | ||||||
|  | src/appsettings.js | ||||||
							
								
								
									
										19
									
								
								mobile/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								mobile/README.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | # mobile | ||||||
|  | 
 | ||||||
|  | ## Project setup | ||||||
|  | ``` | ||||||
|  | npm install | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Compiles and hot-reloads for development | ||||||
|  | ``` | ||||||
|  | npm run serve | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Compiles and minifies for production | ||||||
|  | ``` | ||||||
|  | npm run build | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Customize configuration | ||||||
|  | See [Configuration Reference](https://cli.vuejs.org/config/). | ||||||
							
								
								
									
										5
									
								
								mobile/babel.config.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								mobile/babel.config.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | module.exports = { | ||||||
|  |   presets: [ | ||||||
|  |     '@vue/cli-plugin-babel/preset' | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										11346
									
								
								mobile/package-lock.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										11346
									
								
								mobile/package-lock.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										26
									
								
								mobile/package.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								mobile/package.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | { | ||||||
|  |   "name": "theo-mobile", | ||||||
|  |   "version": "0.1.0", | ||||||
|  |   "private": true, | ||||||
|  |   "scripts": { | ||||||
|  |     "serve": "vue-cli-service serve", | ||||||
|  |     "build": "vue-cli-service build" | ||||||
|  |   }, | ||||||
|  |   "dependencies": { | ||||||
|  |     "buefy": "^0.9.4", | ||||||
|  |     "byjove": "^0.1.9", | ||||||
|  |     "core-js": "^3.6.5", | ||||||
|  |     "js-cookie": "^2.2.1", | ||||||
|  |     "vue": "^2.6.11", | ||||||
|  |     "vue-resource": "^1.5.1", | ||||||
|  |     "vue-router": "^3.2.0", | ||||||
|  |     "vuex": "^3.4.0" | ||||||
|  |   }, | ||||||
|  |   "devDependencies": { | ||||||
|  |     "@vue/cli-plugin-babel": "~4.5.0", | ||||||
|  |     "@vue/cli-plugin-router": "~4.5.0", | ||||||
|  |     "@vue/cli-plugin-vuex": "~4.5.0", | ||||||
|  |     "@vue/cli-service": "~4.5.0", | ||||||
|  |     "vue-template-compiler": "^2.6.11" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								mobile/public/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mobile/public/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 4.2 KiB | 
							
								
								
									
										18
									
								
								mobile/public/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								mobile/public/index.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang=""> | ||||||
|  |   <head> | ||||||
|  |     <meta charset="utf-8"> | ||||||
|  |     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||||
|  |     <meta name="viewport" content="width=device-width,initial-scale=1.0"> | ||||||
|  |     <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | ||||||
|  |     <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.2/css/all.css"> | ||||||
|  |     <title>Theo Mobile</title> | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  |     <noscript> | ||||||
|  |       <strong>We're sorry but Theo Mobile doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | ||||||
|  |     </noscript> | ||||||
|  |     <div id="app"></div> | ||||||
|  |     <!-- built files will be auto injected --> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										122
									
								
								mobile/src/App.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								mobile/src/App.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,122 @@ | ||||||
|  | <template> | ||||||
|  |   <div id="app"> | ||||||
|  |     <byjove-app :appsettings="appsettings"> | ||||||
|  |       <app-nav></app-nav> | ||||||
|  |       <template v-slot:footer> | ||||||
|  |         <router-link to="/about">{{ appsettings.appTitle}} {{ appsettings.version }}</router-link> | ||||||
|  |         <div> | ||||||
|  |           <br /> | ||||||
|  |           <a href="/">View Desktop Site</a> | ||||||
|  |         </div> | ||||||
|  |       </template> | ||||||
|  |     </byjove-app> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import {ByjoveApp} from 'byjove' | ||||||
|  | import AppNav from './components/AppNav.vue' | ||||||
|  | import appsettings from './appsettings' | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     name: 'app', | ||||||
|  |     components: { | ||||||
|  |         ByjoveApp, | ||||||
|  |         AppNav, | ||||||
|  |     }, | ||||||
|  |     data() { | ||||||
|  |         return { | ||||||
|  |             appsettings: appsettings, | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  | 
 | ||||||
|  | /****************************** | ||||||
|  |  * main app layout | ||||||
|  | ******************************/ | ||||||
|  | 
 | ||||||
|  | #app { | ||||||
|  |     font-family: 'Avenir', Helvetica, Arial, sans-serif; | ||||||
|  |     -webkit-font-smoothing: antialiased; | ||||||
|  |     -moz-osx-font-smoothing: grayscale; | ||||||
|  |     color: #2c3e50; | ||||||
|  | } | ||||||
|  | .main-container { | ||||||
|  |     display: flex; | ||||||
|  |     min-height: 100vh; | ||||||
|  |     flex-direction: column; | ||||||
|  | } | ||||||
|  | .page-content { | ||||||
|  |     flex: 1; | ||||||
|  |     padding: 0.5rem 1rem 0.5rem 0.5rem; | ||||||
|  | } | ||||||
|  | .footer { | ||||||
|  |     text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************************** | ||||||
|  |  * general stuff | ||||||
|  | ******************************/ | ||||||
|  | 
 | ||||||
|  | h1 { | ||||||
|  |     font-size: 1.5rem; | ||||||
|  |     font-weight: bold; | ||||||
|  |     margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h2 { | ||||||
|  |     font-size: 1.2rem; | ||||||
|  |     font-weight: bold; | ||||||
|  |     margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h3 { | ||||||
|  |     font-size: 1.1rem; | ||||||
|  |     font-weight: bold; | ||||||
|  |     margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************************** | ||||||
|  |  * root-user | ||||||
|  | ******************************/ | ||||||
|  | 
 | ||||||
|  | [role=menuitem].root-user { | ||||||
|  |     background-color: red; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************************** | ||||||
|  |  * model-index | ||||||
|  | ******************************/ | ||||||
|  | 
 | ||||||
|  | .model-index .menu { | ||||||
|  |     margin-bottom: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .model-index .menu-list li { | ||||||
|  |     border-bottom: 1px solid #4a4a4a; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .model-index .menu-list li:last-child { | ||||||
|  |     border: 0px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************************** | ||||||
|  |  * model-crud | ||||||
|  | ******************************/ | ||||||
|  | 
 | ||||||
|  | .model-crud .menu { | ||||||
|  |     margin-bottom: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .model-crud .menu-list li { | ||||||
|  |     border-bottom: 1px solid #4a4a4a; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .model-crud .menu-list li:last-child { | ||||||
|  |     border: 0px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | </style> | ||||||
							
								
								
									
										14
									
								
								mobile/src/appsettings.js.dist
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								mobile/src/appsettings.js.dist
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | // -*- mode: js; -*- | ||||||
|  | 
 | ||||||
|  | import packageData from '../package.json' | ||||||
|  | 
 | ||||||
|  | var appsettings = { | ||||||
|  |     systemTitle: "Theo", | ||||||
|  |     appTitle: "Theo-Mobile", | ||||||
|  |     version: packageData.version, | ||||||
|  |     logo: '/tailbone/img/home_logo.png', | ||||||
|  |     production: false, | ||||||
|  |     watermark: 'url("/tailbone/img/testing.png")', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default appsettings; | ||||||
							
								
								
									
										
											BIN
										
									
								
								mobile/src/assets/logo.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mobile/src/assets/logo.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										40
									
								
								mobile/src/components/AppNav.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								mobile/src/components/AppNav.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | <template> | ||||||
|  |   <byjove-menu :appsettings="appsettings"> | ||||||
|  | 
 | ||||||
|  |     <b-dropdown-item aria-role="menuitem" has-link> | ||||||
|  |       <router-link to="/">Home</router-link> | ||||||
|  |     </b-dropdown-item> | ||||||
|  | 
 | ||||||
|  |     <b-dropdown-item v-if="$hasPerm('ordering.list')" | ||||||
|  |                      aria-role="menuitem" | ||||||
|  |                      has-link> | ||||||
|  |       <router-link to="/ordering/">Ordering</router-link> | ||||||
|  |     </b-dropdown-item> | ||||||
|  | 
 | ||||||
|  |   </byjove-menu> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import {ByjoveMenu} from 'byjove' | ||||||
|  | import appsettings from '../appsettings' | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     name: 'AppNav', | ||||||
|  |     components: { | ||||||
|  |         ByjoveMenu, | ||||||
|  |     }, | ||||||
|  |     data() { | ||||||
|  |         return { | ||||||
|  |             appsettings: appsettings, | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     computed: { | ||||||
|  |         user: function() { | ||||||
|  |             return this.$store.state.user | ||||||
|  |         }, | ||||||
|  |         user_is_root: function() { | ||||||
|  |             return this.$store.state.user_is_root | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										34
									
								
								mobile/src/main.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								mobile/src/main.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | import Vue from 'vue' | ||||||
|  | import App from './App.vue' | ||||||
|  | import router from './router' | ||||||
|  | import store from './store' | ||||||
|  | import vueResource from 'vue-resource' | ||||||
|  | import Cookie from 'js-cookie' | ||||||
|  | import Buefy from 'buefy' | ||||||
|  | import 'buefy/dist/buefy.css' | ||||||
|  | import {ByjovePlugin} from 'byjove' | ||||||
|  | 
 | ||||||
|  | Vue.config.productionTip = false | ||||||
|  | 
 | ||||||
|  | Vue.use(vueResource) | ||||||
|  | 
 | ||||||
|  | // the backend API will set a cookie for XSRF-TOKEN, which we will submit
 | ||||||
|  | // *back* to the backend API whenever we call it from then on.  we set this up
 | ||||||
|  | // globally so none of our API calls actually have to mess with it
 | ||||||
|  | Vue.http.interceptors.push((request, next) => { | ||||||
|  |     request.headers.set('X-XSRF-TOKEN', Cookie.get('XSRF-TOKEN')) | ||||||
|  |     next() | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | Vue.use(Buefy, { | ||||||
|  |     // use FontAwesome icon pack
 | ||||||
|  |     defaultIconPack: 'fas', | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | Vue.use(ByjovePlugin) | ||||||
|  | 
 | ||||||
|  | new Vue({ | ||||||
|  |   router, | ||||||
|  |   store, | ||||||
|  |   render: h => h(App) | ||||||
|  | }).$mount('#app') | ||||||
							
								
								
									
										80
									
								
								mobile/src/router/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								mobile/src/router/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,80 @@ | ||||||
|  | import Vue from 'vue' | ||||||
|  | import VueRouter from 'vue-router' | ||||||
|  | import Home from '../views/Home.vue' | ||||||
|  | import Login from '../views/Login.vue' | ||||||
|  | import {OrderingBatches, OrderingBatch, OrderingBatchRow, OrderingBatchWorksheet} from '../views/ordering' | ||||||
|  | 
 | ||||||
|  | Vue.use(VueRouter) | ||||||
|  | 
 | ||||||
|  | const routes = [ | ||||||
|  |     { | ||||||
|  |         path: '/', | ||||||
|  |         name: 'Home', | ||||||
|  |         component: Home | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         path: '/login', | ||||||
|  |         name: 'login', | ||||||
|  |         component: Login | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         path: '/about', | ||||||
|  |         name: 'About', | ||||||
|  |         // route level code-splitting
 | ||||||
|  |         // this generates a separate chunk (about.[hash].js) for this route
 | ||||||
|  |         // which is lazy-loaded when the route is visited.
 | ||||||
|  |         component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     //////////////////////////////
 | ||||||
|  |     // Ordering
 | ||||||
|  |     //////////////////////////////
 | ||||||
|  |     { | ||||||
|  |         path: '/ordering/', | ||||||
|  |         name: 'ordering', | ||||||
|  |         component: OrderingBatches, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         path: '/ordering/new', | ||||||
|  |         name: 'ordering.new', | ||||||
|  |         component: OrderingBatch, | ||||||
|  |         props: {mode: 'creating'}, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         path: '/ordering/:uuid', | ||||||
|  |         name: 'ordering.view', | ||||||
|  |         component: OrderingBatch, | ||||||
|  |         props: {mode: 'viewing'}, | ||||||
|  |     }, | ||||||
|  |     // {
 | ||||||
|  |     //     path: '/ordering/:uuid/edit',
 | ||||||
|  |     //     name: 'ordering.edit',
 | ||||||
|  |     //     component: OrderingBatch,
 | ||||||
|  |     //     props: {mode: 'editing'},
 | ||||||
|  |     // },
 | ||||||
|  |     { | ||||||
|  |         path: '/ordering/rows/:uuid', | ||||||
|  |         name: 'ordering.rows.view', | ||||||
|  |         component: OrderingBatchRow, | ||||||
|  |         props: {mode: 'viewing'}, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         path: '/ordering/rows/:uuid/edit', | ||||||
|  |         name: 'ordering.rows.edit', | ||||||
|  |         component: OrderingBatchRow, | ||||||
|  |         props: {mode: 'editing'}, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         path: '/ordering/:uuid/worksheet', | ||||||
|  |         name: 'ordering.worksheet', | ||||||
|  |         component: OrderingBatchWorksheet, | ||||||
|  |     }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | const router = new VueRouter({ | ||||||
|  |   mode: 'history', | ||||||
|  |   base: process.env.BASE_URL, | ||||||
|  |   routes | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | export default router | ||||||
							
								
								
									
										7
									
								
								mobile/src/store/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								mobile/src/store/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | import Vue from 'vue' | ||||||
|  | import Vuex from 'vuex' | ||||||
|  | import {ByjoveStoreConfig} from 'byjove' | ||||||
|  | 
 | ||||||
|  | Vue.use(Vuex) | ||||||
|  | 
 | ||||||
|  | export default new Vuex.Store(ByjoveStoreConfig) | ||||||
							
								
								
									
										5
									
								
								mobile/src/views/About.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								mobile/src/views/About.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | <template> | ||||||
|  |   <div class="about"> | ||||||
|  |     <h1>This is an about page</h1> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
							
								
								
									
										29
									
								
								mobile/src/views/Home.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								mobile/src/views/Home.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | <template> | ||||||
|  |   <div class="home-page"> | ||||||
|  |     <byjove-logo :appsettings="appsettings"></byjove-logo> | ||||||
|  |     <h2>Welcome to {{ appsettings.appTitle }}</h2> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import appsettings from '@/appsettings' | ||||||
|  | import {ByjoveLogo} from 'byjove' | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     name: 'home', | ||||||
|  |     components: { | ||||||
|  |         ByjoveLogo, | ||||||
|  |     }, | ||||||
|  |     data() { | ||||||
|  |         return { | ||||||
|  |             appsettings: appsettings, | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | .home-page { | ||||||
|  |     text-align: center; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										92
									
								
								mobile/src/views/Login.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								mobile/src/views/Login.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | ||||||
|  | <template> | ||||||
|  |   <div class="login"> | ||||||
|  | 
 | ||||||
|  |     <byjove-logo :appsettings="appsettings"></byjove-logo> | ||||||
|  | 
 | ||||||
|  |     <div v-if="loginError" style="background-color: red;"> | ||||||
|  |       {{ loginError }} | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <form> | ||||||
|  |       <div> | ||||||
|  |         <label for="username">Username</label>  | ||||||
|  |         <input v-model="username" /> | ||||||
|  |       </div> | ||||||
|  |       <br /> | ||||||
|  |       <div> | ||||||
|  |         <label for="password">Password</label>  | ||||||
|  |         <input v-model="password" type="password" /> | ||||||
|  |       </div> | ||||||
|  |       <br /> | ||||||
|  |       <div> | ||||||
|  |         <button type="button" v-on:click="attemptLogin">Login</button>  | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  | 
 | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import appsettings from '@/appsettings' | ||||||
|  | import {ByjoveLogo} from 'byjove' | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     name: 'Login', | ||||||
|  |     components: { | ||||||
|  |         ByjoveLogo, | ||||||
|  |     }, | ||||||
|  |     data: function () { | ||||||
|  |         return { | ||||||
|  |             appsettings: appsettings, | ||||||
|  |             username: null, | ||||||
|  |             password: null, | ||||||
|  |             loginError: null | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     beforeCreate: function() { | ||||||
|  |         this.checkUser() | ||||||
|  |     }, | ||||||
|  |     watch: { | ||||||
|  |         '$store.state.user': 'checkUser', | ||||||
|  |     }, | ||||||
|  |     methods: { | ||||||
|  | 
 | ||||||
|  |         checkUser() { | ||||||
|  |             // send logged-in users to "home" instead | ||||||
|  |             if (this.$store.state.user) { | ||||||
|  |                 this.$router.push('/') | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         attemptLogin: function() { | ||||||
|  |             this.loginError = null; | ||||||
|  |             var creds = { | ||||||
|  |                 username: this.username, | ||||||
|  |                 password: this.password | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             this.$http.post('/api/login', creds).then(response => { | ||||||
|  |                 if (response.data.error) { | ||||||
|  |                     this.loginError = response.data.error; | ||||||
|  |                 } else { | ||||||
|  |                     // let byjove do login proper | ||||||
|  |                     this.$loginUser(response.data.user, response.data.permissions) | ||||||
|  |                     // after user logs in, show home page | ||||||
|  |                     this.$router.push('/') | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | .login { | ||||||
|  |     text-align: center; | ||||||
|  | } | ||||||
|  | img { | ||||||
|  |     max-height: 200px; | ||||||
|  |     max-width: 300px; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										256
									
								
								mobile/src/views/ordering/OrderingBatch.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								mobile/src/views/ordering/OrderingBatch.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,256 @@ | ||||||
|  | <template> | ||||||
|  |   <byjove-model-crud model-name="PurchaseBatch" | ||||||
|  |                      model-index-title="Ordering" | ||||||
|  |                      model-title="Ordering Batch" | ||||||
|  |                      model-title-plural="Ordering Batches" | ||||||
|  |                      model-path-prefix="/ordering" | ||||||
|  |                      model-permission-prefix="ordering" | ||||||
|  |                      row-path-prefix="/ordering/rows" | ||||||
|  |                      :mode="mode" | ||||||
|  |                      @refresh="record => { batch = record }" | ||||||
|  |                      api-index-url="/api/ordering-batches" | ||||||
|  |                      api-object-url="/api/ordering-batch/" | ||||||
|  |                      :header-label-renderer="renderHeaderLabel" | ||||||
|  |                      :allow-edit="false" | ||||||
|  |                      has-rows | ||||||
|  |                      api-rows-url="/api/ordering-batch-rows" | ||||||
|  |                      :row-label-renderer="renderRowLabel" | ||||||
|  |                      :row-route-getter="getRowRoute" | ||||||
|  |                      save-button-text="Make Ordering Batch" | ||||||
|  |                      @save="save"> | ||||||
|  | 
 | ||||||
|  |     <div v-if="mode == 'creating'"> | ||||||
|  | 
 | ||||||
|  |       <b-field label="Vendor" | ||||||
|  |                :type="{'is-danger': !batch.vendor_uuid}"> | ||||||
|  |         <byjove-autocomplete v-model="batch.vendor_uuid" | ||||||
|  |                              service-url="/api/vendors/autocomplete"> | ||||||
|  |         </byjove-autocomplete> | ||||||
|  |       </b-field> | ||||||
|  | 
 | ||||||
|  |       <br /> | ||||||
|  |     </div> <!-- creating --> | ||||||
|  | 
 | ||||||
|  |     <div v-if="mode == 'viewing'"> | ||||||
|  | 
 | ||||||
|  |       <b-field label="Vendor"> | ||||||
|  |         <span> | ||||||
|  |           {{ batch.vendor_display }} | ||||||
|  |         </span> | ||||||
|  |       </b-field> | ||||||
|  | 
 | ||||||
|  |       <b-field label="Date Ordered" | ||||||
|  |                v-if="mode != 'creating' && batch.executed"> | ||||||
|  |         <span> | ||||||
|  |           {{ batch.date_ordered }} | ||||||
|  |         </span> | ||||||
|  |       </b-field> | ||||||
|  | 
 | ||||||
|  |       <b-field label="Total"> | ||||||
|  |         <span> | ||||||
|  |           {{ batch.po_total_calculated_display }} | ||||||
|  |         </span> | ||||||
|  |       </b-field> | ||||||
|  | 
 | ||||||
|  |       <b-field label="Created" | ||||||
|  |                v-if="mode != 'creating' && batch.executed"> | ||||||
|  |         <span> | ||||||
|  |           {{ batch.created }} | ||||||
|  |         </span> | ||||||
|  |       </b-field> | ||||||
|  | 
 | ||||||
|  |       <b-field label="Created By" | ||||||
|  |                v-if="mode != 'creating' && batch.executed"> | ||||||
|  |         <span> | ||||||
|  |           {{ batch.created_by_display }} | ||||||
|  |         </span> | ||||||
|  |       </b-field> | ||||||
|  | 
 | ||||||
|  |       <br v-if="!batch.executed" /> | ||||||
|  | 
 | ||||||
|  |       <div v-if="batch.executed"> | ||||||
|  | 
 | ||||||
|  |         <b-field label="Executed"> | ||||||
|  |           <span> | ||||||
|  |             {{ batch.executed }} | ||||||
|  |           </span> | ||||||
|  |         </b-field> | ||||||
|  | 
 | ||||||
|  |         <b-field label="Executed By"> | ||||||
|  |           <span> | ||||||
|  |             {{ batch.executed_by_display }} | ||||||
|  |           </span> | ||||||
|  |         </b-field> | ||||||
|  | 
 | ||||||
|  |       </div> <!-- executed --> | ||||||
|  | 
 | ||||||
|  |     </div> <!-- viewing --> | ||||||
|  | 
 | ||||||
|  |     <template slot="quick-entry"> | ||||||
|  |       <div v-if="mode == 'viewing' && !batch.executed && !batch.complete"> | ||||||
|  |         <b-input v-model="quickEntry" | ||||||
|  |                  placeholder="Enter UPC" | ||||||
|  |                  icon="search" | ||||||
|  |                  @keypress.native="quickKey"> | ||||||
|  |         </b-input> | ||||||
|  |         <br /> | ||||||
|  |       </div> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |     <template slot="footer" | ||||||
|  |               v-if="mode == 'viewing' && !batch.executed"> | ||||||
|  |       <br /> | ||||||
|  |       <div class="buttons is-centered"> | ||||||
|  |         <b-button v-if="!batch.complete" | ||||||
|  |                   type="is-primary" | ||||||
|  |                   @click="editWorksheet()"> | ||||||
|  |           Edit as Worksheet | ||||||
|  |         </b-button> | ||||||
|  |         <b-button v-if="!batch.complete" | ||||||
|  |                   type="is-primary" | ||||||
|  |                   @click="markOrderingComplete()"> | ||||||
|  |           Ordering is Complete! | ||||||
|  |         </b-button> | ||||||
|  |       </div> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |   </byjove-model-crud> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import {ByjoveModelCrud, ByjoveAutocomplete} from 'byjove' | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     name: 'OrderingBatch', | ||||||
|  |     props: { | ||||||
|  |         mode: String, | ||||||
|  |     }, | ||||||
|  |     components: { | ||||||
|  |         ByjoveModelCrud, | ||||||
|  |         ByjoveAutocomplete, | ||||||
|  |     }, | ||||||
|  |     data: function() { | ||||||
|  |         return { | ||||||
|  |             batch: {}, | ||||||
|  |             quickEntry: '', | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     mounted() { | ||||||
|  |         window.addEventListener('keypress', this.globalKey) | ||||||
|  |     }, | ||||||
|  |     beforeDestroy() { | ||||||
|  |         window.removeEventListener('keypress', this.globalKey) | ||||||
|  |     }, | ||||||
|  |     methods: { | ||||||
|  |         renderHeaderLabel(batch) { | ||||||
|  |             return batch.id_str | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         renderRowLabel(row) { | ||||||
|  |             return `(${row.cases_ordered_display} / ${row.units_ordered_display}) ${row.full_description}` | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         getRowRoute(row) { | ||||||
|  |             return `/ordering/rows/${row.uuid}/edit` | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         save(url) { | ||||||
|  |             if (url === undefined) { | ||||||
|  |                 url = '/api/ordering-batches' | ||||||
|  |             } | ||||||
|  |             let params = { | ||||||
|  |                 vendor_uuid: this.batch.vendor_uuid, | ||||||
|  |             } | ||||||
|  |             this.$http.post(url, params).then(response => { | ||||||
|  |                 if (response.data.data) { | ||||||
|  |                     // navigate to new ordering batch | ||||||
|  |                     this.$router.push('/ordering/' + response.data.data.uuid) | ||||||
|  |                 } else { | ||||||
|  |                     this.$buefy.toast.open({ | ||||||
|  |                         message: response.data.error || "Failed to save batch!", | ||||||
|  |                         type: 'is-danger', | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |             }, response => { | ||||||
|  |                 this.$buefy.toast.open({ | ||||||
|  |                     message: "Failed to save batch!", | ||||||
|  |                     type: 'is-danger', | ||||||
|  |                 }) | ||||||
|  |             }) | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         globalKey(event) { | ||||||
|  |             if (event.target.tagName == 'BODY') { | ||||||
|  | 
 | ||||||
|  |                 // mimic keyboard wedge | ||||||
|  |                 if (event.charCode >= 48 && event.charCode <= 57) { // numeric (qwerty) | ||||||
|  |                     this.$nextTick(function() { | ||||||
|  |                         this.quickEntry += event.key | ||||||
|  |                     }) | ||||||
|  | 
 | ||||||
|  |                 } else if (event.keyCode == 13) { // enter | ||||||
|  |                     this.submitQuickEntry() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         quickKey(event) { | ||||||
|  |             if (event.keyCode == 13) { // enter | ||||||
|  |                 this.submitQuickEntry() | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         submitQuickEntry() { | ||||||
|  |             if (!this.quickEntry) { | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  |             let params = { | ||||||
|  |                 batch_uuid: this.batch.uuid, | ||||||
|  |                 quick_entry: this.quickEntry, | ||||||
|  |             } | ||||||
|  |             let url = '/api/ordering-batch-rows/quick-entry' | ||||||
|  |             this.$http.post(url, params).then(response => { | ||||||
|  |                 if (response.data.data) { | ||||||
|  |                     // let user edit row immediately | ||||||
|  |                     this.$router.push(`/ordering/rows/${response.data.data.uuid}/edit`) | ||||||
|  |                 } else { | ||||||
|  |                     this.$buefy.toast.open({ | ||||||
|  |                         message: response.data.error || "Failed to post quick entry!", | ||||||
|  |                         type: 'is-danger', | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |             }, response => { | ||||||
|  |                 this.$buefy.toast.open({ | ||||||
|  |                     message: "Failed to post quick entry!", | ||||||
|  |                     type: 'is-danger', | ||||||
|  |                 }) | ||||||
|  |             }) | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         editWorksheet() { | ||||||
|  |             this.$router.push(`/ordering/${this.batch.uuid}/worksheet`) | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         markOrderingComplete() { | ||||||
|  |             this.$http.post(`/api/ordering-batch/${this.batch.uuid}/mark-complete`).then(response => { | ||||||
|  |                 if (response.data.data) { | ||||||
|  |                     this.batch = response.data.data | ||||||
|  |                     this.$router.push('/ordering') | ||||||
|  |                 } else { | ||||||
|  |                     this.$buefy.toast.open({ | ||||||
|  |                         message: response.data.error || "Failed to mark ordering complete!", | ||||||
|  |                         type: 'is-danger', | ||||||
|  |                         position: 'is-bottom', | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |             }, response => { | ||||||
|  |                 this.$buefy.toast.open({ | ||||||
|  |                     message: "Failed to mark ordering complete!", | ||||||
|  |                     type: 'is-danger', | ||||||
|  |                     position: 'is-bottom', | ||||||
|  |                 }) | ||||||
|  |             }) | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										127
									
								
								mobile/src/views/ordering/OrderingBatchRow.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								mobile/src/views/ordering/OrderingBatchRow.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,127 @@ | ||||||
|  | <template> | ||||||
|  |   <byjove-model-crud model-name="OrderingBatchRow" | ||||||
|  |                      model-index-title="Ordering" | ||||||
|  |                      :mode="mode" | ||||||
|  |                      :header-label-renderer="renderHeaderLabel" | ||||||
|  |                      :parent-header-label-renderer="renderParentHeaderLabel" | ||||||
|  |                      api-object-url="/api/ordering-batch-row/" | ||||||
|  |                      model-path-prefix="/ordering" | ||||||
|  |                      row-path-prefix="/ordering/rows" | ||||||
|  |                      @refresh="record => { row = record }" | ||||||
|  |                      is-row | ||||||
|  |                      @save="save"> | ||||||
|  | 
 | ||||||
|  |     <b-field label="UPC"> | ||||||
|  |       <span> | ||||||
|  |         {{ row.upc_pretty }} | ||||||
|  |       </span> | ||||||
|  |     </b-field> | ||||||
|  | 
 | ||||||
|  |     <b-field label="Description"> | ||||||
|  |       <span> | ||||||
|  |         {{ row.description }} | ||||||
|  |       </span> | ||||||
|  |     </b-field> | ||||||
|  | 
 | ||||||
|  |     <b-field label="Cases Ordered"> | ||||||
|  |       <b-input v-if="mode == 'editing'" | ||||||
|  |                v-model="row.cases_ordered" | ||||||
|  |                type="number" | ||||||
|  |                ref="casesOrdered" | ||||||
|  |                @keypress.native="handleEnter"> | ||||||
|  |       </b-input> | ||||||
|  |       <span v-if="mode == 'viewing'"> | ||||||
|  |         {{ row.cases_ordered_display }} | ||||||
|  |       </span> | ||||||
|  |     </b-field> | ||||||
|  | 
 | ||||||
|  |     <b-field label="Units Ordered"> | ||||||
|  |       <b-input v-if="mode == 'editing'" | ||||||
|  |                v-model="row.units_ordered" | ||||||
|  |                type="number" | ||||||
|  |                ref="unitsOrdered" | ||||||
|  |                @keypress.native="handleEnter"> | ||||||
|  |       </b-input> | ||||||
|  |       <span v-if="mode == 'viewing'"> | ||||||
|  |         {{ row.units_ordered_display }} | ||||||
|  |       </span> | ||||||
|  |     </b-field> | ||||||
|  | 
 | ||||||
|  |     <b-field label="PO Unit Cost"> | ||||||
|  |       <span> | ||||||
|  |         {{ row.po_unit_cost_display }} | ||||||
|  |       </span> | ||||||
|  |     </b-field> | ||||||
|  | 
 | ||||||
|  |     <b-field label="Total"> | ||||||
|  |       <span> | ||||||
|  |         {{ row.po_total_calculated_display }} | ||||||
|  |       </span> | ||||||
|  |     </b-field> | ||||||
|  | 
 | ||||||
|  |     <b-field label="Status"> | ||||||
|  |       <span> | ||||||
|  |         {{ row.status_display }} | ||||||
|  |       </span> | ||||||
|  |     </b-field> | ||||||
|  | 
 | ||||||
|  |   </byjove-model-crud> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import {ByjoveModelCrud} from 'byjove' | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     name: 'OrderingBatchRow', | ||||||
|  |     props: { | ||||||
|  |         mode: String, | ||||||
|  |     }, | ||||||
|  |     components: { | ||||||
|  |         ByjoveModelCrud, | ||||||
|  |     }, | ||||||
|  |     data: function() { | ||||||
|  |         return { | ||||||
|  |             row: {}, | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     // TODO: pretty sure this is too aggressive | ||||||
|  |     updated() { | ||||||
|  |         if (this.mode == 'editing') { | ||||||
|  |             this.$nextTick(function() { | ||||||
|  |                 this.$refs.casesOrdered.focus() | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     methods: { | ||||||
|  | 
 | ||||||
|  |         renderParentHeaderLabel(row) { | ||||||
|  |             return row.batch_id_str | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         renderHeaderLabel(row) { | ||||||
|  |             return row.upc_pretty | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         handleEnter(event) { | ||||||
|  |             if (event.keyCode == 13) { // enter | ||||||
|  |                 this.save() | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         save(url) { | ||||||
|  |             // we only update the order quantities | ||||||
|  |             let params = { | ||||||
|  |                 cases_ordered: parseInt(this.row.cases_ordered), | ||||||
|  |                 units_ordered: parseInt(this.row.units_ordered), | ||||||
|  |             } | ||||||
|  |             if (!url) { | ||||||
|  |                 url = `/api/ordering-batch-row/${this.row.uuid}` | ||||||
|  |             } | ||||||
|  |             this.$http.post(url, params).then(response => { | ||||||
|  |                 // go back to the purchase order | ||||||
|  |                 this.$router.push('/ordering/' + response.data.data.batch_uuid) | ||||||
|  |             }) | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										182
									
								
								mobile/src/views/ordering/OrderingBatchWorksheet.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								mobile/src/views/ordering/OrderingBatchWorksheet.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,182 @@ | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <nav class="breadcrumb" aria-label="breadcrumbs"> | ||||||
|  |       <ul> | ||||||
|  |         <li> | ||||||
|  |           <router-link to="/ordering">Ordering</router-link> | ||||||
|  |         </li> | ||||||
|  |         <li> | ||||||
|  |           <router-link :to="`/ordering/${batch.uuid}`"> | ||||||
|  |             {{ batch.id_str }} | ||||||
|  |           </router-link> | ||||||
|  |         </li> | ||||||
|  |         <li> | ||||||
|  |             Worksheet | ||||||
|  |         </li> | ||||||
|  |       </ul> | ||||||
|  |     </nav> | ||||||
|  | 
 | ||||||
|  |     <b-field label="Vendor"> | ||||||
|  |       <span> | ||||||
|  |         {{ batch.vendor_display }} | ||||||
|  |       </span> | ||||||
|  |     </b-field> | ||||||
|  | 
 | ||||||
|  |     <b-field label="Total"> | ||||||
|  |       <span> | ||||||
|  |         {{ batch.po_total_calculated_display }} | ||||||
|  |       </span> | ||||||
|  |     </b-field> | ||||||
|  | 
 | ||||||
|  |     <div v-if="sortedDepartments"> | ||||||
|  | 
 | ||||||
|  |       <div v-for="dept in sortedDepartments" | ||||||
|  |            :key="dept.uuid"> | ||||||
|  |         <h2> | ||||||
|  |           Dept. {{ dept.number }} ({{ dept.name }}) | ||||||
|  |         </h2> | ||||||
|  |         <div v-for="subdept in dept.subdepartments" | ||||||
|  |              :key="subdept.uuid"> | ||||||
|  |           <h3> | ||||||
|  |             Sub. {{ subdept.number }} ({{ subdept.name }}) | ||||||
|  |           </h3> | ||||||
|  | 
 | ||||||
|  |           <b-table :data="subdept.costs"> | ||||||
|  |             <template slot-scope="props"> | ||||||
|  | 
 | ||||||
|  |               <b-table-column field="upc_pretty" label="UPC" sortable> | ||||||
|  |                 {{ props.row.upc_pretty }} | ||||||
|  |               </b-table-column> | ||||||
|  | 
 | ||||||
|  |               <b-table-column field="brand_name" label="Brand" sortable> | ||||||
|  |                 {{ props.row.brand_name }} | ||||||
|  |               </b-table-column> | ||||||
|  | 
 | ||||||
|  |               <b-table-column field="description" label="Description" sortable> | ||||||
|  |                 {{ props.row.description }} {{ props.row.size }} | ||||||
|  |               </b-table-column> | ||||||
|  | 
 | ||||||
|  |               <b-table-column field="case_size" label="Case" numeric sortable> | ||||||
|  |                 {{ props.row.case_size }} {{ props.row.uom_display }} | ||||||
|  |               </b-table-column> | ||||||
|  | 
 | ||||||
|  |               <b-table-column field="vendor_item_code" label="Vend. Code" sortable> | ||||||
|  |                 {{ props.row.vendor_item_code }} | ||||||
|  |               </b-table-column> | ||||||
|  | 
 | ||||||
|  |               <b-table-column field="preferred" label="Pref." sortable> | ||||||
|  |                 {{ props.row.preferred ? "X" : "" }} | ||||||
|  |               </b-table-column> | ||||||
|  | 
 | ||||||
|  |               <b-table-column field="unit_cost" label="Unit Cost" numeric sortable> | ||||||
|  |                 {{ props.row.unit_cost_display }} | ||||||
|  |               </b-table-column> | ||||||
|  | 
 | ||||||
|  |               <b-table-column field="units_ordered" label="Cases / Units"> | ||||||
|  |                 <b-field grouped> | ||||||
|  |                   <b-input v-model="props.row.cases_ordered" | ||||||
|  |                            type="number" | ||||||
|  |                            size="is-small"> | ||||||
|  |                   </b-input> | ||||||
|  |                   <b-input v-model="props.row.units_ordered" | ||||||
|  |                            type="number" | ||||||
|  |                            size="is-small"> | ||||||
|  |                   </b-input> | ||||||
|  |                 </b-field> | ||||||
|  |               </b-table-column> | ||||||
|  | 
 | ||||||
|  |               <b-table-column field="po_total" label="PO Total" numeric sortable> | ||||||
|  |                 {{ props.row.po_total_display }} | ||||||
|  |               </b-table-column> | ||||||
|  | 
 | ||||||
|  |             </template> | ||||||
|  |           </b-table> | ||||||
|  |           <br /> | ||||||
|  | 
 | ||||||
|  |         </div> <!-- subdept --> | ||||||
|  |       </div>   <!-- dept --> | ||||||
|  |     </div>     <!-- if sortedDepartments --> | ||||||
|  | 
 | ||||||
|  |     <b-loading :active="!sortedDepartments"></b-loading> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     name: 'OrderingBatchWorksheet', | ||||||
|  |     data() { | ||||||
|  |         return { | ||||||
|  |             inited: false, | ||||||
|  |             batch: {}, | ||||||
|  |             sortedDepartments: null, | ||||||
|  |             purchaseHistory: null, | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     created: function() { | ||||||
|  |         this.init() | ||||||
|  |     }, | ||||||
|  |     watch: { | ||||||
|  |         '$store.state.session_established': 'init', | ||||||
|  |     }, | ||||||
|  |     methods: { | ||||||
|  | 
 | ||||||
|  |         init() { | ||||||
|  |             // only need to init once | ||||||
|  |             if (this.inited) { | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // cannot init until session is established | ||||||
|  |             if (!this.$store.state.session_established) { | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // is someone logged in? | ||||||
|  |             if (this.$store.state.user) { | ||||||
|  | 
 | ||||||
|  |                 // go ahead and fetch data, but only if user has permission | ||||||
|  |                 if (this.$hasPerm('ordering.worksheet')) { | ||||||
|  |                     this.fetchData() | ||||||
|  |                 } else { // send others back to "home" page | ||||||
|  |                     this.router.push('/') | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             } else { // send logged-out users to "login" | ||||||
|  |                 this.$router.push('/login') | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // we did it! | ||||||
|  |             this.inited = true | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         fetchData(uuid) { | ||||||
|  |             if (uuid === undefined) { | ||||||
|  |                 uuid = this.$route.params.uuid | ||||||
|  |             } | ||||||
|  |             this.$http.get(`/api/ordering-batch/${uuid}`).then(response => { | ||||||
|  |                 this.batch = response.data.purchasebatch | ||||||
|  |                 this.fetchWorksheetData(uuid) | ||||||
|  |             }, response => { | ||||||
|  |                 this.$buefy.toast.open({ | ||||||
|  |                     message: "Failed to fetch batch data!", | ||||||
|  |                     type: 'is-danger', | ||||||
|  |                 }) | ||||||
|  |             }) | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         fetchWorksheetData(uuid) { | ||||||
|  |             this.$http.get(`/api/ordering-batch/${uuid}/worksheet`).then(response => { | ||||||
|  |                 this.sortedDepartments = response.data.sorted_departments | ||||||
|  |                 this.purchaseHistory = response.data.history | ||||||
|  |             }, response => { | ||||||
|  |                 this.$buefy.toast.open({ | ||||||
|  |                     message: "Failed to fetch worksheet data!", | ||||||
|  |                     type: 'is-danger', | ||||||
|  |                 }) | ||||||
|  |             }) | ||||||
|  | 
 | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										40
									
								
								mobile/src/views/ordering/OrderingBatches.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								mobile/src/views/ordering/OrderingBatches.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | <template> | ||||||
|  |   <byjove-model-index model-name="PurchaseBatch" | ||||||
|  |                       model-index-title="Ordering" | ||||||
|  |                       model-title="Ordering Batch" | ||||||
|  |                       model-title-plural="Ordering Batches" | ||||||
|  |                       model-path-prefix="/ordering" | ||||||
|  |                       model-permission-prefix="ordering" | ||||||
|  |                       api-index-url="/api/ordering-batches" | ||||||
|  |                       :api-index-sort="{field: 'id', dir: 'desc'}" | ||||||
|  |                       :api-index-filters="filters" | ||||||
|  |                       :label-renderer="renderLabel"> | ||||||
|  |   </byjove-model-index> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import {ByjoveModelIndex} from 'byjove' | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     name: 'OrderingBatches', | ||||||
|  |     components: { | ||||||
|  |         ByjoveModelIndex, | ||||||
|  |     }, | ||||||
|  |     data() { | ||||||
|  |         return { | ||||||
|  |             filters: [ | ||||||
|  |                 {field: 'executed', op: 'is_null'}, | ||||||
|  |                 {or: [ | ||||||
|  |                     {field: 'complete', op: 'is_null'}, | ||||||
|  |                     {field: 'complete', op: 'eq', value: false}, | ||||||
|  |                 ]}, | ||||||
|  |             ], | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     methods: { | ||||||
|  |         renderLabel(batch) { | ||||||
|  |             return `(${batch.id_str}) ${batch.vendor_display}` | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										11
									
								
								mobile/src/views/ordering/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								mobile/src/views/ordering/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | import OrderingBatches from './OrderingBatches' | ||||||
|  | import OrderingBatch from './OrderingBatch' | ||||||
|  | import OrderingBatchRow from './OrderingBatchRow' | ||||||
|  | import OrderingBatchWorksheet from './OrderingBatchWorksheet' | ||||||
|  | 
 | ||||||
|  | export { | ||||||
|  |     OrderingBatches, | ||||||
|  |     OrderingBatch, | ||||||
|  |     OrderingBatchRow, | ||||||
|  |     OrderingBatchWorksheet, | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								mobile/vue.config.js.dist
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								mobile/vue.config.js.dist
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | // -*- mode: js; -*- | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |     publicPath: '/m/', | ||||||
|  |     devServer: { | ||||||
|  |         host: '127.0.0.1', | ||||||
|  |         port: 6647, | ||||||
|  |         public: 'theo-demo.example.com', | ||||||
|  |         // clientLogLevel: 'debug', | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								setup.py
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								setup.py
									
										
									
									
									
								
							|  | @ -2,7 +2,7 @@ | ||||||
| ################################################################################ | ################################################################################ | ||||||
| # | # | ||||||
| #  Rattail -- Retail Software Framework | #  Rattail -- Retail Software Framework | ||||||
| #  Copyright © 2010-2020 Lance Edgar | #  Copyright © 2010-2021 Lance Edgar | ||||||
| # | # | ||||||
| #  This file is part of Rattail. | #  This file is part of Rattail. | ||||||
| # | # | ||||||
|  | @ -163,6 +163,7 @@ setup( | ||||||
| 
 | 
 | ||||||
|         'paste.app_factory': [ |         'paste.app_factory': [ | ||||||
|             'main = theo.web.app:main', |             'main = theo.web.app:main', | ||||||
|  |             'webapi = theo.web.webapi:main', | ||||||
|         ], |         ], | ||||||
|     }, |     }, | ||||||
| ) | ) | ||||||
|  |  | ||||||
							
								
								
									
										38
									
								
								theo/web/api/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								theo/web/api/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | # -*- coding: utf-8; -*- | ||||||
|  | ################################################################################ | ||||||
|  | # | ||||||
|  | #  Rattail -- Retail Software Framework | ||||||
|  | #  Copyright © 2010-2021 Lance Edgar | ||||||
|  | # | ||||||
|  | #  This file is part of Rattail. | ||||||
|  | # | ||||||
|  | #  Rattail is free software: you can redistribute it and/or modify it under the | ||||||
|  | #  terms of the GNU General Public License as published by the Free Software | ||||||
|  | #  Foundation, either version 3 of the License, or (at your option) any later | ||||||
|  | #  version. | ||||||
|  | # | ||||||
|  | #  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY | ||||||
|  | #  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||||
|  | #  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more | ||||||
|  | #  details. | ||||||
|  | # | ||||||
|  | #  You should have received a copy of the GNU General Public License along with | ||||||
|  | #  Rattail.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | # | ||||||
|  | ################################################################################ | ||||||
|  | """ | ||||||
|  | Theo Web API | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def includeme(config): | ||||||
|  | 
 | ||||||
|  |     # core | ||||||
|  |     config.include('tailbone.api.common') | ||||||
|  |     config.include('tailbone.api.auth') | ||||||
|  | 
 | ||||||
|  |     # models | ||||||
|  |     config.include('tailbone.api.vendors') | ||||||
|  | 
 | ||||||
|  |     # batches | ||||||
|  |     config.include('tailbone.api.batch.ordering') | ||||||
							
								
								
									
										41
									
								
								theo/web/webapi.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								theo/web/webapi.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | ||||||
|  | # -*- coding: utf-8; -*- | ||||||
|  | ################################################################################ | ||||||
|  | # | ||||||
|  | #  Rattail -- Retail Software Framework | ||||||
|  | #  Copyright © 2010-2021 Lance Edgar | ||||||
|  | # | ||||||
|  | #  This file is part of Rattail. | ||||||
|  | # | ||||||
|  | #  Rattail is free software: you can redistribute it and/or modify it under the | ||||||
|  | #  terms of the GNU General Public License as published by the Free Software | ||||||
|  | #  Foundation, either version 3 of the License, or (at your option) any later | ||||||
|  | #  version. | ||||||
|  | # | ||||||
|  | #  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY | ||||||
|  | #  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||||
|  | #  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more | ||||||
|  | #  details. | ||||||
|  | # | ||||||
|  | #  You should have received a copy of the GNU General Public License along with | ||||||
|  | #  Rattail.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | # | ||||||
|  | ################################################################################ | ||||||
|  | """ | ||||||
|  | Theo web API app | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from tailbone import webapi as base | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def main(global_config, **settings): | ||||||
|  |     """ | ||||||
|  |     This function returns a Pyramid WSGI application. | ||||||
|  |     """ | ||||||
|  |     rattail_config = base.make_rattail_config(settings) | ||||||
|  |     pyramid_config = base.make_pyramid_config(settings) | ||||||
|  | 
 | ||||||
|  |     # bring in some Theo / Tailbone | ||||||
|  |     pyramid_config.include('theo.web.subscribers') | ||||||
|  |     pyramid_config.include('theo.web.api') | ||||||
|  | 
 | ||||||
|  |     return pyramid_config.make_wsgi_app() | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar