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

Winter Sprint: Work (#79)

*  init()

* Country-wise Chart of accounts on setup

* Add a sample invoice template

* Some error fixes

* [fix] missing COA
 - move importCOA.js from models/doctype/account to models/doctype/Account

* All Account initial balance zero

*  setup Bank Reconciliation

* New chart of accounts tree component

* GST taxes added. initialized gst reports.

* [chore] add *.db to .gitignore

* [fix] importCOA path error

*  fix error + bank reconciliation fields

* [feat] add gst taxes

* GST report initialized

* GST report finalized

* GST report finalized

*  Complete min. reconciliation

* [feat] auto select tax in invoice based on states

* [chore] fix merge changes

*  Fix date issue - Make Payment

* Add invoice templates and invoice customizer panel

* Restructure invoice vue components

*  update file with fiscal year

* Fix issues in invoice designs

* Add company settings. Dynamic addresses in invoice

* Move invoice styles to different file and add separate components for addresses

* [feat] add export-filtered-data-to-csv to reports

* [feat] add Export Wizard component for customizing export

* Fix invoice customizer position while scrolling. Fix address displayed as undefined in invoice if not found in db

* [chore] change markup for select all chkbox

*  Setup config as doctype

* GSTIN bug fix

* Add custom google fonts

*  Add Send email footer

*  Fix DateTime

*  Complete Merge + Resolve

*  Complete Merge + Resolve

* [chore] change layout of Export Wizard

* [enh] optimize checkNoneSelected, style export modal footer divider

*  Add Tax to SideBar

*  Remove extra logs

* [fix] db name section in sidebar showing absolute path instead of dbname in windows i.e. platform=win32

* Country-wise Chart of accounts on setup (#78)

* Country-wise Chart of accounts on setup

* Some error fixes

* All Account initial balance zero

* Update README.md

- updated installation instructions with more detail

* Merge #79 Winter Sprint: Work

https://github.com/frappe/accounting/pull/79

* Revert "Merge #79 Winter Sprint: Work"

This reverts commit 1715116668.
This commit is contained in:
Nishchith K 2019-02-18 11:12:04 +05:30 committed by Prssanna Desai
parent c7f27601c3
commit 72c6d7f6c7
73 changed files with 2905 additions and 1600 deletions

4
.gitignore vendored
View File

@ -1,7 +1,7 @@
node_modules node_modules
.DS_Store .DS_Store
*.db
Thumbs.db Thumbs.db
*test.db
*.log *.log
.cache .cache
/temp /temp
@ -11,4 +11,4 @@ Frappe Accounting*
build build
!build/icons !build/icons
!.gitkeep !.gitkeep
*.db *.db

View File

@ -20,6 +20,7 @@ function getSettings() {
async function saveSettings(settings) { async function saveSettings(settings) {
await writeFile(configFilePath, JSON.stringify(settings)); await writeFile(configFilePath, JSON.stringify(settings));
} }
console.log(getSettings());
module.exports = { module.exports = {
getSettings, getSettings,

5
email/getConfig.js Normal file
View File

@ -0,0 +1,5 @@
const frappe = require('frappejs');
module.exports = async function getData() {
account = await frappe.getDoc('EmailAccount');
return account;
}

46
email/sender.js Normal file
View File

@ -0,0 +1,46 @@
const nodemailer = require('nodemailer');
const getConfig = require("./getConfig");
const validator = require('./validator');
module.exports = {
'sendMail': async function (mailDetails) {
if (!validator.validate(mailDetails.fromEmailAddress)) {
console.log("INVALID EMAIL");
return false;
}
let account = await getConfig();
if (mailDetails.fromEmailAddress == account.email) {
if (validator.validate(mailDetails.toEmailAddress)) {
mailDetails = {
from: mailDetails.fromEmailAddress,
to: mailDetails.toEmailAddress,
replyTo: mailDetails.toEmailAddress,
inReplyTo: mailDetails.replyId,
references: [mailDetails.replyId],
cc: mailDetails.ccEmailAddress,
bcc: mailDetails.bccEmailAddress,
subject: mailDetails.subject,
text: mailDetails.bodyText,
attachments: [{
filename: 'Invoice.pdf',
path: mailDetails.filePath,
contentType: 'application/pdf'
}],
};
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: account.email,
pass: account.password,
}
});
transporter.sendMail(mailDetails);
return true;
} else {
console.log("Sender Email Invalid");
return false;
}
}
}
};

11
email/validator.js Normal file
View File

@ -0,0 +1,11 @@
// + Check Confirm Password == Password
module.exports = {
validate: function (email) {
var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
if (reg.test(email) == false) {
return false;
}
return true;
}
}

View File

@ -8,7 +8,9 @@
"number_format": "#,###.##", "number_format": "#,###.##",
"timezones": [ "timezones": [
"Asia/Kabul" "Asia/Kabul"
] ],
"fiscal_year_start": "12-20",
"fiscal_year_end": "12-21"
}, },
"Albania": { "Albania": {
"code": "al", "code": "al",
@ -169,7 +171,9 @@
"Australia/Melbourne", "Australia/Melbourne",
"Australia/Perth", "Australia/Perth",
"Australia/Sydney" "Australia/Sydney"
] ],
"fiscal_year_start": "07-01",
"fiscal_year_end": "06-30"
}, },
"Austria": { "Austria": {
"code": "at", "code": "at",
@ -223,7 +227,9 @@
"number_format": "#,###.##", "number_format": "#,###.##",
"timezones": [ "timezones": [
"Asia/Dhaka" "Asia/Dhaka"
] ],
"fiscal_year_start": "07-01",
"fiscal_year_end": "06-30"
}, },
"Barbados": { "Barbados": {
"code": "bb", "code": "bb",
@ -488,7 +494,9 @@
"America/Whitehorse", "America/Whitehorse",
"America/Winnipeg", "America/Winnipeg",
"America/Yellowknife" "America/Yellowknife"
] ],
"fiscal_year_start": "04-01",
"fiscal_year_end": "03-31"
}, },
"Cape Verde": { "Cape Verde": {
"code": "cv", "code": "cv",
@ -639,7 +647,9 @@
"number_format": "#.###,##", "number_format": "#.###,##",
"timezones": [ "timezones": [
"America/Costa_Rica" "America/Costa_Rica"
] ],
"fiscal_year_start": "10-01",
"fiscal_year_end": "09-30"
}, },
"Croatia": { "Croatia": {
"code": "hr", "code": "hr",
@ -765,7 +775,9 @@
"number_format": "#,###.##", "number_format": "#,###.##",
"timezones": [ "timezones": [
"Africa/Cairo" "Africa/Cairo"
] ],
"fiscal_year_start": "07-01",
"fiscal_year_end": "06-30"
}, },
"El Salvador": { "El Salvador": {
"code": "sv", "code": "sv",
@ -1113,7 +1125,9 @@
"number_format": "#,###.##", "number_format": "#,###.##",
"timezones": [ "timezones": [
"Asia/Hong_Kong" "Asia/Hong_Kong"
] ],
"fiscal_year_start": "04-01",
"fiscal_year_end": "03-31"
}, },
"Hungary": { "Hungary": {
"code": "hu", "code": "hu",
@ -1150,7 +1164,9 @@
"number_format": "#,##,###.##", "number_format": "#,##,###.##",
"timezones": [ "timezones": [
"Asia/Kolkata" "Asia/Kolkata"
] ],
"fiscal_year_start": "04-01",
"fiscal_year_end": "03-31"
}, },
"Indonesia": { "Indonesia": {
"code": "id", "code": "id",
@ -1175,7 +1191,9 @@
"number_format": "#,###.##", "number_format": "#,###.##",
"timezones": [ "timezones": [
"Asia/Tehran" "Asia/Tehran"
] ],
"fiscal_year_start": "06-23",
"fiscal_year_end": "06-22"
}, },
"Iraq": { "Iraq": {
"code": "iq", "code": "iq",
@ -1232,7 +1250,9 @@
"date_format": "dd/mm/yyyy", "date_format": "dd/mm/yyyy",
"timezones": [ "timezones": [
"Europe/Rome" "Europe/Rome"
] ],
"fiscal_year_start": "07-01",
"fiscal_year_end": "06-30"
}, },
"Ivory Coast": { "Ivory Coast": {
"code": "ci", "code": "ci",
@ -1714,7 +1734,9 @@
"number_format": "#,###.##", "number_format": "#,###.##",
"timezones": [ "timezones": [
"Asia/Rangoon" "Asia/Rangoon"
] ],
"fiscal_year_start": "04-01",
"fiscal_year_end": "03-31"
}, },
"Namibia": { "Namibia": {
"code": "na", "code": "na",
@ -1782,7 +1804,9 @@
"timezones": [ "timezones": [
"Pacific/Auckland", "Pacific/Auckland",
"Pacific/Chatham" "Pacific/Chatham"
] ],
"fiscal_year_start": "04-01",
"fiscal_year_end": "03-31"
}, },
"Nicaragua": { "Nicaragua": {
"code": "ni", "code": "ni",
@ -1878,7 +1902,9 @@
"number_format": "#,###.##", "number_format": "#,###.##",
"timezones": [ "timezones": [
"Asia/Karachi" "Asia/Karachi"
] ],
"fiscal_year_start": "07-01",
"fiscal_year_end": "06-30"
}, },
"Palau": { "Palau": {
"code": "pw", "code": "pw",
@ -2191,7 +2217,9 @@
"number_format": "#,###.##", "number_format": "#,###.##",
"timezones": [ "timezones": [
"Asia/Singapore" "Asia/Singapore"
] ],
"fiscal_year_start": "04-01",
"fiscal_year_end": "03-31"
}, },
"Sint Maarten (Dutch part)": { "Sint Maarten (Dutch part)": {
"code": "sx", "code": "sx",
@ -2254,7 +2282,9 @@
"number_format": "# ###.##", "number_format": "# ###.##",
"timezones": [ "timezones": [
"Africa/Johannesburg" "Africa/Johannesburg"
] ],
"fiscal_year_start": "03-01",
"fiscal_year_end": "02-28"
}, },
"South Georgia and the South Sandwich Islands": { "South Georgia and the South Sandwich Islands": {
"code": "gs", "code": "gs",
@ -2397,7 +2427,9 @@
"number_format": "#,###.##", "number_format": "#,###.##",
"timezones": [ "timezones": [
"Asia/Bangkok" "Asia/Bangkok"
] ],
"fiscal_year_start": "10-01",
"fiscal_year_end": "09-30"
}, },
"Timor-Leste": { "Timor-Leste": {
"code": "tl", "code": "tl",
@ -2547,7 +2579,9 @@
"number_format": "#,###.##", "number_format": "#,###.##",
"timezones": [ "timezones": [
"Europe/London" "Europe/London"
] ],
"fiscal_year_start": "04-01",
"fiscal_year_end": "03-31"
}, },
"United States": { "United States": {
"code": "us", "code": "us",

176
fixtures/verified/in.json Normal file
View File

@ -0,0 +1,176 @@
{
"country_code": "in",
"name": "India - Chart of Accounts",
"tree": {
"Application of Funds (Assets)": {
"Current Assets": {
"Accounts Receivable": {
"Debtors": {
"is_group": 0,
"account_type": "Receivable"
}
},
"Bank Accounts": {
"account_type": "Bank",
"is_group": 1
},
"Cash In Hand": {
"Cash": {
"account_type": "Cash"
},
"account_type": "Cash"
},
"Loans and Advances (Assets)": {
"is_group": 1
},
"Securities and Deposits": {
"Earnest Money": {}
},
"Stock Assets": {
"Stock In Hand": {
"account_type": "Stock"
},
"account_type": "Stock"
},
"Tax Assets": {
"is_group": 1
}
},
"Fixed Assets": {
"Capital Equipments": {
"account_type": "Fixed Asset"
},
"Electronic Equipments": {
"account_type": "Fixed Asset"
},
"Furnitures and Fixtures": {
"account_type": "Fixed Asset"
},
"Office Equipments": {
"account_type": "Fixed Asset"
},
"Plants and Machineries": {
"account_type": "Fixed Asset"
},
"Buildings": {
"account_type": "Fixed Asset"
},
"Accumulated Depreciations": {
"account_type": "Accumulated Depreciation"
}
},
"Investments": {
"is_group": 1
},
"Temporary Accounts": {
"Temporary Opening": {
"account_type": "Temporary"
}
},
"root_type": "Asset"
},
"Expenses": {
"Direct Expenses": {
"Stock Expenses": {
"Cost of Goods Sold": {
"account_type": "Cost of Goods Sold"
},
"Expenses Included In Valuation": {
"account_type": "Expenses Included In Valuation"
},
"Stock Adjustment": {
"account_type": "Stock Adjustment"
}
}
},
"Indirect Expenses": {
"Administrative Expenses": {},
"Commission on Sales": {},
"Depreciation": {
"account_type": "Depreciation"
},
"Entertainment Expenses": {},
"Freight and Forwarding Charges": {
"account_type": "Chargeable"
},
"Legal Expenses": {},
"Marketing Expenses": {},
"Miscellaneous Expenses": {},
"Office Maintenance Expenses": {},
"Office Rent": {},
"Postal Expenses": {},
"Print and Stationary": {},
"Rounded Off": {
"account_type": "Round Off"
},
"Salary": {},
"Sales Expenses": {},
"Telephone Expenses": {},
"Travel Expenses": {},
"Utility Expenses": {},
"Write Off": {},
"Exchange Gain/Loss": {},
"Gain/Loss on Asset Disposal": {}
},
"root_type": "Expense"
},
"Income": {
"Direct Income": {
"Sales": {
"account_type": "Income Account"
},
"Service": {
"account_type": "Income Account"
},
"account_type": "Income Account"
},
"Indirect Income": {
"account_type": "Income Account",
"is_group": 1
},
"root_type": "Income"
},
"Source of Funds (Liabilities)": {
"Capital Account": {
"Reserves and Surplus": {},
"Shareholders Funds": {}
},
"Current Liabilities": {
"Accounts Payable": {
"Creditors": {
"account_type": "Payable"
},
"Payroll Payable": {}
},
"Stock Liabilities": {
"Stock Received But Not Billed": {
"account_type": "Stock Received But Not Billed"
}
},
"Duties and Taxes": {
"TDS": {
"account_type": "Tax"
},
"IGST": {
"accountType": "Tax"
},
"CGST": {
"accountType": "Tax"
},
"SGST": {
"accountType": "Tax"
},
"Exempt": {
"accountType": "Tax"
}
},
"Loans (Liabilities)": {
"Secured Loans": {},
"Unsecured Loans": {},
"Bank Overdraft Account": {}
}
},
"root_type": "Liability"
}
}
}

View File

@ -0,0 +1,69 @@
const frappe = require('frappejs');
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'];
async function importAccounts(children, parent, rootType, rootAccount) {
for (let accountName in children) {
const child = children[accountName];
if (rootAccount) {
rootType = child.rootType || child.root_type;
}
if (!accountFields.includes(accountName)) {
let isGroup = identifyIsGroup(child);
const doc = frappe.newDoc({
doctype: 'Account',
name: accountName,
parentAccount: parent,
isGroup,
rootType,
balance: 0,
accountType: child.accountType
})
await doc.insert()
await importAccounts(child, accountName, rootType)
}
}
}
function identifyIsGroup(child) {
if (child.isGroup || child.is_group) {
return child.isGroup || child.is_group;
}
const keys = Object.keys(child);
const children = keys.filter(key => !accountFields.includes(key))
if (children.length) {
return 1;
}
return 0;
}
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 {
return standardCOA;
}
}
module.exports = async function importCharts() {
const chart = await getCountryCOA();
await importAccounts(chart, '', '', true)
}

View File

@ -51,12 +51,12 @@ module.exports = {
}, },
{ {
fieldname: "referenceType", fieldname: "referenceType",
label: "Reference Type", label: "Ref. Type",
fieldtype: "Data", fieldtype: "Data",
}, },
{ {
fieldname: "referenceName", fieldname: "referenceName",
label: "Reference Name", label: "Ref. Name",
fieldtype: "DynamicLink", fieldtype: "DynamicLink",
references: "referenceType" references: "referenceType"
}, },

