瀏覽代碼

[ADD] cart item discount

Gogs 7 年之前
父節點
當前提交
88fb753f35

+ 4 - 0
controllers/main.py

@@ -147,6 +147,8 @@ class PosSales(http.Controller):
             'variantCount': product.product_variant_count,
             'quantity': 1,
             'price': product.list_price,
+            'minimumPrice': product.minimum_price,
+            'maximumPrice': product.maximum_price,
             'discount': 0,
             'variants': [{
                 'id': variant.id,
@@ -157,6 +159,8 @@ class PosSales(http.Controller):
                 'listPrice': variant.list_price,
                 'quantity': 1,
                 'price': variant.list_price,
+                'minimumPrice': product.minimum_price,
+                'maximumPrice': product.maximum_price,
                 'discount': 0,
             } for variant in product.product_variant_ids if variant.active]
         } for product in request.env['product.template'].search([('sale_ok', '=', True), ('list_price', '>', 0), ('active', '=', True)])]

+ 2 - 1
src/assets/_variables.sass

@@ -4,4 +4,5 @@ $app-light-color: #f5f5f5
 $app-bg-color: #fff
 $app-border-color: #d3d3d3
 $app-title-color: #d3d3d3
-$app-separator-color: #9e9e9e
+$app-separator-color: #9e9e9e
+$app-error-color: #ef9a9a

+ 4 - 1
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' @onClickDelete='onDeleteItem' :options='defaultOptions.currency')
+                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')
 </template>
 
 <script>
@@ -59,6 +59,9 @@
             onDecrementQty(item) {
                 this.$emit('onDecrementQty', item)
             },
+            onChangePrice(item) {
+                this.$emit('onChangePrice', item)
+            },
             onDeleteItem(item) {
                 this.$emit('onDeleteItem', item)
             }

+ 9 - 3
src/components/common/CartItem.vue

@@ -3,13 +3,14 @@
         h3.item-name {{ item.displayName }}
         input.item-quantity(type='number' min='1' :value='item.quantity')
         span.item-x x
-        span.item-price {{ (item.price || 1) | currency(...options) }}
+        span.item-price {{ item.price | currency(...options) }}
         span.item-equals =
-        span.item-subtotal {{ ((item.price || 1) * (item.quantity || 1)) | currency(...options) }}
+        span.item-subtotal {{ (item.price * (item.quantity || 1)) | currency(...options) }}
         .cart-item-options-wrapper
             .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-trash' @click='onClickDelete')
 </template>
 
@@ -55,6 +56,9 @@
             onClickDecrement() {
                 this.$emit('onClickDecrement', this.item)
             },
+            onClickMoney() {
+                this.$emit('onClickMoney', this.item)
+            },
             onClickDelete() {
                 this.$emit('onClickDelete', this.item)
             }
@@ -126,7 +130,7 @@
             display: flex
             justify-content: center
             .cart-item-options
-                width: 90px
+                width: 100px
                 height: 20px
                 border: 1px solid #d3d3d3
                 border-bottom: none
@@ -146,6 +150,8 @@
                             color: #2196f3
                         &.fa-minus:hover
                             color: #ffc107
+                        &.fa-money:hover
+                            color: #4caf50
                         &.fa-trash:hover
                             color: #f44336
 </style>

+ 9 - 0
src/components/filters/absolute.js

@@ -0,0 +1,9 @@
+/**
+ * 
+ * @param {*} value 
+ */
+const absolute = (value = 0) => {
+    return Math.abs(value)
+}
+
+export default absolute

+ 184 - 0
src/components/modals/DiscountModal.vue

