2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 14:50:56 +00:00

feat: Add Make Payment button

- Generic Dropdown component
This commit is contained in:
Faris Ansari 2019-10-30 01:03:26 +05:30
parent c848ce6812
commit 36db289c6e
9 changed files with 235 additions and 98 deletions

View File

@ -111,6 +111,21 @@ module.exports = {
}
],
quickEditFields: [
'party',
'date',
'account',
'paymentType',
'paymentAccount',
'paymentMethod',
'referenceId',
'referenceDate',
'clearanceDate',
'amount',
'writeoff',
'for'
],
layout: [
{
columns: [

View File

@ -4,6 +4,11 @@ module.exports = {
isSingle: 0,
isChild: 1,
keywordFields: [],
tableFields: [
'referenceType',
'referenceName',
'amount'
],
fields: [
{
fieldname: 'referenceType',

View File

@ -1,5 +1,8 @@
<template>
<div class="relative" v-on-outside-click="() => showDropdown = false">
<Dropdown :items="suggestions">
<template
v-slot="{ toggleDropdown, highlightItemUp, highlightItemDown, selectHighlightedItem }"
>
<input
ref="input"
:class="inputClasses"
@ -7,45 +10,29 @@
:value="linkValue"
:placeholder="inputPlaceholder"
:readonly="df.readOnly"
@focus="onFocus"
@focus="e => onFocus(e, toggleDropdown)"
@input="onInput"
@keydown.up="highlightItemUp"
@keydown.down="highlightItemDown"
@keydown.enter="selectHighlightedItem"
@keydown.tab="showDropdown = false"
@keydown.esc="showDropdown = false"
@keydown.tab="toggleDropdown(false)"
@keydown.esc="toggleDropdown(false)"
/>
<div
class="mt-1 absolute left-0 z-10 bg-white rounded-5px border w-full min-w-56"
v-if="showDropdown"
>
<div class="p-1 max-h-64 overflow-auto" v-if="suggestions.length">
<a
ref="suggestionItems"
class="block p-2 rounded mt-1 first:mt-0 cursor-pointer"
v-for="(s, index) in suggestions"
:key="s.value"
:class="index === highlightedIndex ? 'bg-gray-200' : ''"
@mouseenter="highlightedIndex = index"
@mouseleave="highlightedIndex = -1"
@click="selectItem(s)"
>
<component :is="s.component" v-if="s.component" />
<template v-else>{{ s.label }}</template>
</a>
</div>
</div>
</div>
</template>
</Dropdown>
</template>
<script>
import frappe from 'frappejs';
import Base from './Base';
import Dropdown from '@/components/Dropdown';
export default {
name: 'AutoComplete',
extends: Base,
components: {},
components: {
Dropdown
},
data() {
return {
linkValue: '',
@ -58,7 +45,7 @@ export default {
value: {
immediate: true,
handler(newValue) {
this.linkValue = this.value;
this.linkValue = newValue;
}
}
},
@ -70,7 +57,13 @@ export default {
keyword = e.target.value;
this.linkValue = keyword;
}
this.suggestions = await this.getSuggestions(keyword);
let suggestions = await this.getSuggestions(keyword);
this.suggestions = suggestions.map(d => {
if (!d.action) {
d.action = () => this.setSuggestion(d);
}
return d;
});
},
async getSuggestions(keyword = '') {
keyword = keyword.toLowerCase();
@ -93,57 +86,19 @@ export default {
);
});
},
selectHighlightedItem() {
if (![-1, this.suggestions.length].includes(this.highlightedIndex)) {
// valid selection
let suggestion = this.suggestions[this.highlightedIndex];
this.setSuggestion(suggestion);
}
},
selectItem(suggestion) {
if (suggestion.action) {
suggestion.action();
return;
}
this.setSuggestion(suggestion);
},
setSuggestion(suggestion) {
this.linkValue = suggestion.value;
this.triggerChange(suggestion.value);
this.showDropdown = false;
this.toggleDropdown(false);
},
onFocus() {
this.showDropdown = true;
onFocus(e, toggleDropdown) {
this.toggleDropdown = toggleDropdown;
this.toggleDropdown(true);
this.updateSuggestions();
},
onInput(e) {
this.showDropdown = true;
this.toggleDropdown(true);
this.updateSuggestions(e);
},
highlightItemUp() {
this.highlightedIndex -= 1;
if (this.highlightedIndex < 0) {
this.highlightedIndex = 0;
}
this.$nextTick(() => {
let index = this.highlightedIndex;
if (index !== 0) {
index -= 1;
}
let highlightedElement = this.$refs.suggestionItems[index];
highlightedElement && highlightedElement.scrollIntoView();
});
},
highlightItemDown() {
this.highlightedIndex += 1;
if (this.highlightedIndex > this.suggestions.length) {
this.highlightedIndex = this.suggestions.length;
}
this.$nextTick(() => {
let index = this.highlightedIndex;
let highlightedElement = this.$refs.suggestionItems[index];
highlightedElement && highlightedElement.scrollIntoView();
});
}
}
};

View File

@ -1,16 +1,11 @@
<script>
import frappe from 'frappejs';
import Base from './Base';
import AutoComplete from './AutoComplete';
import Badge from '@/components/Badge';
export default {
name: 'Link',
extends: AutoComplete,
components: {
Badge
},
computed: {},
methods: {
async getSuggestions(keyword = '') {
let doctype = this.df.target;

102
src/components/Dropdown.vue Normal file
View File

@ -0,0 +1,102 @@
<template>
<div class="relative" v-on-outside-click="() => isShown = false">
<div>
<slot
:toggleDropdown="toggleDropdown"
:highlightItemUp="highlightItemUp"
:highlightItemDown="highlightItemDown"
:selectHighlightedItem="selectHighlightedItem"
></slot>
</div>
<div
:class="right ? 'right-0' : 'left-0'"
class="mt-1 absolute z-10 bg-white rounded-5px border w-full min-w-56"
v-if="isShown"
>
<div class="p-1 max-h-64 overflow-auto">
<a
ref="items"
class="block p-2 rounded mt-1 first:mt-0 cursor-pointer whitespace-no-wrap text-sm"
v-for="(d, index) in items"
:key="d.label"
:class="index === highlightedIndex ? 'bg-gray-100' : ''"
@mouseenter="highlightedIndex = index"
@mouseleave="highlightedIndex = -1"
@click="selectItem(d)"
>
<component :is="d.component" v-if="d.component" />
<template v-else>{{ d.label }}</template>
</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Dropdown',
props: {
items: {
type: Array,
default: () => []
},
right: {
type: Boolean,
default: false
}
},
data() {
return {
isShown: false,
highlightedIndex: -1
};
},
methods: {
selectItem(d) {
if (d.action) {
d.action();
}
},
toggleDropdown(flag) {
if (flag == null) {
this.isShown = !this.isShown;
} else {
this.isShown = Boolean(flag);
}
},
selectHighlightedItem() {
if (![-1, this.items.length].includes(this.highlightedIndex)) {
// valid selection
let item = this.items[this.highlightedIndex];
this.selectItem(item);
}
},
highlightItemUp() {
this.highlightedIndex -= 1;
if (this.highlightedIndex < 0) {
this.highlightedIndex = 0;
}
this.$nextTick(() => {
let index = this.highlightedIndex;
if (index !== 0) {
index -= 1;
}
let highlightedElement = this.$refs.items[index];
highlightedElement && highlightedElement.scrollIntoView();
});
},
highlightItemDown() {
this.highlightedIndex += 1;
if (this.highlightedIndex > this.items.length) {
this.highlightedIndex = this.items.length;
}
this.$nextTick(() => {
let index = this.highlightedIndex;
let highlightedElement = this.$refs.items[index];
highlightedElement && highlightedElement.scrollIntoView();
});
}
}
};
</script>

View File

@ -0,0 +1,9 @@
<template>
<svg class="w-3 h-3" viewBox="0 0 8 2" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.633 1.033a.833.833 0 11-1.666 0 .833.833 0 011.666 0zm3.2 0a.833.833 0 11-1.666 0 .833.833 0 011.666 0zm3.2 0a.833.833 0 11-1.666 0 .833.833 0 011.666 0z"
fill="#112B42"
fill-rule="nonzero"
/>
</svg>
</template>

View File

@ -4,6 +4,16 @@
<a class="cursor-pointer font-semibold" slot="title" @click="$router.go(-1)">{{ _('Back') }}</a>
<template slot="actions">
<Button class="text-gray-900 text-xs">{{ _('Customise') }}</Button>
<Dropdown
:items="[{label: 'Make Payment', value: 'Make Payment', action: makePayment }]"
right
>
<template v-slot="{ toggleDropdown }">
<Button class="text-gray-900 text-xs ml-2" :icon="true" @click="toggleDropdown()">
<DotHorizontalIcon />
</Button>
</template>
</Dropdown>
<Button
v-if="doc._notInserted || doc._dirty"
type="primary"
@ -118,7 +128,9 @@ import PageHeader from '@/components/PageHeader';
import Button from '@/components/Button';
import FormControl from '@/components/Controls/FormControl';
import Row from '@/components/Row';
import Dropdown from '@/components/Dropdown';
import AddIcon from '@/components/Icons/Add';
import DotHorizontalIcon from '@/components/Icons/DotHorizontal';
export default {
name: 'InvoiceForm',
@ -128,7 +140,9 @@ export default {
Button,
FormControl,
Row,
AddIcon
AddIcon,
DotHorizontalIcon,
Dropdown
},
provide() {
return {
@ -158,7 +172,7 @@ export default {
PurchaseInvoice: 'supplier'
}[this.doctype];
return this.meta.getField(fieldname);
},
}
},
async mounted() {
this.meta = frappe.getMeta(this.doctype);
@ -181,6 +195,33 @@ export default {
},
async onSubmitClick() {
await this.doc.submit();
},
async makePayment() {
let payment = await frappe.getNewDoc('Payment');
payment.once('afterInsert', () => {
payment.submit();
});
this.$router.push({
query: {
edit: 1,
doctype: 'Payment',
name: payment.name,
hideFields: ['party', 'date', 'account', 'paymentType', 'for'],
values: {
party: this.doc.customer,
account: this.doc.account,
date: new Date().toISOString().slice(0, 10),
paymentType: 'Receive',
for: [
{
referenceType: 'SalesInvoice',
referenceName: this.doc.name,
amount: this.doc.outstandingAmount
}
]
}
}
});
}
}
};

View File

@ -1,10 +1,16 @@
<template>
<div class="border-l h-full">
<div class="flex justify-end px-4 pt-4">
<Button :icon="true" @click="routeToList">
<Button :icon="true" @click="$router.back()">
<XIcon class="w-3 h-3 stroke-current text-gray-700" />
</Button>
<Button :icon="true" @click="insertDoc" type="primary" v-if="doc._notInserted" class="ml-2 flex">
<Button
:icon="true"
@click="insertDoc"
type="primary"
v-if="doc._notInserted"
class="ml-2 flex"
>
<feather-icon name="check" class="text-white" />
</Button>
</div>
@ -53,7 +59,7 @@ import FormControl from '@/components/Controls/FormControl';
export default {
name: 'QuickEditForm',
props: ['doctype', 'name', 'values'],
props: ['doctype', 'name', 'values', 'hideFields'],
components: {
Button,
XIcon,
@ -80,20 +86,26 @@ export default {
methods: {
async fetchMetaAndDoc() {
this.meta = frappe.getMeta(this.doctype);
this.fields = this.meta.getQuickEditFields();
this.fields = this.meta
.getQuickEditFields()
.filter(df => !(this.hideFields || []).includes(df.fieldname));
this.titleDocField = this.meta.getField(this.meta.titleField);
await this.fetchDoc();
// setup the title field
if (this.doc._notInserted) {
if (
this.doc._notInserted &&
!this.titleDocField.readOnly &&
this.doc[this.titleDocField.fieldname]
) {
this.doc.set(this.titleDocField.fieldname, '');
setTimeout(() => {
this.$refs.titleControl.focus();
}, 300);
}
if (this.values) {
this.doc.set(this.values);
}
setTimeout(() => {
this.$refs.titleControl.focus();
}, 300);
},
valueChange(df, value) {
if (!value) return;

View File

@ -10,6 +10,9 @@ module.exports = {
minWidth: {
'56': '14rem'
},
maxWidth: {
'56': '14rem'
},
spacing: {
'14': '3.5rem',
'72': '18rem',