2
0
mirror of https://github.com/frappe/books.git synced 2025-01-03 15:17:30 +00:00

- Dashboard

- SearchBar
- Invoice & Bill Rename
This commit is contained in:
thefalconx33 2019-08-01 17:22:58 +05:30
parent ed164a492e
commit 86d0ef3d06
66 changed files with 922 additions and 474 deletions

View File

@ -1,7 +1,7 @@
module.exports = {
doctype: "PrintFormat",
name: "Standard Invoice Format",
for: "Invoice",
for: "SalesInvoice",
template: `
<h1>{{ doc.name }}</h1>
<div class="row py-4">

View File

@ -3,7 +3,14 @@ const path = require('path');
const fs = require('fs');
const countries = require('../../../fixtures/countryInfo.json');
const standardCOA = require('../../../fixtures/verified/standardCOA.json');
const accountFields = ['accountType', 'rootType', 'isGroup', 'account_type', 'root_type', 'is_group'];
const accountFields = [
'accountType',
'rootType',
'isGroup',
'account_type',
'root_type',
'is_group'
];
async function importAccounts(children, parent, rootType, rootAccount) {
for (let accountName in children) {
@ -22,12 +29,12 @@ async function importAccounts(children, parent, rootType, rootAccount) {
isGroup,
rootType,
balance: 0,
accountType: child.accountType
})
accountType: child.accountType || child.account_type
});
await doc.insert()
await doc.insert();
await importAccounts(child, accountName, rootType)
await importAccounts(child, accountName, rootType);
}
}
}
@ -38,7 +45,7 @@ function identifyIsGroup(child) {
}
const keys = Object.keys(child);
const children = keys.filter(key => !accountFields.includes(key))
const children = keys.filter(key => !accountFields.includes(key));
if (children.length) {
return 1;
@ -51,19 +58,17 @@ async function getCountryCOA(){
const doc = await frappe.getDoc('AccountingSettings');
const conCode = countries[doc.country].code;
const countryCOA = path.resolve(path.join('./fixtures/verified/', conCode + '.json'));
if(fs.existsSync(countryCOA)){
const jsonText = fs.readFileSync(countryCOA, 'utf-8');
const json = JSON.parse(jsonText);
return json.tree;
} else {
try {
const countryCoa = require('../../../fixtures/verified/' +
conCode +
'.json');
return countryCoa.tree;
} catch (e) {
return standardCOA;
}
}
module.exports = async function importCharts() {
const chart = await getCountryCOA();
await importAccounts(chart, '', '', true)
}
await importAccounts(chart, '', '', true);
};

View File

@ -1,4 +0,0 @@
const InvoiceDocument = require('../Invoice/InvoiceDocument');
const frappe = require('frappejs');
module.exports = class Bill extends InvoiceDocument { }

View File

@ -1,18 +0,0 @@
module.exports = {
"name": "BillSettings",
"label": "Bill Settings",
"doctype": "DocType",
"isSingle": 1,
"isChild": 0,
"keywordFields": [],
"fields": [
{
"fieldname": "numberSeries",
"label": "Number Series",
"fieldtype": "Link",
"target": "NumberSeries",
"required": 1,
"default": "BILL"
}
]
}

View File

@ -0,0 +1,24 @@
module.exports = {
name: 'DashboardChart',
doctype: 'DocType',
isChild: 1,
fields: [
{
fieldname: 'account',
fieldtype: 'Link',
label: 'Account',
target: 'Account'
},
{
fieldname: 'type',
fieldtype: 'Select',
options: ['', 'Bar', 'Line', 'Percentage'],
label: 'Chart Type'
},
{
fieldname: 'color',
fieldtype: 'Data',
label: 'Color'
}
]
};

View File

@ -0,0 +1,19 @@
module.exports = {
name: 'Dashboard',
label: 'Dashboard Settings',
doctype: 'DocType',
isSingle: 1,
fields: [
{
fieldname: 'name',
fieldtype: 'Data',
label: 'Dashboard Name'
},
{
fieldname: 'charts',
fieldtype: 'Table',
label: 'Charts',
childtype: 'DashboardChart'
}
]
};

View File

@ -28,25 +28,28 @@ module.exports = class GSTR3B extends BaseDocument {
`${this.year}-${month}-${lastDate}`
]
};
const invoices = frappe.db.getAll({
doctype: 'Invoice',
const salesInvoices = frappe.db.getAll({
doctype: 'SalesInvoice',
filters,
fields: ['*']
});
const bills = frappe.db.getAll({
doctype: 'Bill',
const purchaseInvoices = frappe.db.getAll({
doctype: 'PurchaseInvoice',
filters,
fields: ['*']
});
const [gstr1Data, gstr2Data] = await Promise.all([invoices, bills]);
const [gstr1Data, gstr2Data] = await Promise.all([
salesInvoices,
purchaseInvoices
]);
let gstr3bData = [[], []];
for (let ledgerEntry of gstr1Data) {
ledgerEntry.doctype = 'Invoice';
ledgerEntry.doctype = 'SalesInvoice';
gstr3bData[0].push(await this.makeGSTRow(ledgerEntry));
}
for (let ledgerEntry of gstr2Data) {
ledgerEntry.doctype = 'Bill';
ledgerEntry.doctype = 'BiPurchaseInvoicell';
gstr3bData[1].push(await this.makeGSTRow(ledgerEntry));
}

View File

@ -1,42 +0,0 @@
module.exports = {
name: "InvoiceSettings",
label: "Invoice Settings",
doctype: "DocType",
isSingle: 1,
isChild: 0,
keywordFields: [],
fields: [
{
fieldname: "numberSeries",
label: "Number Series",
fieldtype: "Link",
target: "NumberSeries",
required: 1,
default: "INV"
},
{
fieldname: "template",
label: "Template",
fieldtype: "Select",
options: ["Basic I", "Basic II", "Modern"],
required: 1,
default: "Basic I"
},
{
fieldname: "font",
label: "Font",
fieldtype: "Select",
options: ["Montserrat", "Open Sans", "Oxygen", "Merriweather"],
required: 1,
default: "Montserrat"
},
{
fieldname: "themeColor",
label: "Theme Color",
fieldtype: "Data",
required: 1,
default: "#000000",
hidden: 1
}
]
}

View File

@ -44,7 +44,7 @@ module.exports = {
links: [
{
label: 'Invoices',
label: 'Sales Invoices',
condition: form => form.doc.customer,
action: form => {
form.$router.push({
@ -52,6 +52,15 @@ module.exports = {
});
}
},
{
label: 'Purchase Invoices',
condition: form => form.doc.supplier,
action: form => {
form.$router.push({
path: `/report/purchase-register?&supplier=${form.doc.name}`
});
}
},
{
label: 'Delete',
condition: form => form.doc.customer,

View File

@ -15,7 +15,7 @@ module.exports = class PaymentServer extends BaseDocument {
async beforeSubmit() {
if (this.for.length > 0)
for (let row of this.for) {
if (['Invoice', 'Bill'].includes(row.referenceType)) {
if (['SalesInvoice', 'PurchaseInvoice'].includes(row.referenceType)) {
let { outstandingAmount, grandTotal } = await frappe.getDoc(
row.referenceType,
row.referenceName

View File

@ -2,14 +2,15 @@ const frappe = require('frappejs');
const utils = require('../../../accounting/utils');
module.exports = {
name: 'Bill',
name: 'PurchaseInvoice',
doctype: 'DocType',
documentClass: require('./BillDocument'),
label: 'Purchase Invoice',
documentClass: require('./PurchaseInvoiceDocument'),
isSingle: 0,
isChild: 0,
isSubmittable: 1,
keywordFields: ['name', 'supplier'],
settings: 'BillSettings',
settings: 'PurchaseInvoiceSettings',
showTitle: true,
fields: [
{
@ -50,7 +51,7 @@ module.exports = {
fieldname: 'items',
label: 'Items',
fieldtype: 'Table',
childtype: 'BillItem',
childtype: 'PurchaseInvoiceItem',
required: true
},
{

View File

@ -0,0 +1,4 @@
const SalesInvoiceDocument = require('../SalesInvoice/SalesInvoiceDocument');
const frappe = require('frappejs');
module.exports = class PurchaseInvoice extends SalesInvoiceDocument {};

View File

@ -2,8 +2,8 @@ import { _ } from 'frappejs/utils';
import indicators from 'frappejs/ui/constants/indicators';
export default {
doctype: 'Bill',
title: _('Bill'),
doctype: 'PurchaseInvoice',
title: _('Purchase Invoice'),
columns: [
'supplier',
{

View File

@ -1,8 +1,8 @@
const frappe = require('frappejs');
const Bill = require('./BillDocument');
const PurchaseInvoice = require('./PurchaseInvoiceDocument');
const LedgerPosting = require('../../../accounting/ledgerPosting');
module.exports = class BillServer extends Bill {
module.exports = class PurchaseInvoiceServer extends PurchaseInvoice {
async getPosting() {
let entries = new LedgerPosting({ reference: this, party: this.supplier });
await entries.credit(this.account, this.grandTotal);
@ -36,7 +36,7 @@ module.exports = class BillServer extends Bill {
const entries = await this.getPosting();
await entries.post();
await frappe.db.setValue(
'Bill',
'PurchaseInvoice',
this.name,
'outstandingAmount',
this.grandTotal

View File

@ -1,5 +1,6 @@
module.exports = {
name: 'BillItem',
name: 'PurchaseInvoiceItem',
label: 'Purchase Invoice Item',
doctype: 'DocType',
isSingle: 0,
isChild: 1,

View File

@ -0,0 +1,18 @@
module.exports = {
name: 'PurchaseInvoiceSettings',
label: 'Purchase Invoice Settings',
doctype: 'DocType',
isSingle: 1,
isChild: 0,
keywordFields: [],
fields: [
{
fieldname: 'numberSeries',
label: 'Number Series',
fieldtype: 'Link',
target: 'NumberSeries',
required: 1,
default: 'PINV'
}
]
};

View File

@ -1,16 +1,20 @@
const model = require('frappejs/model');
const Bill = require('../Bill/Bill');
const PurchaseInvoice = require('../PurchaseInvoice/PurchaseInvoice');
module.exports = model.extend(Bill, {
name: "PurchaseOrder",
label: "Purchase Order",
settings: "PurchaseOrderSettings",
module.exports = model.extend(
PurchaseInvoice,
{
name: 'PurchaseOrder',
label: 'Purchase Order',
settings: 'PurchaseOrderSettings',
fields: [
{
"fieldname": "items",
"childtype": "PurchaseOrderItem"
fieldname: 'items',
childtype: 'PurchaseOrderItem'
}
]
}, {
},
{
skipFields: ['account']
});
}
);

View File

@ -1,6 +1,6 @@
const model = require('frappejs/model');
const BillItem = require('../BillItem/BillItem');
const PurchaseInvoiceItem = require('../PurchaseInvoiceItem/PurchaseInvoiceItem');
module.exports = model.extend(BillItem, {
module.exports = model.extend(PurchaseInvoiceItem, {
name: "PurchaseOrderItem"
});

View File

@ -1,7 +1,7 @@
const model = require('frappejs/model');
const BillSettings = require('../BillSettings/BillSettings');
const PurchaseInvoiceSettings = require('../PurchaseInvoiceSettings/PurchaseInvoiceSettings');
module.exports = model.extend(BillSettings, {
module.exports = model.extend(PurchaseInvoiceSettings, {
"name": "PurchaseOrderSettings",
"label": "Purchase Order Settings",
"fields": [

View File

@ -2,9 +2,10 @@ const frappe = require('frappejs');
const utils = require('../../../accounting/utils');
module.exports = {
name: 'Invoice',
name: 'SalesInvoice',
label: 'Sales Invoice',
doctype: 'DocType',
documentClass: require('./InvoiceDocument.js'),
documentClass: require('./SalesInvoiceDocument'),
print: {
printFormat: 'Standard Invoice Format'
},
@ -12,7 +13,7 @@ module.exports = {
isChild: 0,
isSubmittable: 1,
keywordFields: ['name', 'customer'],
settings: 'InvoiceSettings',
settings: 'SalesInvoiceSettings',
showTitle: true,
fields: [
{
@ -62,7 +63,7 @@ module.exports = {
fieldname: 'items',
label: 'Items',
fieldtype: 'Table',
childtype: 'InvoiceItem',
childtype: 'SalesInvoiceItem',
required: true
},
{

View File

@ -1,7 +1,7 @@
const BaseDocument = require('frappejs/model/document');
const frappe = require('frappejs');
module.exports = class Invoice extends BaseDocument {
module.exports = class SalesInvoice extends BaseDocument {
async getRowTax(row) {
if (row.tax) {
let tax = await this.getTax(row.tax);

View File

@ -2,8 +2,8 @@ import { _ } from 'frappejs/utils';
import indicators from 'frappejs/ui/constants/indicators';
export default {
doctype: 'Invoice',
title: _('Invoice'),
doctype: 'SalesInvoice',
title: _('Sales Invoice'),
columns: [
'customer',
{

View File

@ -26,9 +26,9 @@
</div>
</template>
<script>
import InvoiceTemplate1 from '@/../models/doctype/Invoice/Templates/InvoiceTemplate1';
import InvoiceTemplate2 from '@/../models/doctype/Invoice/Templates/InvoiceTemplate2';
import InvoiceTemplate3 from '@/../models/doctype/Invoice/Templates/InvoiceTemplate3';
import InvoiceTemplate1 from '@/../models/doctype/SalesInvoice/Templates/InvoiceTemplate1';
import InvoiceTemplate2 from '@/../models/doctype/SalesInvoice/Templates/InvoiceTemplate2';
import InvoiceTemplate3 from '@/../models/doctype/SalesInvoice/Templates/InvoiceTemplate3';
import InvoiceCustomizer from '@/components/InvoiceCustomizer';
const invoiceTemplates = {

View File

@ -1,7 +1,7 @@
const Invoice = require('./InvoiceDocument');
const SalesInvoice = require('./SalesInvoiceDocument');
const LedgerPosting = require('../../../accounting/ledgerPosting');
module.exports = class InvoiceServer extends Invoice {
module.exports = class SalesInvoiceServer extends SalesInvoice {
async getPosting() {
let entries = new LedgerPosting({ reference: this, party: this.customer });
await entries.debit(this.account, this.grandTotal);
@ -35,7 +35,7 @@ module.exports = class InvoiceServer extends Invoice {
const entries = await this.getPosting();
await entries.post();
await frappe.db.setValue(
'Invoice',
'SalesInvoice',
this.name,
'outstandingAmount',
this.grandTotal

View File

@ -1,5 +1,5 @@
module.exports = {
name: 'InvoiceItem',
name: 'SalesInvoiceItem',
doctype: 'DocType',
isSingle: 0,
isChild: 1,

View File

@ -0,0 +1,42 @@
module.exports = {
name: 'SalesInvoiceSettings',
label: 'SalesInvoice Settings',
doctype: 'DocType',
isSingle: 1,
isChild: 0,
keywordFields: [],
fields: [
{
fieldname: 'numberSeries',
label: 'Number Series',
fieldtype: 'Link',
target: 'NumberSeries',
required: 1,
default: 'SINV'
},
{
fieldname: 'template',
label: 'Template',
fieldtype: 'Select',
options: ['Basic I', 'Basic II', 'Modern'],
required: 1,
default: 'Basic I'
},
{
fieldname: 'font',
label: 'Font',
fieldtype: 'Select',
options: ['Montserrat', 'Open Sans', 'Oxygen', 'Merriweather'],
required: 1,
default: 'Montserrat'
},
{
fieldname: 'themeColor',
label: 'Theme Color',
fieldtype: 'Data',
required: 1,
default: '#000000',
hidden: 1
}
]
};

View File

@ -1,6 +1,8 @@
module.exports = {
models: {
SetupWizard: require('./doctype/SetupWizard/SetupWizard'),
DashboardSettings: require('./doctype/DashboardSettings/DashboardSettings'),
DashboardChart: require('./doctype/DashboardChart/DashboardChart'),
Account: require('./doctype/Account/Account.js'),
AccountingSettings: require('./doctype/AccountingSettings/AccountingSettings'),
CompanySettings: require('./doctype/CompanySettings/CompanySettings'),
@ -13,13 +15,13 @@ module.exports = {
Item: require('./doctype/Item/Item.js'),
Invoice: require('./doctype/Invoice/Invoice.js'),
InvoiceItem: require('./doctype/InvoiceItem/InvoiceItem.js'),
InvoiceSettings: require('./doctype/InvoiceSettings/InvoiceSettings.js'),
SalesInvoice: require('./doctype/SalesInvoice/SalesInvoice.js'),
SalesInvoiceItem: require('./doctype/SalesInvoiceItem/SalesInvoiceItem.js'),
SalesInvoiceSettings: require('./doctype/SalesInvoiceSettings/SalesInvoiceSettings.js'),
Bill: require('./doctype/Bill/Bill.js'),
BillItem: require('./doctype/BillItem/BillItem.js'),
BillSettings: require('./doctype/BillSettings/BillSettings.js'),
PurchaseInvoice: require('./doctype/PurchaseInvoice/PurchaseInvoice.js'),
PurchaseInvoiceItem: require('./doctype/PurchaseInvoiceItem/PurchaseInvoiceItem.js'),
PurchaseInvoiceSettings: require('./doctype/PurchaseInvoiceSettings/PurchaseInvoiceSettings.js'),
Tax: require('./doctype/Tax/Tax.js'),
TaxDetail: require('./doctype/TaxDetail/TaxDetail.js'),

View File

@ -60,6 +60,7 @@
"dependencies": {
"cross-env": "^5.2.0",
"file-saver": "^2.0.2",
"frappe-charts": "^1.2.4",
"frappejs": "github:thefalconx33/frappejs#test",
"nodemailer": "^4.7.0",
"popper.js": "^1.14.4",

View File

@ -2,7 +2,6 @@ const frappe = require('frappejs');
module.exports = class AccountsReceivablePayable {
async run(reportType, { date }) {
const rows = await getReceivablePayable({
reportType,
date
@ -10,12 +9,13 @@ module.exports = class AccountsReceivablePayable {
return { rows };
}
}
};
async function getReceivablePayable({ reportType = 'Receivable', date }) {
let entries = [];
const debitOrCredit = reportType === 'Receivable' ? 'debit' : 'credit';
const referenceType = reportType === 'Receivable' ? 'Invoice' : 'Bill';
const referenceType =
reportType === 'Receivable' ? 'SalesInvoice' : 'PurchaseInvoice';
entries = await getLedgerEntries();
const vouchers = await getVouchers();
@ -29,7 +29,6 @@ async function getReceivablePayable({ reportType = 'Receivable', date }) {
let data = [];
for (let entry of validEntries) {
const { outStandingAmount, creditNoteAmount } = getOutstandingAmount(entry);
console.log(outStandingAmount);
@ -81,16 +80,22 @@ async function getReceivablePayable({ reportType = 'Receivable', date }) {
return entries.filter(entry => {
return (
entry.date <= date &&
entry.referenceType === referenceType && entry[debitOrCredit] > 0
entry.referenceType === referenceType &&
entry[debitOrCredit] > 0
);
});
}
function getOutstandingAmount(entry) {
let paymentAmount = 0.0, creditNoteAmount = 0.0;
let paymentAmount = 0.0,
creditNoteAmount = 0.0;
let reverseDebitOrCredit = debitOrCredit === 'debit' ? 'credit' : 'debit';
for (let e of getEntriesFor(entry.party, entry.referenceType, entry.referenceName)) {
for (let e of getEntriesFor(
entry.party,
entry.referenceType,
entry.referenceName
)) {
if (e.date <= date) {
const amount = e[reverseDebitOrCredit] - e[debitOrCredit];
@ -103,14 +108,18 @@ async function getReceivablePayable({ reportType = 'Receivable', date }) {
}
return {
outStandingAmount: (entry[debitOrCredit] - entry[reverseDebitOrCredit]) - paymentAmount - creditNoteAmount,
outStandingAmount:
entry[debitOrCredit] -
entry[reverseDebitOrCredit] -
paymentAmount -
creditNoteAmount,
creditNoteAmount
}
};
}
function getEntriesFor(party, againstVoucherType, againstVoucher) {
// TODO
return []
return [];
}
function getFutureEntries() {
@ -131,7 +140,7 @@ async function getReceivablePayable({ reportType = 'Receivable', date }) {
return entries;
}
const partyType = reportType === 'Receivable' ? 'customer': 'supplier'
const partyType = reportType === 'Receivable' ? 'customer' : 'supplier';
const partyList = (await frappe.db.getAll({
doctype: 'Party',
filters: {
@ -141,9 +150,16 @@ async function getReceivablePayable({ reportType = 'Receivable', date }) {
return await frappe.db.getAll({
doctype: 'AccountingLedgerEntry',
fields: ['name', 'date', 'account', 'party',
'referenceType', 'referenceName',
'sum(debit) as debit', 'sum(credit) as credit'],
fields: [
'name',
'date',
'account',
'party',
'referenceType',
'referenceName',
'sum(debit) as debit',
'sum(credit) as credit'
],
filters: {
party: ['in', partyList]
},

View File

@ -8,7 +8,7 @@ module.exports = class GeneralLedgerView extends ReportPage {
filterFields: [
{
fieldtype: 'Select',
options: ['', 'Invoice', 'Payment'],
options: ['', 'SalesInvoice', 'Payment'],
label: 'Reference Type',
fieldname: 'referenceType'
},

View File

@ -5,7 +5,7 @@ const viewConfig = {
filterFields: [
{
fieldtype: 'Select',
options: ['', 'Invoice', 'Payment', 'Bill'],
options: ['', 'SalesInvoice', 'Payment', 'PurchaseInvoice'],
label: 'Reference Type',
fieldname: 'referenceType'
},

View File

@ -4,13 +4,13 @@ class BaseGSTR {
async getCompleteReport(gstrType, filters) {
if (['GSTR-1', 'GSTR-2'].includes(gstrType)) {
let entries = await frappe.db.getAll({
doctype: gstrType === 'GSTR-1' ? 'Invoice' : 'Bill',
doctype: gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice',
filters
});
let tableData = [];
for (let entry of entries) {
entry.doctype = gstrType === 'GSTR-1' ? 'Invoice' : 'Bill';
entry.doctype = gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice';
const row = await this.getRow(entry);
tableData.push(row);
}

View File

@ -1,14 +1,25 @@
const frappe = require('frappejs');
class PurchaseRegister {
async run({ fromDate, toDate }) {
async run({ fromDate, toDate, supplier }) {
let filters = {};
if (supplier) {
filters.supplier = supplier;
}
if (fromDate && toDate) {
filters.date = ['>=', fromDate, '<=', toDate];
} else if (fromDate) {
filters.date = ['>=', fromDate];
} else if (toDate) {
filters.date = ['<=', toDate];
}
filters.submitted = 1;
const bills = await frappe.db.getAll({
doctype: 'Bill',
doctype: 'PurchaseInvoice',
fields: ['name', 'date', 'supplier', 'account', 'netTotal', 'grandTotal'],
filters: {
date: ['>=', fromDate, '<=', toDate],
submitted: 1
},
filters,
orderBy: 'date',
order: 'desc'
});
@ -19,7 +30,7 @@ class PurchaseRegister {
doctype: 'TaxSummary',
fields: ['parent', 'amount'],
filters: {
parenttype: 'Bill',
parenttype: 'PurchaseInvoice',
parent: ['in', billNames]
},
orderBy: 'name'

View File

@ -4,7 +4,7 @@ const RegisterView = require('../Register/RegisterView');
module.exports = class PurchaseRegisterView extends RegisterView {
constructor() {
super({
title: frappe._('Purchase Register'),
title: frappe._('Purchase Register')
});
this.method = 'purchase-register';
@ -12,13 +12,13 @@ module.exports = class PurchaseRegisterView extends RegisterView {
getColumns() {
return [
{ label: 'Bill', fieldname: 'name' },
{ label: 'PurchaseInvoice', fieldname: 'name' },
{ label: 'Posting Date', fieldname: 'date' },
{ label: 'Supplier', fieldname: 'supplier' },
{ label: 'Payable Account', fieldname: 'account' },
{ label: 'Net Total', fieldname: 'netTotal', fieldtype: 'Currency' },
{ label: 'Total Tax', fieldname: 'totalTax', fieldtype: 'Currency' },
{ label: 'Grand Total', fieldname: 'grandTotal', fieldtype: 'Currency' },
{ label: 'Grand Total', fieldname: 'grandTotal', fieldtype: 'Currency' }
];
}
}
};

View File

@ -0,0 +1,42 @@
const title = 'Purchase Register';
module.exports = {
title: title,
method: 'purchase-register',
filterFields: [
{
fieldtype: 'Link',
target: 'Party',
label: 'Supplier Name',
fieldname: 'supplier',
getFilters: query => {
if (query)
return {
keywords: ['like', query],
supplier: 1
};
return {
supplier: 1
};
}
},
{
fieldtype: 'Date',
fieldname: 'fromDate',
label: 'From Date',
required: 1
},
{ fieldtype: 'Date', fieldname: 'toDate', label: 'To Date', required: 1 }
],
getColumns() {
return [
{ label: 'PurchaseInvoice', fieldname: 'name' },
{ label: 'Posting Date', fieldname: 'date' },
{ label: 'Supplier', fieldname: 'supplier' },
{ label: 'Payable Account', fieldname: 'account' },
{ label: 'Net Total', fieldname: 'netTotal', fieldtype: 'Currency' },
{ label: 'Total Tax', fieldname: 'totalTax', fieldtype: 'Currency' },
{ label: 'Grand Total', fieldname: 'grandTotal', fieldtype: 'Currency' }
];
}
};

View File

@ -19,7 +19,7 @@ module.exports = class SalesRegisterView extends RegisterView {
getColumns() {
return [
{ label: 'Invoice', fieldname: 'name' },
{ label: 'SalesInvoice', fieldname: 'name' },
{ label: 'Posting Date', fieldname: 'date' , fieldtype: 'Date' },
{ label: 'Customer', fieldname: 'customer' },
{ label: 'Receivable Account', fieldname: 'account' },

View File

@ -3,19 +3,40 @@ module.exports = {
title: title,
method: 'sales-register',
filterFields: [
{ fieldtype: 'Link', target: 'Party', label: 'Customer Name', fieldname: 'customer' },
{ fieldtype: 'Date', fieldname: 'fromDate', label: 'From Date', required: 1 },
{
fieldtype: 'Link',
target: 'Party',
label: 'Customer Name',
fieldname: 'customer',
getFilters: query => {
if (query)
return {
keywords: ['like', query],
customer: 1
};
return {
customer: 1
};
}
},
{
fieldtype: 'Date',
fieldname: 'fromDate',
label: 'From Date',
required: 1
},
{ fieldtype: 'Date', fieldname: 'toDate', label: 'To Date', required: 1 }
],
getColumns() {
return [
{ label: 'Invoice', fieldname: 'name' },
{ label: 'SalesInvoice', fieldname: 'name' },
{ label: 'Posting Date', fieldname: 'date', fieldtype: 'Date' },
{ label: 'Customer', fieldname: 'customer' },
{ label: 'Receivable Account', fieldname: 'account' },
{ label: 'Net Total', fieldname: 'netTotal', fieldtype: 'Currency' },
{ label: 'Total Tax', fieldname: 'totalTax', fieldtype: 'Currency' },
{ label: 'Grand Total', fieldname: 'grandTotal', fieldtype: 'Currency' },
{ label: 'Grand Total', fieldname: 'grandTotal', fieldtype: 'Currency' }
];
}
};

View File

@ -1,6 +1,7 @@
module.exports = {
'general-ledger': require('./GeneralLedger/viewConfig'),
'sales-register': require('./SalesRegister/viewConfig'),
'purchase-register': require('./PurchaseRegister/viewConfig'),
'profit-and-loss': require('./ProfitAndLoss/viewConfig'),
'trial-balance': require('./TrialBalance/viewConfig'),
'bank-reconciliation': require('./BankReconciliation/viewConfig'),

View File

@ -4,9 +4,9 @@ const registerServerMethods = require('./registerServerMethods');
module.exports = async function postStart() {
// set server-side modules
frappe.models.Invoice.documentClass = require('../models/doctype/Invoice/InvoiceServer.js');
frappe.models.SalesInvoice.documentClass = require('../models/doctype/SalesInvoice/SalesInvoiceServer.js');
frappe.models.Payment.documentClass = require('../models/doctype/Payment/PaymentServer.js');
frappe.models.Bill.documentClass = require('../models/doctype/Bill/BillServer.js');
frappe.models.PurchaseInvoice.documentClass = require('../models/doctype/PurchaseInvoice/PurchaseInvoiceServer.js');
frappe.models.JournalEntry.documentClass = require('../models/doctype/JournalEntry/JournalEntryServer.js');
frappe.models.GSTR3B.documentClass = require('../models/doctype/GSTR3B/GSTR3BServer.js');
@ -15,8 +15,8 @@ module.exports = async function postStart() {
frappe.syncDoc(require('../fixtures/invoicePrint'));
// init naming series if missing
await naming.createNumberSeries('INV-', 'InvoiceSettings');
await naming.createNumberSeries('BILL-', 'BillSettings');
await naming.createNumberSeries('SINV-', 'SalesInvoiceSettings');
await naming.createNumberSeries('PINV-', 'PurchaseInvoiceSettings');
await naming.createNumberSeries('PAY-', 'PaymentSettings');
await naming.createNumberSeries('JV-', 'JournalEntrySettings');
await naming.createNumberSeries('QTN-', 'QuotationSettings');

View File

@ -0,0 +1,26 @@
<template>
<div class="col-6">
<div class="card mx-2 my-2">
<div class="card-body">
<div :ref="chartData.title"></div>
</div>
</div>
</div>
</template>
<script>
import { Chart } from 'frappe-charts';
export default {
name: 'DashboardCard',
props: ['chartData'],
mounted() {
const chart = new Chart(this.$refs[this.chartData.title], {
title: this.chartData.title,
data: this.chartData.data,
type: this.chartData.type, // or 'bar', 'line', 'scatter', 'pie', 'percentage'
height: 250,
colors: [this.chartData.color]
});
}
};
</script>

View File

@ -3,7 +3,7 @@
<div v-if="doc" class="p-4">
<div class="row">
<div class="col-6 text-center">
<h4>Customizer</h4>
<h4>Customize</h4>
</div>
<div class="col-6 text-right">
<f-button secondary @click="saveAndClose">{{ _('Save & Close') }}</f-button>
@ -36,9 +36,9 @@ export default {
};
},
async created() {
this.doc = await frappe.getDoc('InvoiceSettings');
this.doc = await frappe.getDoc('SalesInvoiceSettings');
this.color.hex = this.doc.themeColor;
const meta = frappe.getMeta('InvoiceSettings');
const meta = frappe.getMeta('SalesInvoiceSettings');
this.fields = meta.fields.filter(
field => field.fieldname !== 'numberSeries'
);

View File

@ -1,5 +1,7 @@
<template>
<div class="page-header px-4 py-3 border-bottom bg-white d-flex align-items-center">
<div
class="page-header px-4 py-2 border-bottom bg-white d-flex align-items-center justify-content-between"
>
<h5 class="m-0" v-if="title">{{ title }}</h5>
<div v-if="breadcrumbs">
<a v-for="(item, index) in clickableBreadcrumbs" :key="index" :href="item.route">
@ -10,11 +12,19 @@
</a>
<h5 class="breadCrumbRoute">{{ lastBreadcrumb.title }}</h5>
</div>
<div class="col-4 p-0">
<SearchBar />
</div>
</div>
</template>
<script>
import SearchBar from './SearchBar';
export default {
props: ['title', 'breadcrumbs'],
components: {
SearchBar
},
computed: {
clickableBreadcrumbs() {
return this.breadcrumbs.slice(0, this.breadcrumbs.length - 1);

View File

@ -0,0 +1,152 @@
<template>
<div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text pt-0">
<feather-icon name="search" style="color: #212529 !important;"></feather-icon>
</span>
</div>
<input
type="search"
class="form-control"
placeholder="Search..."
autocomplete="off"
spellcheck="false"
v-model="inputValue"
@keyup.enter="search"
aria-label="Recipient's username"
/>
</div>
<div v-if="inputValue" class="suggestion-list position-absolute shadow-sm" style="width: 100%">
<list-row
v-for="doc in suggestion"
:key="doc.name"
:class="doc.sep ? 'seperator': ''"
class="d-flex align-items-center"
@click.native="routeTo(doc.route)"
>
<span v-if="!doc.sep">
<feather-icon class="mr-1" name="minus" />
</span>
<div :class="doc.sep ? 'small' : ''">{{ doc.name }}</div>
<div class="small ml-auto">{{ doc.doctype }}</div>
</list-row>
</div>
</div>
</template>
<script>
import frappe from 'frappejs';
import ListRow from '../pages/ListView/ListRow';
import ListCell from '../pages/ListView/ListCell';
export default {
data() {
return {
inputValue: '',
suggestion: []
};
},
components: {
ListRow,
ListCell
},
watch: {
inputValue() {
if (!this.inputValue.length) this.suggestion = [];
}
},
methods: {
async search() {
const searchableDoctypes = frappe.getDoctypeList({
isSingle: 0,
isChild: 0
});
const documents = await this.getSearchedDocuments(searchableDoctypes);
const doctypes = await this.getSearchedDoctypes(searchableDoctypes);
this.suggestion = documents.concat(doctypes);
if (this.suggestion.length === 0)
this.suggestion = [{ sep: true, name: 'No results found.' }];
},
clearInput() {
this.inputValue = '';
this.$emit('change', null);
},
async getSearchedDocuments(searchableDoctypes) {
const promises = searchableDoctypes.map(doctype => {
return frappe.db.getAll({
doctype,
filters: { name: ['includes', this.inputValue] },
fields: ['name']
});
});
const data = await Promise.all(promises);
// data contains list of documents, sorted according to position of its doctype in searchableDoctypes
const items = [];
items.push({
sep: true,
name: 'Documents'
});
for (let i = 0; i < data.length; i++) {
// i represents doctype position in searchableDoctypes
if (data[i].length > 0) {
for (let doc of data[i]) {
let doctype = searchableDoctypes[i];
items.push({
doctype,
name: doc.name,
route: `/edit/${doctype}/${doc.name}`
});
}
}
}
if (items.length !== 1) return items;
return [];
},
getSearchedDoctypes(searchableDoctypes) {
const items = [{ sep: true, name: 'DocTypes' }];
let filteredDoctypes = searchableDoctypes.filter(doctype => {
return doctype.indexOf(this.inputValue) != -1;
});
filteredDoctypes = filteredDoctypes.map(doctype => {
return {
name: doctype,
route: `/list/${doctype}`
};
});
if (filteredDoctypes.length > 0) return items.concat(filteredDoctypes);
return [];
},
routeTo(route) {
this.$router.push(route);
this.inputValue = '';
}
}
};
</script>
<style lang="scss" scoped>
@import '../styles/variables';
.input-group-text {
background-color: var(--white);
border-right: none;
}
.seperator {
background-color: var(--light);
}
input {
border-left: none;
height: 27px;
}
input:focus {
border: 1px solid #ced4da;
border-left: none;
outline: none !important;
box-shadow: none !important;
}
.suggestion-list {
z-index: 2;
}
</style>

View File

@ -1,39 +0,0 @@
<template>
<div class="input-group">
<input
type="search"
class="form-control"
placeholder="Search..."
autocomplete="off"
spellcheck="false"
v-model="inputValue"
@keyup.enter="$emit('change', inputValue)"
aria-label="Recipient's username"
>
<div class="input-group-append">
<button
class="btn btn-outline-secondary"
type="button"
:disabled="!inputValue"
@click="clearInput"
>
{{ _('Clear') }}
</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
inputValue: ''
}
},
methods: {
clearInput() {
this.inputValue = '';
this.$emit('change', null);
}
}
}
</script>

View File

@ -2,7 +2,7 @@
<div class="page-sidebar bg-dark p-2 text-light d-flex flex-column justify-content-between">
<div>
<div class="company-name px-3 py-2 my-2">
<h6 class="m-0">{{ companyName }}</h6>
<h6 class="m-0" @click="$router.push('/')">{{ companyName }}</h6>
</div>
<div>
<transition-group name="slide-fade" mode="out-in">
@ -95,6 +95,9 @@ export default {
@import '../styles/variables.scss';
@import '../styles/animation.scss';
.company-name {
cursor: pointer;
}
.page-sidebar {
height: 100vh;
}

119
src/pages/Dashboard.vue Normal file
View File

@ -0,0 +1,119 @@
<template>
<div>
<PageHeader title="Dashboard" />
<div class="container mt-3">
<div class="row no-gutters">
<DashboardCard v-for="chart in chartData" :key="chart.title" :chartData="chart" />
</div>
</div>
</div>
</template>
<script>
import PageHeader from '../components/PageHeader';
import DashboardCard from '../components/DashboardCard';
import frappe from 'frappejs';
export default {
name: 'Dashboard',
components: {
PageHeader,
DashboardCard
},
data() {
return {
chartData: [],
charts: undefined
};
},
async beforeMount() {
const dashboardSettings = await frappe.getDoc('DashboardSettings');
this.charts = dashboardSettings.charts;
this.charts.forEach(async c => {
const { labels, datasets } = await this.getAccountData(c.account, c.type);
this.chartData.push({
title: c.account,
type: c.type.toLowerCase(),
color: c.color,
data: {
labels,
datasets
}
});
});
},
methods: {
async getAccountData(account, chartType) {
let entriesArray = [];
let accountType;
async function getAccountEntries(accountName) {
const account = await frappe.getDoc('Account', accountName);
accountType = account.rootType;
if (account.isGroup != 1) {
const ledgerEntries = await frappe.db.getAll({
doctype: 'AccountingLedgerEntry',
filters: { account: account.name },
fields: ['*']
});
entriesArray = entriesArray.concat(ledgerEntries);
} else {
const children = await frappe.db.getAll({
doctype: 'Account',
filters: { parentAccount: account.name }
});
if (children.length)
for (let account of children) {
entriesArray = await getAccountEntries(account.name);
}
}
return entriesArray;
}
let ledgerEntries = await getAccountEntries(account);
accountType = ['Asset', 'Expense'].includes(accountType)
? 'debit'
: 'credit';
let { labels, datasets } = this.createLabelsAndDataSet(chartType);
const currentMonthIndex = new Date().getMonth();
for (let entry of ledgerEntries) {
let monthIndex = parseInt(entry.date.split('-')[1]) - 1;
let pos = (monthIndex + currentMonthIndex) % 13;
if (accountType === 'debit')
datasets[0].values[pos] += entry.debit || -entry.credit;
else if (accountType === 'credit')
datasets[0].values[pos] += -entry.debit || entry.credit;
}
return { labels, datasets };
},
createLabelsAndDataSet(chartType) {
const currentMonthIndex = new Date().getMonth();
const currentYear = new Date().getFullYear();
const monthName = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'June',
'July',
'Aug',
'Sept',
'Oct',
'Nov',
'Dec'
];
let labels = [];
let datasets = [
{
type: chartType,
values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
];
for (let i = 0; i < 13; i++) {
let year = i + currentMonthIndex >= 12 ? currentYear : currentYear - 1;
labels.push(monthName[(i + currentMonthIndex) % 12]);
}
return { labels, datasets };
}
}
};
</script>

View File

@ -6,7 +6,7 @@
:docfield="{
fieldtype: 'Select',
fieldname: 'referenceDoctype',
options: ['Select a doctype...', 'ToDo', 'Item', 'Party', 'Invoice']
options: ['Select a doctype...', 'ToDo', 'Item', 'Party', 'SalesInvoice']
}"
@change="doctype => showTable(doctype)"
/>

View File

@ -1,7 +1,7 @@
<template>
<div class="row no-gutters">
<sidebar class="col-2" />
<div class="page-container col-10 bg-light">
<div class="row no-gutters d-flex">
<sidebar class="sidebar" />
<div class="page-container bg-white">
<router-view :key="$route.fullPath" />
</div>
</div>
@ -18,7 +18,12 @@ export default {
<style>
.page-container {
height: 100vh;
overflow: auto;
overflow-x: hidden;
width: 100%;
padding-left: 208px;
overflow: hidden;
}
.sidebar {
position: fixed;
/* flex-basis: 220px; */
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="bg-light">
<div class="bg-white">
<page-header :breadcrumbs="breadcrumbs" />
<div class="form-container col-10 bg-white mt-4 ml-auto mr-auto border p-5">
<div class="form-container col-sm-8 col-lg-6 mx-2 mt-4">
<form-actions
v-if="shouldRenderForm"
:doc="doc"
@ -56,12 +56,12 @@ export default {
if (this.doc)
return [
{
title: this.doctype,
title: this.meta.label,
route: '#/list/' + this.doctype
},
{
title: this.doc._notInserted
? 'New ' + this.doctype
? 'New ' + this.meta.label
: this.doc.name,
route: ''
}

View File

@ -4,14 +4,14 @@
</div>
</template>
<style lang="scss">
@import "../../styles/variables";
@import '../../styles/variables';
.list-row {
cursor: pointer;
border: 1px solid $border-color;
background-color: var(--white);
&:hover {
background-color: var(--light);
background-color: var(--white);
}
}

View File

@ -1,7 +1,7 @@
<template>
<div class="row">
<div class="col-6 d-flex">
<search-input class="mr-2" @change="keyword => filterList(keyword)"/>
<!-- <search-bar class="mr-2" @change="keyword => filterList(keyword)" /> -->
</div>
<div class="col-6 d-flex flex-row-reverse">
<f-button primary @click="$emit('newClick')">{{ _('New {0}', listConfig.title) }}</f-button>
@ -9,22 +9,18 @@
</div>
</template>
<script>
import SearchInput from '@/components/SearchInput';
import SearchBar from '@/components/SearchBar';
export default {
name: 'ListToolbar',
props: ['listConfig'],
components: {
SearchInput
SearchBar
},
methods: {
async newInvoice() {
const doc = await frappe.getNewDoc('Invoice');
this.$formModal.open(doc);
},
filterList(keyword) {
frappe.listView.trigger('filterList', keyword);
}
}
}
};
</script>

View File

@ -1,5 +1,5 @@
<template>
<div class="bg-light">
<div class="bg-white">
<page-header :title="listConfig.title" />
<div class="px-4 py-3">
<list-toolbar
@ -8,9 +8,7 @@
@filterList="keyword => filterList(keyword)"
class="mb-4"
/>
<list
:listConfig="listConfig"
/>
<list :listConfig="listConfig" />
</div>
</div>
</template>
@ -51,5 +49,5 @@ export default {
return listConfigs[this.listName];
}
}
}
};
</script>

View File

@ -1,5 +1,5 @@
import Invoice from '../../../models/doctype/Invoice/InvoiceList';
import Bill from '../../../models/doctype/Bill/BillList';
import SalesInvoice from '../../../models/doctype/SalesInvoice/SalesInvoiceList';
import PurchaseInvoice from '../../../models/doctype/PurchaseInvoice/PurchaseInvoiceList';
import Customer from '../../../models/doctype/Party/CustomerList';
import Supplier from '../../../models/doctype/Party/SupplierList';
import Item from '../../../models/doctype/Item/ItemList';
@ -10,8 +10,8 @@ import Account from '../../../models/doctype/Account/AccountList';
import GSTR3B from '../../../models/doctype/GSTR3B/GSTR3BList';
export default {
Invoice,
Bill,
SalesInvoice,
PurchaseInvoice,
Customer,
Supplier,
Item,

View File

@ -1,17 +1,17 @@
<template>
<div class="bg-light">
<div class="bg-white">
<page-header :breadcrumbs="breadcrumbs" />
<component :is="printComponent" v-if="doc" :doc="doc" @send="send" @makePDF="makePDF" />
</div>
</template>
<script>
import PageHeader from '@/components/PageHeader';
import InvoicePrint from '@/../models/doctype/Invoice/InvoicePrint';
import SalesInvoicePrint from '@/../models/doctype/SalesInvoice/SalesInvoicePrint';
import GSTR3BPrintView from '@/../models/doctype/GSTR3B/GSTR3BPrintView';
import EmailSend from '../Email/EmailSend';
const printComponents = {
Invoice: InvoicePrint,
SalesInvoice: SalesInvoicePrint,
GSTR3B: GSTR3BPrintView
};
export default {

View File

@ -1,9 +1,9 @@
<template>
<div>
<div class="px-3">
<div class="row pb-4">
<page-header :class="linksExists ? 'col-6':'col-12'" :breadcrumbs="breadcrumbs" />
<report-links class="col-6 d-flex pr-0 flex-row-reverse" v-if="linksExists" :links="links"></report-links>
<div class="row pb-4 d-flex">
<page-header :breadcrumbs="breadcrumbs" style="flex-grow: 1;" />
<report-links class="d-flex flex-row-reverse" v-if="linksExists" :links="links"></report-links>
</div>
<div class="row pb-4">
<report-filters

View File

@ -7,7 +7,7 @@
class="mt-2"
title="General Ledger"
description="List of all ledger entries booked against all accounts"
@click="routeTo('general-ledger', { 'referenceType': 'Invoice' })"
@click="routeTo('general-ledger', { 'referenceType': 'SalesInvoice' })"
/>
<clickable-card
class="mt-2"
@ -33,7 +33,8 @@
description="Bank Reconciliation statement"
@click="routeTo('bank-reconciliation',{'toDate' : (new Date()).toISOString()})"
/>
<clickable-card v-if="country === 'India'"
<clickable-card
v-if="country === 'India'"
class="mt-2"
title="Goods and Service Tax"
description="See your goods and services tax here."
@ -52,8 +53,8 @@ export default {
name: 'Report',
data() {
return {
country: '',
}
country: ''
};
},
components: {
PageHeader,
@ -62,7 +63,6 @@ export default {
async created() {
const doc = await frappe.getDoc('AccountingSettings');
this.country = doc.country;
},
methods: {
routeTo(route, filters) {

View File

@ -2,14 +2,18 @@
<div>
<page-header title="Settings" />
<div class="row">
<div class="col-8 bg-white mt-4 mx-auto border p-5">
<div class="col-12 bg-white border p-5">
<div class="col-9 col-lg-6 ml-1">
<setting-section doctype="AccountingSettings" />
<hr class="mt-4">
<hr class="mt-4" />
<setting-section doctype="CompanySettings" />
<hr class="mt-4">
<hr class="mt-4" />
<setting-section doctype="EmailAccount" />
<hr class="mt-4">
<hr class="mt-4" />
<setting-section doctype="SystemSettings" />
<hr class="mt-4" />
<setting-section doctype="DashboardSettings" />
</div>
</div>
</div>
</div>

View File

@ -2,6 +2,7 @@ import Vue from 'vue';
import Router from 'vue-router';
import ListView from '../pages/ListView';
import Dashboard from '../pages/Dashboard';
import FormView from '../pages/FormView/FormView';
import PrintView from '../pages/PrintView';
@ -22,7 +23,7 @@ Vue.use(Router);
const routes = [
{
path: '/',
redirect: '/chartOfAccounts'
component: Dashboard
},
{
path: '/list/:listName',

View File

@ -2,10 +2,28 @@ import frappe from 'frappejs';
import { _ } from 'frappejs/utils';
const path = require('path');
export default {
const config = {
getTitle: async () => {
const accountingSettings = await frappe.getSingle('AccountingSettings');
return accountingSettings.companyName;
const { companyName, country } = await frappe.getSingle(
'AccountingSettings'
);
if (country === 'India') {
config.groups[2].items.push(
{
label: _('GSTR 1'),
route: '/report/gstr-1?transferType=B2B'
},
{
label: _('GSTR 2'),
route: '/report/gstr-2?transferType=B2B'
},
{
label: _('GSTR 3B'),
route: '/list/GSTR3B'
}
);
}
return companyName;
},
getDbName() {
if (localStorage.dbPath) {
@ -55,12 +73,12 @@ export default {
title: _('Transactions'),
items: [
{
label: _('Invoice'),
route: '/list/Invoice'
label: _('Sales Invoice'),
route: '/list/SalesInvoice'
},
{
label: _('Bill'),
route: '/list/Bill'
label: _('Purchase Invoice'),
route: '/list/PurchaseInvoice'
},
{
label: _('Journal Entry'),
@ -87,21 +105,13 @@ export default {
label: _('Sales Register'),
route: '/report/sales-register'
},
{
label: _('Purchase Register'),
route: '/report/purchase-register'
},
{
label: _('Bank Reconciliation'),
route: '/report/bank-reconciliation'
},
{
label: _('GSTR 1'),
route: '/report/gstr-1?transferType=B2B'
},
{
label: _('GSTR 2'),
route: '/report/gstr-2?transferType=B2B'
},
{
label: _('GSTR 3B'),
route: '/list/GSTR3B'
}
]
},
@ -120,3 +130,5 @@ export default {
}
]
};
export default config;