Jelajahi Sumber

[ADD] add manually quantity changed

Gogs 7 tahun lalu
induk
melakukan
bda0ff3e15

+ 1 - 0
controllers/main.py

@@ -128,6 +128,7 @@ class PosSales(http.Controller):
             'name': customer.name,
             'displayName': customer.display_name,
             'imageMedium': customer.image_medium,
+            'ruc': customer.ruc,
             'phone': customer.phone,
             'mobile': customer.mobile,
             'email': customer.email

+ 17 - 6
src/components/common/Card.vue

@@ -2,8 +2,8 @@
     .card(@click='onClick' :class="{ 'selected-card': isSelected }")
         h2.card-title {{ title }}
         img.card-image(:src="'data:image/png;base64,' + (image || 'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAACXZwQWcAAABAAAAAQADq8/hgAAAEWklEQVRYw9WX6XKjRhCAef8HiySQvGt5vfZuEselOUAcEpe4GdI9MAgQOjb5k3SVyzY1801PX9OtNf9StP80QJR5miRpXtb/AFCnvmMySgmhlJn2Mal+BSBSj1NCGeNSGAMOd0/iQYCI95TAXnm+FCr/I2ZYPwJILEJhPaGm7flBFIW+Z5sUvwEivguovG7pMR0cV2e+BbYArF3cBqQclKfEvryvSB2KaHa6BYhgDSP7ZN7gmUNQCf86wCdgcBaKq04/cTzAuwbA/czKb8VdZYMSI8IAEOJ+XjTiFkF4SDjOARIIHLiBK+4E/xHOIdEloMSAAwZx7hEOBKIquwA4lFPbR/3uEhzCqSUmgBiwrGgeIlQm5b0zO0CN3yKw34QgQC4JKZqrGAFC0MpWvuwJ3V6hWD3BI5wchoDaBAumzYQgmsrd7ewZx5bosHIAAAtQp4+nXUuA+2yXy9Xyi4OsIorjauBLZQWtd0Gqrt3EvCXQlb4BMZYfsPP7cr0gvS4FaNw6Qus0ovtez8DZcYyHt8Wmk9XWdF+Mjf570Ke4q46UgAgUCtX55mKl/wSbsD83hrEE0VGJ1RrEWHz2aaXuIAEe7b3SNG/601oSzL/W20/T2r2uDNACARvjWelZQTTaCiCg2vSR1bzrsFgSQMk8SbPi8FWX+0GFbX2OXMarDoAmOGfo+wpXt7cwj4Hv+1n+rSMYW3HOfS4TAgHZIDIVYG38wNzchyB+kj4ZUwB4npw6ABokmgA2qz9kfbIkoWDLzQSQ0tbw2gA20kA/nmyqCHG8nmqQd2prbSKQZAIwnk5B5PSE/EWfACCUZGFSgHQKeE6DsCcExfc5wKEDRLMaJHBwTwA/zFzhOLBBPGODoCfEyYUb0XVBB1AGHXvho/SVDsSjF15QrtMG1xlpsDbCrCewj7UxAWAJSjsAlJOuHI0AX9Mi8IMgsJnMC2MMOJA2f7RhXI8AG/2LVxZZVlQWmKElnAFiT5nMH62L67Mb3lTmbIzVK3Uc9r6GvJAEyMa6d0KXP1oXliqbRPPzN0NvBcrBAmSpr37wlrB8GeRS6zkJECZVNRKeuLfty1C+wc/zp7TD9jVQN7DUDq2vkUEzfAymIl9uZ5iL1B0U1Rw7surmc4SE/sUBE3KaDB8Wd1QS7hJQga4Kayow2aAsXiV0L458HE/jx9UbPi33CIf+ITwDSnxM/IcIcAGIrHzaH+BX8Ky4awdq41nBZYsjG4/kEQLjg9Q5A9A1jJ7u3CJEa1OzmuvSKgubwPA24IT7WT7fJ5YmEtwbASWO2AkP94871WpPOCc8vmYHaORhv5lf75VrV3bD+9nZIrUJamhXN9v9kMlu3wonYVlGe9msU1/cGTgKpx0YmO2fsrKq66rMk8Bh7dd99sDIk+xxxsE5icqhqfsLflkz1pkbukSCBzI5bqG0EGrPGvfK2FeGDseRi1I5eVFuB8WvDp51FvsH13Fcz4+y6n86Oz8kfwPMD02INEiadQAAAABJRU5ErkJggg==')")
