File: //home/slfopp7cb1df/www/inventorypacket.com/resources/src/views/app/pages/sales/create_sale.vue
<template>
<div class="main-content">
<breadcumb :page="$t('AddSale')" :folder="$t('ListSales')"/>
<div v-if="isLoading" class="loading_page spinner spinner-primary mr-3"></div>
<validation-observer ref="create_sale" v-if="!isLoading">
<b-form @submit.prevent="Submit_Sale">
<b-row>
<b-col lg="12" md="12" sm="12">
<b-card>
<b-row>
<b-modal hide-footer id="open_scan" size="md" title="Barcode Scanner">
<qrcode-scanner
:qrbox="250"
:fps="10"
style="width: 100%; height: calc(100vh - 56px);"
@result="onScan"
/>
</b-modal>
<!-- date -->
<b-col lg="4" md="4" sm="12" class="mb-3">
<validation-provider
name="date"
:rules="{ required: true}"
v-slot="validationContext"
>
<b-form-group :label="$t('date') + ' ' + '*'">
<b-form-input
:state="getValidationState(validationContext)"
aria-describedby="date-feedback"
type="date"
v-model="sale.date"
></b-form-input>
<b-form-invalid-feedback
id="OrderTax-feedback"
>{{ validationContext.errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Customer -->
<b-col lg="4" md="4" sm="12" class="mb-3">
<validation-provider name="Customer" :rules="{ required: true}">
<b-form-group slot-scope="{ valid, errors }" :label="$t('Customer') + ' ' + '*'">
<v-select
:class="{'is-invalid': !!errors.length}"
:state="errors[0] ? false : (valid ? true : null)"
v-model="sale.client_id"
@input="Selected_customer"
:reduce="label => label.value"
:placeholder="$t('Choose_Customer')"
:options="clients.map(clients => ({label: clients.name, value: clients.id}))"
/>
<b-form-invalid-feedback>{{ errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- warehouse -->
<b-col lg="4" md="4" sm="12" class="mb-3">
<validation-provider name="warehouse" :rules="{ required: true}">
<b-form-group slot-scope="{ valid, errors }" :label="$t('warehouse') + ' ' + '*'">
<v-select
:class="{'is-invalid': !!errors.length}"
:state="errors[0] ? false : (valid ? true : null)"
:disabled="details.length > 0"
@input="Selected_Warehouse"
v-model="sale.warehouse_id"
:reduce="label => label.value"
:placeholder="$t('Choose_Warehouse')"
:options="warehouses.map(warehouses => ({label: warehouses.name, value: warehouses.id}))"
/>
<b-form-invalid-feedback>{{ errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Product -->
<b-col md="12" class="mb-5">
<h6>{{$t('ProductName')}}</h6>
<div id="autocomplete" class="autocomplete">
<div class="input-with-icon">
<img src="/assets_setup/scan.png" alt="Scan" class="scan-icon" @click="showModal">
<input
:placeholder="$t('Scan_Search_Product_by_Code_Name')"
@input='e => search_input = e.target.value'
@keyup="search(search_input)"
@focus="handleFocus"
@blur="handleBlur"
ref="product_autocomplete"
class="autocomplete-input" />
</div>
<ul class="autocomplete-result-list" v-show="focused">
<li class="autocomplete-result" v-for="product_fil in product_filter" @mousedown="SearchProduct(product_fil)">{{getResultValue(product_fil)}}</li>
</ul>
</div>
</b-col>
<!-- order products -->
<b-col md="12" class="mb-4">
<h5>{{$t('order_products')}} *</h5>
<div class="table-responsive">
<table class="table table-hover">
<thead class="bg-gray-300">
<tr>
<th scope="col">#</th>
<th scope="col">{{$t('ProductName')}}</th>
<th scope="col">{{$t('Net_Unit_Price')}}</th>
<th scope="col">{{$t('CurrentStock')}}</th>
<th scope="col">{{$t('Qty')}}</th>
<th scope="col">{{$t('Discount')}}</th>
<th scope="col">{{$t('Tax')}}</th>
<th scope="col">{{$t('SubTotal')}}</th>
<th scope="col" class="text-center">
<i class="i-Close-Window text-25"></i>
</th>
</tr>
</thead>
<tbody>
<tr v-if="details.length <=0">
<td colspan="9">{{$t('NodataAvailable')}}</td>
</tr>
<tr v-for="detail in details" >
<td >{{detail.detail_id}}</td>
<td>
<span>{{detail.code}}</span>
<br>
<span class="badge badge-success">{{detail.name}}</span>
</td>
<td>{{currentUser.currency}} {{formatNumber(detail.Net_price, 3)}}</td>
<td>
<span class="badge badge-warning" v-if="detail.product_type == 'is_service'">----</span>
<span class="badge badge-warning" v-else>{{detail.stock}} {{detail.unitSale}}</span>
</td>
<td>
<div class="quantity">
<b-input-group>
<b-input-group-prepend>
<span
class="btn btn-primary btn-sm"
@click="decrement(detail ,detail.detail_id)"
>-</span>
</b-input-group-prepend>
<input
class="form-control"
@keyup="Verified_Qty(detail,detail.detail_id)"
:min="0.00"
:max="detail.stock"
v-model.number="detail.quantity"
>
<b-input-group-append>
<span
class="btn btn-primary btn-sm"
@click="increment(detail ,detail.detail_id)"
>+</span>
</b-input-group-append>
</b-input-group>
</div>
</td>
<td>{{currentUser.currency}} {{formatNumber(detail.DiscountNet * detail.quantity, 2)}}</td>
<td>{{currentUser.currency}} {{formatNumber(detail.taxe * detail.quantity, 2)}}</td>
<td>{{currentUser.currency}} {{detail.subtotal.toFixed(2)}}</td>
<td>
<i v-if="currentUserPermissions && currentUserPermissions.includes('edit_product_sale')"
@click="Modal_Updat_Detail(detail)" class="i-Edit text-25 text-success cursor-pointer"></i>
<i @click="delete_Product_Detail(detail.detail_id)" class="i-Close-Window text-25 text-danger cursor-pointer"></i>
</td>
</tr>
</tbody>
</table>
</div>
</b-col>
<div class="offset-md-9 col-md-3 mt-4">
<table class="table table-striped table-sm">
<tbody>
<tr>
<td class="bold">{{$t('OrderTax')}}</td>
<td>
<span>{{currentUser.currency}} {{sale.TaxNet.toFixed(2)}} ({{formatNumber(sale.tax_rate,2)}} %)</span>
</td>
</tr>
<tr>
<td class="bold">{{$t('Discount')}}</td>
<td>{{currentUser.currency}} {{sale.discount.toFixed(2)}}</td>
</tr>
<tr>
<td class="bold">{{$t('Shipping')}}</td>
<td>{{currentUser.currency}} {{sale.shipping.toFixed(2)}}</td>
</tr>
<tr>
<td>
<span class="font-weight-bold">{{$t('Total')}}</span>
</td>
<td>
<span
class="font-weight-bold"
>{{currentUser.currency}} {{GrandTotal.toFixed(2)}}</span>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Order Tax -->
<b-col lg="4" md="4" sm="12" class="mb-3" v-if="currentUserPermissions && currentUserPermissions.includes('edit_tax_discount_shipping_sale')">
<validation-provider
name="Order Tax"
:rules="{ regex: /^\d*\.?\d*$/}"
v-slot="validationContext"
>
<b-form-group :label="$t('OrderTax')">
<b-input-group append="%">
<b-form-input
:state="getValidationState(validationContext)"
aria-describedby="OrderTax-feedback"
label="Order Tax"
v-model.number="sale.tax_rate"
@keyup="keyup_OrderTax()"
></b-form-input>
</b-input-group>
<b-form-invalid-feedback
id="OrderTax-feedback"
>{{ validationContext.errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Discount -->
<b-col lg="4" md="4" sm="12" class="mb-3" v-if="currentUserPermissions && currentUserPermissions.includes('edit_tax_discount_shipping_sale')">
<validation-provider
name="Discount"
:rules="{ regex: /^\d*\.?\d*$/}"
v-slot="validationContext"
>
<b-form-group :label="$t('Discount')">
<b-input-group :append="currentUser.currency">
<b-form-input
:state="getValidationState(validationContext)"
aria-describedby="Discount-feedback"
label="Discount"
v-model.number="sale.discount"
@keyup="keyup_Discount()"
></b-form-input>
</b-input-group>
<b-form-invalid-feedback
id="Discount-feedback"
>{{ validationContext.errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Shipping -->
<b-col lg="4" md="4" sm="12" class="mb-3" v-if="currentUserPermissions && currentUserPermissions.includes('edit_tax_discount_shipping_sale')">
<validation-provider
name="Shipping"
:rules="{ regex: /^\d*\.?\d*$/}"
v-slot="validationContext"
>
<b-form-group :label="$t('Shipping')">
<b-input-group :append="currentUser.currency">
<b-form-input
:state="getValidationState(validationContext)"
aria-describedby="Shipping-feedback"
label="Shipping"
v-model.number="sale.shipping"
@keyup="keyup_Shipping()"
></b-form-input>
</b-input-group>
<b-form-invalid-feedback
id="Shipping-feedback"
>{{ validationContext.errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Status -->
<b-col lg="4" md="4" sm="12" class="mb-3">
<validation-provider name="Status" :rules="{ required: true}">
<b-form-group slot-scope="{ valid, errors }" :label="$t('Status') + ' ' + '*'">
<v-select
@input="Selected_Status"
:class="{'is-invalid': !!errors.length}"
:state="errors[0] ? false : (valid ? true : null)"
v-model="sale.statut"
:reduce="label => label.value"
:placeholder="$t('Choose_Status')"
:options="
[
{label: 'completed', value: 'completed'},
{label: 'Pending', value: 'pending'},
{label: 'ordered', value: 'ordered'}
]"
></v-select>
<b-form-invalid-feedback>{{ errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- PaymentStatus -->
<b-col md="4" v-if="sale.statut == 'completed'">
<validation-provider name="PaymentStatus">
<b-form-group :label="$t('PaymentStatus')">
<v-select
@input="Selected_PaymentStatus"
:reduce="label => label.value"
v-model="payment.status"
:placeholder="$t('Choose_Status')"
:options="
[
{label: 'Paid', value: 'paid'},
{label: 'partial', value: 'partial'},
{label: 'Pending', value: 'pending'},
]"
></v-select>
</b-form-group>
</validation-provider>
</b-col>
<!-- Payment choice -->
<b-col md="4" v-if="payment.status != 'pending' && sale.statut == 'completed'">
<validation-provider name="Payment choice" :rules="{ required: true}">
<b-form-group slot-scope="{ valid, errors }" :label="$t('Paymentchoice') + ' ' + '*'">
<v-select
:class="{'is-invalid': !!errors.length}"
:state="errors[0] ? false : (valid ? true : null)"
:reduce="label => label.value"
@input="Selected_PaymentMethod"
v-model="payment.Reglement"
:placeholder="$t('PleaseSelect')"
:options="
[
{label: 'Cash', value: 'Cash'},
{label: 'credit card', value: 'credit card'},
{label: 'TPE', value: 'tpe'},
{label: 'cheque', value: 'cheque'},
{label: 'Western Union', value: 'Western Union'},
{label: 'bank transfer', value: 'bank transfer'},
{label: 'other', value: 'other'},
]"
></v-select>
<b-form-invalid-feedback>{{ errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Received Amount -->
<b-col md="4" v-if="payment.status != 'pending' && sale.statut == 'completed'">
<validation-provider
name="Received Amount"
:rules="{ required: true , regex: /^\d*\.?\d*$/}"
v-slot="validationContext"
>
<b-form-group :label="$t('Received_Amount') + ' ' + '*'">
<b-form-input
@keyup="Verified_Received_Amount(payment.received_amount)"
label="Received_Amount"
:placeholder="$t('Received_Amount')"
v-model.number="payment.received_amount"
:state="getValidationState(validationContext)"
aria-describedby="Received_Amount-feedback"
></b-form-input>
<b-form-invalid-feedback
id="Received_Amount-feedback"
>{{ validationContext.errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Amount -->
<b-col md="4" v-if="payment.status != 'pending' && sale.statut == 'completed'">
<validation-provider
name="Amount"
:rules="{ required: true , regex: /^\d*\.?\d*$/}"
v-slot="validationContext"
>
<b-form-group :label="$t('Paying_Amount') + ' ' + '*'">
<b-form-input
:disabled="payment.status == 'paid'"
label="Amount"
:placeholder="$t('Paying_Amount')"
v-model.number="payment.amount"
@keyup="Verified_paidAmount(payment.amount)"
:state="getValidationState(validationContext)"
aria-describedby="Amount-feedback"
></b-form-input>
<b-form-invalid-feedback
id="Amount-feedback"
>{{ validationContext.errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- change Amount -->
<b-col md="4" v-if="payment.status != 'pending' && sale.statut == 'completed'">
<label>{{$t('Change')}} :</label>
<p
class="change_amount"
>{{parseFloat(payment.received_amount - payment.amount).toFixed(2)}}</p>
</b-col>
<b-col md="12" class="mt-3"
v-if="payment.status != 'pending' && payment.Reglement == 'credit card' && sale.statut == 'completed'">
<b-card v-show="payment.Reglement == 'credit card'">
<div v-once class="typo__p" v-if="submit_showing_credit_card">
<div class="spinner sm spinner-primary mt-3"></div>
</div>
<div v-if="displaySavedPaymentMethods && !submit_showing_credit_card">
<div class="mt-3"><span class="mr-3">Saved Credit Card Info For This Client </span>
<b-button variant="outline-info" @click="show_new_credit_card()">
<span>
<i class="i-Two-Windows"></i>
New Credit Card
</span>
</b-button>
</div>
<table class="table table-hover mt-3">
<thead>
<tr>
<th>Last 4 digits</th>
<th>Type</th>
<th>Exp</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="card in savedPaymentMethods" :class="{ 'bg-selected-card': isSelectedCard(card) }">
<td>**** {{card.last4}}</td>
<td>{{card.type}}</td>
<td>{{card.exp}}</td>
<td>
<b-button variant="outline-primary" @click="selectCard(card)" v-if="!isSelectedCard(card) && card_id != card.card_id">
<span>
<i class="i-Drag-Up"></i>
Use This
</span>
</b-button>
<i v-if="isSelectedCard(card) || card_id == card.card_id" class="i-Yes" style=" font-size: 20px; "></i>
</td>
</tr>
</tbody>
</table>
</div>
<div v-if="displayFormNewCard && !submit_showing_credit_card">
<form id="payment-form">
<label for="card-element" class="leading-7 text-sm text-gray-600">
{{$t('Credit_Card_Info')}}
<b-button variant="outline-info" @click="show_saved_credit_card()" v-if="savedPaymentMethods && savedPaymentMethods.length > 0">
<span>
<i class="i-Two-Windows"></i>
Use Saved Credit Card
</span>
</b-button>
</label>
<div id="card-element">
</div>
<div id="card-errors" class="is-invalid" role="alert"></div>
</form>
</div>
</b-card>
</b-col>
<!-- Account -->
<b-col lg="4" md="4" sm="12" v-if="payment.status != 'pending' && sale.statut == 'completed'">
<validation-provider name="Account">
<b-form-group slot-scope="{ valid, errors }" :label="$t('Account')">
<v-select
:class="{'is-invalid': !!errors.length}"
:state="errors[0] ? false : (valid ? true : null)"
v-model="payment.account_id"
:reduce="label => label.value"
:placeholder="$t('Choose_Account')"
:options="accounts.map(accounts => ({label: accounts.account_name, value: accounts.id}))"
/>
<b-form-invalid-feedback>{{ errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<b-col md="12" class="mt-3">
<b-form-group :label="$t('Note')">
<textarea
v-model="sale.notes"
rows="4"
class="form-control"
:placeholder="$t('Afewwords')"
></textarea>
</b-form-group>
</b-col>
<b-col md="12">
<b-form-group>
<b-button variant="primary" :disabled="paymentProcessing" @click="Submit_Sale"><i class="i-Yes me-2 font-weight-bold"></i> {{$t('submit')}}</b-button>
<div v-once class="typo__p" v-if="paymentProcessing">
<div class="spinner sm spinner-primary mt-3"></div>
</div>
</b-form-group>
</b-col>
</b-row>
</b-card>
</b-col>
</b-row>
</b-form>
</validation-observer>
<!-- Modal Update detail Product -->
<validation-observer ref="Update_Detail">
<b-modal hide-footer size="lg" id="form_Update_Detail" :title="detail.name">
<b-form @submit.prevent="submit_Update_Detail">
<b-row>
<!-- Unit Price -->
<b-col lg="6" md="6" sm="12">
<validation-provider
name="Product Price"
:rules="{ required: true , regex: /^\d*\.?\d*$/}"
v-slot="validationContext"
>
<b-form-group :label="$t('ProductPrice') + ' ' + '*'" id="Price-input">
<b-form-input
label="Product Price"
v-model="detail.Unit_price"
:state="getValidationState(validationContext)"
aria-describedby="Price-feedback"
></b-form-input>
<b-form-invalid-feedback id="Price-feedback">{{ validationContext.errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Tax Method -->
<b-col lg="6" md="6" sm="12">
<validation-provider name="Tax Method" :rules="{ required: true}">
<b-form-group slot-scope="{ valid, errors }" :label="$t('TaxMethod') + ' ' + '*'">
<v-select
:class="{'is-invalid': !!errors.length}"
:state="errors[0] ? false : (valid ? true : null)"
v-model="detail.tax_method"
:reduce="label => label.value"
:placeholder="$t('Choose_Method')"
:options="
[
{label: 'Exclusive', value: '1'},
{label: 'Inclusive', value: '2'}
]"
></v-select>
<b-form-invalid-feedback>{{ errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Tax Rate -->
<b-col lg="6" md="6" sm="12">
<validation-provider
name="Order Tax"
:rules="{ required: true , regex: /^\d*\.?\d*$/}"
v-slot="validationContext"
>
<b-form-group :label="$t('OrderTax') + ' ' + '*'">
<b-input-group append="%">
<b-form-input
label="Order Tax"
v-model="detail.tax_percent"
:state="getValidationState(validationContext)"
aria-describedby="OrderTax-feedback"
></b-form-input>
</b-input-group>
<b-form-invalid-feedback id="OrderTax-feedback">{{ validationContext.errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Discount Method -->
<b-col lg="6" md="6" sm="12">
<validation-provider name="Discount Method" :rules="{ required: true}">
<b-form-group slot-scope="{ valid, errors }" :label="$t('Discount_Method') + ' ' + '*'">
<v-select
v-model="detail.discount_Method"
:reduce="label => label.value"
:placeholder="$t('Choose_Method')"
:class="{'is-invalid': !!errors.length}"
:state="errors[0] ? false : (valid ? true : null)"
:options="
[
{label: 'Percent %', value: '1'},
{label: 'Fixed', value: '2'}
]"
></v-select>
<b-form-invalid-feedback>{{ errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Discount Rate -->
<b-col lg="6" md="6" sm="12">
<validation-provider
name="Discount Rate"
:rules="{ required: true , regex: /^\d*\.?\d*$/}"
v-slot="validationContext"
>
<b-form-group :label="$t('Discount') + ' ' + '*'">
<b-form-input
label="Discount"
v-model.number="detail.discount"
:state="getValidationState(validationContext)"
aria-describedby="Discount-feedback"
></b-form-input>
<b-form-invalid-feedback id="Discount-feedback">{{ validationContext.errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Unit Sale -->
<b-col lg="6" md="6" sm="12" v-if="detail.product_type != 'is_service'">
<validation-provider name="Unit Sale" :rules="{ required: true}">
<b-form-group slot-scope="{ valid, errors }" :label="$t('UnitSale') + ' ' + '*'">
<v-select
:class="{'is-invalid': !!errors.length}"
:state="errors[0] ? false : (valid ? true : null)"
v-model="detail.sale_unit_id"
:placeholder="$t('Choose_Unit_Sale')"
:reduce="label => label.value"
:options="units.map(units => ({label: units.name, value: units.id}))"
/>
<b-form-invalid-feedback>{{ errors[0] }}</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
</b-col>
<!-- Imei or serial numbers -->
<b-col lg="12" md="12" sm="12" v-show="detail.is_imei">
<b-form-group :label="$t('Add_product_IMEI_Serial_number')">
<b-form-input
label="Add_product_IMEI_Serial_number"
v-model="detail.imei_number"
:placeholder="$t('Add_product_IMEI_Serial_number')"
></b-form-input>
</b-form-group>
</b-col>
<b-col md="12">
<b-form-group>
<b-button
variant="primary"
type="submit"
:disabled="Submit_Processing_detail"
><i class="i-Yes me-2 font-weight-bold"></i> {{$t('submit')}}</b-button>
<div v-once class="typo__p" v-if="Submit_Processing_detail">
<div class="spinner sm spinner-primary mt-3"></div>
</div>
</b-form-group>
</b-col>
</b-row>
</b-form>
</b-modal>
</validation-observer>
</div>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
import NProgress from "nprogress";
import { loadStripe } from "@stripe/stripe-js";
export default {
metaInfo: {
title: "Create Sale"
},
data() {
return {
focused: false,
timer:null,
search_input:'',
product_filter:[],
stripe_key:'',
stripe: {},
cardElement: {},
savedPaymentMethods: [],
hasSavedPaymentMethod: false,
useSavedPaymentMethod: false,
selectedCard:null,
card_id:'',
is_new_credit_card: false,
submit_showing_credit_card: false,
paymentProcessing: false,
Submit_Processing_detail:false,
isLoading: true,
warehouses: [],
clients: [],
accounts: [],
client: {},
products: [],
details: [],
detail: {},
sales: [],
payment: {
status: "pending",
Reglement: "Cash",
amount: "",
received_amount: "",
account_id: "",
},
sale: {
id: "",
date: new Date().toISOString().slice(0, 10),
statut: "completed",
notes: "",
client_id: "",
warehouse_id: "",
tax_rate: 0,
TaxNet: 0,
shipping: 0,
discount: 0
},
timer:null,
total: 0,
GrandTotal: 0,
units:[],
product: {
id: "",
product_type: "",
code: "",
stock: "",
quantity: 1,
discount: "",
DiscountNet: "",
discount_Method: "",
name: "",
sale_unit_id:"",
fix_stock:"",
fix_price:"",
unitSale: "",
Net_price: "",
Unit_price: "",
Total_price: "",
subtotal: "",
product_id: "",
detail_id: "",
taxe: "",
tax_percent: "",
tax_method: "",
product_variant_id: "",
is_imei: "",
imei_number:"",
}
};
},
computed: {
...mapGetters(["currentUserPermissions","currentUser"]),
displaySavedPaymentMethods() {
if(this.hasSavedPaymentMethod){
return true;
}else{
return false;
}
},
displayFormNewCard() {
if(this.useSavedPaymentMethod){
return false;
}else{
return true;
}
},
isSelectedCard() {
return card => this.selectedCard === card;
},
},
methods: {
showModal() {
this.$bvModal.show('open_scan');
},
onScan (decodedText, decodedResult) {
const code = decodedText;
this.search_input = code;
this.search();
this.$bvModal.hide('open_scan');
},
async loadStripe_payment() {
this.stripe = await loadStripe(`${this.stripe_key}`);
const elements = this.stripe.elements();
this.cardElement = elements.create("card", {
classes: {
base:
"bg-gray-100 rounded border border-gray-300 focus:border-indigo-500 text-base outline-none text-gray-700 p-3 leading-8 transition-colors duration-200 ease-in-out"
}
});
this.cardElement.mount("#card-element");
},
handleFocus() {
this.focused = true
},
handleBlur() {
this.focused = false
},
//---------------------- Event Select customer ------------------------------\\
Selected_customer(value) {
this.payment.Reglement = 'Cash';
this.savedPaymentMethods= [];
this.hasSavedPaymentMethod= false;
this.useSavedPaymentMethod= false;
this.selectedCard=null;
this.card_id='';
this.is_new_credit_card= false;
this.submit_showing_credit_card= false;
},
//---------------------- Event Select Payment Method ------------------------------\\
async Selected_PaymentMethod(value) {
if (value === 'credit card') {
this.savedPaymentMethods = [];
this.submit_showing_credit_card = true;
this.selectedCard = null
this.card_id = '';
// Check if the customer has saved payment methods
await axios.get(`/retrieve-customer?customerId=${this.sale.client_id}`)
.then(response => {
// If the customer has saved payment methods, display them
this.savedPaymentMethods = response.data.data;
this.card_id = response.data.customer_default_source;
this.hasSavedPaymentMethod = true;
this.useSavedPaymentMethod = true;
this.is_new_credit_card = false;
this.submit_showing_credit_card = false;
})
.catch(error => {
// If the customer does not have saved payment methods, show the card element for them to enter their payment information
this.hasSavedPaymentMethod = false;
this.useSavedPaymentMethod = false;
this.is_new_credit_card = true;
this.card_id = '';
setTimeout(() => {
this.loadStripe_payment();
}, 1000);
this.submit_showing_credit_card = false;
});
}else{
this.hasSavedPaymentMethod = false;
this.useSavedPaymentMethod = false;
this.is_new_credit_card = false;
}
},
show_saved_credit_card() {
this.hasSavedPaymentMethod = true;
this.useSavedPaymentMethod = true;
this.is_new_credit_card = false;
this.Selected_PaymentMethod('credit card');
},
show_new_credit_card() {
this.selectedCard = null;
this.card_id = '';
this.useSavedPaymentMethod = false;
this.hasSavedPaymentMethod = false;
this.is_new_credit_card = true;
setTimeout(() => {
this.loadStripe_payment();
}, 500);
},
selectCard(card) {
this.selectedCard = card;
this.card_id = card.card_id;
},
//---------------------- Event Select Status ------------------------------\\
Selected_Status(value){
if (value != "completed") {
this.payment.status = 'pending';
}
},
//---------------------- Event Select Payment Status ------------------------------\\
Selected_PaymentStatus(value){
if (value == "paid") {
var payment_amount = this.GrandTotal.toFixed(2);
this.payment.amount = this.formatNumber(payment_amount, 2);
this.payment.received_amount = this.formatNumber(payment_amount, 2);
}else{
this.payment.amount = 0;
this.payment.received_amount = 0;
}
},
//---------- keyup paid Amount
Verified_paidAmount() {
if (isNaN(this.payment.amount)) {
this.payment.amount = 0;
} else if (this.payment.amount > this.payment.received_amount) {
this.makeToast(
"warning",
this.$t("Paying_amount_is_greater_than_Received_amount"),
this.$t("Warning")
);
this.payment.amount = 0;
}
else if (this.payment.amount > this.GrandTotal) {
this.makeToast(
"warning",
this.$t("Paying_amount_is_greater_than_Grand_Total"),
this.$t("Warning")
);
this.payment.amount = 0;
}
},
//---------- keyup Received Amount
Verified_Received_Amount() {
if (isNaN(this.payment.received_amount)) {
this.payment.received_amount = 0;
}
},
//--- Submit Validate Create Sale
Submit_Sale() {
this.$refs.create_sale.validate().then(success => {
if (!success) {
this.makeToast(
"danger",
this.$t("Please_fill_the_form_correctly"),
this.$t("Failed")
);
} else if (this.payment.amount > this.payment.received_amount) {
this.makeToast(
"warning",
this.$t("Paying_amount_is_greater_than_Received_amount"),
this.$t("Warning")
);
this.payment.received_amount = 0;
}
else if (this.payment.amount > this.GrandTotal) {
this.makeToast(
"warning",
this.$t("Paying_amount_is_greater_than_Grand_Total"),
this.$t("Warning")
);
this.payment.amount = 0;
}else{
this.Create_Sale();
}
});
},
//---Submit Validation Update Detail
submit_Update_Detail() {
this.$refs.Update_Detail.validate().then(success => {
if (!success) {
return;
} else {
this.Update_Detail();
}
});
},
//---Validate State Fields
getValidationState({ dirty, validated, valid = null }) {
return dirty || validated ? valid : null;
},
//------ Toast
makeToast(variant, msg, title) {
this.$root.$bvToast.toast(msg, {
title: title,
variant: variant,
solid: true
});
},
//---------------------- get_units ------------------------------\\
get_units(value) {
axios
.get("get_units?id=" + value)
.then(({ data }) => (this.units = data));
},
//------ Show Modal Update Detail Product
Modal_Updat_Detail(detail) {
NProgress.start();
NProgress.set(0.1);
this.detail = {};
this.get_units(detail.product_id);
this.detail.detail_id = detail.detail_id;
this.detail.sale_unit_id = detail.sale_unit_id;
this.detail.product_type = detail.product_type;
this.detail.name = detail.name;
this.detail.Unit_price = detail.Unit_price;
this.detail.fix_price = detail.fix_price;
this.detail.fix_stock = detail.fix_stock;
this.detail.stock = detail.stock;
this.detail.tax_method = detail.tax_method;
this.detail.discount_Method = detail.discount_Method;
this.detail.discount = detail.discount;
this.detail.quantity = detail.quantity;
this.detail.tax_percent = detail.tax_percent;
this.detail.is_imei = detail.is_imei;
this.detail.imei_number = detail.imei_number;
setTimeout(() => {
NProgress.done();
this.$bvModal.show("form_Update_Detail");
}, 1000);
},
//------ Submit Update Detail Product
Update_Detail() {
NProgress.start();
NProgress.set(0.1);
this.Submit_Processing_detail = true;
for (var i = 0; i < this.details.length; i++) {
if (this.details[i].detail_id === this.detail.detail_id) {
// this.convert_unit();
for(var k=0; k<this.units.length; k++){
if (this.units[k].id == this.detail.sale_unit_id) {
if(this.units[k].operator == '/'){
this.details[i].stock = this.detail.fix_stock * this.units[k].operator_value;
this.details[i].unitSale = this.units[k].ShortName;
}else{
this.details[i].stock = this.detail.fix_stock / this.units[k].operator_value;
this.details[i].unitSale = this.units[k].ShortName;
}
}
}
if (this.details[i].stock < this.details[i].quantity) {
this.details[i].quantity = this.details[i].stock;
} else {
this.details[i].quantity =1;
}
this.details[i].Unit_price = this.detail.Unit_price;
this.details[i].tax_percent = this.detail.tax_percent;
this.details[i].tax_method = this.detail.tax_method;
this.details[i].discount_Method = this.detail.discount_Method;
this.details[i].discount = this.detail.discount;
this.details[i].sale_unit_id = this.detail.sale_unit_id;
this.details[i].imei_number = this.detail.imei_number;
this.details[i].product_type = this.detail.product_type;
if (this.details[i].discount_Method == "2") {
//Fixed
this.details[i].DiscountNet = this.details[i].discount;
} else {
//Percentage %
this.details[i].DiscountNet = parseFloat(
(this.details[i].Unit_price * this.details[i].discount) / 100
);
}
if (this.details[i].tax_method == "1") {
//Exclusive
this.details[i].Net_price = parseFloat(
this.details[i].Unit_price - this.details[i].DiscountNet
);
this.details[i].taxe = parseFloat(
(this.details[i].tax_percent *
(this.details[i].Unit_price - this.details[i].DiscountNet)) /
100
);
} else {
//Inclusive
this.details[i].taxe = parseFloat(
(this.details[i].Unit_price - this.details[i].DiscountNet) *
(this.details[i].tax_percent / 100)
);
this.details[i].Net_price = parseFloat(
this.details[i].Unit_price -
this.details[i].taxe -
this.details[i].DiscountNet
);
}
this.$forceUpdate();
}
}
this.Calcul_Total();
setTimeout(() => {
NProgress.done();
this.Submit_Processing_detail = false;
this.$bvModal.hide("form_Update_Detail");
}, 1000);
},
// Search Products
search(){
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
if (this.search_input.length < 2) {
return this.product_filter= [];
}
if (this.sale.warehouse_id != "" && this.sale.warehouse_id != null) {
this.timer = setTimeout(() => {
const product_filter = this.products.filter(product => product.code === this.search_input || product.barcode.includes(this.search_input));
if(product_filter.length === 1){
this.SearchProduct(product_filter[0])
}else{
this.product_filter= this.products.filter(product => {
return (
product.name.toLowerCase().includes(this.search_input.toLowerCase()) ||
product.code.toLowerCase().includes(this.search_input.toLowerCase()) ||
product.barcode.toLowerCase().includes(this.search_input.toLowerCase())
);
});
// Check if product_filter is empty and show alert
if (this.product_filter.length <= 0) {
this.makeToast(
"warning",
"Product Not Found",
"Warning"
);
}
}
}, 800);
} else {
this.makeToast(
"warning",
this.$t("SelectWarehouse"),
this.$t("Warning")
);
}
},
//------------------------- get Result Value Search Product
getResultValue(result) {
return result.code + " " + "(" + result.name + ")";
},
//------------------------- Submit Search Product
SearchProduct(result) {
this.product = {};
if (
this.details.length > 0 &&
this.details.some(detail => detail.code === result.code)
) {
this.makeToast("warning", this.$t("AlreadyAdd"), this.$t("Warning"));
} else {
if( result.product_type =='is_service'){
this.product.quantity = 1;
this.product.code = result.code;
this.product.stock = '---';
this.product.fix_stock = '---';
}else{
this.product.code = result.code;
this.product.stock = result.qte_sale;
this.product.fix_stock = result.qte;
if (result.qte_sale < 1) {
this.product.quantity = result.qte_sale;
} else {
this.product.quantity = 1;
}
}
this.product.product_variant_id = result.product_variant_id;
this.Get_Product_Details(result.id, result.product_variant_id);
}
this.search_input= '';
this.$refs.product_autocomplete.value = "";
this.product_filter = [];
},
//---------------------- Event Select Warehouse ------------------------------\\
Selected_Warehouse(value) {
this.search_input= '';
this.product_filter = [];
this.Get_Products_By_Warehouse(value);
},
//------------------------------------ Get Products By Warehouse -------------------------\\
Get_Products_By_Warehouse(id) {
// Start the progress bar.
NProgress.start();
NProgress.set(0.1);
axios
.get("get_Products_by_warehouse/" + id + "?stock=" + 1 + "&is_sale=" + 1 + "&product_service=" + 1 + "&product_combo=" + 1)
.then(response => {
this.products = response.data;
NProgress.done();
})
.catch(error => {
});
},
//----------------------------------------- Add Product to order list -------------------------\\
add_product() {
if (this.details.length > 0) {
this.Last_Detail_id();
} else if (this.details.length === 0) {
this.product.detail_id = 1;
}
this.details.push(this.product);
if(this.product.is_imei){
this.Modal_Updat_Detail(this.product);
}
},
//-----------------------------------Verified QTY ------------------------------\\
Verified_Qty(detail, id) {
for (var i = 0; i < this.details.length; i++) {
if (this.details[i].detail_id === id) {
if (isNaN(detail.quantity)) {
this.details[i].quantity = detail.stock;
}
if (detail.quantity > detail.stock) {
this.makeToast("warning", this.$t("LowStock"), this.$t("Warning"));
this.details[i].quantity = detail.stock;
} else {
this.details[i].quantity = detail.quantity;
}
}
}
this.$forceUpdate();
this.Calcul_Total();
},
//-----------------------------------increment QTY ------------------------------\\
increment(detail, id) {
for (var i = 0; i < this.details.length; i++) {
if (this.details[i].detail_id == id) {
if (detail.quantity + 1 > detail.stock) {
this.makeToast("warning", this.$t("LowStock"), this.$t("Warning"));
} else {
this.formatNumber(this.details[i].quantity++, 2);
}
}
}
this.$forceUpdate();
this.Calcul_Total();
},
//-----------------------------------decrement QTY ------------------------------\\
decrement(detail, id) {
for (var i = 0; i < this.details.length; i++) {
if (this.details[i].detail_id == id) {
if (detail.quantity - 1 > 0) {
if (detail.quantity - 1 > detail.stock) {
this.makeToast(
"warning",
this.$t("LowStock"),
this.$t("Warning")
);
} else {
this.formatNumber(this.details[i].quantity--, 2);
}
}
}
}
this.$forceUpdate();
this.Calcul_Total();
},
//------------------------------Formetted Numbers -------------------------\\
formatNumber(number, dec) {
const value = (typeof number === "string"
? number
: number.toString()
).split(".");
if (dec <= 0) return value[0];
let formated = value[1] || "";
if (formated.length > dec)
return `${value[0]}.${formated.substr(0, dec)}`;
while (formated.length < dec) formated += "0";
return `${value[0]}.${formated}`;
},
//-----------------------------------------Calcul Total ------------------------------\\
Calcul_Total() {
this.total = 0;
for (var i = 0; i < this.details.length; i++) {
var tax = this.details[i].taxe * this.details[i].quantity;
this.details[i].subtotal = parseFloat(
this.details[i].quantity * this.details[i].Net_price + tax
);
this.total = parseFloat(this.total + this.details[i].subtotal);
}
const total_without_discount = parseFloat(
this.total - this.sale.discount
);
this.sale.TaxNet = parseFloat(
(total_without_discount * this.sale.tax_rate) / 100
);
this.GrandTotal = parseFloat(
total_without_discount + this.sale.TaxNet + this.sale.shipping
);
var grand_total = this.GrandTotal.toFixed(2);
this.GrandTotal = parseFloat(grand_total);
if(this.payment.status == 'paid'){
this.payment.amount = this.formatNumber(this.GrandTotal, 2);
}
},
//-----------------------------------Delete Detail Product ------------------------------\\
delete_Product_Detail(id) {
for (var i = 0; i < this.details.length; i++) {
if (id === this.details[i].detail_id) {
this.details.splice(i, 1);
this.Calcul_Total();
}
}
},
//-----------------------------------verified Order List ------------------------------\\
verifiedForm() {
if (this.details.length <= 0) {
this.makeToast(
"warning",
this.$t("AddProductToList"),
this.$t("Warning")
);
return false;
} else {
var count = 0;
for (var i = 0; i < this.details.length; i++) {
if (
this.details[i].quantity == "" ||
this.details[i].quantity === 0 ||
this.details[i].quantity > this.details[i].stock
) {
count += 1;
if(this.details[i].quantity > this.details[i].stock){
this.makeToast("warning", this.$t("LowStock"), this.$t("Warning"));
return false;
}
}
}
if (count > 0) {
this.makeToast("warning", this.$t("AddQuantity"), this.$t("Warning"));
return false;
} else {
return true;
}
}
},
//---------- keyup OrderTax
keyup_OrderTax() {
if (isNaN(this.sale.tax_rate)) {
this.sale.tax_rate = 0;
} else if(this.sale.tax_rate == ''){
this.sale.tax_rate = 0;
this.Calcul_Total();
}else {
this.Calcul_Total();
}
},
//---------- keyup Discount
keyup_Discount() {
if (isNaN(this.sale.discount)) {
this.sale.discount = 0;
} else if(this.sale.discount == ''){
this.sale.discount = 0;
this.Calcul_Total();
}else {
this.Calcul_Total();
}
},
//---------- keyup Shipping
keyup_Shipping() {
if (isNaN(this.sale.shipping)) {
this.sale.shipping = 0;
} else if(this.sale.shipping == ''){
this.sale.shipping = 0;
this.Calcul_Total();
}else {
this.Calcul_Total();
}
},
async processPayment() {
this.paymentProcessing = true;
const { token, error } = await this.stripe.createToken(
this.cardElement
);
if (error) {
this.paymentProcessing = false;
NProgress.done();
this.makeToast("danger", this.$t("InvalidData"), this.$t("Failed"));
} else {
axios
.post("sales", {
date: this.sale.date,
client_id: this.sale.client_id,
warehouse_id: this.sale.warehouse_id,
statut: this.sale.statut,
notes: this.sale.notes,
tax_rate: this.sale.tax_rate?this.sale.tax_rate:0,
TaxNet: this.sale.TaxNet?this.sale.TaxNet:0,
discount: this.sale.discount?this.sale.discount:0,
shipping: this.sale.shipping?this.sale.shipping:0,
GrandTotal: this.GrandTotal,
details: this.details,
payment: this.payment,
amount: parseFloat(this.payment.amount).toFixed(2),
received_amount: parseFloat(this.payment.received_amount).toFixed(2),
change: parseFloat(this.payment.received_amount - this.payment.amount).toFixed(2),
token: token.id,
is_new_credit_card: this.is_new_credit_card,
selectedCard: this.selectedCard,
card_id: this.card_id,
})
.then(response => {
this.paymentProcessing = false;
this.makeToast(
"success",
this.$t("Create.TitleSale"),
this.$t("Success")
);
NProgress.done();
this.$router.push({ name: "index_sales" });
})
.catch(error => {
this.paymentProcessing = false;
NProgress.done();
this.makeToast("danger", this.$t("InvalidData"), this.$t("Failed"));
});
}
},
//--------------------------------- Create Sale -------------------------\\
Create_Sale() {
if (this.verifiedForm()) {
// Start the progress bar.
NProgress.start();
NProgress.set(0.1);
if (this.payment.Reglement == "credit card" && this.is_new_credit_card) {
if(this.stripe_key != ''){
this.processPayment();
}else{
this.makeToast("danger", this.$t("credit_card_account_not_available"), this.$t("Failed"));
NProgress.done();
}
}else{
this.paymentProcessing = true;
axios
.post("sales", {
date: this.sale.date,
client_id: this.sale.client_id,
warehouse_id: this.sale.warehouse_id,
statut: this.sale.statut,
notes: this.sale.notes,
tax_rate: this.sale.tax_rate?this.sale.tax_rate:0,
TaxNet: this.sale.TaxNet?this.sale.TaxNet:0,
discount: this.sale.discount?this.sale.discount:0,
shipping: this.sale.shipping?this.sale.shipping:0,
GrandTotal: this.GrandTotal,
details: this.details,
payment: this.payment,
amount: parseFloat(this.payment.amount).toFixed(2),
received_amount: parseFloat(this.payment.received_amount).toFixed(2),
change: parseFloat(this.payment.received_amount - this.payment.amount).toFixed(2),
is_new_credit_card: this.is_new_credit_card,
selectedCard: this.selectedCard,
card_id: this.card_id,
})
.then(response => {
this.makeToast(
"success",
this.$t("Create.TitleSale"),
this.$t("Success")
);
NProgress.done();
this.paymentProcessing = false;
this.$router.push({ name: "index_sales" });
})
.catch(error => {
NProgress.done();
this.paymentProcessing = false;
this.makeToast(
"danger",
this.$t("InvalidData"),
this.$t("Failed")
);
});
}
}
},
//-------------------------------- Get Last Detail Id -------------------------\\
Last_Detail_id() {
this.product.detail_id = 0;
var len = this.details.length;
this.product.detail_id = this.details[len - 1].detail_id + 1;
},
//---------------------------------Get Product Details ------------------------\\
Get_Product_Details(product_id, variant_id) {
axios.get("/show_product_data/" + product_id +"/"+ variant_id).then(response => {
this.product.discount = 0;
this.product.DiscountNet = 0;
this.product.discount_Method = "2";
this.product.product_id = response.data.id;
this.product.product_type = response.data.product_type;
this.product.name = response.data.name;
this.product.Net_price = response.data.Net_price;
this.product.Unit_price = response.data.Unit_price;
this.product.taxe = response.data.tax_price;
this.product.tax_method = response.data.tax_method;
this.product.tax_percent = response.data.tax_percent;
this.product.unitSale = response.data.unitSale;
this.product.fix_price = response.data.fix_price;
this.product.sale_unit_id = response.data.sale_unit_id;
this.product.is_imei = response.data.is_imei;
this.product.imei_number = '';
this.add_product();
this.Calcul_Total();
});
},
//---------------------------------------Get Elements ------------------------------\\
GetElements() {
axios
.get("sales/create")
.then(response => {
this.clients = response.data.clients;
this.warehouses = response.data.warehouses;
this.accounts = response.data.accounts;
this.stripe_key = response.data.stripe_key;
this.isLoading = false;
})
.catch(response => {
setTimeout(() => {
this.isLoading = false;
}, 500);
});
}
},
//----------------------------- Created function-------------------
created() {
this.GetElements();
}
};
</script>
<style>
.input-with-icon {
display: flex;
align-items: center;
}
.scan-icon {
width: 50px; /* Adjust size as needed */
height: 50px;
margin-right: 8px; /* Adjust spacing as needed */
cursor: pointer;
}
</style>