Created
August 17, 2021 16:49
-
-
Save ghaffaru/5092ed16e535e2bfe2ad57aa7bc7e435 to your computer and use it in GitHub Desktop.
Revisions
-
ghaffaru created this gist
Aug 17, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,1100 @@ <template> <div> <div :class="$vuetify.theme.isDark ? 'bg-black' : 'bg-white'" v-if="!not_allowed" class="col-md-10 mx-auto col-sm-12 card"> <v-overlay :value="loading" :dark="false" > <v-progress-circular :size="50" color="green" indeterminate ></v-progress-circular> </v-overlay> <div class="card-header"> <v-row> <v-col cols="12" sm="2"> <v-btn text color="blue darken-4" to="/sales"><v-icon>mdi-arrow-left</v-icon> {{ $t("main.back") }}</v-btn> </v-col> <v-col cols="12" sm="10"> <h3 class="font-weight-light">{{ $t("quick_sale.create_new_sale") }}</h3> </v-col> </v-row> </div> <v-row> <v-col cols="12" sm="12" class="mx-auto"> <v-row> <v-col cols="12" sm="12" class="text-center"> <h1 class="font-weight-black">{{ $t("main.amount_due") }}: {{amount_due | toMoney | currency_symbol}}</h1> <h4 class="font-weight-light"> {{ $t("main.subtotal") }}: {{gross_amount | toMoney | currency_symbol}} | {{ $t("main.taxes") }}: {{tax_amount | toMoney | currency_symbol}} | {{ $t("main.discount") }}: ({{discount_amount | toMoney | currency_symbol}}) </h4> </v-col> </v-row> <v-row> <v-col cols="12" sm="3" class="text-right"> <v-switch color="success" :label="$t('main.print_receipt')" v-model="print_receipt"></v-switch> </v-col> <v-col cols="12" sm="3"> <v-menu v-model="startDate_menu" :close-on-content-click="false" :nudge-right="40" transition="scale-transition" offset-y min-width="290px" > <template v-slot:activator="{ on }"> <v-text-field v-model="entrydate" :label="$t('main.entry_date')" prepend-icon="event" readonly outlined="" v-on="on" ></v-text-field> </template> <v-date-picker v-model="entrydate" @input="startDate_menu = false"></v-date-picker> </v-menu> </v-col> <v-col cols="12" sm="4"> <account-dropdown :accountTypes="['Assets']" :subAccounts="payment_accounts" :label="$t('main.payment_account')" :hint="$t('main.payment_account_hint')" @accountChange="(id) => handleAccountChange(id, 'payment')" ></account-dropdown> </v-col> </v-row> <v-row> <v-col cols="12" sm="12" class="mx-auto text-center" > <h5 v-if="!sales_items.length>0" class="font-italic align-center">{{ $t("quick_sale.add_item") }}</h5> <v-form ref="item_form" class="mb-2"> <v-simple-table v-if="sales_items" class="table-hover"> <tbody> <tr v-for="(item, index) in sales_items" v-bind:key="index"> <td> <v-autocomplete :disabled="saving_progress" :items="items" item-value="id" item-text="name" :label="$t('main.item')" cache-items eager v-model="item.item" @change="apply_item(index)" return-object :no-data-text="$t('main.empty_product_service')" @focus="new_product= items.length===0" ></v-autocomplete> </td> <td> <v-textarea rows="2" small :label="$t('main.description')" v-model="item.description" auto-grow > </v-textarea> </td> <td> <v-text-field @keyup="compute_amount(item)" @change="item.rawUnit_price ? item.rawUnit_price=item.rawUnit_price : item.rawUnit_price =0; compute_amount(item)" type="number" :label="$t('main.price')" v-model="item.rawUnit_price" :prefix="$store.state.user.user_infor.current_business.currency.symbol" > </v-text-field> </td> <td> <v-text-field @keyup="compute_amount(item)" @change="item.invoice_quantity ? item.invoice_quantity=item.invoice_quantity : item.invoice_quantity=1; compute_amount(item)" type="number" :label="$t('main.quantity')" v-model="item.invoice_quantity" :rules="required_rules" > </v-text-field> </td> <td class="text-center"> {{item.invoice_amount | toMoney}} <br> {{ $t("main.amount") }} </td> <td> <v-select :items="item.taxes" item-value="id" item-text="display_name" v-model="item.tax_id" clearable @change="compute_amount(item)" :hint="item.tax_amount | toMoney |currency_symbol" persistent-hint :label="$t('main.tax')" > </v-select> </td> <td> <v-row> <v-col cols="12" sm="12" class="p-0"> <v-btn class="text-muted" @click="moveItem_up(index,item)" v-if="index != 0 " icon small><v-icon>mdi-arrow-up</v-icon></v-btn> </v-col> <v-col cols="12" sm="12" class="p-0"> <v-btn @click="remove_item(index)" color="red" icon small><v-icon>mdi-close</v-icon></v-btn> </v-col> <v-col cols="12" sm="12" class="p-0"> <v-btn @click="moveItem_down(index,item)" class="text-muted" v-if="index < (sales_items.length-1)" icon small><v-icon>mdi-arrow-down</v-icon></v-btn> </v-col> </v-row> </td> </tr> </tbody> </v-simple-table> </v-form> <v-btn :disabled="quantity_error" rounded x-large outlined color="green" @click="add_line" >{{ sales_items.length>0 ? 'Add another item' : 'Add item' }}</v-btn> <v-btn small :disabled="quantity_error" rounded x-large outlined color="green" text dark @click="$store.state.new_item = true; retailbyDefaule=true;" ><v-icon>mdi-plus</v-icon> {{ $t("main.new_item") }}</v-btn> </v-col> <tag-input :message="$t('tags.sale_tag')" color="green" @tags="setTags" :tagFill="tagFill" > </tag-input> </v-row> <v-row class="mb-2"> <v-col cols="12" sm="10" class="mx-auto" v-if="sales_items.length"> <v-textarea outlined :label="$t('main.note')" v-model="note" rows="2" auto-grow > </v-textarea> </v-col> </v-row> </v-col> </v-row> <div class="card-footer"> <v-row> <v-spacer></v-spacer> <v-col cols="12" sm="6"> <v-menu v-model="discount_dlg" :close-on-content-click="false" :nudge-width="200" offset-x ref="discount_menu" > <template v-slot:activator="{ on }"> <v-btn x-large outlined color="green" dark rounded v-on="on" > {{ $t("main.add_discount") }} </v-btn> </template> <v-card style="max-width: 400px" class="p-1" > <v-list> <v-list-item> <v-list-item-title> {{ $t("main.apply_discount") }}</v-list-item-title> </v-list-item> <v-list-item> <v-list-item-action> <v-switch :label="$t('main.enter_amount')" @change="discount_percentageSwitch = ! discount_percentageSwitch" v-model="discount_amountSwitch" color="blue darken-4"></v-switch> </v-list-item-action> <v-list-item-title> <v-text-field class="m-2" outlined @keyup="compute_total_amount" v-model="discount_amount" :disabled="!discount_amountSwitch" :placeholder="$t('main.amount')" ></v-text-field> </v-list-item-title> </v-list-item> <v-list-item > <v-list-item-action> <v-switch label=" %" @change="discount_amountSwitch = !discount_amountSwitch" v-model="discount_percentageSwitch" color="purple"></v-switch> </v-list-item-action> <v-list-item-title> <v-text-field class="m-2" outlined @keyup="compute_discountPercentage" v-model="discount_percentage" :disabled="!discount_percentageSwitch" @change="compute_discountPercentage" placeholder="%" ></v-text-field> </v-list-item-title> </v-list-item> <v-list-item> <v-btn @click="$refs.discount_menu.isActive =false;" small text color="success"> {{ $t("main.done") }}</v-btn> </v-list-item> </v-list> </v-card> </v-menu> <span v-if="discount_percentageSwitch" style="font-size: 18px;" class="font-weight-light ">{{discount_percentage}}%: {{discount_amount | toMoney | currency_symbol}} </span> <span v-if="discount_amountSwitch" style="font-size: 18px;" class="font-weight-light ">{{ discount_amount | toMoney | currency_symbol}} </span> </v-col> <v-col cols="12" sm="2" > <v-btn :disabled="quantity_error" :loading="saving_progress" @click="save_cart" rounded color="success" x-large>{{ $t("main.save") }}</v-btn> </v-col> </v-row> </div> </div> <cannotview-component v-else></cannotview-component> <new-item-component @ItemCreated="create_item" :selling_default="retailbyDefaule" > </new-item-component> <v-snackbar v-model="success_message" :timeout="snack_timeout" > {{toast_msg}} <v-btn color="success" text @click="success_message = false" > {{ $t("main.cancel") }} </v-btn> </v-snackbar> <v-snackbar v-model="error_message" color="error" :timeout="snack_timeout" > {{error_msg}} <v-btn color="light" text @click="error_message = false" > {{ $t("main.close") }} </v-btn> </v-snackbar> </div> </template> <script> import moment from "moment"; import CannotviewComponent from "./cannotviewComponent"; import NewItemComponent from "../product/NewItemComponent"; const AccountDropdown = ()=>import('./accountDropdown.vue'); const loginComponent = ()=>import('./loginComponent'); const tagInput = () => import("./tagInput") export default { name: "newsaleComponent", components:{ NewItemComponent, CannotviewComponent, tagInput, AccountDropdown }, data(){ return{ retailbyDefaule:false, tags: [], tagFill: false, categories:[], cat_loading:false, category_id:null, img_preview:'/img/item_photo.png', has_photo:false, change_photo:false, not_allowed:false, print_receipt:true, selling_price:0, display_unitPrice:0, code:'', quantity_error:false, unit_price:0, name:'', formValid:false, saving_progress:false, is_product:true, new_product:false, search_value:'', payment_accounts:[], payment_account:null, delete_dialog:false, sale_id:'', gross_amount:0, amount_due:0, snack_timeout:4500, snack_infinite:0, search:'', business_name:'', business_logo:'', business_address:'', business_phone:'', business_email:'', loading:false, success_message:false, sales_preview:false, toast_msg:'', items:[], items_alt:[], sales:[], error_msg:'', error_message:false, client_error:false, editing:false, create_overlay:false, discount_percentage:0, discount_percentageSwitch:true, discount_amountSwitch:false, discount_amount:0, discounted_amount:0, note:'', sale_number:'', discount_dlg:false, sales_items:[], sales_dialog:false, startDate_menu:false, entrydate:'', clear_dialog:false, item_type:'Product', item_types:[{text:'Product',value:'Product'},{text:'Service',value:'Service'}], buy:false, tax_amount:0, retail:true, taxed:false, sales_accounts:[], sales_account:null, purchase_accounts:[], purchase_account:null, track_inventory:false, description:'', alltaxes:[], item_tax:null, nameRules: [ v => !!v || this.$t("main.item_name_required"), //v => (v && v.length <= 10) || 'Name must be less than 10 characters', ], required_rules:[ v => !!v || this.$t("main.required"), v => v>0 || this.$t("main.invalid_value"), ], headers: [ { text: 'SALE #', align: 'center', sortable: true, value: 'sales_number', }, { text: 'NET AMOUNT', value: 'amount_due' }, { text: 'ENTRY DATE', value: 'entry_date', align:'center', }, { text: 'CREATED ON', value: 'created_at' }, { text: 'ACTIONS', value: 'id', align:'center' }, ] } }, computed: { baseUrl() { return this.$store.state.baseURL }, businessCurrency(){ return this.$store.state.user.user_infor.current_business.currency; } }, methods:{ setTags(tags) { this.tags = tags }, handleAccountChange(accountId, type) { if (type === "payment") this.payment_account = accountId if (type === "sales") this.sales_account = accountId if (type === "purchase") this.purchase_account = accountId }, get_categories(){ this.cat_loading=true; axios.get('/api/itemcategories') .then(res=>{ this.categories = res.data; this.category_id = this.categories[0].id; this.cat_loading=false; }) }, remove_photo(){ this.photo=null; this.img_preview='/img/item_photo.png'; this.has_photo=false; this.change_photo=true; }, select_image(){ let image_selector = document.getElementById('item_photo'); image_selector.click(); }, set_image(e){ this.photo = e.target.files[0]; const fr = new FileReader(); fr.readAsDataURL(this.photo); fr.onload = ()=>{ this.img_preview = fr.result; this.has_photo=true; } }, get_default_account(accounts,account_name=null){ return accounts.filter(account=>{ return account.account_name == account_name; }); }, apply_item(i){ let item = this.sales_items[i]; if(item.track_inventory === 1 && Number(item.display_unitPrice) ===0){ this.error_msg = this.$t("item.stock_error"); this.error_message =true; } let product= JSON.parse(JSON.stringify(item.item)); product.invoice_quantity =1; this.sales_items[i] = product ; this.sales_items[i].item = product; //item = product ; this.compute_amount(this.sales_items[i]); //this.compute_total_amount(); this.compute_discountPercentage(); }, get_country_and_taxes(){ axios.get('/api/getcountryinfo') .then(res=>{ this.$store.state.user.countryInfo = res.data; this.alltaxes = res.data.usertaxes; this.loading = false; }) .catch(()=>{ this.error_msg = this.$t("quick_sale.sales_account_error"); this.error_message = true; this.sales_dialog = false; this.loading = false; }); }, type_change(){ this.is_product = this.item_type=='Product' ? true:false; }, create_item(item){ this.add_item(item); this.items.push(item); this.retailbyDefaule = false; }, search_item(){ let new_list = []; new_list = new_list.length > 0 ? new_list : JSON.parse(JSON.stringify(this.items)); let search_match = []; if(this.search_value.length >0){ for(let i =0; i<new_list.length;i++){ if(new_list[i].name.toLowerCase().indexOf(this.search_value.toLowerCase())> -1){ search_match.push(new_list[i]); } } this.items = search_match; }else{ this.items = JSON.parse(JSON.stringify(this.items_alt)); } }, print_invoice(){ window.print(); }, view_details(item){ this.$store.state.selected_invoice = item; this.sales_preview = true; }, new_sale(){ this.editing = false; this.sales_dialog = true; this.sale_number =''; this.get_items(); }, compute_discountPercentage(){ if(this.discount_percentageSwitch){ this.discount_amount = this.gross_amount*(this.discount_percentage/100); this.amount_due =this.gross_amount - this.discount_amount; this.amount_due+=Number(this.tax_amount); } }, compute_inputTax(rate,amount){ return (Number(rate)/100)*Number(amount); }, compute_total_amount(){ let gross_amount = 0; let tax_amount = 0; this.quantity_error=false; this.sales_items.forEach(item=>{ if(item.track_inventory==1){ if(item.invoice_quantity > item.quantity){ this.quantity_error = true; this.error_msg = "Invalid quantity provided for "+item.name+", you have "+item.quantity+" of "+item.name+" in stock"; this.error_message =true; }else{ this.quantity_error = false; this.error_message =false; } }else{ this.quantity_error = false; } let total_tax = 0; if(item.tax_id){ let selected_tax = item.taxes.find(tax=>{ return tax.id == item.tax_id; }); if(selected_tax.type=='Flat'){ item.tax_amount = this.compute_inputTax(selected_tax.rate,item.invoice_amount); tax_amount += item.tax_amount; gross_amount += item.invoice_amount; }else if(selected_tax.type=='Compound'){ //compute input taxes before applying compound taxes let subamount = this.compute_inputTax(selected_tax.sub_rate,item.invoice_amount); let compound_amount = Number(subamount) + Number(item.invoice_amount); let compound_taxAmount = this.compute_inputTax(selected_tax.rate,compound_amount); item.tax_amount = compound_taxAmount+subamount; item.total_tax_rate = total_tax; // gross_amount+= Number(item.tax_amount)+Number(item.invoice_amount)+Number(subamount); // item.tax_amount+=subamount; tax_amount += item.tax_amount; gross_amount += item.invoice_amount; } }else{ item.tax_amount=0; item.total_tax_rate = 0; gross_amount+=Number(item.invoice_amount); } }); this.tax_amount = tax_amount; this.compute_discountPercentage(); this.gross_amount = gross_amount; this.amount_due = (Number(gross_amount) - Number(this.discount_amount)) +this.tax_amount; }, remove_item(index){ this.sales_items.splice(index,1); this.compute_total_amount(); this.compute_discountPercentage(); }, compute_amount(item){ if(item.invoice_quantity){ item.invoice_amount = (Number(item.rawUnit_price) * Number(item.invoice_quantity)); } this.compute_total_amount(); }, delete_item(item){ this.sale_number = item.sales_number; this.sale_id = item.id; this.delete_dialog = true; }, get_items(){ this.loading = true; axios.get("/api/getinvoiceitems") .then(res=>{ this.items=res.data; this.items_alt=res.data; this.loading=false; if(this.$route.query.item_id){ let searchItem = this.items.find(item=>{ return item.id==this.$route.query.item_id}); if (searchItem){ this.create_item(searchItem); } } this.get_country_and_taxes(); }) .catch(()=>{ this.error_msg=this.$t("main.error_msg"); this.error_message=true; }); }, reload_items(){ this.loading = true; axios.get("/api/getinvoiceitems") .then(res=>{ this.items=res.data; this.items_alt=res.data; this.loading=false; }) .catch(()=>{ this.error_msg=this.$t("main.error_msg"); this.error_message=true; }); }, get_salenumber(){ axios.get('/api/getsalenumber') .then(number=>{ this.sale_number = number.data; this.loading=false; }) .catch(()=>{ this.sales_dialog=false; this.error_msg=this.$t("main.error_msg"); this.error_message=true; }); }, prep_items(items){ let new_items=[]; items.forEach(item=>{ item.item=null; new_items.push(item); }); return new_items; }, reprep_items(){ this.sales_items.forEach(item=>{ item.item=item; }); }, save_cart(){ if (this.quantity_error){ this.error_message=true; return false; } if (!this.validate_items()){ return false; } if(this.sales_items.length){ if(!this.payment_account){ this.error_msg=this.$t("main.payment_account_selected_error"); this.error_message=true; return false; } if(this.$refs.item_form.validate()){ this.saving_progress=true; let cart={ entry_date:this.entrydate, sale_number:this.sale_number, gross_amount:this.gross_amount, discount_amount:this.discount_amount, amount_due:this.amount_due, items:this.prep_items(this.sales_items), note:this.note, tag: this.tags.toString(), payment_accountID:this.payment_account }; axios.post('/api/savesale',cart) .then(sale=>{ this.saving_progress=false; this.toast_msg=this.$t("quick_sale.sale_create_success"); this.success_message=true; this.sales_items = []; this.discount_amount=0; this.discount_percentage=0; this.tax_amount=0; this.amount_due=0; this.gross_amount=0; this.tags = [] this.tagFill = true if (this.print_receipt){ let w = window.open(this.baseUrl + '/printposreceipt/'+sale.data.enc_id,"Sales receipt"+sale.data.sales_number, 'height=800,width=900'); w.onload=function (e) { w.print(); }; } this.reload_items(); }) .catch(err=>{ this.saving_progress=false; this.error_msg= err.response.status===302 ? err.response.data : this.$t("quick_sale.item_create_error"); this.error_message=true; this.reprep_items(); }); } }else{ this.error_msg=this.$t("quick_sale.cart_empty_error"); this.error_message=true; } }, // add_item(item){ // if(item.track_inventory === 1 && Number(item.display_unitPrice) ==0){ // this.error_msg = "This item is out of stocked, please purchase this item before you can sell it"; // this.error_message =true; // return false; // } // let newitem =JSON.parse(JSON.stringify(item)); // newitem.invoice_quantity=1; // this.sales_items.push(newitem); // // this.compute_total_amount(); // this.compute_discountPercentage(); // // this.$refs.item_menu.isActive = false; // // }, validate_items(){ let res = true; for (let item of this.sales_items){ if (!item.id){ this.error_msg=this.$t("item.item_line_error"); this.error_message=true; res = false; break; } } return res; }, add_line(){ let item = { id:null, description:'', tax_id:'' , rawUnit_price:0, invoice_amount:0, invoice_quantity:1, item:{} }; this.sales_items.push(item); }, add_item(item){ if(item.track_inventory == 1 && Number(item.display_unitPrice) ==0){ this.error_msg = this.$t("item.stock_error") this.error_message =true; return false; } let newitem =JSON.parse(JSON.stringify(item)); this.items.push(newitem); newitem.item = JSON.parse(JSON.stringify(item)); newitem.invoice_quantity=1; this.sales_items.push(newitem); this.compute_total_amount(); this.compute_discountPercentage(); }, moveItem_up(index,item){ let index2 = index-1; let item2 = this.sales_items[index2]; this.sales_items[index]=item2; this.sales_items[index2]=item; this.sales_items.push({}); let lastindex = (this.sales_items.length -1); this.remove_item(lastindex); }, moveItem_down(index,item){ let index2 = index+1; let item2 = this.sales_items[index2]; this.sales_items[index]=item2; this.sales_items[index2]=item; this.sales_items.push({}); let lastindex = (this.sales_items.length -1); this.remove_item(lastindex); } }, mounted() { if (this.$store.state.user.user_infor.is_admin==1){ this.not_allowed=false; this.get_items(); }else { if(this.$store.state.user.user_infor.components[2].roles.create == 1){ this.not_allowed=false; this.get_items(); }else { this.not_allowed=true; this.progress = false; return false; } } let sales_sub = this.$store.state.user.countryInfo.incomeAccounts.subtypes.filter(account_type=>{ return account_type.name == "Revenue"; }); this.sales_accounts = sales_sub[0].accounts; this.sales_account = this.sales_accounts[1].id; let expense_sub = this.$store.state.user.countryInfo.expenseAccounts.subtypes.filter(account_type=>{ return account_type.name == "Cost of Sale"; }); let expense_sub2 = this.$store.state.user.countryInfo.expenseAccounts.subtypes.filter(account_type=>{ return account_type.name == "Operating Expenses"; }); this.purchase_accounts = [...expense_sub[0].accounts, ...expense_sub2[0].accounts]; // let default_account = this.get_default_account(expense_sub[0].accounts,'Purchases'); // let default_sales = this.get_default_account(sales_sub[0].accounts,'Sales Revenue'); // // this.sales_account=default_sales[0].id; // this.purchase_account = default_account[0].id; this.entrydate = moment().format('Y-M-D'); let sales2_sub = this.$store.state.user.countryInfo.assetAccounts.subtypes.filter(account_type=>{ return account_type.name == "Cash & Bank"; }); let all_payment_accounts = sales2_sub[0].accounts; this.payment_accounts = all_payment_accounts.filter(account=>{ return !account.currency || account.currency === this.businessCurrency.code; }); } } </script> <style scoped> .v-menu__content{ max-height: 100%; overflow-y: auto !important; } @media screen and (max-device-width:768px) { td { display: table-row; width: 100% !important; } td> .v-input{ min-width: 360px !important; align-self: center; } } @media screen and (device-width:768px) { td { display: table-row; width: 100% !important; } td> .v-input{ min-width: 660px !important; align-self: center; } } </style>