-        .card-description(v-if='!!description')
-            span {{ description }}
+        .card-details(v-if='details.length > 0')
+            span(v-for='detail in details') {{ computeDetail(detail) }}
 </template>
 
 <script>
@@ -17,16 +17,27 @@
                 type: String,
                 default: 'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAACXZwQWcAAABAAAAAQADq8/hgAAAEWklEQVRYw9WX6XKjRhCAef8HiySQvGt5vfZuEselOUAcEpe4GdI9MAgQOjb5k3SVyzY1801PX9OtNf9StP80QJR5miRpXtb/AFCnvmMySgmhlJn2Mal+BSBSj1NCGeNSGAMOd0/iQYCI95TAXnm+FCr/I2ZYPwJILEJhPaGm7flBFIW+Z5sUvwEivguovG7pMR0cV2e+BbYArF3cBqQclKfEvryvSB2KaHa6BYhgDSP7ZN7gmUNQCf86wCdgcBaKq04/cTzAuwbA/czKb8VdZYMSI8IAEOJ+XjTiFkF4SDjOARIIHLiBK+4E/xHOIdEloMSAAwZx7hEOBKIquwA4lFPbR/3uEhzCqSUmgBiwrGgeIlQm5b0zO0CN3yKw34QgQC4JKZqrGAFC0MpWvuwJ3V6hWD3BI5wchoDaBAumzYQgmsrd7ewZx5bosHIAAAtQp4+nXUuA+2yXy9Xyi4OsIorjauBLZQWtd0Gqrt3EvCXQlb4BMZYfsPP7cr0gvS4FaNw6Qus0ovtez8DZcYyHt8Wmk9XWdF+Mjf570Ke4q46UgAgUCtX55mKl/wSbsD83hrEE0VGJ1RrEWHz2aaXuIAEe7b3SNG/601oSzL/W20/T2r2uDNACARvjWelZQTTaCiCg2vSR1bzrsFgSQMk8SbPi8FWX+0GFbX2OXMarDoAmOGfo+wpXt7cwj4Hv+1n+rSMYW3HOfS4TAgHZIDIVYG38wNzchyB+kj4ZUwB4npw6ABokmgA2qz9kfbIkoWDLzQSQ0tbw2gA20kA/nmyqCHG8nmqQd2prbSKQZAIwnk5B5PSE/EWfACCUZGFSgHQKeE6DsCcExfc5wKEDRLMaJHBwTwA/zFzhOLBBPGODoCfEyYUb0XVBB1AGHXvho/SVDsSjF15QrtMG1xlpsDbCrCewj7UxAWAJSjsAlJOuHI0AX9Mi8IMgsJnMC2MMOJA2f7RhXI8AG/2LVxZZVlQWmKElnAFiT5nMH62L67Mb3lTmbIzVK3Uc9r6GvJAEyMa6d0KXP1oXliqbRPPzN0NvBcrBAmSpr37wlrB8GeRS6zkJECZVNRKeuLfty1C+wc/zp7TD9jVQN7DUDq2vkUEzfAymIl9uZ5iL1B0U1Rw7surmc4SE/sUBE3KaDB8Wd1QS7hJQga4Kayow2aAsXiV0L458HE/jx9UbPi33CIf+ITwDSnxM/IcIcAGIrHzaH+BX8Ky4awdq41nBZYsjG4/kEQLjg9Q5A9A1jJ7u3CJEa1OzmuvSKgubwPA24IT7WT7fJ5YmEtwbASWO2AkP94871WpPOCc8vmYHaORhv5lf75VrV3bD+9nZIrUJamhXN9v9kMlu3wonYVlGe9msU1/cGTgKpx0YmO2fsrKq66rMk8Bh7dd99sDIk+xxxsE5icqhqfsLflkz1pkbukSCBzI5bqG0EGrPGvfK2FeGDseRi1I5eVFuB8WvDp51FvsH13Fcz4+y6n86Oz8kfwPMD02INEiadQAAAABJRU5ErkJggg=='
             },