View File

@ -9,13 +9,12 @@ module.exports = {
isSubmittable: 0, isSubmittable: 0,
settings: null, settings: null,
keywordFields: [], keywordFields: [],
fields: [ fields: [{
{
label: "Company Name", label: "Company Name",
fieldname: "companyName", fieldname: "companyName",
fieldtype: "Data", fieldtype: "Data",
required: 1, required: 1,
disabled: 1 disabled: 0
}, },
{ {

View File

@ -45,11 +45,6 @@ module.exports = {
"fieldtype": "Data", "fieldtype": "Data",
"required": 1 "required": 1
}, },
{
"fieldname": "county",
"label": "County",
"fieldtype": "Data"
},
{ {
"fieldname": "state", "fieldname": "state",
"label": "State", "label": "State",
@ -93,11 +88,11 @@ module.exports = {
} }
], ],
events: { // events: {
validate: (doc) => { // validate: (doc) => {
} // }
}, // },
listSettings: { listSettings: {
getFields(list) { getFields(list) {
@ -113,11 +108,17 @@ module.exports = {
{ {
columns: [ columns: [
{ {
fields: [ "addressTitle", "addressType", "addressLine1", fields: [
"addressLine2", "city", "county", "state", "country", "addressTitle", "addressType", "addressLine1",
"postalCode"] "addressLine2", "city", "country", "state",
"postalCode"
]
}, },
{ fields: [ "emailAddress", "phone", "fax", "isPreferredBilling", "isShippingBilling" ] } {
fields: [
"emailAddress", "phone", "fax", "isPreferredBilling", "isShippingBilling"
]
}
] ]
} }
] ]

View File

@ -0,0 +1,27 @@
module.exports = {
"name": "CompanySettings",
"label": "Company Settings",
"naming": "autoincrement",
"isSingle": true,
"isChild": false,
"keywordFields": [
"companyName"
],
"fields": [
{
"fieldname": "companyName",
"label": "Company Name",
"fieldtype": "Data",
"disabled": false,
"required": true
},
{
"fieldname": "companyAddress",
"label": "Company Address",
"fieldtype": "Link",
"disabled": false,
"required": true,
"target": "Address"
}
]
}

View File

@ -0,0 +1,72 @@
const frappe = require('frappejs');
module.exports = {
"name": "Email",
"doctype": "DocType",
"pageSettings": {
hideTitle: true
},
"isSingle": 0,
"isChild": 0,
"keywordFields": ["name"],
"fields": [{
"fieldname": "name",
"label": "name",
"fieldtype": "Data",
"required": 0,
"hidden": 1,
"disabled": 0
},
{
"fieldname": "fromEmailAddress",
"label": "From",
"fieldtype": "Data",
"required": 1,
"hidden": 0,
formula: async () => {
const accountingSettings = await frappe.getDoc('AccountingSettings');
return accountingSettings.email;
},
"disabled": 1
},
{
"fieldname": "toEmailAddress",
"label": "To",
"fieldtype": "Data",
"required": 1,
"hidden": 0,
"disabled": 0
},
{
"fieldname": "date",
"label": "Date",
"fieldtype": "Datetime",
"required": 0,
"hidden": 0,
"disabled": 1
},
{
"fieldname": "subject",
"label": "Subject",
"fieldtype": "Data",
"required": 0,
"hidden": 0,
"disabled": 0
},
{
"fieldname": "bodyText",
"label": "Body",
"fieldtype": "Text",
"required": 0,
"hidden": 0,
"disabled": 0
},
{
"fieldname": "filePath",
"label": "File Path",
"fieldtype": "Text",
"required": 0,
"hidden": 1,
}
]
}

View File

@ -0,0 +1,59 @@
const frappe = require('frappejs');
module.exports = {
"name": "EmailAccount",
"label": "Email Account",
"doctype": "DocType",
"isSingle": true,
"isChild": false,
"keywordFields": [
"email"
],
"fields": [
{
"fieldname": "email",
"label": "Email",
"fieldtype": "Data",
"required": 1,
formula: async () => {
const accountingSettings = await frappe.getDoc('AccountingSettings');
return accountingSettings.email;
},
},
{
"fieldname": "password",
"label": "Password",
"fieldtype": "Password",
"required": 1
},
{
"fieldname": "confirmPassword",
"label": "Confirm Password",
"fieldtype": "Password",
"required": 1
},
{
"fieldname": "smtpHost",
"label": "SMTP Host",
"fieldtype": "Select",
"options": [
"smtp.gmail.com",
"smtp.mail.yahoo.com",
"smtp-mail.outlook.com",
"smtp.mail.me.com",
"smtp.aol.com"
],
"default": "smtp.gmail.com"
},
{
"fieldname": "smtpPort",
"label": "SMTP Port",
"fieldtype": "Select",
"options": [
"465",
"587"
],
"default": "465"
}
]
}

View File

@ -18,7 +18,8 @@ module.exports = {
{ {
fieldname: 'date', fieldname: 'date',
label: 'Date', label: 'Date',
fieldtype: 'Date' fieldtype: 'Date',
// default: (new Date()).toISOString()
}, },
{ {
fieldname: 'customer', fieldname: 'customer',

View File

@ -1,73 +1,61 @@
<template> <template>
<div class="print-view p-5"> <component :themeColor="color" :font="fontFamily" :is="invoiceTemplate" v-if="doc" :doc="doc"/>
<h1>{{ doc.name }}</h1>
<div class="row py-4">
<div class="col-6">
<div><b>{{ _("Customer") }}</b></div>
<div>{{ doc.customer }}</div>
</div>
<div class="col-6">
<div><b>{{ _("Date") }}</b></div>
<div>{{ frappe.format(doc.date, 'Date') }}</div>
</div>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th style='width: 30px'></th>
<th>{{ _("Item") }}</th>
<th class='text-right'>{{ _("Qty") }}</th>
<th class='text-right'>{{ _("Rate") }}</th>
<th class='text-right'>{{ _("Amount") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in doc.items" :key="row.idx">
<td class='text-right'>{{ row.idx + 1 }}</td>
<td>{{ row.item }}<br>{{ frappe.format(row.description, 'Text') }}</td>
<td class='text-right'>{{ row.quantity }}</td>
<td class='text-right'>{{ frappe.format(row.rate, 'Currency') }}</td>
<td class='text-right'>{{ frappe.format(row.amount, 'Currency') }}</td>
</tr>
</tbody>
</table>
<div class='row'>
<div class='col-6'></div>
<div class='col-6'>
<div class='row'>
<div class='col-6'>
{{ _("Total") }}
</div>
<div class='col-6 text-right'>
{{ frappe.format(doc.netTotal, 'Currency')}}
</div>
</div>
<div class='row' v-for="tax in doc.taxes" :key="tax.name">
<div class='col-6'>
{{ tax.account }} ({{ tax.rate }}%)
</div>
<div class='col-6 text-right'>
{{ frappe.format(tax.amount, 'Currency')}}
</div>
</div>
<div class='row py-3'>
<div class='col-6'>
<h5>{{ _("Grand Total") }}</h5>
</div>
<div class='col-6 text-right'>
<h5>{{ frappe.format(doc.grandTotal, 'Currency')}}</h5>
</div>
</div>
</div>
</div>
<div class='py-3'>
{{ frappe.format(doc.terms, 'Text') }}
</div>
</div>
</template> </template>
<script> <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';
const invoiceTemplates = {
'Basic I': InvoiceTemplate1,
'Basic II': InvoiceTemplate2,
'Modern': InvoiceTemplate3
};
export default { export default {
name: 'InvoicePrint', name: 'InvoicePrint',
props: ['doc'] props: ['doc', 'themeColor', 'template', 'font'],
} data() {
</script> return {
color: undefined,
fontFamily: undefined,
invoiceTemplate: undefined
};
},
watch: {
themeColor: async function() {
await this.loadInvoice();
},
font: async function() {
await this.loadInvoice();
},
template: async function() {
await this.loadInvoice();
}
},
async created() {
await this.loadInvoice();
},
methods: {
async loadInvoice() {
this.color = this.themeColor !== undefined ? this.themeColor : await this.getColor();
this.fontFamily = this.font !== undefined ? this.font : await this.getFont();
let template = this.template !== undefined ? this.template : await this.getTemplate();
let templateFile = invoiceTemplates[template];
this.invoiceTemplate = templateFile;
},
async getTemplate() {
let invoiceSettings = await frappe.getDoc('InvoiceSettings');
return invoiceSettings.template;
},
async getColor() {
let invoiceSettings = await frappe.getDoc('InvoiceSettings');
return invoiceSettings.themeColor;
},
async getFont() {
let invoiceSettings = await frappe.getDoc('InvoiceSettings');
return invoiceSettings.font;
}
}
};
</script>

View File

@ -0,0 +1,28 @@
async function getCompanyDetails() {
let companyDetails = {
name: null,
address: {}
};
let companySettings = await frappe.getDoc('CompanySettings');
companyDetails.name = companySettings.companyName;
let companyAddress = await getAddress(companySettings.companyAddress);
companyDetails.address = companyAddress;
return companyDetails;
}
async function getCustomerAddress(customer) {
let customers = await frappe.db.getAll({ doctype: 'Party', fields:['name, address'], filters: { name: customer }});
let customerDetails = await frappe.getDoc('Party', customers[0].name);
return await getAddress(customerDetails.address);
}
async function getAddress(addressName) {
return await frappe.getDoc('Address', addressName);
}
module.exports = {
getCompanyDetails,
getCustomerAddress
}

View File

@ -0,0 +1,38 @@
<template>
<div v-if="detailsPresent">
<p :style="[$.bold]" style="font-size: 1.3em">{{ companyDetails.name }}</p>
<p :style="$.paraStyle">{{ companyDetails.address.addressLine1 }}</p>
<p :style="$.paraStyle">{{ companyDetails.address.addressLine2 }}</p>
<p :style="$.paraStyle">
{{ companyDetails.address.city + ' ' + companyDetails.address.state }}
</p>
<p :style="$.paraStyle">
{{ companyDetails.address.country + ' - ' + companyDetails.address.postalCode }}
</p>
</div>
</template>
<script>
import addressDetails from './AddressDetails';
import styles from './InvoiceStyles';
export default {
name: 'CompanyAddress',
data() {
return {
$: styles,
detailsPresent: true,
companyDetails: {
name: null,
address: {}
}
}
},
async created() {
this.$ = styles;
try {
this.companyDetails = await addressDetails.getCompanyDetails();
} catch(e) {
this.detailsPresent = false;
}
}
}
</script>

View File

@ -0,0 +1,37 @@
<template>
<div v-if="this.detailsPresent">
<p :style="[$.bold, $.mediumFontSize]">Billed To</p>
<p :style="[$.bold, $.paraStyle]">{{ customer }}</p>
<p :style="$.paraStyle">{{ customerAddress.addressLine1 }}</p>
<p :style="$.paraStyle">{{ customerAddress.addressLine2 }}</p>
<p :style="$.paraStyle">
{{ customerAddress.city + ' ' + customerAddress.state }}
</p>
<p :style="$.paraStyle">
{{ customerAddress.country + ' - ' + customerAddress.postalCode }}
</p>
</div>
</template>
<script>
import addressDetails from './AddressDetails';
import styles from './InvoiceStyles';
export default {
name: 'CustomerAddress',
props: ['customer'],
data() {
return {
$: styles,
detailsPresent: true,
customerAddress: {}
}
},
async created() {
this.$ = styles;
try {
this.customerAddress = await addressDetails.getCustomerAddress(this.customer);
} catch(e) {
this.detailsPresent = false;
}
}
}
</script>

View File

@ -0,0 +1,54 @@
module.exports = {
bold: {
fontWeight: 'bold'
},
font: {
fontFamily: null
},
regularFontSize: {
fontSize: '0.8rem'
},
mediumFontSize: {
fontSize: '1rem'
},
paraStyle: {
margin: '0.5rem',
marginLeft: 0,
marginRight: 0
},
bgColor: {
backgroundColor: null
},
showBorderBottom: {
borderBottom: null
},
hideBorderTop: {
borderTop: '0px solid black'
},
showBorderRight: {
borderRight: '1px solid #e0e0d1'
},
tablePadding: {
paddingTop: '3%',
paddingBottom: '3%'
},
fontColor: {
color: null
},
headerColor: {
backgroundColor: null,
color: 'white'
},
showNoticeBorderBottom: {
borderBottom: null
},
showBorderTop: {
borderTop: null
},
headerFontColor: {
color: null
},
showBorderTop: {
borderTop: null
}
}

View File

@ -0,0 +1,108 @@
<template>
<div :style="$.font" style="font-family: sans-serif;">
<div class="row no-gutters pl-5 pr-5 mt-5">
<div :style="$.regularFontSize" class="col-6">
<company-address />
</div>
<div :style="$.regularFontSize" class="col-6 text-right">
<h2 :style="$.headerFontColor">INVOICE</h2>
<p :style="$.paraStyle"><strong>{{ doc.name }}</strong></p>
<p :style="$.paraStyle">{{ frappe.format(doc.date, 'Date') }}</p>
</div>
</div>
<div class="row pl-5 mt-5">
<div :style="$.regularFontSize" class="col-6 mt-1">
<customer-address :customer="doc.customer" />
</div>
</div>
<div :style="$.regularFontSize" class="row pl-5 pr-5 mt-5">
<div class="col-12">
<table class="table p-0">
<thead>
<tr :style="$.showBorderBottom">
<th :style="$.hideBorderTop" class="text-left pl-0" style="width: 10%">{{ _("NO") }}</th>
<th :style="$.hideBorderTop" class="text-left" style="width: 60%">{{ _("ITEM") }}</th>
<th :style="$.hideBorderTop" class="text-left pl-0" style="width: 20%">{{ _("RATE") }}</th>
<th :style="$.hideBorderTop" class="text-left" style="width: 15%">{{ _("QTY") }}</th>
<th :style="$.hideBorderTop" class="text-right pr-1" style="width: 20%">{{ _("AMOUNT") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in doc.items" :key="row.idx">
<td class="text-left pl-1">{{ row.idx + 1 }}</td>
<td class="text-left">{{ row.item }}</td>
<td class="text-left pl-0">{{ frappe.format(row.rate, 'Currency') }}</td>
<td class="text-left">{{ row.quantity }}</td>
<td class="text-right pr-1">{{ frappe.format(row.amount, 'Currency') }}</td>
</tr>
<tr>
<td colspan="2" class="text-left pl-1"></td>
<td colspan="2" :style="$.bold" class="text-left pl-0">SUBTOTAL</td>
<td :style="$.bold" class="text-right pr-1">{{ frappe.format(doc.netTotal, 'Currency') }}</td>
</tr>
<tr v-for="tax in doc.taxes" :key="tax.name">
<td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td>
<td colspan="2" :style="$.bold" class="text-left pl-0">{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td>
<td :style="$.bold" class="text-right pr-1">{{ frappe.format(tax.amount, 'Currency') }}</td>
</tr>
<tr>
<td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td>
<td colspan="2" :style="[$.bold, $.mediumFontSize, $.showBorderTop]" class="text-left pl-0">TOTAL</td>
<td :style="[$.bold, $.mediumFontSize, $.showBorderTop]" class="text-right pr-1" style="color: green;">
{{ frappe.format(doc.grandTotal, 'Currency') }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row pl-5 pr-5 mt-5">
<div :style="$.regularFontSize" class="col-12">
<table class="table">
<tbody>
<tr :style="[$.bold, $.showBorderBottom]" ><td :style="$.hideBorderTop" class="pl-0">NOTICE</td></tr>
<tr><td class="pl-0">{{ doc.terms }}</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import Styles from './InvoiceStyles';
import CompanyAddress from './CompanyAddress';
import CustomerAddress from './CustomerAddress';
export default {
name: 'InvoiceTemplate1',
components: {
CompanyAddress,
CustomerAddress
},
props: ['doc', 'themeColor', 'font'],
data() {
return {
$: Styles
}
},
watch: {
themeColor: function() {
this.setTheme();
},
font: function() {
this.setTheme();
}
},
async created() {
this.$ = Styles;
this.setTheme();
},
methods: {
setTheme() {
this.$.headerFontColor.color = this.themeColor;
this.$.showBorderBottom.borderBottom = `0.22rem solid ${this.themeColor}`;
this.$.showBorderTop.borderTop = `0.22rem solid ${this.themeColor}`;
this.$.font.fontFamily = this.font;
}
}
}
</script>

View File

@ -0,0 +1,117 @@
<template>
<div :style="[$.regularFontSize, $.font]" style="font-family: sans-serif;">
<div class="row no-gutters p-5" :style="$.headerColor">
<div class="col-8 text-left">
<h1>INVOICE</h1>
</div>
<div class="col-4 text-right">
<company-address />
</div>
</div>
<div class="row p-5 mt-4">
<div class="col-4">
<customer-address :customer="doc.customer" />
</div>
<div class="col-4">
<p :style="[$.bold, $.mediumFontSize]">Invoice Number</p>
<p :style="$.paraStyle">{{ doc.name }}</p><br>
<p :style="[$.bold, $.mediumFontSize]">Date</p>
<p :style="$.paraStyle">{{doc.date}}</p>
</div>
<div class="col-4 text-right">
<p :style="[$.bold, $.mediumFontSize]">Invoice Total</p>
<h2 :style="$.fontColor">{{ frappe.format(doc.grandTotal, 'Currency') }}</h2>
</div>
</div>
<div class="row pl-5 pr-5 mt-3">
<div class="col-12">
<table class="table table-borderless p-0">
<thead>
<tr :style="[$.showBorderTop, $.fontColor]">
<th class="text-left pl-0" style="width: 10%">{{ _("NO") }}</th>
<th class="text-left" style="width: 60%">{{ _("ITEM") }}</th>
<th class="text-left pl-0" style="width: 20%">{{ _("RATE") }}</th>
<th class="text-left" style="width: 15%">{{ _("QTY") }}</th>
<th class="text-right pr-1" style="width: 20%">{{ _("AMOUNT") }}</th>
</tr>
</thead>
<tbody>
<tr :style="$.showBorderBottom" v-for="row in doc.items" :key="row.idx">
<td class="text-left pl-1">{{ row.idx + 1 }}</td>
<td class="text-left">{{ row.item }}</td>
<td class="text-left pl-0">{{ frappe.format(row.rate, 'Currency') }}</td>
<td class="text-left">{{ row.quantity }}</td>
<td class="text-right pr-1">{{ frappe.format(row.amount, 'Currency') }}</td>
</tr>
<tr><td colspan="5" style="padding: 4%"></td></tr>
<tr>
<td colspan="2" class="text-left pl-1"></td>
<td colspan="2" :style="[$.bold, $.fontColor]" class="text-left pl-0">SUBTOTAL</td>
<td :style="$.bold" class="text-right pr-1">{{ frappe.format(doc.netTotal, 'Currency') }}</td>
</tr>
<tr v-for="tax in doc.taxes" :key="tax.name">
<td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td>
<td colspan="2" :style="[$.bold, $.fontColor]" class="text-left pl-0">{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td>
<td :style="$.bold" class="text-right pr-1">{{ frappe.format(tax.amount, 'Currency') }}</td>
</tr>
<tr>
<td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td>
<td colspan="2" :style="[$.bold, $.fontColor, $.mediumFontSize]" class="text-left pl-0">TOTAL</td>
<td :style="[$.bold, $.mediumFontSize]" class="text-right pr-1">{{ frappe.format(doc.grandTotal, 'Currency') }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row pl-5 pr-5 mt-5">
<div class="col-12">
<table class="table">
<tbody>
<tr :style="[$.bold, $.showNoticeBorderBottom]" ><td :style="$.hideBorderTop" class="pl-0">NOTICE</td></tr>
<tr><td class="pl-0">{{ doc.terms }}</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import Styles from './InvoiceStyles';
import CompanyAddress from './CompanyAddress';
import CustomerAddress from './CustomerAddress';
export default {
name: 'InvoicePrint',
props: ['doc', 'themeColor', 'font'],
components: {
CompanyAddress,
CustomerAddress
},
data() {
return {
$: Styles,
}
},
watch: {
themeColor: function() {
this.setTheme();
},
font: function() {
this.setTheme();
}
},
async created() {
this.$ = Styles;
this.setTheme();
},
methods: {
setTheme() {
this.$.fontColor.color = this.themeColor;
this.$.headerColor.backgroundColor = this.themeColor;
this.$.showBorderBottom.borderBottom = '0.1rem solid #e0e0d1';
this.$.showNoticeBorderBottom.borderBottom = `0.22rem solid ${this.themeColor}`;
this.$.showBorderTop.borderTop = `0.22rem solid ${this.themeColor}`;
this.$.font.fontFamily = this.font;
}
}
}
</script>

View File

@ -0,0 +1,113 @@
<template>
<div :style="[$.regularFontSize, $.font]" style="font-family: sans-serif;">
<div class="row no-gutters mt-5">
<div class="col-6" :style="$.bgColor"></div>
<div class="col-4 text-center" style="vertical-align: middle">
<h1>INVOICE</h1>
</div>
<div class="col-2" :style="$.bgColor"></div>
</div>
<div class="row no-gutters mt-5">
<div class="col-6 text-left pl-5">
<company-address />
</div>
<div class="col-6 pr-5 text-right">
<p :style="[$.bold, $.paraStyle, $.mediumFontSize]">{{ doc.name }}</p>
<p :style="$.paraStyle">{{ frappe.format(doc.date, 'Date') }}</p>
</div>
</div>
<div class="row no-gutters mt-5">
<div class="col-6 text-left pl-5">
<customer-address :customer="doc.customer" />
</div>
<div class="col-6"></div>
</div>
<div class="row mt-5 no-gutters">
<div class="col-12">
<table class="table">
<tbody>
<tr>
<td :style="[$.bold, $.showBorderRight, $.tablePadding]" style="width: 15" class="pl-5">{{ _("NO") }}</td>
<td :style="[$.bold, $.showBorderRight, $.tablePadding]" style="width: 40%">{{ _("ITEM") }}</td>
<td class="text-left" :style="[$.bold, $.showBorderRight, $.tablePadding]" style="width: 20%">{{ _("RATE") }}</td>
<td :style="[$.bold, $.showBorderRight, $.tablePadding]" style="width: 10%">{{ _("QTY") }}</td>
<td class="text-right pr-5" :style="[$.bold, $.tablePadding]" style="width: 20%">{{ _("AMOUNT") }}</td>
</tr>
<tr v-for="row in doc.items" :key="row.idx">
<td :style="$.tablePadding" class="pl-5 pr-5">{{ row.idx + 1 }}</td>
<td :style="$.tablePadding">{{ row.item }}</td>
<td :style="$.tablePadding" class="text-left">{{ frappe.format(row.rate, 'Currency') }}</td>
<td :style="$.tablePadding">{{ row.quantity }}</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ frappe.format(row.amount, 'Currency') }}</td>
</tr>
<tr><td colspan="5" style="padding: 4%"></td></tr>
<tr>
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<td :style="$.tablePadding" colspan="2">SUBTOTAL</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ frappe.format(doc.netTotal, 'Currency') }}</td>
</tr>
<tr v-for="tax in doc.taxes" :key="tax.name">
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<td :style="$.tablePadding" med colspan="2">{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ frappe.format(tax.amount, 'Currency') }}</td>
</tr>
<tr>
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<td :style="[$.bold, $.tablePadding, $.mediumFontSize]" colspan="2">TOTAL</td>
<td :style="[$.bold, $.tablePadding, $.mediumFontSize]" class="text-right pr-5">{{ frappe.format(doc.grandTotal, 'Currency') }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row mt-5">
<div :style="$.regularFontSize" class="col-12">
<table class="table">
<tbody>
<tr :style="[$.bold, $.showBorderBottom]" >
<td :style="$.hideBorderTop" class="pl-5">NOTICE</td>
</tr>
<tr><td class="pl-5">{{ doc.terms }}</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import Styles from './InvoiceStyles';
import CompanyAddress from './CompanyAddress';
import CustomerAddress from './CustomerAddress';
export default {
name: 'InvoicePrint',
props: ['doc', 'themeColor', 'font'],
components: {
CompanyAddress,
CustomerAddress
},
data() {
return {
$: Styles,
}
},
watch: {
themeColor: function() {
this.setTheme();
},
font: function() {
this.setTheme();
}
},
async created() {
this.$ = Styles;
this.setTheme();
},
methods: {
setTheme() {
this.$.bgColor.backgroundColor = this.themeColor;
this.$.showBorderBottom.borderBottom = `0.22rem solid ${this.themeColor}`;
this.$.font.fontFamily = this.font;
}
}
}
</script>

View File

@ -5,8 +5,7 @@ module.exports = {
isChild: 1, isChild: 1,
keywordFields: [], keywordFields: [],
layout: 'ratio', layout: 'ratio',
fields: [ fields: [{
{
fieldname: 'item', fieldname: 'item',
label: 'Item', label: 'Item',
fieldtype: 'Link', fieldtype: 'Link',

View File

@ -1,18 +1,42 @@
module.exports = { module.exports = {
"name": "InvoiceSettings", name: "InvoiceSettings",
"label": "Invoice Settings", label: "Invoice Settings",
"doctype": "DocType", doctype: "DocType",
"isSingle": 1, isSingle: 1,
"isChild": 0, isChild: 0,
"keywordFields": [], keywordFields: [],
"fields": [ fields: [
{ {
"fieldname": "numberSeries", fieldname: "numberSeries",
"label": "Number Series", label: "Number Series",
"fieldtype": "Link", fieldtype: "Link",
"target": "NumberSeries", target: "NumberSeries",
"required": 1, required: 1,
"default": "INV" 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

@ -6,8 +6,7 @@ module.exports = {
'name', 'name',
'description' 'description'
], ],
fields: [ fields: [{
{
fieldname: 'name', fieldname: 'name',
label: 'Item Name', label: 'Item Name',
fieldtype: 'Data', fieldtype: 'Data',
@ -59,25 +58,31 @@ module.exports = {
layout: [ layout: [
// section 1 // section 1
{ {
columns: [ columns: [{
{ fields: ['name', 'unit'] }, fields: ['name', 'unit']
{ fields: ['rate'] } },
{
fields: ['rate']
}
] ]
}, },
// section 2 // section 2
{ {
columns: [ columns: [{
{ fields: ['description'] } fields: ['description']
] }]
}, },
// section 3 // section 3
{ {
title: 'Accounting', title: 'Accounting',
columns: [ columns: [{
{ fields: ['incomeAccount', 'expenseAccount'] }, fields: ['incomeAccount', 'expenseAccount']
{ fields: ['tax'] } },
{
fields: ['tax']
}
] ]
} }
] ]

View File

@ -6,24 +6,29 @@ module.exports = {
"keywordFields": [ "keywordFields": [
"name" "name"
], ],
"fields": [ "fields": [{
{
"fieldname": "name", "fieldname": "name",
"label": "Name", "label": "Name",
"fieldtype": "Data", "fieldtype": "Data",
"required": 1 "required": 1
}, },
{ {
fieldname: 'defaultAccount', fieldname: "address",
label: 'Default Account', label: "Address",
fieldtype: 'Link', fieldtype: "Link",
target: 'Account', target: "Address"
getFilters: (query, control) => { },
return { {
isGroup: 0, fieldname: 'defaultAccount',
accountType: 'Receivable' label: 'Default Account',
}; fieldtype: 'Link',
} target: 'Account',
getFilters: (query, control) => {
return {
isGroup: 0,
accountType: 'Receivable'
};
}
}, },
{ {
"fieldname": "customer", "fieldname": "customer",
@ -37,15 +42,13 @@ module.exports = {
} }
], ],
links: [ links: [{
{ label: 'Invoices',
label: 'Invoices', condition: (form) => form.doc.customer,
condition: (form) => form.doc.customer, action: form => {
action: form => { form.$router.push({
form.$router.push({
path: `/report/sales-register?&customer=${form.doc.name}` path: `/report/sales-register?&customer=${form.doc.name}`
}); });
}
} }
] }]
} }

View File

@ -0,0 +1,12 @@
const party = require('./Party')
party.fields.splice(3, 0, { //insert at 3rd position
fieldname: 'gstin',
label: 'GSTIN No.',
fieldtype: 'Data',
hidden: 0
})
party.fields.join()
const newParty = party
module.exports = newParty

View File

@ -8,11 +8,11 @@ module.exports = {
isSubmittable: 1, isSubmittable: 1,
keywordFields: [], keywordFields: [],
settings: "PaymentSettings", settings: "PaymentSettings",
fields: [ fields: [{
{
"fieldname": "date", "fieldname": "date",
"label": "Date", "label": "Posting Date",
"fieldtype": "Date" "fieldtype": "Date",
// default: (new Date()).toISOString()
}, },
{ {
fieldname: "party", fieldname: "party",
@ -35,6 +35,23 @@ module.exports = {
target: "Account", target: "Account",
required: 1 required: 1
}, },
{
fieldname: "referenceId",
label: "Ref. / Cheque No.",
fieldtype: "Data",
default: "ABC",
required: 1 // TODO: UNIQUE
},
{
fieldname: "referenceDate",
label: "Ref. Date",
fieldtype: "Date",
},
{
fieldname: "clearanceDate",
label: "Clearance Date",
fieldtype: "Date",
},
{ {
fieldname: "amount", fieldname: "amount",
label: "Amount", label: "Amount",
@ -57,26 +74,33 @@ module.exports = {
} }
], ],
layout: [ layout: [{
{ columns: [{
columns: [ fields: ['date', 'party']
{ fields: ['date', 'party'] }, },
{ fields: ['account', 'paymentAccount'] }, {
fields: ['account', 'paymentAccount']
},
] ]
}, },
{ {
columns: [ columns: [{
{ fields: ['referenceId']
fields: ['for'] }, {
} fields: ['referenceDate']
] },{
fields: ['clearanceDate']
}]
}, },
{ {
columns: [ columns: [{
{ fields: ['for']
fields: ['amount', 'writeoff'] }]
} },
] {
columns: [{
fields: ['amount', 'writeoff']
}]
} }
], ],

View File

@ -1,13 +1,30 @@
import { _ } from 'frappejs/utils'; import { _ } from 'frappejs/utils';
import indicators from 'frappejs/ui/constants/indicators';
export default { export default {
doctype: 'Payment', doctype: 'Payment',
title: _('Payment'), title: _('Payment'),
columns: [ columns: [
'party', 'party',
{
label: 'Payment',
getValue(doc) {
if (doc.submitted === 1 && doc.clearanceDate !== null) {
return 'Reconciled';
}
return 'Not Reconciled';
},
getIndicator(doc) {
if (doc.submitted === 1 && doc.clearanceDate !== null) {
return indicators.GREEN;
}
return indicators.ORANGE;
}
},
'account', 'account',
'amount', 'amount',
'date', 'date',
'clearanceDate',
'name' 'name'
] ]
} }

View File

@ -0,0 +1,102 @@
const countryList = require('../../../fixtures/countryInfo.json');
module.exports = {
name: "SetupWizard",
label: "Setup Wizard",
naming: "name",
isSingle: 1,
isChild: 0,
isSubmittable: 0,
settings: null,
keywordFields: [],
fields: [{
fieldname: 'country',
label: 'Country',
fieldtype: 'Autocomplete',
required: 1,
getList: () => Object.keys(countryList).sort()
},
{
fieldname: 'fullname',
label: 'Name',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'email',
label: 'Email',
fieldtype: 'Data',
required: 1,
inputType: 'email'
},
{
fieldname: 'companyName',
label: 'Company Name',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'bankName',
label: 'Bank Name',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'fiscalYearStart',
label: 'Fiscal Year Start Date',
fieldtype: 'Date',
formula: (doc) => {
let date = countryList[doc.country]["fiscal_year_start"].split("-");
var currentYear = (new Date).getFullYear();
let currentMonth = date[0] - 1 ;
let currentDate = date[1];
return new Date(currentYear,currentMonth,currentDate).toISOString().substr(0, 10);;
},
required: 1
},
{
fieldname: 'fiscalYearEnd',
label: 'Fiscal Year End Date',
fieldtype: 'Date',
formula: (doc) => {
let date = countryList[doc.country]["fiscal_year_end"].split("-");
var currentYear = (new Date).getFullYear() + 1 ;
let currentMonth = date[0] - 1 ;
let currentDate = date[1];
return new Date(currentYear,currentMonth,currentDate).toISOString().substr(0, 10);;
},
required: 1
}
],
layout: {
paginated: true,
sections: [{
title: 'Select Country',
columns: [{
fields: ['country']
}]
},
{
title: 'Add a Profile',
columns: [{
fields: ['fullname', 'email']
}]
},
{
title: 'Add your Company',
columns: [{
fields: ['companyName', 'bankName', 'fiscalYearStart', 'fiscalYearEnd']
}]
}
].filter(Boolean)
}
}

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
"name": "Tax", "name": "Tax",
"label": "Tax",
"doctype": "DocType", "doctype": "DocType",
"isSingle": 0, "isSingle": 0,
"isChild": 0, "isChild": 0,

View File

@ -0,0 +1,10 @@
import { _ } from 'frappejs/utils';
import indicators from 'frappejs/ui/constants/indicators';
export default {
doctype: 'Tax',
title: _('Tax'),
columns: [
'name'
]
}

View File

@ -52,7 +52,7 @@ async function getCountryCOA(){
const conCode = countries[doc.country].code; const conCode = countries[doc.country].code;
const countryCOA = path.resolve(path.join('./fixtures/verified/', conCode + '.json')); const countryCOA = path.resolve(path.join('./fixtures/verified/', conCode + '.json'));
if(fs.existsSync(countryCOA)){ if(fs.existsSync(countryCOA)){
const jsonText = fs.readFileSync(countryCOA, 'utf-8'); const jsonText = fs.readFileSync(countryCOA, 'utf-8');
const json = JSON.parse(jsonText); const json = JSON.parse(jsonText);

View File

@ -1,9 +1,11 @@
module.exports = { module.exports = {
models: { models: {
SetupWizard: require('./doctype/SetupWizard/SetupWizard'),
Account: require('./doctype/Account/Account.js'), Account: require('./doctype/Account/Account.js'),
AccountingSettings: require('./doctype/AccountingSettings/AccountingSettings'), AccountingSettings: require('./doctype/AccountingSettings/AccountingSettings'),
CompanySettings: require('./doctype/CompanySettings/CompanySettings'),
AccountingLedgerEntry: require('./doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'), AccountingLedgerEntry: require('./doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'),
Party: require('./doctype/Party/Party.js'), // Party: require('./doctype/Party/Party.js'),
Payment: require('./doctype/Payment/Payment.js'), Payment: require('./doctype/Payment/Payment.js'),
PaymentFor: require('./doctype/PaymentFor/PaymentFor.js'), PaymentFor: require('./doctype/PaymentFor/PaymentFor.js'),
@ -53,5 +55,8 @@ module.exports = {
Event: require('./doctype/Event/Event'), Event: require('./doctype/Event/Event'),
EventSchedule: require('./doctype/EventSchedule/EventSchedule'), EventSchedule: require('./doctype/EventSchedule/EventSchedule'),
EventSettings: require('./doctype/EventSettings/EventSettings'), EventSettings: require('./doctype/EventSettings/EventSettings'),
Email: require('./doctype/Email/Email'),
EmailAccount: require('./doctype/EmailAccount/EmailAccount'),
} }
} }

271
package-lock.json generated
View File

@ -15,9 +15,9 @@
"integrity": "sha512-XtGk+IF57pr852UK1AhQJXqmm1WmSgS5uISL+LPs0z/iAxXouMvdlLJrHPeukP6gd7yR2rDTMSMkHNODgwIq7A==" "integrity": "sha512-XtGk+IF57pr852UK1AhQJXqmm1WmSgS5uISL+LPs0z/iAxXouMvdlLJrHPeukP6gd7yR2rDTMSMkHNODgwIq7A=="
}, },
"@types/node": { "@types/node": {
"version": "8.10.36", "version": "8.10.38",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.36.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.38.tgz",
"integrity": "sha512-SL6KhfM7PTqiFmbCW3eVNwVBZ+88Mrzbuvn9olPsfv43mbiWaFY+nRcz/TGGku0/lc2FepdMbImdMY1JrQ+zbw==" "integrity": "sha512-EibsnbJerd0hBFaDjJStFrVbVBAtOy4dgL8zZFw0uOvPqzBAX59Ci8cgjg3+RgJIWhsB5A4c+pi+D4P9tQQh/A=="
}, },
"@vue/component-compiler-utils": { "@vue/component-compiler-utils": {
"version": "2.2.0", "version": "2.2.0",
@ -1867,6 +1867,11 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"clamp": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz",
"integrity": "sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ="
},
"class-utils": { "class-utils": {
"version": "0.3.6", "version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
@ -2824,12 +2829,12 @@
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
}, },
"electron": { "electron": {
"version": "2.0.12", "version": "3.0.12",
"resolved": "https://registry.npmjs.org/electron/-/electron-2.0.12.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-3.0.12.tgz",
"integrity": "sha512-mw8hoM/GPtFPP8FGiJcVNe8Rx63YJ7O8bf7McQj21HAvrXGAwReGFrpIe5xN6ec10fDXNSNyfzRucjFXtOtLcg==", "integrity": "sha512-stvGbqYzWv5qHHtjZZgA7gET3NPGLuxs68IHTrJqsqujQfXGkhMOh8tstpXl86kBdRpzZn7GaDlTWcgeFSmsPw==",
"requires": { "requires": {
"@types/node": "^8.0.24", "@types/node": "^8.0.24",
"electron-download": "^3.0.1", "electron-download": "^4.1.0",
"extract-zip": "^1.0.3" "extract-zip": "^1.0.3"
} }
}, },
@ -2902,33 +2907,38 @@
} }
}, },
"electron-download": { "electron-download": {
"version": "3.3.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-3.3.0.tgz", "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz",
"integrity": "sha1-LP1U1pZsAZxNSa1l++Zcyc3vaMg=", "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==",
"requires": { "requires": {
"debug": "^2.2.0", "debug": "^3.0.0",
"fs-extra": "^0.30.0", "env-paths": "^1.0.0",
"home-path": "^1.0.1", "fs-extra": "^4.0.1",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"nugget": "^2.0.0", "nugget": "^2.0.1",
"path-exists": "^2.1.0", "path-exists": "^3.0.0",
"rc": "^1.1.2", "rc": "^1.2.1",
"semver": "^5.3.0", "semver": "^5.4.1",
"sumchecker": "^1.2.0" "sumchecker": "^2.0.2"
}, },
"dependencies": { "dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"requires": {
"ms": "^2.1.1"
}
},
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}, },
"path-exists": { "ms": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
"requires": {
"pinkie-promise": "^2.0.0"
}
} }
} }
}, },
@ -3163,6 +3173,11 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
}, },
"env-paths": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz",
"integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA="
},
"errno": { "errno": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
@ -3539,6 +3554,11 @@
"schema-utils": "^0.4.5" "schema-utils": "^0.4.5"
} }
}, },
"file-saver": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.0.tgz",
"integrity": "sha512-cYM1ic5DAkg25pHKgi5f10ziAM7RJU37gaH1XQlyNDrtUnzhC/dfoV9zf2OmF0RMKi42jG5B0JWBnPQqyj/G6g=="
},
"fill-range": { "fill-range": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@ -3720,13 +3740,13 @@
"mysql": "^2.15.0", "mysql": "^2.15.0",
"node-fetch": "^1.7.3", "node-fetch": "^1.7.3",
"node-sass": "^4.7.2", "node-sass": "^4.7.2",
"nodemailer": "^4.7.0",
"nunjucks": "^3.1.0", "nunjucks": "^3.1.0",
"octicons": "^7.2.0", "octicons": "^7.2.0",
"passport": "^0.4.0", "passport": "^0.4.0",
"passport-jwt": "^4.0.0", "passport-jwt": "^4.0.0",
"puppeteer": "^1.2.0", "puppeteer": "^1.2.0",
"sass-loader": "^7.0.3", "sass-loader": "^7.0.3",
"sharp": "^0.20.8",
"showdown": "^1.8.6", "showdown": "^1.8.6",
"socket.io": "^2.0.4", "socket.io": "^2.0.4",
"sqlite3": "^4.0.2", "sqlite3": "^4.0.2",
@ -3738,6 +3758,78 @@
"webpack": "^4.16.1", "webpack": "^4.16.1",
"webpack-dev-server": "^3.1.4", "webpack-dev-server": "^3.1.4",
"webpack-hot-middleware": "^2.22.3" "webpack-hot-middleware": "^2.22.3"
},
"dependencies": {
"electron": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/electron/-/electron-2.0.12.tgz",
"integrity": "sha512-mw8hoM/GPtFPP8FGiJcVNe8Rx63YJ7O8bf7McQj21HAvrXGAwReGFrpIe5xN6ec10fDXNSNyfzRucjFXtOtLcg==",
"requires": {
"@types/node": "^8.0.24",
"electron-download": "^3.0.1",
"extract-zip": "^1.0.3"
},
"dependencies": {
"electron-download": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-3.3.0.tgz",
"integrity": "sha1-LP1U1pZsAZxNSa1l++Zcyc3vaMg=",
"requires": {
"debug": "^2.2.0",
"fs-extra": "^0.30.0",
"home-path": "^1.0.1",
"minimist": "^1.2.0",
"nugget": "^2.0.0",
"path-exists": "^2.1.0",
"rc": "^1.1.2",
"semver": "^5.3.0",
"sumchecker": "^1.2.0"
}
}
}
},
"fs-extra": {
"version": "0.30.0",
"resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^2.1.0",
"klaw": "^1.0.0",
"path-is-absolute": "^1.0.0",
"rimraf": "^2.2.8"
}
},
"jsonfile": {
"version": "2.4.0",
"resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"requires": {
"graceful-fs": "^4.1.6"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"path-exists": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
"requires": {
"pinkie-promise": "^2.0.0"
}
},
"sumchecker": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-1.3.1.tgz",
"integrity": "sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0=",
"requires": {
"debug": "^2.2.0",
"es6-promise": "^4.0.5"
}
}
} }
}, },
"fresh": { "fresh": {
@ -3804,15 +3896,13 @@
"integrity": "sha512-2QY5eeqVv4m2PfyMiEuy9adxNP+ajf+8AR05cEi+OAzPcOj90hvFImeZhTmKLBgSd9EvG33jsD7ZRxsx9dThkQ==" "integrity": "sha512-2QY5eeqVv4m2PfyMiEuy9adxNP+ajf+8AR05cEi+OAzPcOj90hvFImeZhTmKLBgSd9EvG33jsD7ZRxsx9dThkQ=="
}, },
"fs-extra": { "fs-extra": {
"version": "0.30.0", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
"requires": { "requires": {
"graceful-fs": "^4.1.2", "graceful-fs": "^4.1.2",
"jsonfile": "^2.1.0", "jsonfile": "^4.0.0",
"klaw": "^1.0.0", "universalify": "^0.1.0"
"path-is-absolute": "^1.0.0",
"rimraf": "^2.2.8"
} }
}, },
"fs-extra-p": { "fs-extra-p": {
@ -5372,9 +5462,9 @@
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE="
}, },
"jsonfile": { "jsonfile": {
"version": "2.4.0", "version": "4.0.0",
"resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": { "requires": {
"graceful-fs": "^4.1.6" "graceful-fs": "^4.1.6"
} }
@ -5599,6 +5689,11 @@
"resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz",
"integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=" "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ="
}, },
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"loglevel": { "loglevel": {
"version": "1.6.1", "version": "1.6.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
@ -5679,6 +5774,11 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"material-colors": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
"md5.js": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -6157,23 +6257,23 @@
} }
}, },
"tar": { "tar": {
"version": "4.4.6", "version": "4.4.8",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.6.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz",
"integrity": "sha512-tMkTnh9EdzxyfW+6GK6fCahagXsnYk6kE6S9Gr9pjVdys769+laCTbodXDhPAjzVtEBazRgP0gYqOjnk9dQzLg==", "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
"requires": { "requires": {
"chownr": "^1.0.1", "chownr": "^1.1.1",
"fs-minipass": "^1.2.5", "fs-minipass": "^1.2.5",
"minipass": "^2.3.3", "minipass": "^2.3.4",
"minizlib": "^1.1.0", "minizlib": "^1.1.1",
"mkdirp": "^0.5.0", "mkdirp": "^0.5.0",
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"yallist": { "yallist": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
"integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
} }
} }
}, },
@ -6214,6 +6314,11 @@
} }
} }
}, },
"nodemailer": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.7.0.tgz",
"integrity": "sha512-IludxDypFpYw4xpzKdMAozBSkzKHmNBvGanUREjJItgJ2NYcK/s8+PggVhj7c2yGFQykKsnnmv1+Aqo0ZfjHmw=="
},
"noop-logger": { "noop-logger": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
@ -7923,49 +8028,6 @@
} }
} }
}, },
"sharp": {
"version": "0.20.8",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.20.8.tgz",
"integrity": "sha512-A8NaPGWRDKpmHTi8sl2xzozYXhTQWBb/GaJ8ZPU7L/vKW8wVvd4Yq+isJ0c7p9sX5gnjPQcM3eOfHuvvnZ2fOQ==",
"requires": {
"color": "^3.0.0",
"detect-libc": "^1.0.3",
"fs-copy-file-sync": "^1.1.1",
"nan": "^2.11.0",
"npmlog": "^4.1.2",
"prebuild-install": "^4.0.0",
"semver": "^5.5.1",
"simple-get": "^2.8.1",
"tar": "^4.4.6",
"tunnel-agent": "^0.6.0"
},
"dependencies": {
"nan": {
"version": "2.11.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz",
"integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA=="
},
"tar": {
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.6.tgz",
"integrity": "sha512-tMkTnh9EdzxyfW+6GK6fCahagXsnYk6kE6S9Gr9pjVdys769+laCTbodXDhPAjzVtEBazRgP0gYqOjnk9dQzLg==",
"requires": {
"chownr": "^1.0.1",
"fs-minipass": "^1.2.5",
"minipass": "^2.3.3",
"minizlib": "^1.1.0",
"mkdirp": "^0.5.0",
"safe-buffer": "^5.1.2",
"yallist": "^3.0.2"
}
},
"yallist": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
"integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k="
}
}
},
"shebang-command": { "shebang-command": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@ -8459,9 +8521,9 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
}, },
"sqlite3": { "sqlite3": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.2.tgz", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.4.tgz",
"integrity": "sha512-51ferIRwYOhzUEtogqOa/y9supADlAht98bF/gbIi6WkzRJX6Yioldxbzj1MV4yV+LgdKD/kkHwFTeFXOG4htA==", "integrity": "sha512-CO8vZMyUXBPC+E3iXOCc7Tz2pAdq5BWfLcQmOokCOZW5S5sZ/paijiPOCdvzpdP83RroWHYa5xYlVqCxSqpnQg==",
"requires": { "requires": {
"nan": "~2.10.0", "nan": "~2.10.0",
"node-pre-gyp": "^0.10.3", "node-pre-gyp": "^0.10.3",
@ -8632,12 +8694,11 @@
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
}, },
"sumchecker": { "sumchecker": {
"version": "1.3.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-1.3.1.tgz", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz",
"integrity": "sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0=", "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=",
"requires": { "requires": {
"debug": "^2.2.0", "debug": "^2.2.0"
"es6-promise": "^4.0.5"
} }
}, },
"supports-color": { "supports-color": {
@ -8747,6 +8808,11 @@
"setimmediate": "^1.0.4" "setimmediate": "^1.0.4"
} }
}, },
"tinycolor2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
"integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g="
},
"to-array": { "to-array": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
@ -9248,6 +9314,17 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.5.17.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.17.tgz",
"integrity": "sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ==" "integrity": "sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ=="
}, },
"vue-color": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/vue-color/-/vue-color-2.7.0.tgz",
"integrity": "sha512-fak9oPRL3BsYtakTGmWIS2yNRppRYNlMgGGq78CMH34ipU8fLgi/bT9JiSPcscpdTNLGracuOFuZ8OFeml+SQQ==",
"requires": {
"clamp": "^1.0.1",
"lodash.throttle": "^4.0.0",
"material-colors": "^1.0.0",
"tinycolor2": "^1.1.2"
}
},
"vue-flatpickr-component": { "vue-flatpickr-component": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/vue-flatpickr-component/-/vue-flatpickr-component-7.0.6.tgz", "resolved": "https://registry.npmjs.org/vue-flatpickr-component/-/vue-flatpickr-component-7.0.6.tgz",
@ -9799,4 +9876,4 @@
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
} }
} }
} }