@@ -0,0 +1,184 @@
+<template lang="pug">
+    modal(name='product-discount' transition='nice-modal-fade' @before-close='beforeClose' :classes="['v--modal', 'product-discount']")
+        form
+            .discount-item
+                label.discount-label Precio unitario
+                input.discount-input(:value='(!item || item.price) | currency(...options)' readonly)
+            .discount-item
+                label.discount-label Precio mínimo
+                input.discount-input(:value='(!item || item.minimumPrice) | currency(...options)' readonly)
+            .discount-item
+                label.discount-label Precio máximo
+                input.discount-input(:value='(!item || item.maximumPrice) | currency(...options)' readonly)
+            hr
+            .discount-item
+                label.discount-label Precio aplicado
+                input.discount-input(v-model='formattedAmmount' :class="{'discount-input-invalid': !isValid()}" autofocus)
+            .discount-item
+                label.discount-label {{ discount < 0 ? 'Ganancia' : 'Descuento' }}
+                input.discount-input(:value='discount | absolute | currency(...options)' readonly)
+            .discount-options
+                button.discount-button(@click='onAccept' :disabled='isValid() === false') Aceptar
+                button.discount-button(@click='onCancel') Cancelar
+</template>
+
+<script>
+    export default {
+        props: {
+            item: {
+                type: Object,
+                default: {
+                    price: 0,
+                    minimumPrice: 0,
+                    maximumPrice: 0
+                }
+            },
+            options: {
+                type: Object,
+                default: {
+                    symbol: '$',
+                    position: 'before',
+                    thousandsSeparator: '.',
+                    decimalPlaces: 2,
+                    decimalSeparator: ',' 
+                }
+            },
+            show: {
+                type: Boolean,
+                default: false
+            }
+        },
+        computed: {
+            formattedAmmount: {
+                get() {
+                    let formatted = this.$options.filters.currency(this.ammount, {...this.options})
+
+                    return this.ammount !== 0 ? formatted : formatted.replace(/\d/, '')
+                },
+                set(value) {
+                    value = value.replace(/[\.|,](\d{0,2}$)/, '?$1').split(/\?/)
+                    value[0] = value[0].replace(/[^0-9]/g, '')
+                    value = Number.parseFloat(value.join('.')) || 0
+
+                    this.ammount = value
+                    this.computeDiscount()
+                }
+            }
+        },
+        watch: {
+            show(value) {
+                if (value) {
+                    this.$modal.show('product-discount')
+                } else {
+                    this.ammount = 0
+                    this.discount = 0
+
+                    this.$modal.hide('product-discount')
+                }
+            }
+        },
+        methods: {
+            beforeClose(e) {
+                if (this.show) {
+                    e.stop()
+                }
+            },
+            onAccept() {
+                this.$emit('onAccept', this.ammount)
+            },
+            onCancel() {
+                this.$emit('onCancel')
+            },
+            computeDiscount() {
+                if (this.ammount !== 0) {
+                    this.discount = this.item.price - this.ammount
+                } else {
+                    this.discount = 0
+                }
+            },
+            isValid() {
+                if (!this.item) {
+                    return false
+                }
+
+                if (this.ammount === 0) {
+                    return false
+                }
+
+                if (this.item.minimumPrice === 0 && this.item.maximumPrice === 0) {
+                    return true
+                }
+
+                if (this.ammount >= this.item.minimumPrice && this.ammount <= this.item.maximumPrice) {
+                    return true
+                }
+
+                return false
+            }
+        },
+        data() {
+            return {
+                discount: 0,
+                ammount: 0
+            }
+        }
+    }
+</script>
+
+<style lang="sass">
+    @import '../../assets/variables'
+    .product-discount
+        width: 600px
+        height: 340px !important
+        form
+            width: 100%
+            height: 290px
+            padding: 15px
+            .discount-item
+                width: 100%
+                height: 45px
+                margin-bottom: 10px
+                &:nth-child(1)
+                    .discount-input
+                        border: none
+                &:nth-child(2), &:nth-child(3), &:nth-child(6) 
+                    height: 35px
+                    margin-bottom: 5px
+                    .discount-label
+                        width: 30%
+                        height: 35px
+                        font-size: 10pt
+                        color: $app-dark-color
+                    .discount-input
+                        width: 70%
+                        height: 35px
+                        font-size: 18pt
+                        text-align: right
+                        border: none
+                .discount-label
+                    width: 30%
+                    height: 45px
+                    font-size: 14pt
+                .discount-input
+                    width: 70%
+                    height: 45px
+                    font-size: 28pt
+                    text-align: right
+                    border-radius: 0
+                    &.discount-input-invalid
+                        border-color: $app-error-color
+                        box-shadow: 1px 1px 2px $app-error-color, -1px -1px 2px $app-error-color
+                        &:focus
+                            outline: none
+        .discount-options
+            float: right
+            .discount-button
+                width: 160px
+                height: 40px
+                border: none
+                box-shadow: none
+                border-radius: 0
+                margin-right: 5px
+                background: $app-main-color
+                color: $app-bg-color
+</style>

+ 9 - 3
src/components/steps/Product.vue