-            description: {
-                type: String,
-                default: ''
+            details: {
+                type: Array,
+                default: []
             },
             isSelected: {
                 type: Boolean,
                 default: true
+            },
+            options: {
+                type: Object,
+                default: {}
             }
         },
         methods: {
+            computeDetail(detail) {
+                if (detail.format === 'currency') {
+                    return this.$options.filters.currency(detail.value, {...this.options})
+                }
+
+                return detail.value
+            },
             onClick() {
                 this.$emit('onClick')
             }
@@ -67,7 +78,7 @@
             left: 50%
             margin-right: -50%
             transform: translate(-50%, -50%)
-        .card-description
+        .card-details
             width: 100%
             height: 30px
             padding-top: 5px

+ 76 - 7
src/components/common/CardGrid.vue

@@ -4,7 +4,7 @@
             spinner(type='wave')
         .card-grid(v-else)
             add-card(v-if='canAdd' @onClickAdd='onClickAdd')
-            card(v-for='item in items' :key='item.id' :title='item.name' :image='item.imageMedium' :isSelected='item.id === selectedId' :description='getDescription(item)' @onClick='onClickCard(item)')
+            card(v-for='item in items' :key='item.id' :title='item.name' :image='item.imageMedium' :isSelected='item.id === selectedId' :details='computeDetails(item)' :options='defaultOptions.currency' @onClick='onClickCard(item)')
 </template>
 
 <script>
@@ -22,13 +22,17 @@
                 type: Boolean,
                 default: false
             },
-            description: {
-                type: String,
-                default: ''
+            details: {
+                type: Array,
+                default: []
             },
             loading: {
                 type: Boolean,
                 default: false
+            },
+            options: {
+                type: Object,
+                default: {}
             }
         },
         components: {
@@ -36,9 +40,65 @@
             Card,
             Spinner
         },
