Untitled

mail@pastecode.io avatar
unknown
plain_text
a year ago
15 kB
2
Indexable
Never
<template>
  <tr>
    <td
      v-if="!$vuetify.breakpoint.mobile"
      width="40%"
      class="d-block d-sm-table-cell"
    >
      <div>
        <v-autocomplete
          outlined
          :items="items"
          :disabled="disabled"
          item-value="id"
          item-text="name"
          cache-items
          eager
          dense
          v-model="currentItem"
          @change="applyItem"
          return-object
          :no-data-text="$t('main.empty_product_service')"
        >
        </v-autocomplete>
        <label>Description</label>
        <v-textarea
          class="text-md"
          :disabled="disabled"
          v-model="description"
          @change="itemDescriptionChange"
          filled
          outlined
          auto-grow
          rows="2"
          dense
        ></v-textarea>
      </div>
    </td>
    <td
      v-if="!$vuetify.breakpoint.mobile"
      class="d-block d-sm-table-cell"
      width="25%"
    >
      <div>
        <v-text-field
          type="number"
          dense
          outlined
          :disabled="disabled"
          @keyup="computeAmount"
          v-model="rawUnit_price"
          :prefix="invoiceSymbol"
          @change="itemPriceChange"
        >
        </v-text-field>
        <label>Tax</label>
        <v-select
          :disabled="disabled"
          :items="taxes"
          dense
          item-value="id"
          multiple
          item-text="display_name"
          :hint="tax_amount | toMoney | invoiceCurrencySymbol"
          v-model="applied_taxes"
          @change="calculateTax"
          persistent-hint
        >
          <template v-slot:prepend-item>
            <v-btn
              style="text-decoration: none"
              to="/settings?open=taxes"
              block
              rounded
              text
              color="blue"
              >Manage Taxes <v-icon small>mdi-arrow-right</v-icon></v-btn
            >
          </template>
          <template v-slot:selection="{ item, index }">
            <div v-if="index === 0" class="chip">
              <span class="text-sm">{{ item.name }}</span>
            </div>
            <span v-if="index === 1" class="grey--text text-caption">
              (+{{ applied_taxes.length - 1 }} others)
            </span>
          </template>
          <template v-slot:item="{ item }">
            <v-checkbox
              color="blue"
              :value="applied_taxes.includes(item.id)"
            ></v-checkbox>

            <span>
              {{ item.display_name }}({{ item.rate }}%)
              <small
                class="text--disabled d-block"
                v-if="item.type === 'Compound'"
              >
                Includes:
                <span v-for="st in item.sub_tax"
                  >{{ st.name }}({{ st.rate }}%),
                </span>
              </small>
            </span>
          </template>
        </v-select>
      </div>
    </td>
    <td
      v-if="!$vuetify.breakpoint.mobile"
      class="d-block d-sm-table-cell"
      width="10%"
    >
      <v-text-field
        type="number"
        dense
        outlined
        :disabled="disabled"
        @keyup="computeAmount"
        @change="itemQuantityChange"
        v-model="invoice_quantity"
        :rules="requiredRules"
      >
      </v-text-field>

      <v-menu
        max-width="500px"
        min-width="400px"
        :close-on-content-click="false"
        transition="slide-y-transition"
      >
        <template v-slot:activator="{ on, attr }">
          <v-btn
            v-on="on"
            v-bind="attr"
            color="blue"
            text
            :disabled="!Boolean(currentItem)"
          >
            Discount:{{ discount_amount | toMoney | invoiceCurrencySymbol }}
            <v-icon>mdi-chevron-down</v-icon>
          </v-btn>
        </template>
        <v-list>
          <v-list-item>
            <v-list-item-content>
              <v-list-item-title
                >Amount:{{
                  discount_amount | toMoney | invoiceCurrencySymbol
                }}</v-list-item-title
              >
              <v-list-item-subtitle
                >{{
                  Number(discount_percent).toFixed(2)
                }}%</v-list-item-subtitle
              >
            </v-list-item-content>
          </v-list-item>

          <v-list-item>
            <v-switch
              color="blue"
              inset
              label="Enter discount % "
              v-model="percent"
              @click="switchPercent"
            >
            </v-switch>
          </v-list-item>
          <v-list-item v-if="percent">
            <v-text-field
              outlined
              label="Discount percent"
              v-model="discount_percent"
              @input="cumputeDiscountAmount()"
              @blur="computeAmount()"
              ref="discountPinput"
              filled
            >
            </v-text-field>
          </v-list-item>

          <v-list-item v-else>
            <v-text-field
              outlined
              label="Absolute discount amount"
              v-model="discount_amount"
              @input="cumputePercentage()"
              @blur="computeAmount()"
              ref="discount_amount_input"
              filled
            >
            </v-text-field>
          </v-list-item>
        </v-list>
      </v-menu>
    </td>

    <td
      v-if="!$vuetify.breakpoint.mobile"
      class="d-block d-sm-table-cell"
      width="40%"
    >
      <div class="d-flex justify-start">
        <p class="m-0">
          {{ invoice_amount.toFixed(2) | toMoney | invoiceCurrencySymbol }}
        </p>
        <v-btn color="red" icon small class="ml-3 m-0 up" @click="removeItem">
          <v-icon>mdi-trash-can</v-icon>
        </v-btn>
      </div>
      <br />
      <br />
      <strong
        >Amount due:{{
          (Number(amount_due) + Number(tax_amount))
            | toMoney
            | invoiceCurrencySymbol
        }}</strong
      >
    </td>

    <!-- only way to make responsive is to have separate td for mobile -->

    <td v-if="$vuetify.breakpoint.mobile" class="d-block d-sm-table-cell mb-5">
      <v-autocomplete
        outlined
        label="Item"
        :items="items"
        :disabled="disabled"
        item-value="id"
        item-text="name"
        cache-items
        eager
        dense
        v-model="currentItem"
        @change="applyItem"
        return-object
        :no-data-text="$t('main.empty_product_service')"
      >
      </v-autocomplete>
    </td>
    <td v-if="$vuetify.breakpoint.mobile" class="d-block d-sm-table-cell mb-5">
      <v-textarea
        class="text-md"
        :disabled="disabled"
        v-model="description"
        label="Description"
        @change="itemDescriptionChange"
        filled
        outlined
        auto-grow
        rows="2"
        dense
      ></v-textarea>
    </td>

    <td v-if="$vuetify.breakpoint.mobile" class="d-block d-sm-table-cell mb-5">
      <v-text-field
        type="number"
        dense
        label="Price"
        outlined
        :disabled="disabled"
        @keyup="computeAmount"
        v-model="rawUnit_price"
        :prefix="invoiceSymbol"
        @change="itemPriceChange"
      >
      </v-text-field>
    </td>
    <td v-if="$vuetify.breakpoint.mobile" class="d-block d-sm-table-cell mb-5">
      <v-select
        :disabled="disabled"
        :items="taxes"
        dense
        item-value="id"
        multiple
        label="Tax Amount"
        item-text="display_name"
        :hint="tax_amount | toMoney | invoiceCurrencySymbol"
        v-model="applied_taxes"
        @change="calculateTax"
        persistent-hint
      >
        <template v-slot:selection="{ item, index }">
          <div v-if="index === 0" small class="chip">
            <span class="text-sm">{{ item.name }}</span>
          </div>
          <span v-if="index === 1" class="grey--text caption"> (+1) </span>
        </template>
      </v-select>
    </td>

    <td v-if="$vuetify.breakpoint.mobile" class="d-block d-sm-table-cell mb-5">
      <v-text-field
        type="number"
        label="Quantity"
        dense
        outlined
        :disabled="disabled"
        @keyup="computeAmount"
        @change="itemQuantityChange"
        v-model="invoice_quantity"
        :rules="requiredRules"
      >
      </v-text-field>
    </td>

    <td v-if="$vuetify.breakpoint.mobile" class="d-block d-sm-table-cell">
      <p class="m-0">
        Amount: {{ invoice_amount.toFixed(4) | invoiceCurrencySymbol }}
      </p>
    </td>
    <td
      v-if="$vuetify.breakpoint.mobile"
      class="d-block d-sm-table-cell d-flex justify-center"
    >
      <v-btn color="red" icon large @click="removeItem">
        <v-icon>mdi-trash-can</v-icon>
      </v-btn>
    </td>
  </tr>