View File

@ -20,8 +20,7 @@
"dist/electron" "dist/electron"
], ],
"dmg": { "dmg": {
"contents": [ "contents": [{
{
"x": 410, "x": 410,
"y": 150, "y": 150,
"type": "link", "type": "link",
@ -52,7 +51,7 @@
"scripts": { "scripts": {
"test": "mocha tests", "test": "mocha tests",
"start": "frappe start", "start": "frappe start",
"electron": "cross-env ELECTRON=true frappe start electron", "electron": "ELECTRON=true frappe start electron",
"pack-electron": "cross-env NODE_ENV=production ELECTRON=true frappe build electron", "pack-electron": "cross-env NODE_ENV=production ELECTRON=true frappe build electron",
"build-electron": "npm run pack-electron && electron-builder", "build-electron": "npm run pack-electron && electron-builder",
"postinstall": "electron-builder install-app-deps" "postinstall": "electron-builder install-app-deps"
@ -60,7 +59,9 @@
"dependencies": { "dependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"frappejs": "github:frappe/frappejs", "frappejs": "github:frappe/frappejs",
"nodemailer": "^4.7.0",
"popper.js": "^1.14.4", "popper.js": "^1.14.4",
"vue-color": "^2.7.0",
"vue-toasted": "^1.1.25" "vue-toasted": "^1.1.25"
} }
} }

View File

@ -29,7 +29,6 @@ async function getReceivablePayable({ reportType = 'Receivable', date }) {
let data = []; let data = [];
for (let entry of validEntries) { for (let entry of validEntries) {
// console.log(entry);
const { outStandingAmount, creditNoteAmount } = getOutstandingAmount(entry); const { outStandingAmount, creditNoteAmount } = getOutstandingAmount(entry);

View File

@ -0,0 +1,42 @@
const frappe = require('frappejs');
class BankReconciliation {
async run(params) {
const filters = {};
if (params.paymentAccount) filters.paymentAccount = params.paymentAccount;
if (params.party) filters.party = params.party;
// if (params.referenceType) filters.referenceType = params.referenceType;
// if (params.referenceName) filters.referenceName = params.referenceName;
if (params.toDate || params.fromDate) {
filters.date = [];
if (params.toDate) filters.date.push('<=', params.toDate);
if (params.fromDate) filters.date.push('>=', params.fromDate);
}
let data = await frappe.db.getAll({
doctype: 'Payment',
fields: ['date', 'account', 'paymentAccount', 'party', 'name', 'referenceDate','clearanceDate'],
filters: filters,
});
for (var i = 0; i < data.length; i++) {
let ledger = await frappe.db.getAll({
doctype: 'AccountingLedgerEntry',
fields: ['date', 'referenceType', 'referenceName', 'debit', 'credit'],
filters: {
referenceType: 'Payment',
account: data[i].paymentAccount,
referenceName: data[i].name
}
})
data[i].credit = ledger[0].credit;
data[i].debit = ledger[0].debit;
data[i].referenceName = ledger[0].referenceName;
data[i].referenceType = ledger[0].referenceType;
}
return data;
}
}
module.exports = BankReconciliation;

View File

@ -0,0 +1,80 @@
const ReportPage = require('frappejs/client/desk/reportpage');
const frappe = require('frappejs');
module.exports = class BankReconciliationView extends ReportPage {
constructor() {
super({
title: frappe._('Bank Reconciliation'),
filterFields: [{
fieldtype: 'Link',
target: 'Account',
label: 'Payment Account'
},
{
fieldtype: 'Link',
target: 'Party',
label: 'Party'
},
{
fieldtype: 'Date',
label: 'From Date'
},
{
fieldtype: 'Date',
label: 'To Date'
}
]
});
this.method = 'bank-reconciliation';
}
getColumns() {
return [{
label: 'Posting Date',
fieldtype: 'Date',
fieldname: 'date'
},
{
label: 'Payment Account',
fieldtype: 'Link'
},
{
label: 'Debit',
fieldtype: 'Currency'
},
{
label: 'Credit',
fieldtype: 'Currency'
},
{
label: 'Balance',
fieldtype: 'Currency'
},
{
label: 'Ref. Type',
fieldtype: 'Data',
fieldname: 'referenceType'
},
{
label: 'Ref. Name',
fieldtype: 'Data',
fieldname: 'referenceName'
},
{
label: 'Ref. Date',
fieldtype: 'Date',
fieldname: 'referenceDate'
},
{
label: 'Clearance Date',
fieldtype: 'Date',
fieldname: 'clearanceDate'
},
{
label: 'Party',
fieldtype: 'Link'
}
];
}
};

View File

@ -0,0 +1,76 @@
const title = 'Bank Reconciliation';
module.exports = {
title: title,
method: 'bank-reconciliation',
filterFields: [{
fieldtype: 'Link',
target: 'Account',
label: 'Payement Account',
fieldname: 'paymentAccount'
},
{
fieldtype: 'Link',
target: 'Party',
label: 'Party',
fieldname: 'party'
},
{
fieldtype: 'Date',
label: 'From Date',
fieldname: 'fromDate'
},
{
fieldtype: 'Date',
label: 'To Date',
fieldname: 'toDate'
}
],
getColumns() {
return [{
label: 'Posting Date',
fieldtype: 'Date',
fieldname: 'date'
},
{
label: 'Payment Account',
fieldtype: 'Link'
},
{
label: 'Debit',
fieldtype: 'Currency'
},
{
label: 'Credit',
fieldtype: 'Currency'
},
{
label: 'Balance',
fieldtype: 'Currency'
},
{
label: 'Ref. Type',
fieldtype: 'Data',
fieldname: 'referenceType'
},
{
label: 'Ref. Name',
fieldtype: 'Data',
fieldname: 'referenceName'
},
{
label: 'Ref. Date',
fieldtype: 'Date',
fieldname: 'referenceDate'
},
{
label: 'Clearance Date',
fieldtype: 'Date',
fieldname: 'clearanceDate'
},
{
label: 'Party',
fieldtype: 'Link'
}
];
}
};

View File

@ -12,7 +12,6 @@ class GeneralLedger {
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);
} }
let data = await frappe.db.getAll({ let data = await frappe.db.getAll({
doctype: 'AccountingLedgerEntry', doctype: 'AccountingLedgerEntry',
fields: ['date', 'account', 'party', 'referenceType', 'referenceName', 'debit', 'credit'], fields: ['date', 'account', 'party', 'referenceType', 'referenceName', 'debit', 'credit'],

View File

@ -5,8 +5,7 @@ module.exports = class GeneralLedgerView extends ReportPage {
constructor() { constructor() {
super({ super({
title: frappe._('General Ledger'), title: frappe._('General Ledger'),
filterFields: [ filterFields: [{
{
fieldtype: 'Select', fieldtype: 'Select',
options: ['', 'Invoice', 'Payment'], options: ['', 'Invoice', 'Payment'],
label: 'Reference Type', label: 'Reference Type',
@ -43,16 +42,42 @@ module.exports = class GeneralLedgerView extends ReportPage {
} }
getColumns() { getColumns() {
return [ return [{
{label: 'Date', fieldtype: 'Date'}, label: 'Date',
{label: 'Account', fieldtype: 'Link'}, fieldtype: 'Date'
{label: 'Debit', fieldtype: 'Currency'}, },
{label: 'Credit', fieldtype: 'Currency'}, {
{label: 'Balance', fieldtype: 'Currency'}, label: 'Account',
{label: 'Reference Type', fieldtype: 'Data'}, fieldtype: 'Link'
{label: 'Reference Name', fieldtype: 'Data'}, },
{label: 'Party', fieldtype: 'Link'}, {
{label: 'Description', fieldtype: 'Data'} label: 'Debit',
fieldtype: 'Currency'
},
{
label: 'Credit',
fieldtype: 'Currency'
},
{
label: 'Balance',
fieldtype: 'Currency'
},
{
label: 'Reference Type',
fieldtype: 'Data'
},
{
label: 'Reference Name',
fieldtype: 'Data'
},
{
label: 'Party',
fieldtype: 'Link'
},
{
label: 'Description',
fieldtype: 'Data'
}
]; ];
} }
}; };

View File

@ -2,8 +2,7 @@ const title = 'General Ledger';
module.exports = { module.exports = {
title: title, title: title,
method: 'general-ledger', method: 'general-ledger',
filterFields: [ filterFields: [{
{
fieldtype: 'Select', fieldtype: 'Select',
options: ['', 'Invoice', 'Payment'], options: ['', 'Invoice', 'Payment'],
label: 'Reference Type', label: 'Reference Type',
@ -15,22 +14,66 @@ module.exports = {
label: 'Reference Name', label: 'Reference Name',
fieldname: 'referenceName' fieldname: 'referenceName'
}, },
{ fieldtype: 'Link', target: 'Account', label: 'Account', fieldname: 'account' }, {
{ fieldtype: 'Link', target: 'Party', label: 'Party', fieldname: 'party' }, fieldtype: 'Link',
{ fieldtype: 'Date', label: 'From Date', fieldname: 'fromDate' }, target: 'Account',
{ fieldtype: 'Date', label: 'To Date', fieldname: 'toDate' } label: 'Account',
fieldname: 'account'
},
{
fieldtype: 'Link',
target: 'Party',
label: 'Party',
fieldname: 'party'
},
{
fieldtype: 'Date',
label: 'From Date',
fieldname: 'fromDate'
},
{
fieldtype: 'Date',
label: 'To Date',
fieldname: 'toDate'
}
], ],
getColumns() { getColumns() {
return [ return [{
{ label: 'Date', fieldtype: 'Date' }, label: 'Date',
{ label: 'Account', fieldtype: 'Link' }, fieldtype: 'Date'
{ label: 'Debit', fieldtype: 'Currency' }, },
{ label: 'Credit', fieldtype: 'Currency' }, {
{ label: 'Balance', fieldtype: 'Currency' }, label: 'Account',
{ label: 'Reference Type', fieldtype: 'Data' }, fieldtype: 'Link'
{ label: 'Reference Name', fieldtype: 'Data' }, },
{ label: 'Party', fieldtype: 'Link' }, {
{ label: 'Description', fieldtype: 'Data' } label: 'Debit',
fieldtype: 'Currency'
},
{
label: 'Credit',
fieldtype: 'Currency'
},
{
label: 'Balance',
fieldtype: 'Currency'
},
{
label: 'Reference Type',
fieldtype: 'Data'
},
{
label: 'Reference Name',
fieldtype: 'Data'
},
{
label: 'Party',
fieldtype: 'Link'
},
{
label: 'Description',
fieldtype: 'Data'
}
]; ];
} }
}; };

View File

@ -0,0 +1,59 @@
const frappe = require('frappejs');
class GoodsAndServiceTax {
async run(params) {
let filters = {};
if (params.toDate || params.fromDate) {
filters.date = [];
if (params.toDate) filters.date.push('<=', params.toDate);
if (params.fromDate) filters.date.push('>=', params.fromDate);
}
if (params.transferType) filters.transferType = params.transferType;
let invoiceNames = await frappe.db.getAll({
doctype: 'Invoice',
filter: filters
});
let tableData = [];
for (let invoice of invoiceNames) {
const row = await this.getRow(invoice.name)
tableData.push(row)
}
if(Object.keys(filters).length != 0){
tableData = tableData.filter((row) => {
if(filters.account) return row.account === filters.account
if(filters.transferType) return row.transferType === filters.transferType
if(filters.place) return row.place === filters.place
return true
})
}
return tableData;
}
async getRow(name) {
let row = {}
let invoiceDetails = await frappe.getDoc('Invoice', name);
let customerDetails = await frappe.getDoc('Party', invoiceDetails.customer);
let addressDetails = await frappe.getDoc('Address', customerDetails.address);
row.gstin = customerDetails.gstin
row.cusName = invoiceDetails.customer
row.invNo = invoiceDetails.name
row.invDate = invoiceDetails.date
row.place = addressDetails.state
row.rate = 0
row.transferType = 'In State';
invoiceDetails.taxes.forEach(tax => {
row.rate += tax.rate
if (tax.account === 'IGST') row.transferType = 'Out of State';
});
row.invAmt = invoiceDetails.grandTotal
row.taxAmt = invoiceDetails.netTotal
return row
}
}
module.exports = GoodsAndServiceTax;

View File

@ -0,0 +1,74 @@
const ReportPage = require('frappejs/client/desk/reportpage');
const frappe = require('frappejs');
module.exports = class GoodsAndServiceTaxView extends ReportPage {
constructor() {
super({
title: frappe._('Goods and Service Tax'),
filterFields: [
{
fieldtype: 'Data',
label: 'Transfer Type'
},
{
fieldtype: 'Data',
label: 'Place'
},
{
fieldtype: 'Date',
label: 'From Date'
},
{
fieldtype: 'Date',
label: 'To Date'
}
]
});
this.method = 'gst-taxes';
}
getColumns() {
return [{
label: 'GSTIN No.',
fieldname: 'gstin',
fieldtype: 'Data'
},
{
fieldtype: 'Data',
fieldname: 'cusName',
label: 'Customer Name'
},
{
label: 'Invoice No.',
fieldname: 'invNo',
fieldtype: 'Data'
},
{
label: 'Invoice Value',
fieldname: 'invAmt',
fieldtype: 'Data'
},
{
label: 'Invoice Date',
fieldname: 'invDate',
fieldtype: 'Date'
},
{
label: 'Place of supply',
fieldname: 'place',
fieldtype: 'Data'
},
{
label: 'Rate',
fieldname: 'rate',
fieldtype: 'Data'
},
{
label: 'Taxable Amount',
fieldname: 'taxAmt',
fieldtype: 'Data'
}
];
}
};

View File

@ -0,0 +1,69 @@
const title = 'Goods and Service Tax';
module.exports = {
title: title,
method: 'gst-taxes',
filterFields: [{
fieldtype: 'Data',
label: 'Transfer Type',
fieldname: 'transferType'
},
{
fieldtype: 'Data',
label: 'Place',
fieldname: 'place'
},
{
fieldtype: 'Date',
label: 'From Date',
fieldname: 'fromDate'
},
{
fieldtype: 'Date',
label: 'To Date',
fieldname: 'toDate'
}],
getColumns() {
return [{
label: 'GSTIN No.',
fieldname: 'gstin',
fieldtype: 'Data'
},
{
fieldtype: 'Data',
fieldname: 'cusName',
label: 'Customer Name'
},
{
label: 'Invoice No.',
fieldname: 'invNo',
fieldtype: 'Data'
},
{
label: 'Invoice Value',
fieldname: 'invAmt',
fieldtype: 'Data'
},
{
label: 'Invoice Date',
fieldname: 'invDate',
fieldtype: 'Date'
},
{
label: 'Place of supply',
fieldname: 'place',
fieldtype: 'Data'
},
{
label: 'Rate',
fieldname: 'rate',
fieldtype: 'Data'
},
{
label: 'Taxable Amount',
fieldname: 'taxAmt',
fieldtype: 'Data'
}
];
}
};

View File

@ -5,12 +5,13 @@ const BalanceSheet = require('./BalanceSheet/BalanceSheet');
const TrialBalance = require('./TrialBalance/TrialBalance'); const TrialBalance = require('./TrialBalance/TrialBalance');
const SalesRegister = require('./SalesRegister/SalesRegister'); const SalesRegister = require('./SalesRegister/SalesRegister');
const PurchaseRegister = require('./PurchaseRegister/PurchaseRegister'); const PurchaseRegister = require('./PurchaseRegister/PurchaseRegister');
const BankReconciliation = require('./BankReconciliation/BankReconciliation');
const GoodsAndServiceTax = require('./GoodsAndServiceTax/GoodsAndServiceTax');
const AccountsReceivablePayable = require('./AccountsReceivablePayable/AccountsReceivablePayable'); const AccountsReceivablePayable = require('./AccountsReceivablePayable/AccountsReceivablePayable');
// called on server side // called on server side
function registerReportMethods() { function registerReportMethods() {
const reports = [ const reports = [{
{
method: 'general-ledger', method: 'general-ledger',
class: GeneralLedger class: GeneralLedger
}, },
@ -33,7 +34,15 @@ function registerReportMethods() {
{ {
method: 'purchase-register', method: 'purchase-register',
class: PurchaseRegister class: PurchaseRegister
} },
{
method: 'bank-reconciliation',
class: BankReconciliation
},
{
method: 'gst-taxes',
class: GoodsAndServiceTax
},
]; ];
reports.forEach(report => { reports.forEach(report => {

View File

@ -2,5 +2,7 @@ module.exports = {
'general-ledger': require('./GeneralLedger/viewConfig'), 'general-ledger': require('./GeneralLedger/viewConfig'),
'sales-register': require('./SalesRegister/viewConfig'), 'sales-register': require('./SalesRegister/viewConfig'),
'profit-and-loss': require('./ProfitAndLoss/viewConfig'), 'profit-and-loss': require('./ProfitAndLoss/viewConfig'),
'trial-balance': require('./TrialBalance/viewConfig') 'trial-balance': require('./TrialBalance/viewConfig'),
'bank-reconciliation': require('./BankReconciliation/viewConfig'),
'gst-taxes': require('./GoodsAndServiceTax/viewConfig'),
} }

View File

@ -1,9 +1,15 @@
const frappe = require('frappejs'); const frappe = require('frappejs');
const registerReportMethods = require('../reports'); const registerReportMethods = require('../reports');
const sender = require('../email/sender');
module.exports = function registerServerMethods() { module.exports = function registerServerMethods() {
registerReportMethods(); registerReportMethods();
frappe.registerMethod({
method: 'send-mail',
handler: sender.sendMail
});
frappe.registerMethod({ frappe.registerMethod({
method: 'import-coa', method: 'import-coa',
async handler() { async handler() {
@ -19,7 +25,7 @@ module.exports = function registerServerMethods() {
const path = require('path'); const path = require('path');
const { getPDFForElectron } = require('frappejs/server/pdf'); const { getPDFForElectron } = require('frappejs/server/pdf');
const { getSettings } = require('../electron/settings'); const { getSettings } = require('../electron/settings');
const destination = path.resolve(getSettings().dbPath, '..') const destination = path.resolve('.')
getPDFForElectron(doctype, name, destination, html); getPDFForElectron(doctype, name, destination, html);
} }
} }

View File

@ -1,4 +1,7 @@
const { app, BrowserWindow } = require('electron'); const {
app,
BrowserWindow
} = require('electron');
const setupMenu = require('./menu'); const setupMenu = require('./menu');
let mainWindow let mainWindow
@ -9,7 +12,9 @@ if (process.env.NODE_ENV !== 'development') {
} }
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
const { getAppConfig } = require('frappejs/webpack/utils'); const {
getAppConfig
} = require('frappejs/webpack/utils');
const appConfig = getAppConfig(); const appConfig = getAppConfig();
winURL = `http://localhost:${appConfig.dev.devServerPort}`; winURL = `http://localhost:${appConfig.dev.devServerPort}`;
} else { } else {
@ -20,6 +25,7 @@ function createWindow() {
/** /**
* Initial window options * Initial window options
*/ */
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 1024, width: 1024,
height: 768, height: 768,

View File

@ -0,0 +1,113 @@
<template>
<div id="exportWizard" class="modal-body">
<div class="ml-4 col-6 text-left">
<input
id="select-cbox"
@change="toggleSelect"
class="form-check-input"
type="checkbox"
v-model="selectAllFlag"
>
<label class="form-check-label bold ml-2" for="select-cbox">{{ "Select/Clear All" }}</label>
</div>
<hr width="93%">
<div class="row ml-4 mb-4">
<div v-for="column in columns" :key="column.id" class="form-check mt-2 col-6">
<input :id="column.id" class="form-check-input" type="checkbox" v-model="column.checked">
<label class="form-check-label" :for="column.id">{{ column.content }}</label>
</div>
</div>
<div class="row footer-divider mb-3">
<div class="col-12" style="border-bottom:1px solid #e9ecef"></div>
</div>
<div class="row">
<div class="col-12 text-right">
<f-button primary @click="save">{{ 'Download CSV' }}</f-button>
</div>
</div>
</div>
</template>
<script>
import path from 'path';
const { writeFile } = require('frappejs/server/utils');
var FileSaver = require('file-saver');
export default {
props: ['title', 'rows', 'columnData'],
data() {
return {
selectAllFlag: true,
columns: this.columnData
};
},
methods: {
toggleSelect() {
this.columns = this.columns.map(column => {
return {
id: column.id,
content: column.content,
checked: this.selectAllFlag
};
});
},
close() {
this.$modal.hide();
},
checkNoneSelected(columns) {
for (let column of columns) {
if (column.checked) return false;
}
return true;
},
async save() {
if (this.checkNoneSelected(this.columns)) {
alert(
`No columns have been selected.\n` +
`Please select at least one column to perform export.`
);
} else {
let selectedColumnIds = this.columns.map(column => {
if (column.checked) return column.id;
});
let selectedColumns = this.columnData.filter(
column => selectedColumnIds.indexOf(column.id) != -1
);
await this.exportData(this.rows, selectedColumns);
this.$modal.hide();
}
},
async exportData(rows = this.rows, columns = this.columnData) {
let title = this.title;
let columnNames = columns.map(column => column.content).toString();
let columnIDs = columns.map(column => column.id);
let rowData = rows.map(row => columnIDs.map(id => row[id]).toString());
let csvDataArray = [columnNames, ...rowData];
let csvData = csvDataArray.join('\n');
let d = new Date();
let fileName = [
title.replace(/\s/g, '-'),
[d.getDate(), d.getMonth(), d.getFullYear()].join('-'),
`${d.getTime()}.csv`
].join('_');
var blob = new Blob([csvData], { type: 'text/plain;charset=utf-8' });
await FileSaver.saveAs(blob, fileName);
}
}
};
</script>
<style scoped>
.fixed-btn-width {
width: 5vw !important;
}
.bold {
font-size: 1.1rem;
font-weight: 600;
}
#select-cbox {
width: 15px;
height: 15px;
}
#exportWizard {
overflow: hidden;
}
</style>

View File

@ -0,0 +1,71 @@
<template>
<div>
<div v-if="doc" class="p-4">
<div class="row">
<div class="col-6 text-center">
<h4>Customizer</h4>
</div>
<div class="col-6 text-right">
<f-button secondary @click="$emit('closeInvoiceCustomizer')">{{ _('Close') }}</f-button>
</div>
</div>
<div class="row">
<div class="col-12 mt-4">
<form-layout
:doc="doc"
:fields="fields"
@updateDoc="saveDoc"
/>
<sketch-picker v-model="color"/>
</div>
</div>
</div>
</div>
</template>
<script>
import FormLayout from 'frappejs/ui/components/Form/FormLayout';
import { Sketch } from 'vue-color';
export default {
name: 'InvoiceCustomizer',
components: {
FormLayout,
'sketch-picker': Sketch
},
data() {
return {
doc: null,
fields: [],
color: null,
}
},
async created() {
this.doc = await frappe.getDoc('InvoiceSettings');
this.color = this.doc.themeColor;
const meta = frappe.getMeta('InvoiceSettings');
this.fields = meta.fields.filter((field) => field.fieldname !== "numberSeries");
},
methods: {
async saveDoc(updatedValue) {
let { fieldname, value } = updatedValue;
if (fieldname === 'template') {
this.$emit('changeTemplate', value);
} else if (fieldname === 'font') {
this.$emit('changeFont', value);
}
await this.doc.update();
}
},
watch: {
color: async function() {
if (this.doc) {
if (this.doc.themeColor != this.color.hex) {
this.doc.themeColor = this.color.hex;
this.$emit('changeColor', this.color.hex);
await this.doc.update();
}
}
}
}
};
</script>

View File

@ -6,7 +6,7 @@
<script> <script>
export default { export default {
props: ['title'] props: ['title']
} };
</script> </script>
<style> <style>
.page-header { .page-header {

View File

@ -4,12 +4,17 @@
<div class="company-name px-3 py-2 my-2"> <div class="company-name px-3 py-2 my-2">
<h6 class="m-0">{{ companyName }}</h6> <h6 class="m-0">{{ companyName }}</h6>
</div> </div>
<div :class="['sidebar-item px-3 py-2 ', isCurrentRoute(item.route) ? 'active' : '']" @click="routeTo(item.route)" v-for="item in items" :key="item.label"> <div
{{ item.label }} :class="['sidebar-item px-3 py-2 ', isCurrentRoute(item.route) ? 'active' : '']"
</div> @click="routeTo(item.route)"
v-for="item in items"
:key="item.label"
>{{ item.label }}</div>
</div> </div>
<div class="sidebar-item px-3 py-2 d-flex align-items-center" <div
v-if="dbFileName" @click="goToDatabaseSelector" class="sidebar-item px-3 py-2 d-flex align-items-center"
v-if="dbFileName"
@click="goToDatabaseSelector"
> >
<feather-icon class="mr-2" name="settings"></feather-icon> <feather-icon class="mr-2" name="settings"></feather-icon>
<span>{{ dbFileName }}</span> <span>{{ dbFileName }}</span>
@ -26,7 +31,7 @@ export default {
items: [ items: [
{ {
label: 'Chart of Accounts', label: 'Chart of Accounts',
route: '/tree/Account' route: '/chartOfAccounts'
}, },
{ {
label: 'Customers', label: 'Customers',
@ -36,6 +41,14 @@ export default {
label: 'Items', label: 'Items',
route: '/list/Item' route: '/list/Item'
}, },
{
label: 'Tax',
route: '/list/Tax'
},
{
label: 'Payments',
route: '/list/Payment'
},
{ {
label: 'Invoices', label: 'Invoices',
route: '/list/Invoice' route: '/list/Invoice'
@ -47,9 +60,9 @@ export default {
{ {
label: 'Settings', label: 'Settings',
route: '/settings' route: '/settings'
}, }
] ]
} };
}, },
async mounted() { async mounted() {
const accountingSettings = await frappe.getDoc('AccountingSettings'); const accountingSettings = await frappe.getDoc('AccountingSettings');
@ -71,11 +84,11 @@ export default {
window.location.reload(); window.location.reload();
} }
} }
} };
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../styles/variables.scss"; @import '../styles/variables.scss';
.page-sidebar { .page-sidebar {
height: 100vh; height: 100vh;

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Frappe Accounting</title> <title>Frappe Accounting</title>
<link href="https://fonts.googleapis.com/css?family=Montserrat|Open+Sans|Oxygen|Merriweather" rel="stylesheet">
<% if (htmlWebpackPlugin.options.nodeModules) { %> <% if (htmlWebpackPlugin.options.nodeModules) { %>
<script> <script>
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>') require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')

View File

@ -6,7 +6,10 @@ import common from 'frappejs/common';
import coreModels from 'frappejs/models'; import coreModels from 'frappejs/models';
import models from '../models'; import models from '../models';
import postStart from '../server/postStart'; import postStart from '../server/postStart';
import { getSettings, saveSettings } from '../electron/settings'; import {
getSettings,
saveSettings
} from '../electron/settings';
// vue imports // vue imports
import Vue from 'vue'; import Vue from 'vue';
@ -23,10 +26,19 @@ import Toasted from 'vue-toasted';
frappe.registerModels(coreModels); frappe.registerModels(coreModels);
frappe.registerModels(models); frappe.registerModels(models);
frappe.fetch = window.fetch.bind(); frappe.fetch = window.fetch.bind();
frappe.events.on('connect-database', async (filepath) => { frappe.events.on('connect-database', async (filepath) => {
await connectToLocalDatabase(filepath); await connectToLocalDatabase(filepath);
const accountingSettings = await frappe.getSingle('AccountingSettings');
const country = accountingSettings.country;
if (country === "India") {
frappe.models.Party = require('../models/doctype/Party/RegionalChanges.js')
} else {
frappe.models.Party = require('../models/doctype/Party/Party.js')
}
frappe.events.trigger('show-desk'); frappe.events.trigger('show-desk');
}); });
frappe.events.on('DatabaseSelector:file-selected', async (filepath) => { frappe.events.on('DatabaseSelector:file-selected', async (filepath) => {
@ -65,34 +77,93 @@ import Toasted from 'vue-toasted';
}); });
await doc.update(); await doc.update();
await frappe.call({ method: 'import-coa' }); await frappe.call({
method: 'import-coa'
});
if (country === "India") {
frappe.models.Party = require('../models/doctype/Party/RegionalChanges.js')
await frappe.db.migrate()
await generateGstTaxes();
}
frappe.events.trigger('show-desk'); frappe.events.trigger('show-desk');
}); });
async function generateGstTaxes() {
const gstPercents = [5, 12, 18, 28];
const gstTypes = ['Out of State', 'In State'];
let newTax = await frappe.getNewDoc('Tax');
for (const type of gstTypes) {
for (const percent of gstPercents) {
switch (type) {
case 'Out of State':
await newTax.set({
name: `${type}-${percent}`,
details: [{
account: "IGST",
rate: percent
}]
})
break;
case 'In State':
await newTax.set({
name: `${type}-${percent}`,
details: [{
account: "CGST",
rate: percent / 2
},
{
account: "SGST",
rate: percent / 2
}
]
})
break;
}
await newTax.insert();
}
}
await newTax.set({
name: `Exempt-0`,
details: [{
account: "Exempt",
rate: 0
}]
})
await newTax.insert();
}
async function connectToLocalDatabase(filepath) { async function connectToLocalDatabase(filepath) {
frappe.login('Administrator'); try {
frappe.db = new SQLite({ dbPath: filepath }); frappe.login('Administrator');
await frappe.db.connect(); frappe.db = new SQLite({
await frappe.db.migrate(); dbPath: filepath
frappe.getSingle('SystemSettings'); });
await postStart(); await frappe.db.connect();
await frappe.db.migrate();
frappe.getSingle('SystemSettings');
await postStart();
} catch (e) {
console.log(e);
}
} }
window.frappe = frappe; window.frappe = frappe;
Vue.config.productionTip = false; Vue.config.productionTip = false;
Vue.use(frappeVue); Vue.use(frappeVue);
Vue.use(Toasted, { Vue.use(Toasted, {
position: 'bottom-right', position: 'bottom-right',
duration : 3000 duration: 3000
}); });
/* eslint-disable no-new */ /* eslint-disable no-new */
new Vue({ new Vue({
el: '#app', el: '#app',
router, router,
components: { App }, components: {
App
},
template: '<App/>' template: '<App/>'
}); });
})() })()

View File

@ -0,0 +1,96 @@
<template>
<div class="branch">
<div class="branch-label px-3 py-2" @click.self="toggleChildren">
<div class="d-flex align-items-center" @click="toggleChildren">
<feather-icon class="mr-1" :name="iconName" v-show="iconName" />
<span>{{ label }}</span>
<div class="ml-auto d-flex align-items-center">
<feather-icon v-if="balance !== ''" style="width:15px; height:15px" name="dollar-sign"/>
<span>{{ balance }}</span>
</div>
</div>
</div>
<div :class="['branch-children', expanded ? '' : 'd-none']">
<branch v-for="child in children" :key="child.label"
:label="child.label"
:balance="child.balance"
:parentValue="child.name"
:doctype="doctype"
/>
</div>
</div>
</template>
<script>
const Branch = {
props: ['label', 'parentValue', 'doctype', 'balance'],
data() {
return {
expanded: false,
children: null
}
},
computed: {
iconName() {
if (this.children && this.children.length ==0) return 'chevron-right';
return this.expanded ? 'chevron-down' : 'chevron-right';
}
},
components: {
Branch: () => Promise.resolve(Branch)
},
mounted() {
this.settings = frappe.getMeta(this.doctype).treeSettings;
},
methods: {
async toggleChildren() {
await this.getChildren();
this.expanded = !this.expanded;
},
async getChildren() {
if (this.children) return;
this.children = [];
let filters = {
[this.settings.parentField]: this.parentValue
};
const children = await frappe.db.getAll({
doctype: this.doctype,
filters,
fields: [this.settings.parentField, 'isGroup', 'name', 'balance'],
orderBy: 'name',
order: 'asc'
});
this.children = children.map(c => {
c.label = c.name;
c.balance = c.balance
return c;
});
}
}
};
export default Branch;
</script>
<style lang="scss">
@import "../../styles/variables";
.branch {
font-size: 1rem;
}
.borderBottom {
border-bottom: 0.1rem solid #aaaaaa;
}
.branch-label {
border-radius: 4px;
cursor: pointer;
}
.branch-label:hover {
background-color: $dropdown-link-hover-bg;
}
.branch-children {
padding-left: 2.25rem;
}
</style>

View File

@ -0,0 +1,50 @@
<template>
<div class="p-3" v-if="root">
<branch :label="root.label" :balance="root.balance" :parentValue="''" :doctype="doctype" ref="root"/>
</div>
</template>
<script>
import frappe from 'frappejs';
import Branch from './Branch';
import { setTimeout } from 'timers';
export default {
components: {
Branch,
},
data() {
return {
root: null,
doctype: "Account"
}
},
async mounted() {
this.settings = frappe.getMeta(this.doctype).treeSettings;
this.root = {
label: await this.settings.getRootLabel(),
balance: 'Net Worth'
};
},
methods: {
async getChildren(parentValue) {
let filters = {
[this.settings.parentField]: parentValue
};
const children = await frappe.db.getAll({
doctype: this.doctype,
filters,
fields: [this.settings.parentField, 'isGroup', 'name', 'balance'],
orderBy: 'name',
order: 'asc'
});
return children.map(c => {
c.label = c.name;
c.balance = c.balance;
return c;
});
}
}
}
</script>

View File

@ -0,0 +1,47 @@
<template>
<div class="frappe-form">
<form-actions v-if="shouldRenderForm" :doc="doc" @send="send"/>
<div class="p-3">
<form-layout
v-if="shouldRenderForm"
:doc="doc"
:fields="meta.fields"
:layout="meta.layout"
:invalid="invalid"
/>
</div>
<not-found v-if="notFound"/>
</div>
</template>
<script>
import frappe from 'frappejs';
import Form from 'frappejs/ui/components/Form/Form';
import FormActions from './EmailSendActions';
export default {
name: 'Form',
extends: Form,
components: {
FormActions
},
methods: {
async send() {
this.doc = await frappe.getDoc(this.doctype, this.name);
var response = await frappe.call({
method: 'send-mail',
args: this.doc.getValidDict()
});
if (response) {
let emailFields = frappe.getMeta('Email').fields;
// this.doc['name'] = this.name;
this.save();
} else {
// Raise Error
console.log('Email Not Found');
}
}
}
};
</script>
<style>
</style>

View File

@ -0,0 +1,25 @@
<template>
<div
class="frappe-form-actions d-flex justify-content-between align-items-center p-3 border-bottom"
>
<h5 class="m-0">Compose Email</h5>
<h5 class="m-0"></h5>
<f-button primary v-if="isDirty" @click="$emit('send')">Send</f-button>
</div>
</template>
<script>
import frappe from 'frappejs';
export default {
props: ['doc'],
data() {
return {
isDirty: false
};
},
created() {
this.doc.on('change', () => {
this.isDirty = this.doc._dirty;
});
}
};
</script>

View File

@ -1,6 +1,5 @@
<template> <template>
<div class="bg-light"> <div class="bg-light">
<page-header :title="doctype" />
<div class="form-container col-8 bg-white mt-4 ml-auto mr-auto border p-5"> <div class="form-container col-8 bg-white mt-4 ml-auto mr-auto border p-5">
<form-actions <form-actions
v-if="shouldRenderForm" v-if="shouldRenderForm"
@ -49,7 +48,7 @@ export default {
links: [], links: [],
isFormInvalid: false, isFormInvalid: false,
notFound: false notFound: false
} };
}, },
computed: { computed: {
shouldRenderForm() { shouldRenderForm() {
@ -70,7 +69,10 @@ export default {
this.doc = null; this.doc = null;
this.doc = await frappe.getDoc(this.doctype, this.name); this.doc = await frappe.getDoc(this.doctype, this.name);
if (this.doc._notInserted && this.meta.fields.map(df => df.fieldname).includes('name')) { if (
this.doc._notInserted &&
this.meta.fields.map(df => df.fieldname).includes('name')
) {
// For a user editable name field, // For a user editable name field,
// it should be unset since it is autogenerated // it should be unset since it is autogenerated
this.doc.set('name', ''); this.doc.set('name', '');
@ -103,7 +105,6 @@ export default {
} }
this.$emit('save', this.doc); this.$emit('save', this.doc);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return; return;
@ -145,5 +146,5 @@ export default {
this.isFormInvalid = !validity; this.isFormInvalid = !validity;
} }
} }
} };
</script> </script>

View File

@ -1,13 +1,11 @@
<template> <template>
<div class="list-container"> <div class="list-container">
<list-row class="text-muted rounded-top bg-light"> <list-row class="text-muted rounded-top bg-light">
<list-cell v-for="column in columns" :key="column.label"> <list-cell v-for="column in columns" :key="column.label">{{ column.label }}</list-cell>
{{ column.label }}
</list-cell>
</list-row> </list-row>
<list-row v-for="doc in data" :key="doc.name" @click.native="openForm(doc.name)"> <list-row v-for="doc in data" :key="doc.name" @click.native="openForm(doc.name)">
<list-cell v-for="column in columns" :key="column.label" class="d-flex align-items-center"> <list-cell v-for="column in columns" :key="column.label" class="d-flex align-items-center">
<indicator v-if="column.getIndicator" :color="column.getIndicator(doc)" class="mr-2" /> <indicator v-if="column.getIndicator" :color="column.getIndicator(doc)" class="mr-2"/>
<span>{{ frappe.format(column.getValue(doc), column.fieldtype || {}) }}</span> <span>{{ frappe.format(column.getValue(doc), column.fieldtype || {}) }}</span>
</list-cell> </list-cell>
</list-row> </list-row>
@ -66,20 +64,22 @@ export default {
}); });
}, },
prepareColumns() { prepareColumns() {
this.columns = this.listConfig.columns.map(col => { this.columns = this.listConfig.columns
if (typeof col === 'string') { .map(col => {
const field = this.meta.getField(col); if (typeof col === 'string') {
if (!field) return null; const field = this.meta.getField(col);
return { if (!field) return null;
label: field.label, return {
fieldtype: field.fieldtype, label: field.label,
getValue(doc) { fieldtype: field.fieldtype,
return doc[col]; getValue(doc) {
} return doc[col];
}; }
} };
return col; }
}).filter(Boolean); return col;
})
.filter(Boolean);
} }
} }
}; };

View File

@ -2,10 +2,12 @@ import Invoice from '../../../models/doctype/Invoice/InvoiceList';
import Customer from '../../../models/doctype/Party/CustomerList'; import Customer from '../../../models/doctype/Party/CustomerList';
import Item from '../../../models/doctype/Item/ItemList'; import Item from '../../../models/doctype/Item/ItemList';
import Payment from '../../../models/doctype/Payment/PaymentList'; import Payment from '../../../models/doctype/Payment/PaymentList';
import Tax from '../../../models/doctype/Tax/TaxList';
export default { export default {
Invoice, Invoice,
Customer, Customer,
Item, Item,
Payment Payment,
Tax
} }

View File

@ -1,35 +1,63 @@
<template> <template>
<div class="bg-light"> <div class="bg-light">
<page-header :title="doctype" /> <page-header :title="doctype"/>
<div class="row no-gutters"> <div class="row no-gutters">
<div v-if="showInvoiceCustomizer" class="col-3 mt-4 mx-auto"></div>
<div class="col-8 mx-auto text-right mt-4"> <div class="col-8 mx-auto text-right mt-4">
<f-button primary @click="makePDF">{{ _('PDF') }}</f-button> <f-button primary @click="send">{{ _('Send') }}</f-button>
<f-button secondary @click="toggleInvoiceCustomizer">{{ _('Customize') }}</f-button>
<f-button secondary @click="makePDF">{{ _('PDF') }}</f-button>
</div> </div>
<div ref="printComponent" class="form-container col-8 bg-white mt-4 mx-auto border"> </div>
<component :is="printComponent" v-if="doc" :doc="doc" /> <div class="row no-gutters">
<div v-if="showInvoiceCustomizer" class="col-3 mt-4 mx-auto">
<invoice-customizer
class="border"
style="position: fixed"
@closeInvoiceCustomizer="toggleInvoiceCustomizer"
@changeColor="changeColor($event)"
@changeTemplate="changeTemplate($event)"
@changeFont="changeFont($event)"
/>
</div>
<div ref="printComponent" class="col-8 bg-white mt-4 mx-auto border shadow">
<component
:themeColor="themeColor"
:template="template"
:font="font"
:is="printComponent"
v-if="doc"
:doc="doc"
/>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import PageHeader from '@/components/PageHeader'; import PageHeader from '@/components/PageHeader';
import InvoicePrint from '@/../models/doctype/Invoice/InvoicePrint' import InvoiceCustomizer from '@/components/InvoiceCustomizer';
import InvoicePrint from '@/../models/doctype/Invoice/InvoicePrint';
import EmailSend from '../Email/EmailSend';
const printComponents = { const printComponents = {
Invoice: InvoicePrint Invoice: InvoicePrint
}; };
export default { export default {
name: 'PrintView', name: 'PrintView',
props: ['doctype', 'name'], props: ['doctype', 'name'],
components: { components: {
PageHeader PageHeader,
InvoiceCustomizer
}, },
data() { data() {
return { return {
doc: null, doc: undefined,
printComponent: null printComponent: undefined,
} themeColor: undefined,
template: undefined,
font: undefined,
showInvoiceCustomizer: false
};
}, },
async mounted() { async mounted() {
this.doc = await frappe.getDoc(this.doctype, this.name); this.doc = await frappe.getDoc(this.doctype, this.name);
@ -44,8 +72,42 @@ export default {
name: this.name, name: this.name,
html: this.$refs.printComponent.innerHTML html: this.$refs.printComponent.innerHTML
} }
}) });
},
async send() {
let doc = await frappe.getNewDoc('Email');
let emailFields = frappe.getMeta('Email').fields;
var file_path = this.name;
doc['fromEmailAddress'] = this.selectedId;
this.makePDF();
doc['filePath'] = this.name + '.pdf';
this.$modal.show({
component: EmailSend,
props: {
doctype: doc.doctype,
name: doc.name
},
modalProps: {
title: 'Send Invoice',
footerMessage: 'Invoice attached along..'
}
});
doc.on('afterInsert', data => {
this.$modal.hide();
});
},
async toggleInvoiceCustomizer() {
this.showInvoiceCustomizer = !this.showInvoiceCustomizer;
},
changeColor(color) {
this.themeColor = color;
},
changeTemplate(template) {
this.template = template;
},
changeFont(font) {
this.font = font;
} }
} }
} };
</script> </script>

111
src/pages/Report/index.vue Normal file
View File

@ -0,0 +1,111 @@
<template>
<div>
<div class="p-4">
<h4 class="pb-2">{{ reportConfig.title }}</h4>
<div class="col-12 text-right mt-4 mb-4">
<f-button primary @click="openExportWizard">{{ 'Export' }}</f-button>
</div>
<report-filters
v-if="filtersExists"
:filters="reportConfig.filterFields"
:filterDefaults="filters"
@change="getReportData"
></report-filters>
<div class="pt-2" ref="datatable" v-once></div>
</div>
<not-found v-if="!reportConfig"/>
</div>
</template>
<script>
import DataTable from 'frappe-datatable';
import frappe from 'frappejs';
import ReportFilters from 'frappejs/ui/pages/Report/ReportFilters';
import utils from 'frappejs/client/ui/utils';
import ExportWizard from '../../components/ExportWizard';
export default {
name: 'Report',
props: ['reportName', 'reportConfig', 'filters'],
computed: {
filtersExists() {
return (this.reportConfig.filterFields || []).length;
}
},
methods: {
async openExportWizard() {
this.$modal.show({
modalProps: {
title: `Export ${this.reportConfig.title}`,
noFooter: true
},
component: ExportWizard,
props: await this.getReportDetails()
});
},
async getReportDetails() {
let { title, filterFields } = this.reportConfig;
let [rows, columns] = await this.getReportData(filterFields || []);
let columnData = columns.map(column => {
return {
id: column.id,
content: column.content,
checked: true
};
});
return {
title: title,
rows: rows,
columnData: columnData
};
},
async getReportData(filters) {
let data = await frappe.call({
method: this.reportConfig.method,
args: filters
});
let rows, columns;
if (data.rows) {
rows = data.rows;
} else {
rows = data;
}
if (data.columns) {
columns = this.getColumns(data);
}
if (!rows) {
rows = [];
}
if (!columns) {
columns = this.getColumns();
}
for (let column of columns) {
column.editable = false;
}
if (this.datatable) {
this.datatable.refresh(rows, columns);
} else {
this.datatable = new DataTable(this.$refs.datatable, {
columns: columns,
data: rows
});
}
return [rows, columns];
},
getColumns(data) {
const columns = this.reportConfig.getColumns(data);
return utils.convertFieldsToDatatableColumns(columns);
}
},
components: {
ReportFilters
}
};
</script>
<style>
</style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<page-header title="Reports" /> <page-header title="Reports"/>
<div class="row"> <div class="row">
<div class="col-8 mx-auto"> <div class="col-8 mx-auto">
<clickable-card <clickable-card
@ -27,25 +27,48 @@
description="Sales transactions for a given period with invoiced amount and tax details" description="Sales transactions for a given period with invoiced amount and tax details"
@click="routeTo('sales-register')" @click="routeTo('sales-register')"
/> />
<clickable-card
class="mt-2"
title="Bank Reconciliation"
description="Bank Reconciliation statement"
@click="routeTo('bank-reconciliation',{'toDate' : (new Date()).toISOString()})"
/>
<clickable-card v-if="country === 'India'"
class="mt-2"
title="Goods and Service Tax"
description="See your goods and services tax here."
@click="routeTo('gst-taxes',{'toDate' : (new Date()).toISOString()})"
/>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import frappe from 'frappejs';
import PageHeader from '../components/PageHeader'; import PageHeader from '../components/PageHeader';
import ClickableCard from '../components/ClickableCard'; import ClickableCard from '../components/ClickableCard';
export default { export default {
name: 'Report', name: 'Report',
data() {
return {
country: '',
}
},
components: { components: {
PageHeader, PageHeader,
ClickableCard ClickableCard
}, },
async created() {
const doc = await frappe.getDoc('AccountingSettings');
this.country = doc.country;
},
methods: { methods: {
routeTo(route, filters) { routeTo(route, filters) {
const query = new URLSearchParams(filters); const query = new URLSearchParams(filters);
this.$router.push(`/report/${route}?${query}`); this.$router.push(`/report/${route}?${query}`);
} }
} }
} };
</script> </script>

View File

@ -1,11 +1,15 @@
<template> <template>
<div> <div>
<page-header title="Settings" /> <page-header title="Settings"/>
<div class="row"> <div class="row">
<div class="col-8 bg-white mt-4 mx-auto border p-5"> <div class="col-8 bg-white mt-4 mx-auto border p-5">
<setting-section doctype="AccountingSettings" /> <setting-section doctype="AccountingSettings"/>
<hr class="mt-4"> <hr class="mt-4">
<setting-section doctype="SystemSettings" /> <setting-section doctype="CompanySettings"/>
<hr class="mt-4">
<setting-section doctype="EmailAccount"/>
<hr class="mt-4">
<setting-section doctype="SystemSettings"/>
</div> </div>
</div> </div>
</div> </div>
@ -20,5 +24,5 @@ export default {
PageHeader, PageHeader,
SettingSection SettingSection
} }
} };
</script> </script>

View File

@ -1,32 +1,31 @@
<template> <template>
<div class="setup-wizard d-flex justify-content-center"> <div v-if="meta" class="setup-wizard d-flex justify-content-center">
<div class="col-4 border rounded shadow-sm p-4 mt-5"> <div class="col-4 border rounded shadow-sm p-4 mt-5">
<h4 class="text-center">Setup your account</h4> <h4 class="text-center">Setup your account</h4>
<div class="progress-indicator d-flex justify-content-center p-4"> <div class="progress-indicator d-flex justify-content-center p-4">
<indicator class="mr-1" :color="indicatorColor(index)" v-for="(section, index) in layout.sections" :key="index" /> <indicator
class="mr-1"
:color="indicatorColor(index)"
v-for="(section, index) in layout.sections"
:key="index"
/>
</div> </div>
<form-layout <form-layout :doc="doc" :fields="fields" :layout="layout" :currentSection="currentSection"/>
:doc="doc"
:fields="fields"
:layout="layout"
:currentSection="currentSection"
/>
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div> <div>
<f-button secondary v-if="currentSection > 0" <f-button secondary v-if="currentSection > 0" @click="prevSection">Prev</f-button>
@click="prevSection">
Prev
</f-button>
</div> </div>
<div> <div>
<f-button primary v-if="currentSection < layout.sections.length - 1" <f-button
@click="nextSection"> primary
Next v-if="currentSection < layout.sections.length - 1"
</f-button> @click="nextSection"
<f-button primary v-if="currentSection === layout.sections.length - 1" >Next</f-button>
@click="submit"> <f-button
Complete primary
</f-button> v-if="currentSection === layout.sections.length - 1"
@click="submit"
>Complete</f-button>
</div> </div>
</div> </div>
</div> </div>
@ -37,12 +36,12 @@ import frappe from 'frappejs';
import Observable from 'frappejs/utils/observable'; import Observable from 'frappejs/utils/observable';
import FormLayout from 'frappejs/ui/components/Form/FormLayout'; import FormLayout from 'frappejs/ui/components/Form/FormLayout';
import indicatorColor from 'frappejs/ui/constants/indicators'; import indicatorColor from 'frappejs/ui/constants/indicators';
import setupConfig from './config';
export default { export default {
name: 'SetupWizard', name: 'SetupWizard',
data() { data() {
return { return {
meta: null,
currentSection: 0 currentSection: 0
}; };
}, },
@ -75,10 +74,10 @@ export default {
}, },
computed: { computed: {
fields() { fields() {
return setupConfig.fields; return this.meta.fields;
}, },
layout() { layout() {
return setupConfig.layout; return this.meta.layout;
} }
} }
}; };

View File

@ -1,83 +0,0 @@
const frappe = require('frappejs');
const countryList = Object.keys(require('../../../fixtures/countryInfo.json')).sort();
export default {
fields: [
{
fieldname: 'country',
label: 'Country',
fieldtype: 'Autocomplete',
required: 1,
getList: () => countryList
},
{
fieldname: 'name',
label: 'Name',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'email',
label: 'Email',
fieldtype: 'Data',
required: 1,
inputType: 'email'
},
{
fieldname: 'companyName',
label: 'Company Name',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'bankName',
label: 'Bank Name',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'fiscalYearStart',
label: 'Fiscal Year Start Date',
fieldtype: 'Date',
required: 1
},
{
fieldname: 'fiscalYearEnd',
label: 'Fiscal Year End Date',
fieldtype: 'Date',
required: 1
}
],
layout: {
paginated: true,
sections: [
{
title: 'Select Country',
columns: [
{ fields: ['country'] }
]
},
{
title: 'Add a Profile',
columns: [
{ fields: ['name', 'email'] }
]
},
{
title: 'Add your Company',
columns: [
{ fields: ['companyName', 'bankName', 'fiscalYearStart', 'fiscalYearEnd'] }
]
}
].filter(Boolean)
}
}

View File

@ -5,14 +5,15 @@ import ListView from '../pages/ListView';
import FormView from '../pages/FormView/FormView'; import FormView from '../pages/FormView/FormView';
import PrintView from '../pages/PrintView'; import PrintView from '../pages/PrintView';
import Report from 'frappejs/ui/pages/Report'; import Report from '../pages/Report';
import reportViewConfig from '../../reports/view'; import reportViewConfig from '../../reports/view';
import DataImport from '../pages/DataImport'; import DataImport from '../pages/DataImport';
import Settings from '../pages/Settings/Settings'; import Settings from '../pages/Settings/Settings';
import ReportList from '../pages/Report'; import ReportList from '../pages/ReportList';
import ChartOfAccounts from '../pages/ChartOfAccounts';
import Tree from 'frappejs/ui/components/Tree'; import Tree from 'frappejs/ui/components/Tree';
@ -71,6 +72,12 @@ const routes = [
component: Tree, component: Tree,
props: true props: true
}, },
{
path: '/chartOfAccounts',
name: 'Chart Of Accounts',
component: ChartOfAccounts,
props: true
},
]; ];
export default new Router({ routes }); export default new Router({ routes });

View File

@ -49,6 +49,12 @@ export default {
}, },
{ {
label: _('Sales Register'), route: '#/report/sales-register' label: _('Sales Register'), route: '#/report/sales-register'
},
{
label: _('Bank Reconciliation'), route: '#/report/bank-reconciliation'
},
{
label: _('Goods and Service Tax'), route: '#/report/gst-taxes'
} }
] ]
}, },

1155
yarn.lock

File diff suppressed because it is too large Load Diff