+        watch: {
+            options(value) {
+                this.computeOptions(value)
+            }
+        },
         methods: {
-            getDescription(item) {
-                return (!!this.description && item[this.description]) || ''
+            computeDetails(item) {
+                if (!this.details) {
+                    return []
+                }
+
+                if (this.details.length === 0) {
+                    return []
+                }
+
+                let results = []
+                let computableDetails = this.details.map(item => item.split(/:/))
+
+                for (let detail of computableDetails) {
+                    for (let field in item) {
+                        if (field === detail[0]) {
+                            results.push({
+                                value: item[field],
+                                format: (() => {
+                                    if (!detail[1] || detail[1] === 's') {
+                                        return 'string'
+                                    }
+                                    
+                                    if (detail[1] === 'c') {
+                                        return 'currency'
+                                    }
+
+                                    if (detail[1] === 'd') {
+                                        return 'date'
+                                    }
+
+                                    return 'string'
+                                })()
+                            })
+
+                            break
+                        }
+                    }
+                }
+
+                return results
+            },
+            computeOptions(value) {
+                if (!value) {
+                    return
+                }
+
+                for(let key in value) {
+                    if(!this.defaultOptions.currency[key]) {
+                        continue
+                    }
+
+                    this.defaultOptions.currency[key] = value[key]
+                }
             },
             onClickAdd() {
                 this.$emit('onAdd')
@@ -50,7 +110,16 @@
         },
         data() {
             return {
-                selectedId: -1
+                selectedId: -1,
+                defaultOptions: {
+                    currency: {
+                        symbol: '$',
+                        position: 'before',
+                        thousandsSeparator: '.',
+                        decimalPlaces: 2,
+                        decimalSeparator: ',' 
+                    },
+                } 
             }
         }
     }

+ 5 - 2
src/components/common/Cart.vue

@@ -4,7 +4,7 @@
             h2.currency-cart-total {{ total | currency(...defaultOptions.currency) }}
         .cart-items-wrapper
             transition-group(name='list' tag='ul' class='cart-items')
-                cart-item(v-for='(item, index) in items' :key='index' :index='index' :item='item' @onChange='onItemChanged' @onClickIncrement='onIncrementQty' @onClickDecrement='onDecrementQty' @onClickMoney='onChangePrice' @onClickDelete='onDeleteItem' :options='defaultOptions.currency')
+                cart-item(v-for='(item, index) in items' :key='index' :index='index' :item='item' @onClickQuantity='onClickQuantity' @onChange='onItemChanged' @onClickIncrement='onIncrementQty' @onClickDecrement='onDecrementQty' @onClickMoney='onChangePrice' @onClickUndo='onUndoPrice' @onClickDelete='onDeleteItem' :options='defaultOptions.currency')
 </template>
 
 <script>
@@ -43,7 +43,7 @@
                 let sum = 0
 
                 for (let item of this.items) {
-                    sum = sum + ((item.price || 1) * (item.quantity || 1))
+                    sum = sum + ((item.price || 0) * (item.quantity || 0))
                 }
 
                 this.total = sum
@@ -62,6 +62,9 @@
             onChangePrice(item) {
                 this.$emit('onChangePrice', item)
             },
+            onUndoPrice(item) {
+                this.$emit('onUndoPrice', item)
+            },
             onDeleteItem(item) {
                 this.$emit('onDeleteItem', item)
             }

+ 87 - 8
src/components/common/CartItem.vue

@@ -1,7 +1,7 @@
 <template lang="pug">
-    li.cart-item
+    li.cart-item(:class="{'cart-item-invalid': !isValid()}")
         h3.item-name {{ item.displayName }}
-        input.item-quantity(type='number' min='1' :value='item.quantity' readonly)
+        input.item-quantity(type='number' v-model.number='quantity' @focus='onFocus' @blur='onBlur')
         span.item-x x
         span.item-price {{ item.price | currency(...options) }}
         span.item-equals =
@@ -10,7 +10,8 @@
             .cart-item-options
                 .cart-item-option(class='fa fa-plus' @click='onClickIncrement')
                 .cart-item-option(class='fa fa-minus' @click='onClickDecrement')
-                .cart-item-option(class="fa fa-money" @click='onClickMoney')
+                .cart-item-option(class='fa fa-money' @click='onClickMoney')
+                .cart-item-option(class='fa fa-undo' @click='onClickUndo')
                 .cart-item-option(class='fa fa-trash' @click='onClickDelete')
 </template>
 
@@ -37,6 +38,24 @@
                 }
             }
         },