@@ -5,7 +5,8 @@
             card-grid(:items='visibleProducts' :loading='loadingProducts' @onAdd='showProductForm' @onSelect='selectProduct')
             product-modal(:show='showingProductForm' @onAccept='submitProduct' @onCancel='hideProductForm')
             variant-modal
-        cart(:items='cartItems' @onTotalComputed='changeCartTotal' @onIncrementQty='addToCart' @onDecrementQty='decreaseFromCart' @onDeleteItem='removeFromCart' :options='selectedCurrency')
+            discount-modal(:item='itemToDiscount' :options='selectedCurrency' :show='!!itemToDiscount' @onAccept='applyPrice' @onCancel='applyPrice')
+        cart(:items='cartItems' @onTotalComputed='changeCartTotal' @onIncrementQty='addToCart' @onChangePrice='changePrice' @onDecrementQty='decreaseFromCart' @onDeleteItem='removeFromCart' :options='selectedCurrency')
 </template>
 
 <script>
@@ -13,8 +14,9 @@
     import { Searcher, CardGrid, Cart } from '@@/common'
     import ProductModal from '@@/modals/ProductModal'
     import VariantModal from '@@/modals/VariantModal'
+    import DiscountModal from '@@/modals/DiscountModal'
 
-    import { FILTER_PRODUCTS, SELECT_PRODUCT, SHOW_PRODUCT_FORM, HIDE_PRODUCT_FORM, SUBMIT_PRODUCT, CHANGE_CART_TOTAL, ADD_TO_CART, DECREASE_FROM_CART, REMOVE_FROM_CART } from '@/constants/actionTypes'
+    import { FILTER_PRODUCTS, SELECT_PRODUCT, SHOW_PRODUCT_FORM, HIDE_PRODUCT_FORM, SUBMIT_PRODUCT, CHANGE_CART_TOTAL, ADD_TO_CART, DECREASE_FROM_CART, CHANGE_PRICE, APPLY_PRICE, REMOVE_FROM_CART } from '@/constants/actionTypes'
 
     export default {
         components: {
@@ -22,7 +24,8 @@
             CardGrid,
             Cart,
             ProductModal,
-            VariantModal
+            VariantModal,
+            DiscountModal
         },
         computed: {
             ...mapGetters([
@@ -31,6 +34,7 @@
                 'loadingProducts',
                 'showingProductForm',
                 'cartItems',
+                'itemToDiscount',
                 'selectedCurrency'
             ])
         },
@@ -43,6 +47,8 @@
             CHANGE_CART_TOTAL,
             ADD_TO_CART,
             DECREASE_FROM_CART,
+            CHANGE_PRICE,
+            APPLY_PRICE,
             REMOVE_FROM_CART
         ])
     }

+ 5 - 1
src/constants/actionTypes.js

@@ -76,6 +76,10 @@ const ADD_TO_CART = 'addToCart'
 
 const DECREASE_FROM_CART = 'decreaseFromCart'
 
+const CHANGE_PRICE = 'changePrice'
+
+const APPLY_PRICE = 'applyPrice'
+
 const REMOVE_FROM_CART = 'removeFromCart'
 
 const RESET_CART = 'resetCart'
@@ -106,5 +110,5 @@ export {
     INIT_DATE, RESET_DATE, // Date
     INIT_CUSTOMERS, FILTER_CUSTOMERS, SHOW_CUSTOMER_FORM, HIDE_CUSTOMER_FORM, SUBMIT_CUSTOMER, CREATE_CUSTOMER, RECEIVE_CUSTOMER, SELECT_CUSTOMER, RESET_CUSTOMER, // Customer
     INIT_CURRENCIES, RESET_CURRENCY, // Currencies
-    CHANGE_CART_TOTAL, ADD_TO_CART, DECREASE_FROM_CART, REMOVE_FROM_CART, RESET_CART // Cart
+    CHANGE_CART_TOTAL, ADD_TO_CART, DECREASE_FROM_CART, CHANGE_PRICE, APPLY_PRICE, REMOVE_FROM_CART, RESET_CART // Cart
 }

+ 5 - 1
src/constants/mutationTypes.js

@@ -76,6 +76,10 @@ const PULL_FROM_CART = 'pullFromCart'
 
 const SET_CART_TOTAL = 'setCartTotal'
 
