2
0
mirror of https://github.com/frappe/books.git synced 2025-01-10 18:24:40 +00:00

Resolved merge conflicts

This commit is contained in:
Piyush Singhania 2022-01-10 16:50:53 +05:30
commit 1798902835
14 changed files with 1245 additions and 2003 deletions

View File

@ -5,6 +5,7 @@ import { DateTime } from 'luxon';
import { saveExportData } from '../reports/commonExporter'; import { saveExportData } from '../reports/commonExporter';
import { getSavePath } from '../src/utils'; import { getSavePath } from '../src/utils';
// prettier-ignore
export const stateCodeMap = { export const stateCodeMap = {
'JAMMU AND KASHMIR': '1', 'JAMMU AND KASHMIR': '1',
'HIMACHAL PRADESH': '2', 'HIMACHAL PRADESH': '2',
@ -184,7 +185,7 @@ async function generateB2clData(invoices) {
for (let invoice of invoices) { for (let invoice of invoices) {
const stateInvoiceRecord = { const stateInvoiceRecord = {
pos: stateCodeMap[invoice.place.toUpperCase()], pos: stateCodeMap[invoice.place.toUpperCase()],
inv: [] inv: [],
}; };
const invRecord = { const invRecord = {
@ -194,7 +195,7 @@ async function generateB2clData(invoices) {
), ),
val: invoice.invAmt, val: invoice.invAmt,
itms: [], itms: [],
} };
let items = await frappe.db let items = await frappe.db
.knex('SalesInvoiceItem') .knex('SalesInvoiceItem')
@ -231,22 +232,20 @@ async function generateB2csData(invoices) {
const b2cs = []; const b2cs = [];
for (let invoice of invoices) { for (let invoice of invoices) {
const pos = invoice.place.toUpperCase(); const pos = invoice.place.toUpperCase();
const invRecord = { const invRecord = {
"sply_ty": invoice.inState ? "INTRA" : "INTER", sply_ty: invoice.inState ? 'INTRA' : 'INTER',
"pos": stateCodeMap[pos], pos: stateCodeMap[pos],
// "OE" - Abbreviation for errors and omissions excepted. // "OE" - Abbreviation for errors and omissions excepted.
// https://specialties.bayt.com/en/specialties/q/53093/what-is-meant-by-e-amp-oe-on-bill-or-invoice-or-any-document/#:~:text=E%26OE%20on,not%20purposely%20written typ: 'OE',
"typ": "OE", txval: invoice.taxVal,
"txval": invoice.taxVal, rt: invoice.rate,
"rt": invoice.rate, iamt: !invoice.inState ? (invoice.taxVal * invoice.rate) / 100 : 0,
"iamt": !invoice.inState ? (invoice.taxVal * invoice.rate / 100) : 0, camt: invoice.inState ? invoice.cgstAmt : 0,
"camt": invoice.inState ? invoice.cgstAmt : 0, samt: invoice.inState ? invoice.sgstAmt : 0,
"samt": invoice.inState ? invoice.sgstAmt : 0, csamt: 0,
"csamt": 0 };
}
b2cs.push(invRecord); b2cs.push(invRecord);
} }

View File

@ -1,3 +1,16 @@
import { stateCodeMap } from '../../../accounting/gst';
import countryList from '../../../fixtures/countryInfo.json';
import { titleCase } from '../../../src/utils';
function getStates(doc) {
switch (doc.country) {
case 'India':
return Object.keys(stateCodeMap).map(titleCase).sort();
default:
return [];
}
}
export default { export default {
name: 'Address', name: 'Address',
doctype: 'DocType', doctype: 'DocType',
@ -9,7 +22,7 @@ export default {
'city', 'city',
'state', 'state',
'country', 'country',
'postalCode' 'postalCode',
], ],
fields: [ fields: [
{ {
@ -23,69 +36,71 @@ export default {
fieldname: 'addressLine2', fieldname: 'addressLine2',
label: 'Address Line 2', label: 'Address Line 2',
placeholder: 'Address Line 2', placeholder: 'Address Line 2',
fieldtype: 'Data' fieldtype: 'Data',
}, },
{ {
fieldname: 'city', fieldname: 'city',
label: 'City / Town', label: 'City / Town',
placeholder: 'City / Town', placeholder: 'City / Town',
fieldtype: 'Data', fieldtype: 'Data',
required: 1 required: 1,
}, },
{ {
fieldname: 'state', fieldname: 'state',
label: 'State', label: 'State',
placeholder: 'State', placeholder: 'State',
fieldtype: 'Data', fieldtype: 'AutoComplete',
getList: getStates,
}, },
{ {
fieldname: 'country', fieldname: 'country',
label: 'Country', label: 'Country',
placeholder: 'Country', placeholder: 'Country',
fieldtype: 'Data', fieldtype: 'AutoComplete',
required: 1 getList: () => Object.keys(countryList).sort(),
required: 1,
}, },
{ {
fieldname: 'postalCode', fieldname: 'postalCode',
label: 'Postal Code', label: 'Postal Code',
placeholder: 'Postal Code', placeholder: 'Postal Code',
fieldtype: 'Data' fieldtype: 'Data',
}, },
{ {
fieldname: 'emailAddress', fieldname: 'emailAddress',
label: 'Email Address', label: 'Email Address',
placeholder: 'Email Address', placeholder: 'Email Address',
fieldtype: 'Data' fieldtype: 'Data',
}, },
{ {
fieldname: 'phone', fieldname: 'phone',
label: 'Phone', label: 'Phone',
placeholder: 'Phone', placeholder: 'Phone',
fieldtype: 'Data' fieldtype: 'Data',
}, },
{ {
fieldname: 'fax', fieldname: 'fax',
label: 'Fax', label: 'Fax',
fieldtype: 'Data' fieldtype: 'Data',
}, },
{ {
fieldname: 'addressDisplay', fieldname: 'addressDisplay',
fieldtype: 'Text', fieldtype: 'Text',
label: 'Address Display', label: 'Address Display',
readOnly: true, readOnly: true,
formula: doc => { formula: (doc) => {
return [ return [
doc.addressLine1, doc.addressLine1,
doc.addressLine2, doc.addressLine2,
doc.city, doc.city,
doc.state, doc.state,
doc.country, doc.country,
doc.postalCode doc.postalCode,
] ]
.filter(Boolean) .filter(Boolean)
.join(', '); .join(', ');
} },
} },
], ],
quickEditFields: [ quickEditFields: [
'addressLine1', 'addressLine1',
@ -93,7 +108,7 @@ export default {
'city', 'city',
'state', 'state',
'country', 'country',
'postalCode' 'postalCode',
], ],
inlineEditDisplayField: 'addressDisplay' inlineEditDisplayField: 'addressDisplay',
}; };

View File

@ -1,6 +1,7 @@
import { cloneDeep, capitalize } from 'lodash'; import { cloneDeep } from 'lodash';
import AddressOriginal from './Address';
import { stateCodeMap } from '../../../accounting/gst'; import { stateCodeMap } from '../../../accounting/gst';
import { titleCase } from '../../../src/utils';
import AddressOriginal from './Address';
export default function getAugmentedAddress({ country }) { export default function getAugmentedAddress({ country }) {
const Address = cloneDeep(AddressOriginal); const Address = cloneDeep(AddressOriginal);
@ -8,22 +9,21 @@ export default function getAugmentedAddress({ country }) {
return Address; return Address;
} }
const stateList = Object.keys(stateCodeMap).map(titleCase).sort();
if (country === 'India') { if (country === 'India') {
Address.fields = [ Address.fields = [
...Address.fields, ...Address.fields,
{ {
fieldname: 'pos', fieldname: 'pos',
label: 'Place of Supply', label: 'Place of Supply',
fieldtype: 'Select', fieldtype: 'AutoComplete',
placeholder: 'Place of Supply', placeholder: 'Place of Supply',
options: Object.keys(stateCodeMap).map((key) => capitalize(key)), formula: (doc) => (stateList.includes(doc.state) ? doc.state : ''),
getList: () => stateList,
}, },
]; ];
Address.quickEditFields = [ Address.quickEditFields = [...Address.quickEditFields, 'pos'];
...Address.quickEditFields,
'pos',
];
} }
return Address; return Address;

View File

@ -26,12 +26,11 @@
"csvjson-csv2json": "^5.0.6", "csvjson-csv2json": "^5.0.6",
"electron-store": "^8.0.1", "electron-store": "^8.0.1",
"frappe-charts": "1.6.1", "frappe-charts": "1.6.1",
"frappejs": "https://github.com/frappe/frappejs", "frappejs": "frappe/frappejs",
"knex": "^0.95.12", "knex": "^0.95.12",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"luxon": "^2.0.2", "luxon": "^2.0.2",
"portal-vue": "^2.1.7", "sqlite3": "npm:@vscode/sqlite3@^5.0.7",
"sqlite3": "^5.0.2",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-router": "^3.5.3" "vue-router": "^3.5.3"
}, },

View File

@ -1,23 +1,27 @@
import frappe from 'frappejs'; import frappe from 'frappejs';
import { stateCodeMap } from '../../accounting/gst' import { stateCodeMap } from '../../accounting/gst';
class BaseGSTR { class BaseGSTR {
async getCompleteReport(gstrType, filters) { async getCompleteReport(gstrType, filters) {
if (['GSTR-1', 'GSTR-2'].includes(gstrType)) { if (['GSTR-1', 'GSTR-2'].includes(gstrType)) {
const place = filters.place;
delete filters.place;
let entries = await frappe.db.getAll({ let entries = await frappe.db.getAll({
doctype: gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice', doctype: gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice',
filters filters,
}); });
filters.place = place;
let tableData = []; let tableData = [];
for (let entry of entries) { for (let entry of entries) {
entry.doctype = gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice'; entry.doctype =
gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice';
const row = await this.getRow(entry); const row = await this.getRow(entry);
tableData.push(row); tableData.push(row);
} }
if (Object.keys(filters).length != 0) { if (Object.keys(filters).length != 0) {
tableData = tableData.filter(row => { tableData = tableData.filter((row) => {
if (filters.account) return row.account === filters.account; if (filters.account) return row.account === filters.account;
if (filters.transferType) if (filters.transferType)
return row.transferType === filters.transferType; return row.transferType === filters.transferType;
@ -25,6 +29,7 @@ class BaseGSTR {
return true; return true;
}); });
} }
return tableData; return tableData;
} else { } else {
return []; return [];
@ -32,37 +37,55 @@ class BaseGSTR {
} }
async getRow(ledgerEntry) { async getRow(ledgerEntry) {
let row = {};
ledgerEntry = await frappe.getDoc(ledgerEntry.doctype, ledgerEntry.name); ledgerEntry = await frappe.getDoc(ledgerEntry.doctype, ledgerEntry.name);
const row = {};
const { gstin } = frappe.AccountingSettings; const { gstin } = frappe.AccountingSettings;
let party = await frappe.getDoc( let party = await frappe.getDoc(
'Party', 'Party',
ledgerEntry.customer || ledgerEntry.supplier ledgerEntry.customer || ledgerEntry.supplier
); );
if (party.address) { if (party.address) {
let addressDetails = await frappe.getDoc('Address', party.address); let addressDetails = await frappe.getDoc('Address', party.address);
row.place = addressDetails.pos || ''; row.place = addressDetails.pos || '';
} }
row.gstin = party.gstin; row.gstin = party.gstin;
row.partyName = ledgerEntry.customer || ledgerEntry.supplier; row.partyName = ledgerEntry.customer || ledgerEntry.supplier;
row.invNo = ledgerEntry.name; row.invNo = ledgerEntry.name;
row.invDate = ledgerEntry.date; row.invDate = ledgerEntry.date;
row.rate = 0; row.rate = 0;
row.inState = gstin && gstin.substring(0, 2) === stateCodeMap[row.place.toUpperCase()]; row.inState =
gstin && gstin.substring(0, 2) === stateCodeMap[row.place?.toUpperCase()];
row.reverseCharge = !party.gstin ? 'Y' : 'N'; row.reverseCharge = !party.gstin ? 'Y' : 'N';
ledgerEntry.taxes?.forEach(tax => {
ledgerEntry.taxes?.forEach((tax) => {
row.rate += tax.rate; row.rate += tax.rate;
const taxAmt = (tax.rate * ledgerEntry.netTotal) / 100; const taxAmt = (tax.rate * ledgerEntry.netTotal) / 100;
if (tax.account === 'IGST') row.igstAmt = taxAmt;
if (tax.account === 'IGST') row.inState = false; switch (tax.account) {
if (tax.account === 'CGST') row.cgstAmt = taxAmt; case 'IGST': {
if (tax.account === 'SGST') row.sgstAmt = taxAmt; row.igstAmt = taxAmt;
if (tax.account === 'Nil Rated') row.nilRated = true; row.inState = false;
if (tax.account === 'Exempt') row.exempt = true; }
if (tax.account === 'Non GST') row.nonGST = true; case 'CGST':
row.cgstAmt = taxAmt;
case 'SGST':
row.sgstAmt = taxAmt;
case 'Nil Rated':
row.nilRated = true;
case 'Exempt':
row.exempt = true;
case 'Non GST':
row.nonGST = true;
}
}); });
row.invAmt = ledgerEntry.grandTotal; row.invAmt = ledgerEntry.grandTotal;
row.taxVal = ledgerEntry.netTotal; row.taxVal = ledgerEntry.netTotal;
return row; return row;
} }
} }

View File

@ -1,4 +1,6 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { stateCodeMap } from '../../accounting/gst';
import { titleCase } from '../../src/utils';
const transferTypeMap = { const transferTypeMap = {
B2B: 'B2B', B2B: 'B2B',
@ -6,6 +8,7 @@ const transferTypeMap = {
B2CS: 'B2C-Small', B2CS: 'B2C-Small',
NR: 'Nil Rated, Exempted and Non GST supplies', NR: 'Nil Rated, Exempted and Non GST supplies',
}; };
const stateList = Object.keys(stateCodeMap).map(titleCase).sort();
export default { export default {
filterFields: [ filterFields: [
@ -20,11 +23,12 @@ export default {
size: 'small', size: 'small',
}, },
{ {
fieldtype: 'Data', fieldtype: 'AutoComplete',
label: 'Place', label: 'Place',
size: 'small', size: 'small',
placeholder: 'Place', placeholder: 'Place',
fieldname: 'place', fieldname: 'place',
getList: () => stateList,
}, },
{ {
fieldtype: 'Date', fieldtype: 'Date',

View File

@ -8,6 +8,8 @@ class GSTR1 extends BaseGSTR {
filters.cancelled = 0; filters.cancelled = 0;
if (params.toDate || params.fromDate) { if (params.toDate || params.fromDate) {
filters.date = []; filters.date = [];
if (params.place) filters.place = params.place;
if (params.toDate) filters.date.push('<=', params.toDate); if (params.toDate) filters.date.push('<=', params.toDate);
if (params.fromDate) filters.date.push('>=', params.fromDate); if (params.fromDate) filters.date.push('>=', params.fromDate);
} }

View File

@ -18,7 +18,6 @@
@setup-complete="showSetupWizardOrDesk(true)" @setup-complete="showSetupWizardOrDesk(true)"
@setup-canceled="setupCanceled" @setup-canceled="setupCanceled"
/> />
<portal-target name="popovers" multiple></portal-target>
<div id="toast-container" class="absolute bottom-0 right-0 mr-6 mb-3"> <div id="toast-container" class="absolute bottom-0 right-0 mr-6 mb-3">
<div id="toast-target" /> <div id="toast-target" />
</div> </div>

View File

@ -59,6 +59,9 @@ export default {
}, },
}, },
}, },
inject: {
doc: { default: null },
},
computed: {}, computed: {},
methods: { methods: {
async updateSuggestions(e) { async updateSuggestions(e) {
@ -81,7 +84,7 @@ export default {
keyword = keyword.toLowerCase(); keyword = keyword.toLowerCase();
let list = this.df.getList let list = this.df.getList
? await this.df.getList() ? await this.df.getList(this.doc)
: this.df.options || []; : this.df.options || [];
let items = list.map((d) => { let items = list.map((d) => {
@ -118,6 +121,12 @@ export default {
async onBlur(value) { async onBlur(value) {
if (value === '' || value == null) { if (value === '' || value == null) {
this.triggerChange(''); this.triggerChange('');
return;
}
if (value && this.suggestions.length === 0) {
this.triggerChange(value);
return;
} }
if ( if (

View File

@ -5,7 +5,6 @@
</div> </div>
<div <div
class=" class="
relative
flex flex
items-center items-center
justify-between justify-between
@ -38,7 +37,7 @@
</select> </select>
<svg <svg
class="w-3 h-3" class="w-3 h-3"
style="background: inherit; z-index: 1; transform: translateX(3px)" style="background: inherit; margin-right: -3px"
viewBox="0 0 5 10" viewBox="0 0 5 10"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >

View File

@ -3,17 +3,15 @@
<div class="h-full"> <div class="h-full">
<slot name="target" :togglePopover="togglePopover"></slot> <slot name="target" :togglePopover="togglePopover"></slot>
</div> </div>
<portal to="popovers"> <div
<div ref="popover"
ref="popover" :class="popoverClass"
:class="popoverClass" class="bg-white rounded border shadow-md popover-container relative"
class="bg-white rounded border shadow-md popover-container relative" v-show="isOpen"
v-show="isOpen" >
> <div v-if="!hideArrow" class="popover-arrow" ref="popover-arrow"></div>
<div v-if="!hideArrow" class="popover-arrow" ref="popover-arrow"></div> <slot name="content" :togglePopover="togglePopover"></slot>
<slot name="content" :togglePopover="togglePopover"></slot> </div>
</div>
</portal>
</div> </div>
</template> </template>
@ -25,17 +23,17 @@ export default {
props: { props: {
hideArrow: { hideArrow: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
showPopup: { showPopup: {
default: null default: null,
}, },
right: Boolean, right: Boolean,
placement: { placement: {
type: String, type: String,
default: 'bottom-start' default: 'bottom-start',
}, },
popoverClass: [String, Object, Array] popoverClass: [String, Object, Array],
}, },
watch: { watch: {
showPopup(value) { showPopup(value) {
@ -45,18 +43,18 @@ export default {
if (value === false) { if (value === false) {
this.close(); this.close();
} }
} },
}, },
data() { data() {
return { return {
isOpen: false isOpen: false,
}; };
}, },
mounted() { mounted() {
let listener = e => { let listener = (e) => {
let $els = [this.$refs.reference, this.$refs.popover]; let $els = [this.$refs.reference, this.$refs.popover];
let insideClick = $els.some( let insideClick = $els.some(
$el => $el && (e.target === $el || $el.contains(e.target)) ($el) => $el && (e.target === $el || $el.contains(e.target))
); );
if (insideClick) { if (insideClick) {
return; return;
@ -83,17 +81,17 @@ export default {
{ {
name: 'arrow', name: 'arrow',
options: { options: {
element: this.$refs['popover-arrow'] element: this.$refs['popover-arrow'],
} },
}, },
{ {
name: 'offset', name: 'offset',
options: { options: {
offset: [0, 10] offset: [0, 10],
} },
} },
] ]
: [] : [],
}); });
} else { } else {
this.popper.update(); this.popper.update();
@ -126,8 +124,8 @@ export default {
} }
this.isOpen = false; this.isOpen = false;
this.$emit('close'); this.$emit('close');
} },
} },
}; };
</script> </script>
<style scoped> <style scoped>

View File

@ -2,7 +2,6 @@ import { ipcRenderer } from 'electron';
import frappe from 'frappejs'; import frappe from 'frappejs';
import FeatherIcon from 'frappejs/ui/components/FeatherIcon'; import FeatherIcon from 'frappejs/ui/components/FeatherIcon';
import outsideClickDirective from 'frappejs/ui/plugins/outsideClickDirective'; import outsideClickDirective from 'frappejs/ui/plugins/outsideClickDirective';
import PortalVue from 'portal-vue';
import Vue from 'vue'; import Vue from 'vue';
import models from '../models'; import models from '../models';
import App from './App'; import App from './App';
@ -36,7 +35,6 @@ import router from './router';
Vue.config.productionTip = false; Vue.config.productionTip = false;
Vue.component('feather-icon', FeatherIcon); Vue.component('feather-icon', FeatherIcon);
Vue.directive('on-outside-click', outsideClickDirective); Vue.directive('on-outside-click', outsideClickDirective);
Vue.use(PortalVue);
Vue.mixin({ Vue.mixin({
computed: { computed: {
frappe() { frappe() {

View File

@ -4,6 +4,7 @@ import router from '@/router';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import frappe from 'frappejs'; import frappe from 'frappejs';
import { _ } from 'frappejs/utils'; import { _ } from 'frappejs/utils';
import lodash from 'lodash';
import Vue from 'vue'; import Vue from 'vue';
import { IPC_ACTIONS, IPC_MESSAGES } from './messages'; import { IPC_ACTIONS, IPC_MESSAGES } from './messages';
@ -337,3 +338,16 @@ export function showToast(props) {
}, },
}); });
} }
export function titleCase(phrase) {
return phrase
.split(' ')
.map((word) => {
const wordLower = word.toLowerCase();
if (['and', 'an', 'a', 'from', 'by', 'on'].includes(wordLower)) {
return wordLower;
}
return lodash.capitalize(wordLower);
})
.join(' ');
}

3007
yarn.lock

File diff suppressed because it is too large Load Diff