Merge branch 'master' of github.com:akaunting/akaunting into 2.1-dev

# Conflicts:
#	app/Http/Controllers/Common/Items.php
#	resources/views/modules/item/documentation.blade.php
#	resources/views/modules/item/show.blade.php
#	resources/views/partials/admin/header.blade.php
#	resources/views/purchases/bills/show.blade.php
#	resources/views/purchases/vendors/show.blade.php
#	resources/views/sales/customers/show.blade.php
#	resources/views/sales/invoices/show.blade.php
#	resources/views/wizard/companies/edit.blade.php
#	resources/views/wizard/currencies/index.blade.php
#	resources/views/wizard/finish/index.blade.php
#	resources/views/wizard/taxes/index.blade.php
This commit is contained in:
Cüneyt Şentürk
2020-08-28 19:24:26 +03:00
824 changed files with 14086 additions and 5612 deletions

View File

@ -19,9 +19,12 @@
<el-carousel-item v-for="(screenshot, index) in screenshots" :key="index">
<img class="d-block w-100 carousel-frame" height="365px" :src="screenshot.path_string" :alt="screenshot.alt_attribute">
<div class="carousel-description py-2">
<div class="carousel-description py-2" v-if="screenshot.description">
{{ screenshot.description }}
</div>
<div class="carousel-description py-2" v-else>
{{ name }}
</div>
</el-carousel-item>
</el-carousel>
</div>

View File

@ -0,0 +1,201 @@
<template>
<div class="countdown">
<section class="flex text-6xl justify-center content-center">
<div class="days mr-2 relative">
{{ displayDays}}
<div class="label text-sm absolut bottom-0"> {{ textDays }} </div>
</div>
<span class="leading-snug">:</span>
<div class="hours mx-2 relative">
{{ displayHours }}
<div class="label text-sm absolut bottom-0">{{ textHours }} </div>
</div>
<span class="leading-snug">:</span>
<div class="minutes mx-2 relative">
{{ displayMinutes }}
<div class="label text-sm absolut bottom-0"> {{ textMinutes }} </div>
</div>
<span class="leading-snug">:</span>
<div class="seconds ml-2 relative">
{{ displaySeconds }}
<div class="label text-sm absolut bottom-0"> {{ textSeconds }} </div>
</div>
</section>
</div>
</template>
<script>
export default {
name: 'akaunting-countdown',
props: {
textDays: {
type: String,
default: 'days',
description: "Modal header title"
},
textHours: {
type: String,
default: 'hours',
description: "Modal header title"
},
textMinutes: {
type: String,
default: 'minutes',
description: "Modal header title"
},
textSeconds: {
type: String,
default: 'seconds',
description: "Modal header title"
},
year: {
type: Number,
default: 0,
description: "Modal header title"
},
month: {
type: Number,
default: 0,
description: "Modal header title"
},
date: {
type: Number,
default: 0,
description: "Input readonly status"
},
hour: {
type: Number,
default: 0,
description: "Input disabled status"
},
minute: {
type: Number,
default: 0,
description: "Input value defalut"
},
second: {
type: Number,
default: 0,
description: "Input model defalut"
},
millisecond: {
type: Number,
default: 0,
description: "Prepend icon (left)"
}
},
data:() => ({
displayDays: 0,
displayHours: 0,
displayMinutes: 0,
displaySeconds: 0,
loaded: false,
expired: false
}),
computed: {
_seconds: () => 1000,
_minutes() {
return this._seconds * 60;
},
_hours() {
return this._minutes * 60;
},
_days() {
return this._hours * 24;
},
end() {
return new Date(
this.year,
this.month,
this.date,
this.hour,
this.minute,
this.second,
this.millisecond
);
}
},
mounted() {
this.showRemainig();
},
methods: {
formatNum: num =>(num < 10 ? '0' + num : num),
showRemainig() {
const timer = setInterval(() => {
const now = new Date();
const distance = this.end.getTime() - now.getTime();
if (distance < 0) {
clearInterval(timer);
this.expired = true;
return;
}
const days = Math.floor(distance / this._days);
const hours = Math.floor((distance % this._days) / this._hours);
const minutes = Math.floor((distance % this._hours) / this._minutes);
const seconds = Math.floor((distance % this._minutes) / this._seconds);
this.displayMinutes = this.formatNum(minutes);
this.displaySeconds = this.formatNum(seconds);
this.displayHours = this.formatNum(hours);
this.displayDays = this.formatNum(days);
this.loaded = true;
}, 1000);
}
}
}
</script>
<style scoped>
.countdown {
text-align: justify;
}
.relative {
position: relative;
}
.flex {
display: flex;
}
.justify-center {
justify-content: center;
}
.content-center {
align-content: center;
}
.seconds {
max-width: 60px;
}
.leading-snug {
line-height: 1.375;
}
.days, .hours, .minutes, .seconds {
text-align: center;
}
</style>