</template>

<script>
export default {
  name: "invoiceItem",
  props: ["items", "id", "defaultItem", "disabled", "index"],
  data() {
    return {
      discount_percent_input: 0,
      discount_percent: 0,
      discount_amount_input: 0,
      discount_amount: 0,
      percent: true,
      currentItem: null,
      description: "",
      rawUnit_price: "",
      applied_taxes: [],
      invoice_quantity: 0,
      tax_amount: 0,
      invoice_amount: 0,
      quantity_error: false,
      requiredRules: [(v) => !!v || this.$t("main.required")],
    };
  },
  watch: {
    discount_percent_input() {
      this.cumputeDiscountAmount();
      this.cumputePercentage();
    },

    discount_amount_input() {
      this.cumputeDiscountAmount();
      this.cumputePercentage();
    },

    defaultItem() {
      if (this.defaultItem) {
        this.currentItem = this.defaultItem;
        //this.applyItem();
        //this.computeAmount();
      }
    },
    invoice_amount() {
      this.computeAmount();
    },
  },
  computed: {
    amount_due() {
      return (
        Number(this.invoice_amount) - Number(this.discount_amount)
      ).toFixed(2);
    },
    taxes() {
      return this.$store.state.user.user_infor.current_business.taxes;
    },
    invoiceSymbol() {
      return this.$store.state.invoiceCurrencySymbol;
    },
  },
  methods: {
    cumputePercentage() {
      const amount = Number(this.invoice_amount);
      const d = Number(this.discount_amount);
      const p = d > 0 ? (d / amount) * 100 : 0;
      if (this.percent) {
        this.discount_percent = Number(p).toFixed(2);
      } else {
        this.discount_percent = p
      }
      return p;
    },
    cumputeDiscountAmount() {
      const amount = Number(this.invoice_amount);
      const p = Number(this.discount_percent);
      this.discount_amount =
        Number(p) > 0 ? ((Number(p) / 100) * Number(amount)).toFixed(2) : 0;

      return this.discount_amount;
    },
    switchPercent() {
      if (this.percent) {
        this.discount_percent = Number(this.discount_percent).toFixed(2);
      } else {
        this.discount_percent = Number(this.discount_percent);
      }
    },
    getUpdatedItem() {
      return {
        ...this.currentItem,
        description: this.description,
        unit_price: this.rawUnit_price,
        taxes: this.taxes,
        applied_taxes: this.applied_taxes,
        invoice_quantity: this.invoice_quantity,
        tax_amount: this.tax_amount,
        invoice_amount: this.invoice_amount,
        quantity_error: this.quantity_error,
        amount_due: Number(this.amount_due) + Number(this.tax_amount),
        discount_amount: this.discount_amount,
        discount_percent: this.discount_percent,
      };
    },
    applyItem() {
      if (this.currentItem && this.currentItem.id) {
        this.description = this.currentItem.description;
        this.rawUnit_price = this.currentItem.rawUnit_price;
        this.applied_taxes = this.currentItem.applied_taxes;
        this.invoice_quantity = this.currentItem.invoice_quantity;
        this.invoice_amount = this.currentItem.invoice_amount;
        this.discount_amount = this.currentItem.discount_amount;
        this.discount_percent = this.currentItem.discount_percent;

        this.calculateTax();
      }
    },
    removeItem() {
      this.$emit("remove", this.index);
    },
    itemDescriptionChange() {
      this.$emit("change", this.id, this.getUpdatedItem());
    },
    computeAmount() {
      this.cumputeDiscountAmount();
      this.cumputePercentage();

      if (this.invoice_quantity) {
        this.invoice_amount =
          Number(this.rawUnit_price) * Number(this.invoice_quantity);
      }
      this.calculateTax();
      this.$emit("change", this.id, this.getUpdatedItem());
    },

    generateErrorMsg() {
      const item = this.currentItem;
      return `Invalid quantity provided for ${item.name}, you have ${item.quantity} of ${item.name} in stock`;
    },
    hasQuantityError() {
      if (
        this.currentItem &&
        this.currentItem.track_inventory === 1 &&
        this.invoice_quantity > this.currentItem.quantity
      ) {
        this.errorMsg = this.generateErrorMsg();
        this.quantity_error = true;
        return true;
      }
      this.quantity_error = false;
      return false;
    },
    calculateTaxes() {
      const taxObjs = this.taxes.filter((tax) =>
        this.applied_taxes.includes(tax.id)
      );

      let taxAmount = 0;
      taxObjs.forEach((taxObj) => {
        if (taxObj.type === "Flat") {
          taxAmount += (Number(taxObj.rate) / 100) * Number(this.amount_due);
        } else if (taxObj.type === "Compound") {
          const initialTax =
            (Number(taxObj.sub_rate) / 100) * Number(this.amount_due);

          const compoundAmount = initialTax + Number(this.amount_due);
          const compoundTax =
            (Number(taxObj.rate) / 100) * Number(compoundAmount);
          taxAmount += compoundTax + initialTax;
        }
      });

      return taxAmount;
    },

    calculateTax() {
      this.tax_amount = this.calculateTaxes();

      this.$emit("change", this.id, this.getUpdatedItem());
    },
    itemPriceChange() {
      this.rawUnit_price
        ? (this.rawUnit_price = this.rawUnit_price)
        : (this.rawUnit_price = 0);
      this.computeAmount();
    },
    itemQuantityChange() {
      this.invoice_quantity
        ? (this.invoice_quantity = this.invoice_quantity)
        : (this.invoice_quantity = 1);
      this.computeAmount();
    },
  },
  mounted() {
    if (this.defaultItem) {
      this.currentItem = this.defaultItem;

      this.applyItem();
    }
  },
};
</script>

<style scoped>
td,
td * {
  vertical-align: top !important;
  margin-top: 0.5rem !important;
}

.text-sm {
  font-size: 11px;
}

.text-md {
  font-size: 14px;
}

.text-bold {
  font-weight: 800;
}

.up {
  transform: translateY(-0.5rem);
}

.chip {
  padding: 0.3rem 1rem;
  background-color: #eeeeee;
  border-radius: 6rem;
}
</style>