mirror of
				https://github.com/crater-invoice/crater.git
				synced 2025-11-04 06:23:17 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			418 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			418 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
<template>
 | 
						|
  <tr class="item-row invoice-item-row">
 | 
						|
    <td colspan="5">
 | 
						|
      <table class="full-width">
 | 
						|
        <colgroup>
 | 
						|
          <col style="width: 40%;">
 | 
						|
          <col style="width: 10%;">
 | 
						|
          <col style="width: 15%;">
 | 
						|
          <col v-if="discountPerItem === 'YES'" style="width: 15%;">
 | 
						|
          <col style="width: 15%;">
 | 
						|
        </colgroup>
 | 
						|
        <tbody>
 | 
						|
          <tr>
 | 
						|
            <td class="">
 | 
						|
              <div class="item-select-wrapper">
 | 
						|
                <div class="sort-icon-wrapper handle">
 | 
						|
                  <font-awesome-icon
 | 
						|
                    class="sort-icon"
 | 
						|
                    icon="grip-vertical"
 | 
						|
                  />
 | 
						|
                </div>
 | 
						|
                <item-select
 | 
						|
                  ref="itemSelect"
 | 
						|
                  :invalid="$v.item.name.$error"
 | 
						|
                  :invalid-description="$v.item.description.$error"
 | 
						|
                  :item="item"
 | 
						|
                  @search="searchVal"
 | 
						|
                  @select="onSelectItem"
 | 
						|
                  @deselect="deselectItem"
 | 
						|
                  @onDesriptionInput="$v.item.description.$touch()"
 | 
						|
                  @onSelectItem="isSelected = true"
 | 
						|
                />
 | 
						|
              </div>
 | 
						|
            </td>
 | 
						|
            <td class="text-right">
 | 
						|
              <base-input
 | 
						|
                v-model="item.quantity"
 | 
						|
                :invalid="$v.item.quantity.$error"
 | 
						|
                type="text"
 | 
						|
                small
 | 
						|
                @keyup="updateItem"
 | 
						|
                @input="$v.item.quantity.$touch()"
 | 
						|
              />
 | 
						|
              <div v-if="$v.item.quantity.$error">
 | 
						|
                <span v-if="!$v.item.quantity.maxLength" class="text-danger">{{ $t('validation.quantity_maxlength') }}</span>
 | 
						|
              </div>
 | 
						|
            </td>
 | 
						|
            <td class="text-left">
 | 
						|
              <div class="d-flex flex-column">
 | 
						|
                <div class="flex-fillbd-highlight">
 | 
						|
                  <div class="base-input">
 | 
						|
                    <money
 | 
						|
                      v-model="price"
 | 
						|
                      v-bind="customerCurrency"
 | 
						|
                      class="input-field"
 | 
						|
                      @input="$v.item.price.$touch()"
 | 
						|
                    />
 | 
						|
                  </div>
 | 
						|
                  <div v-if="$v.item.price.$error">
 | 
						|
                    <span v-if="!$v.item.price.maxLength" class="text-danger">{{ $t('validation.price_maxlength') }}</span>
 | 
						|
                  </div>
 | 
						|
                </div>
 | 
						|
 | 
						|
              </div>
 | 
						|
            </td>
 | 
						|
            <td v-if="discountPerItem === 'YES'" class="">
 | 
						|
              <div class="d-flex flex-column bd-highlight">
 | 
						|
                <div
 | 
						|
                  class="btn-group flex-fill bd-highlight"
 | 
						|
                  role="group"
 | 
						|
                >
 | 
						|
                  <base-input
 | 
						|
                    v-model="discount"
 | 
						|
                    :invalid="$v.item.discount_val.$error"
 | 
						|
                    input-class="item-discount"
 | 
						|
                    @input="$v.item.discount_val.$touch()"
 | 
						|
                  />
 | 
						|
                  <v-dropdown :show-arrow="false" theme-light>
 | 
						|
                    <button
 | 
						|
                      slot="activator"
 | 
						|
                      type="button"
 | 
						|
                      class="btn item-dropdown dropdown-toggle"
 | 
						|
                      data-toggle="dropdown"
 | 
						|
                      aria-haspopup="true"
 | 
						|
                      aria-expanded="false"
 | 
						|
                    >
 | 
						|
                      {{ item.discount_type == 'fixed' ? currency.symbol : '%' }}
 | 
						|
                    </button>
 | 
						|
                    <v-dropdown-item>
 | 
						|
                      <a class="dropdown-item" href="#" @click.prevent="selectFixed" >
 | 
						|
                        {{ $t('general.fixed') }}
 | 
						|
                      </a>
 | 
						|
                    </v-dropdown-item>
 | 
						|
                    <v-dropdown-item>
 | 
						|
                      <a class="dropdown-item" href="#" @click.prevent="selectPercentage">
 | 
						|
                        {{ $t('general.percentage') }}
 | 
						|
                      </a>
 | 
						|
                    </v-dropdown-item>
 | 
						|
                  </v-dropdown>
 | 
						|
                </div>
 | 
						|
                <!-- <div v-if="$v.item.discount.$error"> discount error </div> -->
 | 
						|
              </div>
 | 
						|
            </td>
 | 
						|
            <td class="text-right">
 | 
						|
              <div class="item-amount">
 | 
						|
                <span>
 | 
						|
                  <div v-html="$utils.formatMoney(total, currency)" />
 | 
						|
                </span>
 | 
						|
 | 
						|
                <div class="remove-icon-wrapper">
 | 
						|
                  <font-awesome-icon
 | 
						|
                    v-if="index > 0"
 | 
						|
                    class="remove-icon"
 | 
						|
                    icon="trash-alt"
 | 
						|
                    @click="removeItem"
 | 
						|
                  />
 | 
						|
                </div>
 | 
						|
              </div>
 | 
						|
            </td>
 | 
						|
          </tr>
 | 
						|
          <tr v-if="taxPerItem === 'YES'" class="tax-tr">
 | 
						|
            <td />
 | 
						|
            <td colspan="4">
 | 
						|
              <tax
 | 
						|
                v-for="(tax, index) in item.taxes"
 | 
						|
                :key="tax.id"
 | 
						|
                :index="index"
 | 
						|
                :tax-data="tax"
 | 
						|
                :taxes="item.taxes"
 | 
						|
                :discounted-total="total"
 | 
						|
                :total-tax="totalSimpleTax"
 | 
						|
                :total="total"
 | 
						|
                :currency="currency"
 | 
						|
                @update="updateTax"
 | 
						|
                @remove="removeTax"
 | 
						|
              />
 | 
						|
            </td>
 | 
						|
          </tr>
 | 
						|
        </tbody>
 | 
						|
      </table>
 | 
						|
    </td>
 | 
						|
  </tr>
 | 
						|