View File

@ -6,7 +6,7 @@
{'disabled': disabled},
formClasses
]"
:error="formError"
:footer-error="formError"
:prependIcon="icon"
:readonly="readonly"
:disabled="disabled"

View File

@ -49,45 +49,64 @@
let editorRef = this.$refs.editor;
let node = editorRef.children[0];
this.editor.on('text-change', () => {
let html = node.innerHTML
let html = node.innerHTML;
if (html === '<p><br></p>') {
html = '';
}
this.content = html
this.content = html;
this.$emit('input', this.content);
})
},
pasteHTML () {
if (!this.editor) {
return
}
this.editor.pasteHTML(this.value)
this.editor.pasteHTML(this.value);
},
randomString() {
let text = "";
let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for (let i = 0; i < 5; i++)
for (let i = 0; i < 5; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
},
async mounted () {
let Quill = await import('quill')
Quill = Quill.default || Quill
this.content = this.value;
let Quill = await import('quill');
Quill = Quill.default || Quill;
this.editorId = this.randomString();
this.toolbarId = this.randomString();
this.$nextTick(() => {
this.initialize(Quill)
});
},
watch: {
value (newVal) {
if (newVal !== this.content) {
this.pasteHTML(newVal);
}
},
content (newVal) {
this.$emit('input', newVal);
}
}
}

View File

@ -22,6 +22,7 @@
</div>
<div class="modal-body pb-0" v-else>
<form id="form-create" method="POST" action="#"/>
<component v-bind:is="component"></component>
</div>
</slot>
@ -33,7 +34,11 @@
{{ buttons.cancel.text }}
</button>
<button :disabled="form.loading" type="button" class="btn button-submit" :class="buttons.confirm.class" @click="onSubmit">
<a v-if="buttons.payment" :href="buttons.payment.url" class="btn btn-white" :class="buttons.payment.class">
{{ buttons.payment.text }}
</a>
<button :disabled="form.loading" type="button" class="btn button-submit" :class="buttons.confirm.class" @click="onSubmit">
<div class="aka-loader"></div><span>{{ buttons.confirm.text }}</span>
</button>
</div>
@ -139,10 +144,12 @@ export default {
},
mounted() {
let form_prefix = this._uid;
if (this.is_component) {
this.component = Vue.component('add-new-component', (resolve, reject) => {
resolve({
template : '<div id="modal-add-new-form">' + this.message + '</div>',
template : '<div id="modal-add-new-form-' + form_prefix + '">' + this.message + '</div>',
components: {
AkauntingRadioGroup,
@ -159,7 +166,7 @@ export default {
},
mounted() {
let form_id = document.getElementById('modal-add-new-form').children[0].id;
let form_id = document.getElementById('modal-add-new-form-' + form_prefix).children[0].id;
this.form = new Form(form_id);
},
@ -217,7 +224,24 @@ export default {
})
.catch(error => {
});
}
},
// Change bank account get money and currency rate
async onChangePaymentAccount(account_id) {
let payment_account = Promise.resolve(window.axios.get(url + '/banking/accounts/currency', {
params: {
account_id: account_id
}
}));
payment_account.then(response => {
this.form.currency = response.data.currency_name;
this.form.currency_code = response.data.currency_code;
this.form.currency_rate = response.data.currency_rate;
})
.catch(error => {
});
},
}
})
});

View File