+const SET_ITEM_TO_DISCOUNT = 'setItemToDiscount'
+
+const SET_ITEM_PRICE = 'setItemPrice'
+
 export {
     SET_MODE, SET_PROCESSING, SET_COMPLETED, SET_ERROR, // App
     SET_USER, SET_LOADING_USER, // User
@@ -85,5 +89,5 @@ export {
     SET_DATE, SET_LOADING_DATE, // Date
     SET_CUSTOMERS, SET_FILTERED_CUSTOMERS, SET_LOADING_CUSTOMERS, SET_SHOW_CUSTOMER_FORM, SET_SELECTED_CUSTOMER, ADD_CUSTOMER, // Customer
     SET_CURRENCIES, SET_LOADING_CURRENCIES, AUTOSELECT_CURRENCY, // Currency
-    PUSH_TO_CART, PULL_FROM_CART, SET_CART_TOTAL, SET_CART // Cart
+    PUSH_TO_CART, PULL_FROM_CART, SET_CART_TOTAL, SET_CART, SET_ITEM_TO_DISCOUNT, SET_ITEM_PRICE // Cart
 }

+ 3 - 1
src/index.js

@@ -4,14 +4,16 @@ import VueModal from 'vue-js-modal'
 import store from '@/store'
 
 import currency from '@@/filters/currency'
+import absolute from '@@/filters/absolute'
 
 Vue.filter('currency', currency)
+Vue.filter('absolute', absolute)
 
 Vue.use(VueModal)
 
 Vue.config.productionTip = false
 Vue.config.silent = true
-Vue.config.devTools = false
+Vue.config.devTools = true
 
 openerp.eiru_pos = (instance, local) => {
 

+ 55 - 4
src/store/modules/cart.js

@@ -2,12 +2,16 @@ import {
     SET_CART, 
     PUSH_TO_CART, 
     PULL_FROM_CART, 
-    SET_CART_TOTAL 
+    SET_CART_TOTAL,
+    SET_ITEM_TO_DISCOUNT,
+    SET_ITEM_PRICE
 } from '@/constants/mutationTypes'
 
 import { 
     ADD_TO_CART, 
-    DECREASE_FROM_CART, 
+    DECREASE_FROM_CART,
+    CHANGE_PRICE,
+    APPLY_PRICE,
     REMOVE_FROM_CART, 
     CHANGE_CART_TOTAL, 
     RESET_CART 
@@ -15,12 +19,14 @@ import {
 
 const initialState = {
     cartItems: [],
-    cartTotal: 0
+    cartTotal: 0,
+    itemToDiscount: null
 }
 
 const state = {
     cartItems: initialState.cartItems,
-    cartTotal: initialState.cartTotal
+    cartTotal: initialState.cartTotal,
+    itemToDiscount: null
 }
 
 const getters = {
@@ -37,6 +43,13 @@ const getters = {
      */
     cartTotal(state) {
         return state.cartTotal
+    },
+    /**
+     * 
+     * @param {*} state 
+     */
+    itemToDiscount(state) {
+        return state.itemToDiscount
     }
 }
 
@@ -85,6 +98,24 @@ const mutations = {
      */
     [SET_CART_TOTAL] (state, payload) {
         state.cartTotal = payload
+    },
+    /**
+     * 
+     * @param {*} state 
+     * @param {*} payload 
+     */
+    [SET_ITEM_TO_DISCOUNT] (state, payload) {
+        state.itemToDiscount = payload
+    },
+    /**
+     * 
+     * @param {*} state 
+     * @param {*} payload 
+     */
+    [SET_ITEM_PRICE] (state, payload) {
+        let foundProduct = state.cartItems.find(item => item.id === state.itemToDiscount.id)
+
+        foundProduct.price = payload
     }
 }
 
@@ -108,6 +139,26 @@ const actions = {
             mode: 'partial'
         })
     },
+    /**
+     * 
+     * @param {*} param0 
+     * @param {*} payload 
+     */
+    [CHANGE_PRICE] ({ commit }, payload) {
+        commit(SET_ITEM_TO_DISCOUNT, payload)
+    },
+    /**
+     * 
+     * @param {*} param0 
+     * @param {*} payload 
+     */
+    [APPLY_PRICE] ({ commit }, payload) {
+        if (payload) {
+            commit(SET_ITEM_PRICE, payload)
+        }
+
+        commit(SET_ITEM_TO_DISCOUNT, null)
+    },
     /**
      * 
      * @param {*} param0