</template>
 | 
						|
<script>
 | 
						|
import Guid from 'guid'
 | 
						|
import { validationMixin } from 'vuelidate'
 | 
						|
import { mapActions, mapGetters } from 'vuex'
 | 
						|
import TaxStub from '../../stub/tax'
 | 
						|
import InvoiceStub from '../../stub/invoice'
 | 
						|
import ItemSelect from './ItemSelect'
 | 
						|
import Tax from './Tax'
 | 
						|
const { required, minValue, between, maxLength } = require('vuelidate/lib/validators')
 | 
						|
 | 
						|
export default {
 | 
						|
  components: {
 | 
						|
    Tax,
 | 
						|
    ItemSelect
 | 
						|
  },
 | 
						|
  mixins: [validationMixin],
 | 
						|
  props: {
 | 
						|
    itemData: {
 | 
						|
      type: Object,
 | 
						|
      default: null
 | 
						|
    },
 | 
						|
    index: {
 | 
						|
      type: Number,
 | 
						|
      default: null
 | 
						|
    },
 | 
						|
    type: {
 | 
						|
      type: String,
 | 
						|
      default: ''
 | 
						|
    },
 | 
						|
    currency: {
 | 
						|
      type: [Object, String],
 | 
						|
      required: true
 | 
						|
    },
 | 
						|
    taxPerItem: {
 | 
						|
      type: String,
 | 
						|
      default: ''
 | 
						|
    },
 | 
						|
    discountPerItem: {
 | 
						|
      type: String,
 | 
						|
      default: ''
 | 
						|
    }
 | 
						|
  },
 | 
						|
  data () {
 | 
						|
    return {
 | 
						|
      isClosePopup: false,
 | 
						|
      itemSelect: null,
 | 
						|
      item: {...this.itemData},
 | 
						|
      maxDiscount: 0,
 | 
						|
      money: {
 | 
						|
        decimal: '.',
 | 
						|
        thousands: ',',
 | 
						|
        prefix: '$ ',
 | 
						|
        precision: 2,
 | 
						|
        masked: false
 | 
						|
      },
 | 
						|
      isSelected: false
 | 
						|
    }
 | 
						|
  },
 | 
						|
  computed: {
 | 
						|
    ...mapGetters('item', [
 | 
						|
      'items'
 | 
						|
    ]),
 | 
						|
    ...mapGetters('modal', [
 | 
						|
      'modalActive'
 | 
						|
    ]),
 | 
						|
    ...mapGetters('currency', [
 | 
						|
      'defaultCurrencyForInput'
 | 
						|
    ]),
 | 
						|
    customerCurrency () {
 | 
						|
      if (this.currency) {
 | 
						|
        return {
 | 
						|
          decimal: this.currency.decimal_separator,
 | 
						|
          thousands: this.currency.thousand_separator,
 | 
						|
          prefix: this.currency.symbol + ' ',
 | 
						|
          precision: this.currency.precision,
 | 
						|
          masked: false
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        return this.defaultCurrenctForInput
 | 
						|
      }
 | 
						|
    },
 | 
						|
    subtotal () {
 | 
						|
      return this.item.price * this.item.quantity
 | 
						|
    },
 | 
						|
    discount: {
 | 
						|
      get: function () {
 | 
						|
        return this.item.discount
 | 
						|
      },
 | 
						|
      set: function (newValue) {
 | 
						|
        if (this.item.discount_type === 'percentage') {
 | 
						|
          this.item.discount_val = (this.subtotal * newValue) / 100
 | 
						|
        } else {
 | 
						|
          this.item.discount_val = newValue * 100
 | 
						|
        }
 | 
						|
 | 
						|
        this.item.discount = newValue
 | 
						|
      }
 | 
						|
    },
 | 
						|
    total () {
 | 
						|
      return this.subtotal - this.item.discount_val
 | 
						|
    },
 | 
						|
    totalSimpleTax () {
 | 
						|
      return window._.sumBy(this.item.taxes, function (tax) {
 | 
						|
        if (!tax.compound_tax) {
 | 
						|
          return tax.amount
 | 
						|
        }
 | 
						|
 | 
						|
        return 0
 | 
						|
      })
 | 
						|
    },
 | 
						|
    totalCompoundTax () {
 | 
						|
      return window._.sumBy(this.item.taxes, function (tax) {
 | 
						|
        if (tax.compound_tax) {
 | 
						|
          return tax.amount
 | 
						|
        }
 | 
						|
 | 
						|
        return 0
 | 
						|
      })
 | 
						|
    },
 | 
						|
    totalTax () {
 | 
						|
      return this.totalSimpleTax + this.totalCompoundTax
 | 
						|
    },
 | 
						|
    price: {
 | 
						|
      get: function () {
 | 
						|
        if (parseFloat(this.item.price) > 0) {
 | 
						|
          return this.item.price / 100
 | 
						|
        }
 | 
						|
 | 
						|
        return this.item.price
 | 
						|
      },
 | 
						|
      set: function (newValue) {
 | 
						|
        if (parseFloat(newValue) > 0) {
 | 
						|
          this.item.price = newValue * 100
 | 
						|
          this.maxDiscount = this.item.price
 | 
						|
        } else {
 | 
						|
          this.item.price = newValue
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
  watch: {
 | 
						|
    item: {
 | 
						|
      handler: 'updateItem',
 | 
						|
      deep: true
 | 
						|
    },
 | 
						|
    subtotal (newValue) {
 | 
						|
      if (this.item.discount_type === 'percentage') {
 | 
						|
        this.item.discount_val = (this.item.discount * newValue) / 100
 | 
						|
      }
 | 
						|
    },
 | 
						|
    modalActive (val) {
 | 
						|
      if (!val) {
 | 
						|
        this.isSelected = false
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
  validations () {
 | 
						|
    return {
 | 
						|
      item: {
 | 
						|
        name: {
 | 
						|
          required
 | 
						|
        },
 | 
						|
        quantity: {
 | 
						|
          required,
 | 
						|
          minValue: minValue(1),
 | 
						|
          maxLength: maxLength(20)
 | 
						|
        },
 | 
						|
        price: {
 | 
						|
          required,
 | 
						|
          minValue: minValue(1),
 | 
						|
          maxLength: maxLength(20)
 | 
						|
        },
 | 
						|
        discount_val: {
 | 
						|
          between: between(0, this.maxDiscount)
 | 
						|
        },
 | 
						|
        description: {
 | 
						|
          maxLength: maxLength(255)
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
  created () {
 | 
						|
    window.hub.$on('checkItems', this.validateItem)
 | 
						|
    window.hub.$on('newItem', (val) => {
 | 
						|
      if (!this.item.item_id && this.modalActive && this.isSelected) {
 | 
						|
        this.onSelectItem(val)
 | 
						|
      }
 | 
						|
    })
 | 
						|
  },
 | 
						|
  methods: {
 | 
						|
    updateTax (data) {
 | 
						|
      this.$set(this.item.taxes, data.index, data.item)
 | 
						|
 | 
						|
      let lastTax = this.item.taxes[this.item.taxes.length - 1]
 | 
						|
 | 
						|
      if (lastTax.tax_type_id !== 0) {
 | 
						|
        this.item.taxes.push({...TaxStub, id: Guid.raw()})
 | 
						|
      }
 | 
						|
 | 
						|
      this.updateItem()
 | 
						|
    },
 | 
						|
    removeTax (index) {
 | 
						|
      this.item.taxes.splice(index, 1)
 | 
						|
 | 
						|
      this.updateItem()
 | 
						|
    },
 | 
						|
    taxWithPercentage ({ name, percent }) {
 | 
						|
      return `${name} (${percent}%)`
 | 
						|
    },
 | 
						|
    searchVal (val) {
 | 
						|
      this.item.name = val
 | 
						|
    },
 | 
						|
    deselectItem () {
 | 
						|
      this.item = {...InvoiceStub, id: this.item.id, taxes: [{...TaxStub, id: Guid.raw()}]}
 | 
						|
      this.$nextTick(() => {
 | 
						|
        this.$refs.itemSelect.$refs.baseSelect.$refs.search.focus()
 | 
						|
      })
 | 
						|
    },
 | 
						|
    onSelectItem (item) {
 | 
						|
      this.item.name = item.name
 | 
						|
      this.item.price = item.price
 | 
						|
      this.item.item_id = item.id
 | 
						|
      this.item.description = item.description
 | 
						|
 | 
						|
      // if (this.item.taxes.length) {
 | 
						|
      //   this.item.taxes = {...item.taxes}
 | 
						|
      // }
 | 
						|
    },
 | 
						|
    selectFixed () {
 | 
						|
      if (this.item.discount_type === 'fixed') {
 | 
						|
        return
 | 
						|
      }
 | 
						|
 | 
						|
      this.item.discount_val = this.item.discount * 100
 | 
						|
      this.item.discount_type = 'fixed'
 | 
						|
    },
 | 
						|
    selectPercentage () {
 | 
						|
      if (this.item.discount_type === 'percentage') {
 | 
						|
        return
 | 
						|
      }
 | 
						|
 | 
						|
      this.item.discount_val = (this.subtotal * this.item.discount) / 100
 | 
						|
 | 
						|
      this.item.discount_type = 'percentage'
 | 
						|
    },
 | 
						|
    updateItem () {
 | 
						|
      this.$emit('update', {
 | 
						|
        'index': this.index,
 | 
						|
        'item': {
 | 
						|
          ...this.item,
 | 
						|
          total: this.total,
 | 
						|
          totalSimpleTax: this.totalSimpleTax,
 | 
						|
          totalCompoundTax: this.totalCompoundTax,
 | 
						|
          totalTax: this.totalTax,
 | 
						|
          tax: this.totalTax,
 | 
						|
          taxes: [...this.item.taxes]
 | 
						|
        }
 | 
						|
      })
 | 
						|
    },
 | 
						|
    removeItem () {
 | 
						|
      this.$emit('remove', this.index)
 | 
						|
    },
 | 
						|
    validateItem () {
 | 
						|
      this.$v.item.$touch()
 | 
						|
 | 
						|
      if (this.item !== null) {
 | 
						|
        this.$emit('itemValidate', this.index, !this.$v.$invalid)
 | 
						|
      } else {
 | 
						|
        this.$emit('itemValidate', this.index, false)
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
</script>
 |