@ -10,7 +10,7 @@
</span>
</div>
<money :name="name" @input="input" :placeholder="placeholder" v-bind="money" :value="value" :disabled="disabled" :masked="masked" class="form-control"></money>
<money :name="name" @input="input" :placeholder="placeholder" v-bind="money" :value="model" :disabled="disabled" :masked="masked" class="form-control"></money>
</div>
<div class="invalid-feedback d-block" v-if="error" v-html="error"></div>
@ -126,6 +126,8 @@ export default {
},
mounted() {
//this.model = this.value;
this.$emit('interface', this.model);
},
@ -133,10 +135,15 @@ export default {
change() {
//this.$emit('change', this.model);
//this.$emit('interface', this.model);
this.$emit('input', this.model);
},
input(event) {
this.model = event;
this.$emit('input', event);
//this.$emit('change', this.model);
//this.$emit('interface', this.model);
}
@ -144,6 +151,10 @@ export default {
watch: {
dynamicCurrency: function (currency) {
if (!currency) {
return;
}
this.money = {
decimal: currency.decimal_mark,
thousands: currency.thousands_separator,
@ -153,9 +164,11 @@ export default {
masked: this.masked
};
},
value: function (value) {
this.model = value;
},
model: function (model) {
this.$emit('change', this.model);
this.$emit('interface', this.model);

View File

@ -763,7 +763,7 @@
<span slot="infoBlock" class="badge badge-success badge-resize float-right" v-if="new_options[real_model]">{{ new_text }}</span>
<select :name="name" v-model="real_model" class="d-none">
<select :name="name" :id="name" v-model="real_model" class="d-none">
<option v-for="(label, value) in selectOptions" :key="value" :value="value">{{ label }}</option>
</select>
</span>
@ -923,6 +923,7 @@ export default {
add_new_html: '',
form: {},
new_options: false,
couunt: 1,
}
},
@ -989,6 +990,24 @@ export default {
this.$emit('interface', this.real_model);
this.$emit('change', this.real_model);
//this.$children[0].$children[0].$emit('keydown.native.tab');
//this.$children[0].$children[0].handleMenuEnter();
this.$children[0].$children[0].visible = false;
/*
this.$children[0].$children[0].setSoftFocus();
if (this.$children[0].$children[0].visible) return;
let option = {};
option.value = this.real_model;
this.$children[0].$children[0].$nextTick(() => {
this.$children[0].$children[0].scrollToOption(option);
});
*/
},
async onAddItem() {
@ -1180,7 +1199,17 @@ export default {
return;
}
this.change();
if (this.real_model != value) {
this.change();
}
let e = $.Event('keyup');
e.keyCode= 9; // tab
$('#' + this.name).trigger(e);
let event = new window.KeyboardEvent('keydown', { keyCode: 9 }); // Tab key
window.dispatchEvent(event);
},
value: function (value) {
@ -1246,4 +1275,4 @@ export default {
margin-right: 35px;
position: relative;
}
</style>
</style>

View File

@ -12,7 +12,11 @@
]"
:error="formError">
<el-select v-model="real_model" @input="change" disabled filterable v-if="disabled"
:placeholder="placeholder">
remote
reserve-keyword
:placeholder="placeholder"
:remote-method="remoteMethod"
:loading="loading">
<div v-if="addNew.status && options.length != 0" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty">
{{ noMatchingDataText }}
@ -85,7 +89,11 @@
</el-select>
<el-select v-model="real_model" @input="change" filterable v-if="!disabled && !multiple"
:placeholder="placeholder">
remote
reserve-keyword
:placeholder="placeholder"
:remote-method="remoteMethod"
:loading="loading">
<div v-if="addNew.status && options.length != 0" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty">
{{ noMatchingDataText }}
@ -158,7 +166,11 @@
</el-select>
<el-select v-model="real_model" @input="change" filterable v-if="!disabled && multiple && !collapse" multiple
:placeholder="placeholder">
remote
reserve-keyword
:placeholder="placeholder"
:remote-method="remoteMethod"
:loading="loading">
<div v-if="addNew.status && options.length != 0" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty">
{{ noMatchingDataText }}
@ -231,7 +243,11 @@
</el-select>
<el-select v-model="real_model" @input="change" filterable v-if="!disabled && multiple && collapse" multiple collapse-tags
:placeholder="placeholder">
remote
reserve-keyword
:placeholder="placeholder"
:remote-method="remoteMethod"
:loading="loading">
<div v-if="addNew.status && options.length != 0" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty">
{{ noMatchingDataText }}
@ -464,14 +480,14 @@ export default {
},
value: {
type: [String, Number, Array],
default: null,
default: '',
description: "Selectbox selected value"
},
options: null,
model: {
type: [String, Number],
default: '',
default: null,
description: "Selectbox selected model"
},
@ -582,10 +598,21 @@ export default {
},
mounted() {
this.real_model = this.value;
if (this.multiple) {
if (!this.value.length) {
this.real_model = [];
} else {
let pre_value = [];
if (this.multiple && !this.real_model.length) {
this.real_model = [];
this.value.forEach(item => {
pre_value.push(item.toString());
});
this.real_model = pre_value;
}
} else {
this.real_model = this.value;
}
this.$emit('interface', this.real_model);
@ -593,6 +620,10 @@ export default {
methods: {
remoteMethod(query) {
if (document.getElementById('form-select-' + this.name)) {
document.getElementById('form-select-' + this.name).getElementsByTagName("input")[0].readOnly = false;
}
if (query !== '') {
this.loading = true;
@ -634,21 +665,27 @@ export default {
},
change() {
if (typeof(this.real_model) === 'object') {
if (typeof(this.real_model) === 'object' && !Array.isArray(this.real_model)) {
return false;
}
if (Array.isArray(this.real_model) && !this.real_model.length) {
return false;
}
this.$emit('interface', this.real_model);
this.$emit('change', this.real_model);
this.selectOptions.forEach(item => {
if (item.id == this.real_model) {
this.$emit('label', item.name);
this.$emit('option', item);
if (Array.isArray(this.selectOptions)) {
this.selectOptions.forEach(item => {
if (item.id == this.real_model) {
this.$emit('label', item.name);
this.$emit('option', item);
return true;
}
});
return true;
}
});
}
},
onPressEnter() {
@ -871,9 +908,37 @@ export default {
}
},
real_model: function (value) {
if (this.multiple) {
return;
}
if (this.real_model != value) {
this.change();
}
let e = $.Event('keyup');
e.keyCode= 9; // tab
$('#' + this.name).trigger(e);
let event = new window.KeyboardEvent('keydown', { keyCode: 9 }); // Tab key
window.dispatchEvent(event);
},
value: function (value) {
if (this.multiple) {
this.real_model = value;
if (Array.isArray(this.real_model) && !this.real_model.length) {
this.real_model = value;
} else {
let pre_value = [];
value.forEach(item => {
pre_value.push(item.toString());
});
this.real_model = pre_value;
}
} else {
//this.real_model = value.toString();
this.real_model = value;

View File

@ -52,6 +52,11 @@
</div>
</slot>
</div>
<slot name="error">
<div v-if="footerError" class="invalid-feedback d-block"
v-html="footerError">
</div>
</slot>
</div>
</template>
<script>
@ -84,6 +89,10 @@
type: String,
description: "Input error (below input)"
},
footerError: {
type: String,
description: "Input error (below input)"
},
successMessage: {
type: String,
description: "Input success message",

View File

@ -12,6 +12,7 @@ import AkauntingSelectRemote from './../components/AkauntingSelectRemote';
import AkauntingDate from './../components/AkauntingDate';
import AkauntingRecurring from './../components/AkauntingRecurring';
import AkauntingHtmlEditor from './../components/AkauntingHtmlEditor';
import AkauntingCountdown from './../components/AkauntingCountdown';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
@ -33,6 +34,7 @@ export default {
AkauntingDate,
AkauntingRecurring,
AkauntingHtmlEditor,
AkauntingCountdown,
[Select.name]: Select,
[Option.name]: Option,
[Steps.name]: Steps,
@ -56,6 +58,10 @@ export default {
mounted() {
this.checkNotify();
if (aka_currency) {
this.currency = aka_currency;
}
},
methods: {
@ -137,7 +143,7 @@ export default {
this.component = Vue.component('add-new-component', (resolve, reject) => {
resolve({
template : '<div id="dynamic-component"><akaunting-modal v-if="confirm.show" :show="confirm.show" :title="confirm.title" :message="confirm.message" :button_cancel="confirm.button_cancel" :button_delete="confirm.button_delete" @confirm="onDelete" @cancel="cancelDelete"></akaunting-modal></div>',
template : '<div id="dynamic-delete-component"><akaunting-modal v-if="confirm.show" :show="confirm.show" :title="confirm.title" :message="confirm.message" :button_cancel="confirm.button_cancel" :button_delete="confirm.button_delete" @confirm="onDelete" @cancel="cancelDelete"></akaunting-modal></div>',
components: {
AkauntingModal,
@ -184,10 +190,10 @@ export default {
}
})
.then(response => {
this.currency = response.data;
this.form.currency_code = response.data.currency_code;
this.form.currency_rate = response.data.currency_rate;
this.currency = response.data;
})
.catch(error => {
});
@ -308,5 +314,79 @@ export default {
// always executed
});
},
// Delete attachment file
onDeleteFile(file_id, url, title, message, button_cancel, button_delete) {
let file_data = {
page: null,
key: null,
value: null,
ajax: true,
redirect: window.location.href
};
if (this.form['page' + file_id]) {
file_data.page = this.form['page' + file_id];
}
if (this.form['key' + file_id]) {
file_data.key = this.form['key' + file_id];
}
if (this.form['value' + file_id]) {
file_data.value = this.form['value' + file_id];
}
let confirm = {
url: url,
title: title,
message: message,
button_cancel: button_cancel,
button_delete: button_delete,
file_data: file_data,
show: true
};
this.component = Vue.component('add-new-component', (resolve, reject) => {
resolve({
template : '<div id="dynamic-delete-file-component"><akaunting-modal v-if="confirm.show" :show="confirm.show" :title="confirm.title" :message="confirm.message" :button_cancel="confirm.button_cancel" :button_delete="confirm.button_delete" @confirm="onDelete" @cancel="cancelDelete"></akaunting-modal></div>',
components: {
AkauntingModal,
},
data: function () {
return {
confirm: confirm,
}
},
methods: {
// Delete action post
async onDelete() {
let promise = Promise.resolve(axios({
method: 'DELETE',
url: this.confirm.url,
data: file_data
}));
promise.then(response => {
if (response.data.redirect) {
window.location.href = response.data.redirect;
}
})
.catch(error => {
this.success = false;
});
},
// Close modal empty default value
cancelDelete() {
this.confirm.show = false;
},
}
})
});
},
}
}

View File

@ -48,9 +48,35 @@ export default class Form {
this[form_element.getAttribute('data-field')] = field;
}
/*
if (!this[form_element.getAttribute('data-field')][name]) {
this[form_element.getAttribute('data-field')][name] = '';
}
*/
if (type == 'radio') {
if (!this[form_element.getAttribute('data-field')][name]) {
this[form_element.getAttribute('data-field')][name] = (form_element.getAttribute('value') ? 1 : 0) || 0;
}
} else if (type == 'checkbox') {
if (this[form_element.getAttribute('data-field')][name]) {
if (!this[form_element.getAttribute('data-field')][name].push) {
this[form_element.getAttribute('data-field')][name] = [this[form_element.getAttribute('data-field')][name]];
}
if (form_element.checked) {
this[form_element.getAttribute('data-field')][name].push(form_element.value);
}
} else {
if (form_element.checked) {
this[form_element.getAttribute('data-field')][name] = form_element.value;
} else {
this[form_element.getAttribute('data-field')][name] = [];
}
}
} else {
this[form_element.getAttribute('data-field')][name] = form_element.getAttribute('value') || '';
}
continue;
}

View File

@ -40,7 +40,13 @@ const app = new Vue({
},
mounted() {
this.totals.closing_balance = parseFloat(document.getElementById('closing_balance').value);
if (document.getElementById('closing_balance') != null) {
this.totals.closing_balance = parseFloat(document.getElementById('closing_balance').value);
}
if (this.form._method == 'PATCH') {
this.onCalculate();
}
},
methods:{
@ -59,10 +65,13 @@ const app = new Vue({
let transactions = this.form.transactions;
let cleared_amount = 0;
let closing_balance = parseFloat(this.form.closing_balance);
let difference = 0;
let income_total = 0;
let expense_total = 0;
this.totals.closing_balance = closing_balance;
if (transactions) {
// get all transactions.
Object.keys(transactions).forEach(function(transaction) {
@ -86,9 +95,9 @@ const app = new Vue({
}
if (cleared_amount > 0) {
difference = parseFloat(this.form.closing_balance) - parseFloat(cleared_amount);
difference = (parseFloat(this.form.closing_balance) - parseFloat(cleared_amount)).toFixed(this.currency.precision);
} else {
difference = parseFloat(this.form.closing_balance) + parseFloat(cleared_amount);
difference = (parseFloat(this.form.closing_balance) + parseFloat(cleared_amount)).toFixed(this.currency.precision);
}
if (difference != 0) {

View File

@ -38,15 +38,6 @@ const app = new Vue({
tax: 0,
total: 0
},
transaction_form: new Form('transaction'),
payment: {
modal: false,
amount: 0,
title: '',
message: '',
html: '',
errors: new Error()
},
transaction: [],
items: '',
discount: false,
@ -60,7 +51,9 @@ const app = new Vue({
},
mounted() {
this.colspan = document.getElementById("items").rows[0].cells.length - 1;
if ((document.getElementById('items') != null) && (document.getElementById('items').rows)) {
this.colspan = document.getElementById("items").rows[0].cells.length - 1;
}
this.form.items = [];
if (this.form.method) {
@ -71,6 +64,7 @@ const app = new Vue({
let items = [];
let item_backup = this.form.item_backup[0];
let currency_code = this.form.currency_code;
let currency = this.currency;
this.edit.status = true;
@ -80,11 +74,11 @@ const app = new Vue({
currency: currency_code,
item_id: item.item_id,
name: item.name,
price: (item.price).toFixed(2),
price: (item.price).toFixed(currency.precision),
quantity: item.quantity,
tax_id: item.tax_id,
discount: item.discount_rate,
total: (item.total).toFixed(2)
total: (item.total).toFixed(currency.precision)
});
});
@ -183,10 +177,11 @@ const app = new Vue({
case 'fixed':
item_tax_total += tax.rate * item.quantity;
break;
case 'withholding':
item_tax_total += 0 - item_discounted_total * (tax.rate / 100);
break;
default:
let item_tax_amount = (item_discounted_total / 100) * tax.rate;
item_tax_total += item_tax_amount;
item_tax_total += item_discounted_total * (tax.rate / 100);
break;
}
}
@ -294,10 +289,10 @@ const app = new Vue({
this.form.items[index].item_id = item.id;
this.form.items[index].name = item.name;
this.form.items[index].price = (item.purchase_price).toFixed(2);
this.form.items[index].price = (item.purchase_price).toFixed(this.currency.precision);
this.form.items[index].quantity = 1;
this.form.items[index].tax_id = tax_id;
this.form.items[index].total = (item.purchase_price).toFixed(2);
this.form.items[index].total = (item.purchase_price).toFixed(this.currency.precision);
},
// remove bill item row => row_id = index
@ -322,51 +317,120 @@ const app = new Vue({
this.onCalculateTotal();
},
onPayment() {
this.payment.modal = true;
async onPayment() {
let bill_id = document.getElementById('bill_id').value;
let form = this.transaction_form;
this.transaction_form = new Form('transaction');
this.transaction_form.paid_at = form.paid_at;
this.transaction_form.account_id = form.account_id;
this.transaction_form.payment_method = form.payment_method;
},
addPayment() {
this.transaction_form.submit();
this.payment.errors = this.transaction_form.errors;
this.form.loading = true;
this.$emit("confirm");
},
closePayment() {
this.payment = {
let payment = {
modal: false,
amount: 0,
url: url + '/modals/bills/' + bill_id + '/transactions/create',
title: '',
message: '',
errors: this.transaction_form.errors
html: '',
buttons:{}
};
},
// Change bank account get money and currency rate
onChangePaymentAccount(account_id) {
axios.get(url + '/banking/accounts/currency', {
params: {
account_id: account_id
}
})
.then(response => {
this.transaction_form.currency = response.data.currency_name;
this.transaction_form.currency_code = response.data.currency_code;
this.transaction_form.currency_rate = response.data.currency_rate;
let payment_promise = Promise.resolve(window.axios.get(payment.url));
payment_promise.then(response => {
payment.modal = true;
payment.title = response.data.data.title;
payment.html = response.data.html;
payment.buttons = response.data.data.buttons;
this.component = Vue.component('add-new-component', (resolve, reject) => {
resolve({
template: '<div id="dynamic-payment-component"><akaunting-modal-add-new modal-dialog-class="modal-md" :show="payment.modal" @submit="onSubmit" @cancel="onCancel" :buttons="payment.buttons" :title="payment.title" :is_component=true :message="payment.html"></akaunting-modal-add-new></div>',
mixins: [
Global
],
data: function () {
return {
form:{},
payment: payment,
}
},
methods: {
onSubmit(event) {
this.form = event;
this.form.response = {};
this.loading = true;
let data = this.form.data();
FormData.prototype.appendRecursive = function(data, wrapper = null) {
for(var name in data) {
if (wrapper) {
if ((typeof data[name] == 'object' || data[name].constructor === Array) && ((data[name] instanceof File != true ) && (data[name] instanceof Blob != true))) {
this.appendRecursive(data[name], wrapper + '[' + name + ']');
} else {
this.append(wrapper + '[' + name + ']', data[name]);
}
} else {
if ((typeof data[name] == 'object' || data[name].constructor === Array) && ((data[name] instanceof File != true ) && (data[name] instanceof Blob != true))) {
this.appendRecursive(data[name], name);
} else {
this.append(name, data[name]);
}
}
}
};
let form_data = new FormData();
form_data.appendRecursive(data);
window.axios({
method: this.form.method,
url: this.form.action,
data: form_data,
headers: {
'X-CSRF-TOKEN': window.Laravel.csrfToken,
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'multipart/form-data'
}
})
.then(response => {
if (response.data.success) {
if (response.data.redirect) {
this.form.loading = true;
window.location.href = response.data.redirect;
}
}
if (response.data.error) {
this.form.loading = false;
this.form.response = response.data;
}
})
.catch(error => {
this.form.loading = false;
this.form.onFail(error);
this.method_show_html = error.message;
});
},
onCancel() {
this.payment.modal = false;
this.payment.html = null;
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
},
}
})
});
})
.catch(error => {
})
.finally(function () {
// always executed
});
},
}

View File

@ -38,15 +38,6 @@ const app = new Vue({
tax: 0,
total: 0
},
transaction_form: new Form('transaction'),
payment: {
modal: false,
amount: 0,
title: '',
message: '',
html: '',
errors: new Error()
},
transaction: [],
items: '',
discount: false,
@ -60,7 +51,10 @@ const app = new Vue({
},
mounted() {
this.colspan = document.getElementById("items").rows[0].cells.length - 1;
if ((document.getElementById('items') != null) && (document.getElementById('items').rows)) {
this.colspan = document.getElementById("items").rows[0].cells.length - 1;
}
this.form.items = [];
if (this.form.method) {
@ -71,6 +65,7 @@ const app = new Vue({
let items = [];
let item_backup = this.form.item_backup[0];
let currency_code = this.form.currency_code;
let currency = this.currency;
this.edit.status = true;
@ -80,11 +75,11 @@ const app = new Vue({
currency: currency_code,
item_id: item.item_id,
name: item.name,
price: (item.price).toFixed(2),
price: (item.price).toFixed(currency.precision),
quantity: item.quantity,
tax_id: item.tax_id,
discount: item.discount_rate,
total: (item.total).toFixed(2)
total: (item.total).toFixed(currency.precision)
});
});
@ -183,10 +178,11 @@ const app = new Vue({
case 'fixed':
item_tax_total += tax.rate * item.quantity;
break;
case 'withholding':
item_tax_total += 0 - item_discounted_total * (tax.rate / 100);
break;
default:
let item_tax_amount = (item_discounted_total / 100) * tax.rate;
item_tax_total += item_tax_amount;
item_tax_total += item_discounted_total * (tax.rate / 100);
break;
}
}
@ -231,7 +227,7 @@ const app = new Vue({
// set global total variable.
this.totals.sub = sub_total;
this.totals.tax = tax_total;
this.totals.tax = Math.abs(tax_total);
this.totals.item_discount = line_item_discount_total;
// Apply discount to total
@ -294,10 +290,10 @@ const app = new Vue({
this.form.items[index].item_id = item.id;
this.form.items[index].name = item.name;
this.form.items[index].price = (item.sale_price).toFixed(2);
this.form.items[index].price = (item.sale_price).toFixed(this.currency.precision);
this.form.items[index].quantity = 1;
this.form.items[index].tax_id = tax_id;
this.form.items[index].total = (item.sale_price).toFixed(2);
this.form.items[index].total = (item.sale_price).toFixed(this.currency.precision);
},
// remove invocie item row => row_id = index
@ -322,51 +318,120 @@ const app = new Vue({
this.onCalculateTotal();
},
onPayment() {
this.payment.modal = true;
async onPayment() {
let invoice_id = document.getElementById('invoice_id').value;
let form = this.transaction_form;
this.transaction_form = new Form('transaction');
this.transaction_form.paid_at = form.paid_at;
this.transaction_form.account_id = form.account_id;
this.transaction_form.payment_method = form.payment_method;
},
addPayment() {
this.transaction_form.submit();
this.payment.errors = this.transaction_form.errors;
this.form.loading = true;
this.$emit("confirm");
},
closePayment() {
this.payment = {
let payment = {
modal: false,
amount: 0,
url: url + '/modals/invoices/' + invoice_id + '/transactions/create',
title: '',
message: '',
errors: this.transaction_form.errors
html: '',
buttons:{}
};
},
// Change bank account get money and currency rate
onChangePaymentAccount(account_id) {
axios.get(url + '/banking/accounts/currency', {
params: {
account_id: account_id
}
})
.then(response => {
this.transaction_form.currency = response.data.currency_name;
this.transaction_form.currency_code = response.data.currency_code;
this.transaction_form.currency_rate = response.data.currency_rate;
let payment_promise = Promise.resolve(window.axios.get(payment.url));
payment_promise.then(response => {
payment.modal = true;
payment.title = response.data.data.title;
payment.html = response.data.html;
payment.buttons = response.data.data.buttons;
this.component = Vue.component('add-new-component', (resolve, reject) => {
resolve({
template: '<div id="dynamic-payment-component"><akaunting-modal-add-new modal-dialog-class="modal-md" :show="payment.modal" @submit="onSubmit" @cancel="onCancel" :buttons="payment.buttons" :title="payment.title" :is_component=true :message="payment.html"></akaunting-modal-add-new></div>',
mixins: [
Global
],
data: function () {
return {
form:{},
payment: payment,
}
},
methods: {
onSubmit(event) {
this.form = event;
this.form.response = {};
this.loading = true;
let data = this.form.data();
FormData.prototype.appendRecursive = function(data, wrapper = null) {
for(var name in data) {
if (wrapper) {
if ((typeof data[name] == 'object' || data[name].constructor === Array) && ((data[name] instanceof File != true ) && (data[name] instanceof Blob != true))) {
this.appendRecursive(data[name], wrapper + '[' + name + ']');
} else {
this.append(wrapper + '[' + name + ']', data[name]);
}
} else {
if ((typeof data[name] == 'object' || data[name].constructor === Array) && ((data[name] instanceof File != true ) && (data[name] instanceof Blob != true))) {
this.appendRecursive(data[name], name);
} else {
this.append(name, data[name]);
}
}
}
};
let form_data = new FormData();
form_data.appendRecursive(data);
window.axios({
method: this.form.method,
url: this.form.action,
data: form_data,
headers: {
'X-CSRF-TOKEN': window.Laravel.csrfToken,
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'multipart/form-data'
}
})
.then(response => {
if (response.data.success) {
if (response.data.redirect) {
this.form.loading = true;
window.location.href = response.data.redirect;
}
}
if (response.data.error) {
this.form.loading = false;
this.form.response = response.data;
}
})
.catch(error => {
this.form.loading = false;
this.form.onFail(error);
this.method_show_html = error.message;
});
},
onCancel() {
this.payment.modal = false;
this.payment.html = null;
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
},
}
})
});
})
.catch(error => {
})
.finally(function () {
// always executed
});
},
}