+        computed: {
+            quantity: {
+                get() {
+                    return this.item.quantity
+                },
+                set(value) {
+                    this.input = !value ? 1 : value
+
+                    if (this.editing) {
+                        this.handleEditing(value)
+
+                        return
+                    }
+
+                    this.computeQuantity()
+                }
+            }
+        },
         watch: {
             item: {
                 handler(value) {
@@ -47,20 +66,73 @@
             }
         },
         methods: {
+            handleEditing(value) {
+                if (value === '') {
+                    clearTimeout(this.inputDaemon)
+                    return
+                }
+
+                if (this.inputDaemon !== null) {
+                    clearTimeout(this.inputDaemon)
+                }
+
+                this.inputDaemon = setTimeout(() => {
+                    this.computeQuantity()
+                }, 300)
+            },
+            computeQuantity() {
+                if (this.input > this.item.quantity) {
+                    this.onClickIncrement()
+                } else {
+                    this.onClickDecrement()
+                }
+            },
+            onFocus() {
+                this.editing = true
+                this.input = 0
+            },
+            onBlur() {
+                this.editing = false
+                this.input = 0
+            },
+            onInputChange(e) {
+                console.log(e)
+            },
             onChange(item) {
                 this.$emit('onChange', item)
             },
             onClickIncrement() {
-                this.$emit('onClickIncrement', this.item)
+                this.$emit('onClickIncrement', {
+                    id: this.item.id,
+                    quantity: this.editing ? this.input : 1
+                })
             },
             onClickDecrement() {
-                this.$emit('onClickDecrement', this.item)
+                this.$emit('onClickDecrement', {
+                    id: this.item.id,
+                    quantity: this.editing ? this.input : -1
+                })
             },
             onClickMoney() {
                 this.$emit('onClickMoney', this.item)
             },
+            onClickUndo() {
+                this.$emit('onClickUndo', this.item)
+            },
             onClickDelete() {
-                this.$emit('onClickDelete', this.item)
+                this.$emit('onClickDelete', {
+                    id: this.item.id
+                })
+            },
+            isValid() {
+                return this.item.price > 0
+            }
+        },
+        data() {
+            return {
+                editing: false,
+                input: 0,
+                inputDaemon: null
             }
         }
     }
@@ -75,6 +147,8 @@
         border-bottom: 1px solid $app-border-color
         box-sizing: border-box
         position: relative
+        &.cart-item-invalid
+            border-bottom: 2px solid $app-error-color
         &:nth-child(1)
             border-top: 1px solid $app-border-color
         &:hover
@@ -88,12 +162,15 @@
             font-size: 8pt
             display: inline-block
         .item-quantity
-            width: 50px
+            width: 60px
             height: 28px
             margin-top: 6px
             text-align: right
             float: left
             display: inline-block
+            user-select: none
+            cursor: pointer
+            border-radius: 0
         .item-x
             width: 20px
             height: 20px
@@ -130,7 +207,7 @@
             display: flex
             justify-content: center
             .cart-item-options
-                width: 100px
+                width: 120px
                 height: 20px
                 border: 1px solid #d3d3d3
                 border-bottom: none
@@ -152,6 +229,8 @@
                             color: #ffc107
                         &.fa-money:hover
                             color: #4caf50
+                        &.fa-undo:hover
+                            color: #3f51b5
                         &.fa-trash:hover
                             color: #f44336
 </style>

+ 14 - 2
src/store/modules/cart.js

@@ -71,7 +71,15 @@ const mutations = {
         let productFound = state.cartItems.find(item => item.id === payload.id)
 
         if (productFound) {
-            productFound.quantity = productFound.quantity + 1
+            if (payload.quantity > 1) {
+                if (productFound.quantity === payload.quantity) {
+                    productFound.quantity = productFound.quantity + 1    
+                } else {
+                    productFound.quantity = payload.quantity
+                }
+            } else {
+                productFound.quantity = productFound.quantity + payload.quantity
+            }
             return
         }
 
@@ -90,7 +98,11 @@ const mutations = {
         }
 
         if (payload.mode === 'partial') {
-            state.cartItems[productFoundIndex].quantity = state.cartItems[productFoundIndex].quantity - 1
+            if (payload.item.quantity !== -1) {
+                state.cartItems[productFoundIndex].quantity = payload.item.quantity
+            } else {
+                state.cartItems[productFoundIndex].quantity = state.cartItems[productFoundIndex].quantity - 1
+            }
         } else {
             state.cartItems.splice(productFoundIndex, 1)
         }