mirror of
https://github.com/frappe/books.git
synced 2025-01-11 02:36:14 +00:00
Merge pull request #97 from thefalconx33/master
This commit is contained in:
commit
15b3b1c662
BIN
.github/preview.gif
vendored
Normal file
BIN
.github/preview.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
40
README.md
40
README.md
@ -1,7 +1,22 @@
|
||||
# Frappe Accounting
|
||||
<div align="center">
|
||||
<h1>
|
||||
Frappe Accounting
|
||||
</h1>
|
||||
<h3>
|
||||
Simple app for personal and small businesses accounting
|
||||
</h3>
|
||||
<h5>
|
||||
it's pronounced - <em>fra-pay</em>
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
Simple JS based app for personal and small businesses accounting
|
||||
<p align="center">
|
||||
<a href="https://frappe.io/accounting">
|
||||
<img src=".github/preview.gif">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Frappe Accounting is built on [FrappeJS](https://github.com/frappe/frappejs) Framework a Full-Stack VueJS based meta-data driven web framework. Under the hood it uses Electron bundles.
|
||||
|
||||
### Installation
|
||||
|
||||
@ -16,11 +31,19 @@ apt-get install build-essential python git
|
||||
apt-get install libgconf-2-4
|
||||
```
|
||||
|
||||
MacOS
|
||||
|
||||
```bash
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
You will also need [Xcode App](https://apps.apple.com/in/app/xcode/id497799835?mt=12) from App Store
|
||||
|
||||
#### Step 1
|
||||
|
||||
Install [Node.js](https://nodejs.org/en/) LTS (version 8.11.1)
|
||||
Install [Node.js](https://nodejs.org/en/) (version 12.6.0)
|
||||
|
||||
> Tip: The best way to install and manage Node is to install [nvm](https://github.com/creationix/nvm)
|
||||
> Tip: The best way to install and manage Node is to install [nvm](https://github.com/nvm-sh/nvm#usage)
|
||||
|
||||
#### Step 2
|
||||
|
||||
@ -32,7 +55,7 @@ npm install -g yarn
|
||||
|
||||
#### Step 3
|
||||
|
||||
Clone this repo
|
||||
Clone this repo
|
||||
|
||||
```bash
|
||||
git clone https://github.com/frappe/accounting.git
|
||||
@ -51,6 +74,11 @@ yarn
|
||||
|
||||
# Start the electron app
|
||||
yarn electron
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
Frappe Technologies
|
||||
- If you are facing node-gyp errors then you may need to:
|
||||
1. Install Xcode App from App Store.
|
||||
2. Use node v12.6.0
|
||||
3. Delete yarn.lock/package-lock.json
|
||||
|
@ -5,16 +5,40 @@ module.exports = class LedgerPosting {
|
||||
Object.assign(this, arguments[0]);
|
||||
this.entries = [];
|
||||
this.entryMap = {};
|
||||
// To change balance while entering ledger entries
|
||||
this.accountEntries = [];
|
||||
}
|
||||
|
||||
debit(account, amount, referenceType, referenceName) {
|
||||
async debit(account, amount, referenceType, referenceName) {
|
||||
const entry = this.getEntry(account, referenceType, referenceName);
|
||||
amount = frappe.parseNumber(amount);
|
||||
entry.debit += amount;
|
||||
await this.setAccountBalanceChange(account, 'debit', amount);
|
||||
}
|
||||
|
||||
credit(account, amount, referenceType, referenceName) {
|
||||
async credit(account, amount, referenceType, referenceName) {
|
||||
const entry = this.getEntry(account, referenceType, referenceName);
|
||||
amount = frappe.parseNumber(amount);
|
||||
entry.credit += amount;
|
||||
await this.setAccountBalanceChange(account, 'credit', amount);
|
||||
}
|
||||
|
||||
async setAccountBalanceChange(accountName, type, amount) {
|
||||
const debitAccounts = ['Asset', 'Expense'];
|
||||
const { rootType } = await frappe.getDoc('Account', accountName);
|
||||
if (debitAccounts.indexOf(rootType) === -1) {
|
||||
const change = type == 'credit' ? amount : -1 * amount;
|
||||
this.accountEntries.push({
|
||||
name: accountName,
|
||||
balanceChange: change
|
||||
});
|
||||
} else {
|
||||
const change = type == 'debit' ? amount : -1 * amount;
|
||||
this.accountEntries.push({
|
||||
name: accountName,
|
||||
balanceChange: change
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getEntry(account, referenceType, referenceName) {
|
||||
@ -50,6 +74,9 @@ module.exports = class LedgerPosting {
|
||||
entry.debit = entry.credit;
|
||||
entry.credit = temp;
|
||||
}
|
||||
for (let entry of this.accountEntries) {
|
||||
entry.balanceChange = -1 * entry.balanceChange;
|
||||
}
|
||||
await this.insertEntries();
|
||||
}
|
||||
|
||||
@ -58,7 +85,6 @@ module.exports = class LedgerPosting {
|
||||
let credit = 0;
|
||||
let debitAccounts = [];
|
||||
let creditAccounts = [];
|
||||
|
||||
for (let entry of this.entries) {
|
||||
debit += entry.debit;
|
||||
credit += entry.credit;
|
||||
@ -68,9 +94,20 @@ module.exports = class LedgerPosting {
|
||||
creditAccounts.push(entry.account);
|
||||
}
|
||||
}
|
||||
|
||||
debit = Math.floor(debit * 100) / 100;
|
||||
credit = Math.floor(credit * 100) / 100;
|
||||
if (debit !== credit) {
|
||||
throw new frappe.errors.ValidationError(frappe._('Debit {0} must be equal to Credit {1}', [debit, credit]));
|
||||
frappe.call({
|
||||
method: 'show-dialog',
|
||||
args: {
|
||||
title: 'Invalid Entry',
|
||||
message: frappe._('Debit {0} must be equal to Credit {1}', [
|
||||
debit,
|
||||
credit
|
||||
])
|
||||
}
|
||||
});
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,5 +119,10 @@ module.exports = class LedgerPosting {
|
||||
Object.assign(entryDoc, entry);
|
||||
await entryDoc.insert();
|
||||
}
|
||||
for (let entry of this.accountEntries) {
|
||||
let entryDoc = await frappe.getDoc('Account', entry.name);
|
||||
entryDoc.balance += entry.balanceChange;
|
||||
await entryDoc.update();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -271,7 +271,7 @@
|
||||
"currency_fraction_units": 100,
|
||||
"currency_name": "Belize Dollar",
|
||||
"currency_symbol": "$",
|
||||
"date_format": "mm-dd-yyyy",
|
||||
"date_format": "MM-dd-yyyy",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
"America/Belize"
|
||||
@ -356,7 +356,7 @@
|
||||
"currency_fraction": "Centavo",
|
||||
"currency_fraction_units": 100,
|
||||
"currency_symbol": "R$",
|
||||
"date_format": "dd/mm/yyyy",
|
||||
"date_format": "dd/MM/yyyy",
|
||||
"number_format": "#.###,##",
|
||||
"timezones": [
|
||||
"America/Araguaina",
|
||||
@ -463,7 +463,7 @@
|
||||
"currency_fraction_units": 100,
|
||||
"currency_name": "Canadian Dollar",
|
||||
"currency_symbol": "$",
|
||||
"date_format": "mm-dd-yyyy",
|
||||
"date_format": "MM-dd-yyyy",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
"America/Atikokan",
|
||||
@ -563,7 +563,7 @@
|
||||
"code": "cn",
|
||||
"currency": "CNY",
|
||||
"currency_name": "Yuan Renminbi",
|
||||
"date_format": "yyyy-mm-dd",
|
||||
"date_format": "yyyy-MM-dd",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
"Asia/Chongqing",
|
||||
@ -884,7 +884,7 @@
|
||||
"currency_fraction_units": 100,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "# ###,##",
|
||||
"date_format": "dd/mm/yyyy",
|
||||
"date_format": "dd/MM/yyyy",
|
||||
"timezones": [
|
||||
"Europe/Paris"
|
||||
]
|
||||
@ -1136,7 +1136,7 @@
|
||||
"currency_fraction_units": 100,
|
||||
"currency_name": "Forint",
|
||||
"currency_symbol": "Ft",
|
||||
"date_format": "yyyy-mm-dd",
|
||||
"date_format": "yyyy-MM-dd",
|
||||
"number_format": "#.###",
|
||||
"timezones": [
|
||||
"Europe/Budapest"
|
||||
@ -1161,6 +1161,7 @@
|
||||
"currency_fraction_units": 100,
|
||||
"currency_name": "Indian Rupee",
|
||||
"currency_symbol": "\u20b9",
|
||||
"date_format": "dd/MM/yyyy",
|
||||
"number_format": "#,##,###.##",
|
||||
"timezones": [
|
||||
"Asia/Kolkata"
|
||||
@ -1247,7 +1248,7 @@
|
||||
"currency_fraction_units": 100,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#.###,##",
|
||||
"date_format": "dd/mm/yyyy",
|
||||
"date_format": "dd/MM/yyyy",
|
||||
"timezones": [
|
||||
"Europe/Rome"
|
||||
],
|
||||
@ -1471,7 +1472,7 @@
|
||||
"currency_fraction_units": 100,
|
||||
"currency_name": "Lithuanian Litas",
|
||||
"currency_symbol": "Lt",
|
||||
"date_format": "yyyy-mm-dd",
|
||||
"date_format": "yyyy-MM-dd",
|
||||
"number_format": "# ###,##",
|
||||
"timezones": [
|
||||
"Europe/Vilnius"
|
||||
@ -1674,7 +1675,7 @@
|
||||
"currency_fraction_units": 100,
|
||||
"currency_name": "Tugrik",
|
||||
"currency_symbol": "\u20ae",
|
||||
"date_format": "yyyy-mm-dd",
|
||||
"date_format": "yyyy-MM-dd",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
"Asia/Choibalsan",
|
||||
@ -1911,7 +1912,7 @@
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"currency_symbol": "$",
|
||||
"date_format": "mm-dd-yyyy",
|
||||
"date_format": "MM-dd-yyyy",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
"Pacific/Palau"
|
||||
@ -1974,7 +1975,7 @@
|
||||
"currency_fraction_units": 100,
|
||||
"currency_name": "Philippine Peso",
|
||||
"currency_symbol": "\u20b1",
|
||||
"date_format": "mm-dd-yyyy",
|
||||
"date_format": "MM-dd-yyyy",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
"Asia/Manila"
|
||||
@ -2278,7 +2279,7 @@
|
||||
"currency_fraction_units": 100,
|
||||
"currency_name": "Rand",
|
||||
"currency_symbol": "R",
|
||||
"date_format": "yyyy-mm-dd",
|
||||
"date_format": "yyyy-MM-dd",
|
||||
"number_format": "# ###.##",
|
||||
"timezones": [
|
||||
"Africa/Johannesburg"
|
||||
@ -2398,7 +2399,7 @@
|
||||
"Taiwan": {
|
||||
"code": "tw",
|
||||
"currency": "TWD",
|
||||
"date_format": "yyyy-mm-dd",
|
||||
"date_format": "yyyy-MM-dd",
|
||||
"number_format": "#,###.##"
|
||||
},
|
||||
"Tajikistan": {
|
||||
@ -2591,7 +2592,7 @@
|
||||
"smallest_currency_fraction_value": 0.05,
|
||||
"currency_name": "US Dollar",
|
||||
"currency_symbol": "$",
|
||||
"date_format": "mm-dd-yyyy",
|
||||
"date_format": "MM-dd-yyyy",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
"America/Adak",
|
||||
|
@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
doctype: "PrintFormat",
|
||||
name: "Standard Invoice Format",
|
||||
for: "Invoice",
|
||||
for: "SalesInvoice",
|
||||
template: `
|
||||
<h1>{{ doc.name }}</h1>
|
||||
<div class="row py-4">
|
||||
|
@ -1,99 +1,107 @@
|
||||
module.exports = {
|
||||
"name": "Account",
|
||||
"doctype": "DocType",
|
||||
"documentClass": require("./AccountDocument.js"),
|
||||
"isSingle": 0,
|
||||
"isTree": 1,
|
||||
"keywordFields": [
|
||||
"name",
|
||||
"rootType",
|
||||
"accountType"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "name",
|
||||
"label": "Account Name",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "parentAccount",
|
||||
"label": "Parent Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "rootType",
|
||||
"label": "Root Type",
|
||||
"fieldtype": "Select",
|
||||
"options": [
|
||||
"Asset",
|
||||
"Liability",
|
||||
"Equity",
|
||||
"Income",
|
||||
"Expense"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fieldname": "accountType",
|
||||
"label": "Account Type",
|
||||
"fieldtype": "Select",
|
||||
"options": [
|
||||
"Accumulated Depreciation",
|
||||
"Bank",
|
||||
"Cash",
|
||||
"Chargeable",
|
||||
"Cost of Goods Sold",
|
||||
"Depreciation",
|
||||
"Equity",
|
||||
"Expense Account",
|
||||
"Expenses Included In Valuation",
|
||||
"Fixed Asset",
|
||||
"Income Account",
|
||||
"Payable",
|
||||
"Receivable",
|
||||
"Round Off",
|
||||
"Stock",
|
||||
"Stock Adjustment",
|
||||
"Stock Received But Not Billed",
|
||||
"Tax",
|
||||
"Temporary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fieldname": "balance",
|
||||
"label": "Balance",
|
||||
"fieldtype": "Currency",
|
||||
"default": '0',
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"fieldname": "isGroup",
|
||||
"label": "Is Group",
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
],
|
||||
|
||||
events: {
|
||||
validate: (doc) => {
|
||||
|
||||
}
|
||||
name: 'Account',
|
||||
label: 'Account',
|
||||
doctype: 'DocType',
|
||||
documentClass: require('./AccountDocument.js'),
|
||||
isSingle: 0,
|
||||
isTree: 1,
|
||||
keywordFields: ['name', 'rootType', 'accountType'],
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'name',
|
||||
label: 'Account Name',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
|
||||
listSettings: {
|
||||
getFields(list) {
|
||||
return ['name', 'accountType', 'rootType'];
|
||||
},
|
||||
getRowHTML(list, data) {
|
||||
return `<div class="col-11">${list.getNameHTML(data)} (${data.rootType})</div>`;
|
||||
}
|
||||
{
|
||||
fieldname: 'rootType',
|
||||
label: 'Root Type',
|
||||
fieldtype: 'Select',
|
||||
options: [
|
||||
'Select...',
|
||||
'Asset',
|
||||
'Liability',
|
||||
'Equity',
|
||||
'Income',
|
||||
'Expense'
|
||||
],
|
||||
required: 1
|
||||
},
|
||||
|
||||
treeSettings: {
|
||||
parentField: 'parentAccount',
|
||||
async getRootLabel() {
|
||||
let accountingSettings = await frappe.getSingle('AccountingSettings');
|
||||
return accountingSettings.companyName;
|
||||
}
|
||||
{
|
||||
fieldname: 'parentAccount',
|
||||
label: 'Parent Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
getFilters: (query, doc) => {
|
||||
const filter = {
|
||||
isGroup: 1
|
||||
};
|
||||
doc.rootType ? (filter.rootType = doc.rootType) : '';
|
||||
return filter;
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'accountType',
|
||||
label: 'Account Type',
|
||||
fieldtype: 'Select',
|
||||
required: 1,
|
||||
options: [
|
||||
'',
|
||||
'Accumulated Depreciation',
|
||||
'Bank',
|
||||
'Cash',
|
||||
'Chargeable',
|
||||
'Cost of Goods Sold',
|
||||
'Depreciation',
|
||||
'Equity',
|
||||
'Expense Account',
|
||||
'Expenses Included In Valuation',
|
||||
'Fixed Asset',
|
||||
'Income Account',
|
||||
'Payable',
|
||||
'Receivable',
|
||||
'Round Off',
|
||||
'Stock',
|
||||
'Stock Adjustment',
|
||||
'Stock Received But Not Billed',
|
||||
'Tax',
|
||||
'Temporary'
|
||||
]
|
||||
},
|
||||
{
|
||||
fieldname: 'balance',
|
||||
label: 'Balance',
|
||||
fieldtype: 'Currency',
|
||||
default: '0',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
fieldname: 'isGroup',
|
||||
label: 'Is Group',
|
||||
fieldtype: 'Check'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
events: {
|
||||
validate: doc => {}
|
||||
},
|
||||
|
||||
listSettings: {
|
||||
getFields(list) {
|
||||
return ['name', 'accountType', 'rootType'];
|
||||
},
|
||||
getRowHTML(list, data) {
|
||||
return `<div class="col-11">${list.getNameHTML(data)} (${
|
||||
data.rootType
|
||||
})</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
treeSettings: {
|
||||
parentField: 'parentAccount',
|
||||
async getRootLabel() {
|
||||
let accountingSettings = await frappe.getSingle('AccountingSettings');
|
||||
return accountingSettings.companyName;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
7
models/doctype/Account/AccountList.js
Normal file
7
models/doctype/Account/AccountList.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { _ } from 'frappejs/utils';
|
||||
|
||||
export default {
|
||||
doctype: 'Account',
|
||||
title: _('Account'),
|
||||
columns: ['name', 'parentAccount', 'rootType']
|
||||
};
|
@ -3,67 +3,72 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const countries = require('../../../fixtures/countryInfo.json');
|
||||
const standardCOA = require('../../../fixtures/verified/standardCOA.json');
|
||||
const accountFields = ['accountType', 'rootType', 'isGroup', 'account_type', 'root_type', 'is_group'];
|
||||
const accountFields = [
|
||||
'accountType',
|
||||
'rootType',
|
||||
'isGroup',
|
||||
'account_type',
|
||||
'root_type',
|
||||
'is_group'
|
||||
];
|
||||
|
||||
async function importAccounts(children, parent, rootType, rootAccount) {
|
||||
for (let accountName in children) {
|
||||
const child = children[accountName];
|
||||
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)
|
||||
}
|
||||
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 || child.account_type
|
||||
});
|
||||
|
||||
await doc.insert();
|
||||
|
||||
await importAccounts(child, accountName, rootType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function identifyIsGroup(child) {
|
||||
if (child.isGroup || child.is_group) {
|
||||
return child.isGroup || child.is_group;
|
||||
}
|
||||
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))
|
||||
const keys = Object.keys(child);
|
||||
const children = keys.filter(key => !accountFields.includes(key));
|
||||
|
||||
if (children.length) {
|
||||
return 1;
|
||||
}
|
||||
if (children.length) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function getCountryCOA(){
|
||||
const doc = await frappe.getDoc('AccountingSettings');
|
||||
const conCode = countries[doc.country].code;
|
||||
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;
|
||||
}
|
||||
try {
|
||||
const countryCoa = require('../../../fixtures/verified/' +
|
||||
conCode +
|
||||
'.json');
|
||||
return countryCoa.tree;
|
||||
} catch (e) {
|
||||
return standardCOA;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async function importCharts() {
|
||||
const chart = await getCountryCOA();
|
||||
await importAccounts(chart, '', '', true)
|
||||
}
|
||||
|
||||
const chart = await getCountryCOA();
|
||||
await importAccounts(chart, '', '', true);
|
||||
};
|
||||
|
@ -1,70 +1,89 @@
|
||||
const countryList = Object.keys(require('../../../fixtures/countryInfo.json')).sort();
|
||||
const countryList = Object.keys(
|
||||
require('../../../fixtures/countryInfo.json')
|
||||
).sort();
|
||||
|
||||
module.exports = {
|
||||
name: "AccountingSettings",
|
||||
label: "Accounting Settings",
|
||||
naming: "name", // {random|autoincrement}
|
||||
isSingle: 1,
|
||||
isChild: 0,
|
||||
isSubmittable: 0,
|
||||
settings: null,
|
||||
keywordFields: [],
|
||||
fields: [{
|
||||
label: "Company Name",
|
||||
fieldname: "companyName",
|
||||
fieldtype: "Data",
|
||||
required: 1,
|
||||
disabled: 0
|
||||
},
|
||||
name: 'AccountingSettings',
|
||||
label: 'Accounting Settings',
|
||||
naming: 'name', // {random|autoincrement}
|
||||
isSingle: 1,
|
||||
isChild: 0,
|
||||
isSubmittable: 0,
|
||||
settings: null,
|
||||
keywordFields: [],
|
||||
fields: [
|
||||
{
|
||||
label: 'Company Name',
|
||||
fieldname: 'companyName',
|
||||
fieldtype: 'Data',
|
||||
required: 1,
|
||||
disabled: 0
|
||||
},
|
||||
|
||||
{
|
||||
label: "Writeoff Account",
|
||||
fieldname: "writeOffAccount",
|
||||
fieldtype: "Account"
|
||||
},
|
||||
{
|
||||
label: 'Writeoff Account',
|
||||
fieldname: 'writeOffAccount',
|
||||
fieldtype: 'Account'
|
||||
},
|
||||
|
||||
{
|
||||
"fieldname": "country",
|
||||
"label": "Country",
|
||||
"fieldtype": "Autocomplete",
|
||||
"required": 1,
|
||||
getList: () => countryList
|
||||
},
|
||||
{
|
||||
fieldname: 'country',
|
||||
label: 'Country',
|
||||
fieldtype: 'Autocomplete',
|
||||
required: 1,
|
||||
getList: () => countryList
|
||||
},
|
||||
|
||||
{
|
||||
"fieldname": "fullname",
|
||||
"label": "Name",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
fieldname: 'currency',
|
||||
label: 'Country Currency',
|
||||
fieldtype: 'Data',
|
||||
required: 0
|
||||
},
|
||||
|
||||
{
|
||||
"fieldname": "email",
|
||||
"label": "Email",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
fieldname: 'numberFormat',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
|
||||
{
|
||||
"fieldname": "bankName",
|
||||
"label": "Bank Name",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
fieldname: 'symbol',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
|
||||
{
|
||||
"fieldname": "fiscalYearStart",
|
||||
"label": "Fiscal Year Start Date",
|
||||
"fieldtype": "Date",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
fieldname: 'fullname',
|
||||
label: 'Name',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
|
||||
{
|
||||
"fieldname": "fiscalYearEnd",
|
||||
"label": "Fiscal Year End Date",
|
||||
"fieldtype": "Date",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
fieldname: 'email',
|
||||
label: 'Email',
|
||||
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
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -1,124 +1,141 @@
|
||||
module.exports = {
|
||||
"name": "Address",
|
||||
"doctype": "DocType",
|
||||
"isSingle": 0,
|
||||
"keywordFields": [
|
||||
"name"
|
||||
],
|
||||
pageSettings: {
|
||||
hideTitle: true
|
||||
name: 'Address',
|
||||
doctype: 'DocType',
|
||||
isSingle: 0,
|
||||
keywordFields: ['name'],
|
||||
pageSettings: {
|
||||
hideTitle: true
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'name',
|
||||
label: 'Address Title',
|
||||
fieldtype: 'Data',
|
||||
defaultValue: 'Work',
|
||||
required: 1
|
||||
},
|
||||
// "naming": "autoincrement",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "name",
|
||||
"label": "Address Title",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "addressType",
|
||||
"label": "Address Type",
|
||||
"fieldtype": "Select",
|
||||
"options": [
|
||||
"Billing", "Shipping", "Office",
|
||||
"Personal", "Plant", "Postal",
|
||||
"Shop", "Subsidary", "Warehouse",
|
||||
"Current", "Permanent", "Other"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fieldname": "addressLine1",
|
||||
"label": "Address Line 1",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "addressLine2",
|
||||
"label": "Address Line 2",
|
||||
"fieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "city",
|
||||
"label": "City / Town",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "state",
|
||||
"label": "State",
|
||||
"fieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "country",
|
||||
"label": "Country",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "postalCode",
|
||||
"label": "Postal Code",
|
||||
"fieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "emailAddress",
|
||||
"label": "Email Address",
|
||||
"fieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "phone",
|
||||
"label": "Phone",
|
||||
"fieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "fax",
|
||||
"label": "Fax",
|
||||
"fieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "isPreferredBilling",
|
||||
"label": "Preferred Billing Address",
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "isShippingBilling",
|
||||
"label": "Preferred Shipping Address",
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
],
|
||||
|
||||
// events: {
|
||||
// validate: (doc) => {
|
||||
|
||||
// }
|
||||
// },
|
||||
|
||||
listSettings: {
|
||||
getFields(list) {
|
||||
return ['name', 'addressType'];
|
||||
},
|
||||
getRowHTML(list, data) {
|
||||
return `<div class="col-11">${list.getNameHTML(data)} (${data.addressType})</div>`;
|
||||
}
|
||||
{
|
||||
fieldname: 'addressType',
|
||||
label: 'Address Type',
|
||||
fieldtype: 'Select',
|
||||
options: [
|
||||
'Billing',
|
||||
'Shipping',
|
||||
'Office',
|
||||
'Personal',
|
||||
'Plant',
|
||||
'Postal',
|
||||
'Shop',
|
||||
'Subsidary',
|
||||
'Warehouse',
|
||||
'Current',
|
||||
'Permanent',
|
||||
'Other'
|
||||
]
|
||||
},
|
||||
{
|
||||
fieldname: 'addressLine1',
|
||||
label: 'Address Line 1',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'addressLine2',
|
||||
label: 'Address Line 2',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
{
|
||||
fieldname: 'city',
|
||||
label: 'City / Town',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'state',
|
||||
label: 'State',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
{
|
||||
fieldname: 'country',
|
||||
label: 'Country',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'postalCode',
|
||||
label: 'Postal Code',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
{
|
||||
fieldname: 'emailAddress',
|
||||
label: 'Email Address',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
{
|
||||
fieldname: 'phone',
|
||||
label: 'Phone',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
{
|
||||
fieldname: 'fax',
|
||||
label: 'Fax',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
{
|
||||
fieldname: 'isPreferredBilling',
|
||||
label: 'Preferred Billing Address',
|
||||
fieldtype: 'Check'
|
||||
},
|
||||
{
|
||||
fieldname: 'isShippingBilling',
|
||||
label: 'Preferred Shipping Address',
|
||||
fieldtype: 'Check'
|
||||
}
|
||||
],
|
||||
|
||||
layout: [
|
||||
// section 1
|
||||
// events: {
|
||||
// validate: (doc) => {
|
||||
|
||||
// }
|
||||
// },
|
||||
|
||||
listSettings: {
|
||||
getFields(list) {
|
||||
return ['name', 'addressType'];
|
||||
},
|
||||
getRowHTML(list, data) {
|
||||
return `<div class="col-11">${list.getNameHTML(data)} (${
|
||||
data.addressType
|
||||
})</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
layout: [
|
||||
// section 1
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fields: [
|
||||
"name", "addressType", "addressLine1",
|
||||
"addressLine2", "city", "country", "state",
|
||||
"postalCode"
|
||||
]
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
"emailAddress", "phone", "fax", "isPreferredBilling", "isShippingBilling"
|
||||
]
|
||||
}
|
||||
]
|
||||
fields: [
|
||||
'name',
|
||||
'addressType',
|
||||
'addressLine1',
|
||||
'addressLine2',
|
||||
'city',
|
||||
'country',
|
||||
'state',
|
||||
'postalCode'
|
||||
]
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
'emailAddress',
|
||||
'phone',
|
||||
'fax',
|
||||
'isPreferredBilling',
|
||||
'isShippingBilling'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -1,138 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const utils = require('../../../accounting/utils');
|
||||
|
||||
module.exports = {
|
||||
"name": "Bill",
|
||||
"doctype": "DocType",
|
||||
"documentClass": require('./BillDocument'),
|
||||
"isSingle": 0,
|
||||
"isChild": 0,
|
||||
"isSubmittable": 1,
|
||||
"keywordFields": ["name", "supplier"],
|
||||
"settings": "BillSettings",
|
||||
"showTitle": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "date",
|
||||
"label": "Date",
|
||||
"fieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier",
|
||||
"label": "Supplier",
|
||||
"fieldtype": "Link",
|
||||
"target": "Party",
|
||||
"required": 1,
|
||||
getFilters: (query, control) => {
|
||||
return {
|
||||
keywords: ["like", query],
|
||||
supplier: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "account",
|
||||
"label": "Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
getFilters: (query, control) => {
|
||||
return {
|
||||
keywords: ["like", query],
|
||||
isGroup: 0,
|
||||
accountType: "Payable"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "items",
|
||||
"label": "Items",
|
||||
"fieldtype": "Table",
|
||||
"childtype": "BillItem",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "netTotal",
|
||||
"label": "Net Total",
|
||||
"fieldtype": "Currency",
|
||||
formula: (doc) => doc.getSum('items', 'amount'),
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"fieldname": "taxes",
|
||||
"label": "Taxes",
|
||||
"fieldtype": "Table",
|
||||
"childtype": "TaxSummary",
|
||||
"disabled": true,
|
||||
template: (doc, row) => {
|
||||
return `<div class='row'>
|
||||
<div class='col-6'><!-- empty left side --></div>
|
||||
<div class='col-6'>${(doc.taxes || []).map(row => {
|
||||
return `<div class='row'>
|
||||
<div class='col-6'>${row.account} (${row.rate}%)</div>
|
||||
<div class='col-6 text-right'>
|
||||
${frappe.format(row.amount, 'Currency')}
|
||||
</div>
|
||||
</div>`
|
||||
}).join('')}
|
||||
</div></div>`;
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "grandTotal",
|
||||
"label": "Grand Total",
|
||||
"fieldtype": "Currency",
|
||||
formula: (doc) => doc.getGrandTotal(),
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"fieldname": "terms",
|
||||
"label": "Terms",
|
||||
"fieldtype": "Text"
|
||||
}
|
||||
],
|
||||
|
||||
layout: [
|
||||
// section 1
|
||||
{
|
||||
columns: [
|
||||
{ fields: [ "supplier", "account" ] },
|
||||
{ fields: [ "date" ] }
|
||||
]
|
||||
},
|
||||
|
||||
// section 2
|
||||
{ fields: [ "items" ] },
|
||||
|
||||
// section 3
|
||||
{ fields: [ "netTotal", "taxes", "grandTotal" ] },
|
||||
|
||||
// section 4
|
||||
{ fields: [ "terms" ] },
|
||||
],
|
||||
|
||||
links: [
|
||||
{
|
||||
label: 'Make Payment',
|
||||
condition: form => form.doc.submitted,
|
||||
action: async form => {
|
||||
const payment = await frappe.getNewDoc('Payment');
|
||||
payment.party = form.doc.supplier,
|
||||
payment.account = form.doc.account,
|
||||
payment.for = [{referenceType: form.doc.doctype, referenceName: form.doc.name, amount: form.doc.grandTotal}]
|
||||
const formModal = await frappe.desk.showFormModal('Payment', payment.name);
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
listSettings: {
|
||||
getFields(list) {
|
||||
return ['name', 'supplier', 'grandTotal', 'submitted'];
|
||||
},
|
||||
|
||||
getRowHTML(list, data) {
|
||||
return `<div class="col-3">${list.getNameHTML(data)}</div>
|
||||
<div class="col-4 text-muted">${data.supplier}</div>
|
||||
<div class="col-4 text-muted text-right">${frappe.format(data.grandTotal, "Currency")}</div>`;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
const InvoiceDocument = require('../Invoice/InvoiceDocument');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class Bill extends InvoiceDocument { }
|
@ -1,29 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const Bill = require('./BillDocument');
|
||||
const LedgerPosting = require('../../../accounting/ledgerPosting');
|
||||
|
||||
module.exports = class BillServer extends Bill {
|
||||
getPosting() {
|
||||
let entries = new LedgerPosting({reference: this, party: this.supplier});
|
||||
entries.credit(this.account, this.grandTotal);
|
||||
|
||||
for (let item of this.items) {
|
||||
entries.debit(item.account, item.amount);
|
||||
}
|
||||
|
||||
if (this.taxes) {
|
||||
for (let tax of this.taxes) {
|
||||
entries.debit(tax.account, tax.amount);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
async afterSubmit() {
|
||||
await this.getPosting().post();
|
||||
}
|
||||
|
||||
async afterRevert() {
|
||||
await this.getPosting().postReverse();
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
module.exports = {
|
||||
name: "BillItem",
|
||||
doctype: "DocType",
|
||||
isSingle: 0,
|
||||
isChild: 1,
|
||||
keywordFields: [],
|
||||
layout: 'ratio',
|
||||
fields: [
|
||||
{
|
||||
"fieldname": "item",
|
||||
"label": "Item",
|
||||
"fieldtype": "Link",
|
||||
"target": "Item",
|
||||
"required": 1,
|
||||
width: 2
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"label": "Description",
|
||||
"fieldtype": "Text",
|
||||
formula: (row, doc) => doc.getFrom('Item', row.item, 'description'),
|
||||
hidden: 1
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity",
|
||||
"label": "Quantity",
|
||||
"fieldtype": "Float",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "rate",
|
||||
"label": "Rate",
|
||||
"fieldtype": "Currency",
|
||||
"required": 1,
|
||||
formula: (row, doc) => doc.getFrom('Item', row.item, 'rate')
|
||||
},
|
||||
{
|
||||
fieldname: "account",
|
||||
label: "Account",
|
||||
hidden: 1,
|
||||
fieldtype: "Link",
|
||||
target: "Account",
|
||||
formula: (row, doc) => doc.getFrom('Item', row.item, 'expenseAccount')
|
||||
},
|
||||
{
|
||||
"fieldname": "tax",
|
||||
"label": "Tax",
|
||||
"fieldtype": "Link",
|
||||
"target": "Tax",
|
||||
formula: (row, doc) => doc.getFrom('Item', row.item, 'tax')
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"label": "Amount",
|
||||
"fieldtype": "Currency",
|
||||
"disabled": 1,
|
||||
formula: (row, doc) => row.quantity * row.rate
|
||||
},
|
||||
{
|
||||
"fieldname": "taxAmount",
|
||||
"label": "Tax Amount",
|
||||
"hidden": 1,
|
||||
"fieldtype": "Text",
|
||||
formula: (row, doc) => doc.getRowTax(row)
|
||||
}
|
||||
]
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
module.exports = {
|
||||
"name": "BillSettings",
|
||||
"label": "Bill Settings",
|
||||
"doctype": "DocType",
|
||||
"isSingle": 1,
|
||||
"isChild": 0,
|
||||
"keywordFields": [],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
"fieldtype": "Link",
|
||||
"target": "NumberSeries",
|
||||
"required": 1,
|
||||
"default": "BILL"
|
||||
}
|
||||
]
|
||||
}
|
16
models/doctype/Color/Color.js
Normal file
16
models/doctype/Color/Color.js
Normal file
@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
name: 'Color',
|
||||
doctype: 'DocType',
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'name',
|
||||
fieldtype: 'Data',
|
||||
label: 'Color'
|
||||
},
|
||||
{
|
||||
fieldname: 'hexvalue',
|
||||
fieldtype: 'Data',
|
||||
label: 'Hex Value'
|
||||
}
|
||||
]
|
||||
};
|
53
models/doctype/Currency/Currency.js
Normal file
53
models/doctype/Currency/Currency.js
Normal file
@ -0,0 +1,53 @@
|
||||
module.exports = {
|
||||
name: 'Currency',
|
||||
label: 'Currency',
|
||||
doctype: 'DocType',
|
||||
isSingle: 0,
|
||||
keywordFields: ['name', 'symbol'],
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'name',
|
||||
label: 'Currency Name',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'fraction',
|
||||
label: 'Fraction',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
{
|
||||
fieldname: 'fractionUnits',
|
||||
label: 'Fraction Units',
|
||||
fieldtype: 'Int'
|
||||
},
|
||||
{
|
||||
label: 'Smallest Currency Fraction Value',
|
||||
fieldname: 'smallestValue',
|
||||
fieldtype: 'Currency'
|
||||
},
|
||||
{
|
||||
label: 'Symbol',
|
||||
fieldname: 'symbol',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
{
|
||||
fieldname: 'numberFormat',
|
||||
fieldtype: 'Select',
|
||||
label: 'Number Format',
|
||||
options: [
|
||||
'',
|
||||
'#,###.##',
|
||||
'#.###,##',
|
||||
'# ###.##',
|
||||
'# ###,##',
|
||||
"#'###.##",
|
||||
'#, ###.##',
|
||||
'#,##,###.##',
|
||||
'#,###.###',
|
||||
'#.###',
|
||||
'#,###'
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
26
models/doctype/DashboardChart/DashboardChart.js
Normal file
26
models/doctype/DashboardChart/DashboardChart.js
Normal file
@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
name: 'DashboardChart',
|
||||
doctype: 'DocType',
|
||||
isChild: 1,
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'account',
|
||||
fieldtype: 'Link',
|
||||
label: 'Account',
|
||||
target: 'Account'
|
||||
},
|
||||
{
|
||||
fieldname: 'type',
|
||||
fieldtype: 'Select',
|
||||
options: ['Bar', 'Line', 'Percentage'],
|
||||
label: 'Chart Type',
|
||||
defaultValue: 'Bar'
|
||||
},
|
||||
{
|
||||
fieldname: 'color',
|
||||
fieldtype: 'Link',
|
||||
label: 'Color',
|
||||
target: 'Color'
|
||||
}
|
||||
]
|
||||
};
|
14
models/doctype/DashboardSettings/DashboardSettings.js
Normal file
14
models/doctype/DashboardSettings/DashboardSettings.js
Normal file
@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
name: 'Dashboard',
|
||||
label: 'Dashboard Settings',
|
||||
doctype: 'DocType',
|
||||
isSingle: 1,
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'charts',
|
||||
fieldtype: 'Table',
|
||||
label: 'Charts',
|
||||
childtype: 'DashboardChart'
|
||||
}
|
||||
]
|
||||
};
|
77
models/doctype/GSTR3B/GSTR3B.js
Normal file
77
models/doctype/GSTR3B/GSTR3B.js
Normal file
@ -0,0 +1,77 @@
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = {
|
||||
name: 'GSTR3B',
|
||||
label: 'GSTR 3B',
|
||||
doctype: 'DocType',
|
||||
documentClass: require('./GSTR3BDocument.js'),
|
||||
print: {
|
||||
printFormat: 'GSTR3B Print Format'
|
||||
},
|
||||
keywordFields: ['name', 'month', 'year'],
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'year',
|
||||
label: 'Year',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'month',
|
||||
label: 'Month',
|
||||
fieldtype: 'Select',
|
||||
options: [
|
||||
'',
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
],
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'jsonData',
|
||||
label: 'JSON Data',
|
||||
fieldtype: 'Code',
|
||||
formula: doc => doc.getJson(),
|
||||
required: 1,
|
||||
disabled: 1,
|
||||
rows: 15
|
||||
}
|
||||
],
|
||||
layout: [
|
||||
{
|
||||
columns: [{ fields: ['year', 'month', 'jsonData'] }]
|
||||
}
|
||||
],
|
||||
links: [
|
||||
{
|
||||
label: 'Print PDF',
|
||||
condition: form => !form.doc._notInserted,
|
||||
action: async form => {
|
||||
form.$router.push({
|
||||
path: `/print/GSTR3B/${form.doc.name}`
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
condition: form => !form.doc._notInserted,
|
||||
action: async form => {
|
||||
const doc = await frappe.getDoc('GSTR3B', form.doc.name);
|
||||
await doc.delete();
|
||||
form.$router.push({
|
||||
path: `/list/GSTR3B`
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
150
models/doctype/GSTR3B/GSTR3BDocument.js
Normal file
150
models/doctype/GSTR3B/GSTR3BDocument.js
Normal file
@ -0,0 +1,150 @@
|
||||
const BaseDocument = require('frappejs/model/document');
|
||||
const frappe = require('frappejs');
|
||||
const { format } = require('./GSTR3BFormat');
|
||||
|
||||
module.exports = class GSTR3B extends BaseDocument {
|
||||
async getData() {
|
||||
const monthIndex = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
].indexOf(this.month);
|
||||
const month = monthIndex + 1 > 9 ? monthIndex + 1 : `0${monthIndex + 1}`;
|
||||
const lastDate = new Date(this.year, monthIndex + 1, 0).getDate();
|
||||
const filters = {
|
||||
date: [
|
||||
'>=',
|
||||
`${this.year}-${month}-01`,
|
||||
'<=',
|
||||
`${this.year}-${month}-${lastDate}`
|
||||
]
|
||||
};
|
||||
const salesInvoices = frappe.db.getAll({
|
||||
doctype: 'SalesInvoice',
|
||||
filters,
|
||||
fields: ['*']
|
||||
});
|
||||
const purchaseInvoices = frappe.db.getAll({
|
||||
doctype: 'PurchaseInvoice',
|
||||
filters,
|
||||
fields: ['*']
|
||||
});
|
||||
const [gstr1Data, gstr2Data] = await Promise.all([
|
||||
salesInvoices,
|
||||
purchaseInvoices
|
||||
]);
|
||||
let gstr3bData = [[], []];
|
||||
|
||||
for (let ledgerEntry of gstr1Data) {
|
||||
ledgerEntry.doctype = 'SalesInvoice';
|
||||
gstr3bData[0].push(await this.makeGSTRow(ledgerEntry));
|
||||
}
|
||||
for (let ledgerEntry of gstr2Data) {
|
||||
ledgerEntry.doctype = 'PurchaseInvoice';
|
||||
gstr3bData[1].push(await this.makeGSTRow(ledgerEntry));
|
||||
}
|
||||
|
||||
return gstr3bData;
|
||||
}
|
||||
|
||||
async makeGSTRow(ledgerEntry) {
|
||||
let row = {};
|
||||
ledgerEntry = await frappe.getDoc(ledgerEntry.doctype, ledgerEntry.name);
|
||||
let party = await frappe.getDoc(
|
||||
'Party',
|
||||
ledgerEntry.customer || ledgerEntry.supplier
|
||||
);
|
||||
if (party.address) {
|
||||
let addressDetails = await frappe.getDoc('Address', party.address);
|
||||
row.place = addressDetails.state || '';
|
||||
}
|
||||
row.gstin = party.gstin;
|
||||
row.partyName = ledgerEntry.customer || ledgerEntry.supplier;
|
||||
row.invNo = ledgerEntry.name;
|
||||
row.invDate = ledgerEntry.date;
|
||||
row.rate = 0;
|
||||
row.inState = true;
|
||||
row.reverseCharge = !party.gstin ? 'Y' : 'N';
|
||||
ledgerEntry.taxes.forEach(tax => {
|
||||
row.rate += tax.rate;
|
||||
const taxAmt = (tax.rate * ledgerEntry.netTotal) / 100;
|
||||
if (tax.account === 'IGST') row.igstAmt = taxAmt;
|
||||
if (tax.account === 'IGST') row.inState = false;
|
||||
if (tax.account === 'CGST') row.cgstAmt = taxAmt;
|
||||
if (tax.account === 'SGST') row.sgstAmt = taxAmt;
|
||||
if (tax.account === 'Nil Rated') row.nilRated = true;
|
||||
if (tax.account === 'Exempt') row.exempt = true;
|
||||
if (tax.account === 'Non GST') row.nonGST = true;
|
||||
});
|
||||
row.invAmt = ledgerEntry.grandTotal;
|
||||
row.taxVal = ledgerEntry.netTotal;
|
||||
return row;
|
||||
}
|
||||
|
||||
async createJson(data) {
|
||||
let jsonData = JSON.parse(JSON.stringify(format));
|
||||
|
||||
for (let ledgerEntry of data[0]) {
|
||||
if (ledgerEntry.rate > 0) {
|
||||
jsonData['sup_details']['osup_det']['samt'] += ledgerEntry.sgstAmt || 0;
|
||||
jsonData['sup_details']['osup_det']['camt'] += ledgerEntry.cgstAmt || 0;
|
||||
jsonData['sup_details']['osup_det']['iamt'] += ledgerEntry.igstAmt || 0;
|
||||
jsonData['sup_details']['osup_det']['txval'] += ledgerEntry.taxVal;
|
||||
}
|
||||
if (ledgerEntry.rate === 0) {
|
||||
jsonData['sup_details']['osup_zero']['txval'] += ledgerEntry.taxVal;
|
||||
}
|
||||
if (ledgerEntry.nilRated || ledgerEntry.exempt) {
|
||||
jsonData['sup_details']['osup_nil_exmp']['txval'] += ledgerEntry.taxVal;
|
||||
}
|
||||
if (ledgerEntry.nonGST) {
|
||||
jsonData['sup_details']['osup_nongst']['txval'] += ledgerEntry.taxVal;
|
||||
}
|
||||
if (!ledgerEntry.inState && !ledgerEntry.gstin) {
|
||||
jsonData['inter_sup']['unreg_details'].push({
|
||||
pos: ledgerEntry.place,
|
||||
txval: ledgerEntry.taxVal,
|
||||
iAmt: ledgerEntry.igstAmt || 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (let ledgerEntry of data[1]) {
|
||||
if (ledgerEntry.reverseCharge === 'Y') {
|
||||
jsonData['sup_details']['isup_rev']['samt'] += ledgerEntry.sgstAmt || 0;
|
||||
jsonData['sup_details']['isup_rev']['camt'] += ledgerEntry.cgstAmt || 0;
|
||||
jsonData['sup_details']['isup_rev']['iamt'] += ledgerEntry.igstAmt || 0;
|
||||
jsonData['sup_details']['isup_rev']['txval'] += ledgerEntry.taxVal;
|
||||
}
|
||||
if (ledgerEntry.nilRated || ledgerEntry.exempt) {
|
||||
jsonData['inward_sup']['isup_details'][0][
|
||||
ledgerEntry.inState ? 'intra' : 'inter'
|
||||
] += ledgerEntry.taxVal;
|
||||
}
|
||||
if (ledgerEntry.nonGST) {
|
||||
jsonData['inward_sup']['isup_details'][0][
|
||||
ledgerEntry.inState ? 'intra' : 'inter'
|
||||
] += ledgerEntry.taxVal;
|
||||
}
|
||||
}
|
||||
|
||||
return jsonData;
|
||||
}
|
||||
|
||||
async getJson() {
|
||||
if (this.year && this.month) {
|
||||
const data = await this.getData();
|
||||
const json = await this.createJson(data);
|
||||
return JSON.stringify(json, undefined, 2);
|
||||
}
|
||||
}
|
||||
};
|
390
models/doctype/GSTR3B/GSTR3BFormat.js
Normal file
390
models/doctype/GSTR3B/GSTR3BFormat.js
Normal file
@ -0,0 +1,390 @@
|
||||
const format = {
|
||||
gstin: '',
|
||||
ret_period: '',
|
||||
inward_sup: {
|
||||
isup_details: [
|
||||
{
|
||||
ty: 'GST',
|
||||
intra: 0,
|
||||
inter: 0
|
||||
},
|
||||
{
|
||||
ty: 'NONGST',
|
||||
inter: 0,
|
||||
intra: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
sup_details: {
|
||||
osup_zero: {
|
||||
csamt: 0,
|
||||
txval: 0,
|
||||
iamt: 0
|
||||
},
|
||||
osup_nil_exmp: {
|
||||
txval: 0
|
||||
},
|
||||
osup_det: {
|
||||
samt: 0,
|
||||
csamt: 0,
|
||||
txval: 0,
|
||||
camt: 0,
|
||||
iamt: 0
|
||||
},
|
||||
isup_rev: {
|
||||
samt: 0,
|
||||
csamt: 0,
|
||||
txval: 0,
|
||||
camt: 0,
|
||||
iamt: 0
|
||||
},
|
||||
osup_nongst: {
|
||||
txval: 0
|
||||
}
|
||||
},
|
||||
inter_sup: {
|
||||
unreg_details: [],
|
||||
comp_details: [],
|
||||
uin_details: []
|
||||
},
|
||||
itc_elg: {
|
||||
itc_avl: [
|
||||
{
|
||||
csamt: 0,
|
||||
samt: 0,
|
||||
ty: 'IMPG',
|
||||
camt: 0,
|
||||
iamt: 0
|
||||
},
|
||||
{
|
||||
csamt: 0,
|
||||
samt: 0,
|
||||
ty: 'IMPS',
|
||||
camt: 0,
|
||||
iamt: 0
|
||||
},
|
||||
{
|
||||
samt: 0,
|
||||
csamt: 0,
|
||||
ty: 'ISRC',
|
||||
camt: 0,
|
||||
iamt: 0
|
||||
},
|
||||
{
|
||||
ty: 'ISD',
|
||||
iamt: 0,
|
||||
camt: 0,
|
||||
samt: 0,
|
||||
csamt: 0
|
||||
},
|
||||
{
|
||||
samt: 0,
|
||||
csamt: 0,
|
||||
ty: 'OTH',
|
||||
camt: 0,
|
||||
iamt: 0
|
||||
}
|
||||
],
|
||||
itc_rev: [
|
||||
{
|
||||
ty: 'RUL',
|
||||
iamt: 0,
|
||||
camt: 0,
|
||||
samt: 0,
|
||||
csamt: 0
|
||||
},
|
||||
{
|
||||
ty: 'OTH',
|
||||
iamt: 0,
|
||||
camt: 0,
|
||||
samt: 0,
|
||||
csamt: 0
|
||||
}
|
||||
],
|
||||
itc_net: {
|
||||
samt: 0,
|
||||
csamt: 0,
|
||||
camt: 0,
|
||||
iamt: 0
|
||||
},
|
||||
itc_inelg: [
|
||||
{
|
||||
ty: 'RUL',
|
||||
iamt: 0,
|
||||
camt: 0,
|
||||
samt: 0,
|
||||
csamt: 0
|
||||
},
|
||||
{
|
||||
ty: 'OTH',
|
||||
iamt: 0,
|
||||
camt: 0,
|
||||
samt: 0,
|
||||
csamt: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function generateHTML(data) {
|
||||
let template = `
|
||||
<div class="p-5 m-5" style="font-size: 14px !important">
|
||||
<div>
|
||||
<h3 class="text-center">GSTR3B-Form</h3>
|
||||
<h5>GSTIN:   ${data.gstin}</h5>
|
||||
<h5>Period:   ${data.ret_period}</h5>
|
||||
</div>
|
||||
|
||||
<h5>3.1  Details of Outward Supplies and inward supplies liable to reverse charge</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nature Of Supplies</th>
|
||||
<th>Total Taxable value</th>
|
||||
<th>Integrated Tax</th>
|
||||
<th>Central Tax</th>
|
||||
<th>State/UT Tax</th>
|
||||
<th>Cess</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>(a) Outward taxable supplies(other than zero rated, nil rated and exempted</td>
|
||||
<td class="right">${data.sup_details.osup_det.txval}</td>
|
||||
<td class="right">${data.sup_details.osup_det.iamt}</td>
|
||||
<td class="right">${data.sup_details.osup_det.camt}</td>
|
||||
<td class="right">${data.sup_details.osup_det.samt}</td>
|
||||
<td class="right">${data.sup_details.osup_det.csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>(b) Outward taxable supplies(zero rated)</td>
|
||||
<td class="right">${data.sup_details.osup_zero.txval}</td>
|
||||
<td class="right">${data.sup_details.osup_zero.iamt}</td>
|
||||
<td style="background-color:#d9d9d9;"></td>
|
||||
<td style="background-color:#d9d9d9;"></td>
|
||||
<td class="right">${data.sup_details.osup_zero.csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>(b) Other outward supplies(Nil rated,Exempted)</td>
|
||||
<td class="right">${data.sup_details.osup_nil_exmp.txval}</td>
|
||||
<td style="background-color:#d9d9d9;"></td>
|
||||
<td style="background-color:#d9d9d9;"></td>
|
||||
<td style="background-color:#d9d9d9;"></td>
|
||||
<td style="background-color:#d9d9d9;"></td>
|
||||
<tr>
|
||||
<td>(d) Inward Supplies(liable to reverse charge</td>
|
||||
<td class="right">${data.sup_details.isup_rev.txval}</td>
|
||||
<td class="right">${data.sup_details.isup_rev.iamt}</td>
|
||||
<td class="right">${data.sup_details.isup_rev.camt}</td>
|
||||
<td class="right">${data.sup_details.isup_rev.samt}</td>
|
||||
<td class="right">${data.sup_details.isup_rev.csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>(e) Non-GST outward supplies</td>
|
||||
<td class="right">${data.sup_details.osup_nongst.txval}</td>
|
||||
<td style="background-color:#d9d9d9;"></td>
|
||||
<td style="background-color:#d9d9d9;"></td>
|
||||
<td style="background-color:#d9d9d9;"></td>
|
||||
<td style="background-color:#d9d9d9;"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h5>
|
||||
3.2  Of the supplies shown in 3.1 (a) above, details of inter-State supplies made to unregisterd
|
||||
persons, composition taxable persons and UIN holders
|
||||
</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Place Of Supply (State/UT)</th>
|
||||
<th>Total Taxable Value</th>
|
||||
<th>Amount of Integrated Tax</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Supplies made to Unregistered Persons</td>
|
||||
<td class="right">`;
|
||||
for (let row of data.inter_sup.unreg_details) {
|
||||
if (row) template += row.pos + '<br>';
|
||||
}
|
||||
template += '</td><td class="right">';
|
||||
for (let row of data.inter_sup.unreg_details) {
|
||||
if (row) template += row.txval + '<br>';
|
||||
}
|
||||
template += '</td><td class="right">';
|
||||
for (let row of data.inter_sup.unreg_details) {
|
||||
if (row) template += row.iamt + '<br>';
|
||||
}
|
||||
|
||||
template +=
|
||||
'</td></tr><tr><td>Supplies made to Composition Taxable Persons</td><td class="right">';
|
||||
for (let row of data.inter_sup.comp_details) {
|
||||
if (row) template += row.pos + '<br>';
|
||||
}
|
||||
template += '</td><td class="right">';
|
||||
for (let row of data.inter_sup.comp_details) {
|
||||
if (row) template += row.txval + '<br>';
|
||||
}
|
||||
template += '</td><td class="right">';
|
||||
for (let row of data.inter_sup.comp_details) {
|
||||
if (row) template += row.iamt + '<br>';
|
||||
}
|
||||
|
||||
template +=
|
||||
'</td></tr><tr><td>Supplies made to UIN holders</td><td class="right">';
|
||||
for (let row of data.inter_sup.uin_details) {
|
||||
if (row) template += row.pos + '<br>';
|
||||
}
|
||||
template += '</td><td class="right">';
|
||||
for (let row of data.inter_sup.uin_details) {
|
||||
if (row) template += row.txval + '<br>';
|
||||
}
|
||||
template += '</td><td class="right">';
|
||||
for (let row of data.inter_sup.uin_details) {
|
||||
if (row) template += row.iamt + '<br>';
|
||||
}
|
||||
|
||||
template += `</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h5>4.   Eligible ITC</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Details</th>
|
||||
<th>Integrated Tax</th>
|
||||
<th>Central Tax</th>
|
||||
<th>State/UT tax</th>
|
||||
<th>Cess</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>(A) ITC Available (whether in full op part)</b></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>  (1) Import of goods </td>
|
||||
<td class="right">${data.itc_elg.itc_avl[0].iamt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[0].camt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[0].samt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[0].csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>  (2) Import of services</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[1].iamt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[1].camt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[1].samt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[1].csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>  (3) Inward supplies liable to reverse charge (other than 1 & 2 above)</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[2].iamt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[2].camt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[2].samt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[2].csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>  (4) Inward supplies from ISD</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[3].iamt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[3].camt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[3].samt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[3].csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>  (5) All other ITC</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[4].iamt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[4].camt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[4].samt}</td>
|
||||
<td class="right">${data.itc_elg.itc_avl[4].csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>(B) ITC Reversed</b></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>  (1) As per rules 42 & 43 of CGST Rules</td>
|
||||
<td class="right">${data.itc_elg.itc_rev[0].iamt}</td>
|
||||
<td class="right">${data.itc_elg.itc_rev[0].camt}</td>
|
||||
<td class="right">${data.itc_elg.itc_rev[0].samt}</td>
|
||||
<td class="right">${data.itc_elg.itc_rev[0].csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>  (2) Others</td>
|
||||
<td class="right">${data.itc_elg.itc_rev[1].iamt}</td>
|
||||
<td class="right">${data.itc_elg.itc_rev[1].camt}</td>
|
||||
<td class="right">${data.itc_elg.itc_rev[1].samt}</td>
|
||||
<td class="right">${data.itc_elg.itc_rev[1].csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>(C) Net ITC Available(A) - (B)</b></td>
|
||||
<td class="right">${data.itc_elg.itc_net.iamt}</td>
|
||||
<td class="right">${data.itc_elg.itc_net.camt}</td>
|
||||
<td class="right">${data.itc_elg.itc_net.samt}</td>
|
||||
<td class="right">${data.itc_elg.itc_net.csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>(D) Ineligible ITC</b></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>  (1) As per section 17(5)</td>
|
||||
<td class="right">${data.itc_elg.itc_inelg[0].iamt}</td>
|
||||
<td class="right">${data.itc_elg.itc_inelg[0].camt}</td>
|
||||
<td class="right">${data.itc_elg.itc_inelg[0].samt}</td>
|
||||
<td class="right">${data.itc_elg.itc_inelg[0].csamt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>  (2) Others</td>
|
||||
<td class="right">${data.itc_elg.itc_inelg[1].iamt}</td>
|
||||
<td class="right">${data.itc_elg.itc_inelg[1].camt}</td>
|
||||
<td class="right">${data.itc_elg.itc_inelg[1].samt}</td>
|
||||
<td class="right">${data.itc_elg.itc_inelg[1].csamt}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h5>5.    Values of exempt, nil rated and non-GST inward supplies</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nature of Supplies</th>
|
||||
<th>Inter-State Supplies</th>
|
||||
<th>Intra-State Supplies</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>From a supplier under composition scheme, Exempt and Nil rated</td>
|
||||
<td class="right">${data.inward_sup.isup_details[0].inter}</td>
|
||||
<td class="right">${data.inward_sup.isup_details[0].intra}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Non GST Inward Supplies</td>
|
||||
<td class="right">${data.inward_sup.isup_details[1].inter}</td>
|
||||
<td class="right">${data.inward_sup.isup_details[1].intra}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>`;
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
format
|
||||
};
|
7
models/doctype/GSTR3B/GSTR3BList.js
Normal file
7
models/doctype/GSTR3B/GSTR3BList.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { _ } from 'frappejs/utils';
|
||||
|
||||
export default {
|
||||
doctype: 'GSTR3B',
|
||||
title: _('GSTR 3B Report'),
|
||||
columns: ['year', 'month']
|
||||
};
|
303
models/doctype/GSTR3B/GSTR3BPrintView.vue
Normal file
303
models/doctype/GSTR3B/GSTR3BPrintView.vue
Normal file
@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row no-gutters">
|
||||
<div class="col-8 mx-auto text-right mt-4">
|
||||
<f-button primary @click="$emit('makePDF', $refs.printComponent.innerHTML)">{{ _('PDF') }}</f-button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="printComponent" class="col-8 bg-white mt-4 mx-auto border shadow">
|
||||
<div class="print-format" style="padding: 3.5rem; font-size: 8pt !important;">
|
||||
<div>
|
||||
<h3 class="text-center">GSTR3B-Form</h3>
|
||||
<h5>GSTIN: {{ jsonData.gstin }}</h5>
|
||||
<h5>Period: {{ jsonData.ret_period }}</h5>
|
||||
</div>
|
||||
|
||||
<h5>3.1 Details of Outward Supplies and inward supplies liable to reverse charge</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nature Of Supplies</th>
|
||||
<th>Total Taxable value</th>
|
||||
<th>Integrated Tax</th>
|
||||
<th>Central Tax</th>
|
||||
<th>State/UT Tax</th>
|
||||
<th>Cess</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>(a) Outward taxable supplies(other than zero rated, nil rated and exempted</td>
|
||||
<td class="right">{{ jsonData.sup_details.osup_det.txval }}</td>
|
||||
<td class="right">{{ jsonData.sup_details.osup_det.iamt }}</td>
|
||||
<td class="right">{{ jsonData.sup_details.osup_det.camt }}</td>
|
||||
<td class="right">{{ jsonData.sup_details.osup_det.samt }}</td>
|
||||
<td class="right">{{ jsonData.sup_details.osup_det.csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>(b) Outward taxable supplies(zero rated)</td>
|
||||
<td class="right">{{ jsonData.sup_details.osup_zero.txval }}</td>
|
||||
<td class="right">{{ jsonData.sup_details.osup_zero.iamt }}</td>
|
||||
<td class="disabled"></td>
|
||||
<td class="disabled"></td>
|
||||
<td class="right">{{ jsonData.sup_details.osup_zero.csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>(b) Other outward supplies(Nil rated,Exempted)</td>
|
||||
<td class="right">{{ jsonData.sup_details.osup_nil_exmp.txval }}</td>
|
||||
<td class="disabled"></td>
|
||||
<td class="disabled"></td>
|
||||
<td class="disabled"></td>
|
||||
<td class="disabled"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>(d) Inward Supplies(liable to reverse charge</td>
|
||||
<td class="right">{{ jsonData.sup_details.isup_rev.txval }}</td>
|
||||
<td class="right">{{ jsonData.sup_details.isup_rev.iamt }}</td>
|
||||
<td class="right">{{ jsonData.sup_details.isup_rev.camt }}</td>
|
||||
<td class="right">{{ jsonData.sup_details.isup_rev.samt }}</td>
|
||||
<td class="right">{{ jsonData.sup_details.isup_rev.csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>(e) Non-GST outward supplies</td>
|
||||
<td class="right">{{ jsonData.sup_details.osup_nongst.txval }}</td>
|
||||
<td class="disabled"></td>
|
||||
<td class="disabled"></td>
|
||||
<td class="disabled"></td>
|
||||
<td class="disabled"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h5>3.2 Of the supplies shown in 3.1 (a) above, details of inter-State supplies made to unregisterd persons, composition taxable persons and UIN holders</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Place Of Supply (State/UT)</th>
|
||||
<th>Total Taxable Value</th>
|
||||
<th>Amount of Integrated Tax</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Supplies made to Unregistered Persons</td>
|
||||
<td class="right">
|
||||
<div v-for="(row, i) in jsonData.inter_sup.unreg_details" :key="i">
|
||||
<p>{{ row.pos }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<div v-for="(row, i) in jsonData.inter_sup.unreg_details" :key="i">
|
||||
<p>{{ row.txval }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<div v-for="(row, i) in jsonData.inter_sup.unreg_details" :key="i">
|
||||
<p>{{ row.iamt }}</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Suppliies made to Composition Taxable Persons</td>
|
||||
<td class="right">
|
||||
<div v-for="(row, i) in jsonData.inter_sup.comp_details" :key="i">
|
||||
<p>{{ row.pos }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<div v-for="(row, i) in jsonData.inter_sup.comp_details" :key="i">
|
||||
<p>{{ row.txval }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<div v-for="(row, i) in jsonData.inter_sup.comp_details" :key="i">
|
||||
<p>{{ row.iamt }}</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Supplies made to UIN holders</td>
|
||||
<td class="right">
|
||||
<div v-for="(row, i) in jsonData.inter_sup.uin_details" :key="i">
|
||||
<p>{{ row.pos }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<div v-for="(row, i) in jsonData.inter_sup.uin_details" :key="i">
|
||||
<p>{{ row.txval }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<div v-for="(row, i) in jsonData.inter_sup.uin_details" :key="i">
|
||||
<p>{{ row.iamt }}</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h5>4. Eligible ITC</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Details</th>
|
||||
<th>Integrated Tax</th>
|
||||
<th>Central Tax</th>
|
||||
<th>State/UT tax</th>
|
||||
<th>Cess</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<b>(A) ITC Available (whether in full op part)</b>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> (1) Import of goods</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[0].iamt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[0].camt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[0].samt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[0].csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> (2) Import of services</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[1].iamt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[1].camt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[1].samt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[1].csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> (3) Inward supplies liable to reverse charge (other than 1 & 2 above)</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[2].iamt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[2].camt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[2].samt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[2].csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> (4) Inward supplies from ISD</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[3].iamt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[3].camt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[3].samt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[3].csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> (5) All other ITC</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[4].iamt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[4].camt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[4].samt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_avl[4].csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>(B) ITC Reversed</b>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> (1) As per rules 42 & 43 of CGST Rules</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_rev[0].iamt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_rev[0].camt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_rev[0].samt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_rev[0].csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> (2) Others</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_rev[1].iamt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_rev[1].camt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_rev[1].samt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_rev[1].csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>(C) Net ITC Available(A) - (B)</b>
|
||||
</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_net.iamt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_net.camt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_net.samt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_net.csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>(D) Ineligible ITC</b>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> (1) As per section 17(5)</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_inelg[0].iamt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_inelg[0].camt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_inelg[0].samt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_inelg[0].csamt }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> (2) Others</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_inelg[1].iamt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_inelg[1].camt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_inelg[1].samt }}</td>
|
||||
<td class="right">{{ jsonData.itc_elg.itc_inelg[1].csamt }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h5>5. Values of exempt, nil rated and non-GST inward supplies</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nature of Supplies</th>
|
||||
<th>Inter-State Supplies</th>
|
||||
<th>Intra-State Supplies</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>From a supplier under composition scheme, Exempt and Nil rated</td>
|
||||
<td class="right">{{ jsonData.inward_sup.isup_details[0].inter }}</td>
|
||||
<td class="right">{{ jsonData.inward_sup.isup_details[0].intra }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Non GST Inward Supplies</td>
|
||||
<td class="right">{{ jsonData.inward_sup.isup_details[1].inter }}</td>
|
||||
<td class="right">{{ jsonData.inward_sup.isup_details[1].intra }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GSTR3BPrintView',
|
||||
props: ['doc'],
|
||||
computed: {
|
||||
jsonData() {
|
||||
return JSON.parse(this.doc.jsonData);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.print-format {
|
||||
}
|
||||
.disabled {
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
19
models/doctype/GSTR3B/GSTR3BServer.js
Normal file
19
models/doctype/GSTR3B/GSTR3BServer.js
Normal file
@ -0,0 +1,19 @@
|
||||
const GSTR3B = require('./GSTR3BDocument');
|
||||
|
||||
module.exports = class GSTR3BServer extends GSTR3B {
|
||||
async validate() {
|
||||
if (this.month.length === 0 || this.year.length != 4) {
|
||||
frappe.call({
|
||||
method: 'show-dialog',
|
||||
args: {
|
||||
title: 'Invalid Entry',
|
||||
message: `Month or Year is not valid`
|
||||
}
|
||||
});
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
async beforeInsert() {
|
||||
this.name = `${this.doctype} Report ${this.month} ${this.year}`;
|
||||
}
|
||||
};
|
@ -1,156 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const utils = require('../../../accounting/utils');
|
||||
|
||||
module.exports = {
|
||||
name: 'Invoice',
|
||||
doctype: 'DocType',
|
||||
documentClass: require('./InvoiceDocument.js'),
|
||||
print: {
|
||||
printFormat: 'Standard Invoice Format'
|
||||
},
|
||||
isSingle: 0,
|
||||
isChild: 0,
|
||||
isSubmittable: 1,
|
||||
keywordFields: ['name', 'customer'],
|
||||
settings: 'InvoiceSettings',
|
||||
showTitle: true,
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'date',
|
||||
label: 'Date',
|
||||
fieldtype: 'Date',
|
||||
// default: (new Date()).toISOString()
|
||||
},
|
||||
{
|
||||
fieldname: 'customer',
|
||||
label: 'Customer',
|
||||
fieldtype: 'Link',
|
||||
target: 'Party',
|
||||
required: 1,
|
||||
getFilters: (query) => {
|
||||
return {
|
||||
customer: 1
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'account',
|
||||
label: 'Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
formula: (doc) => doc.getFrom('Party', doc.customer , 'defaultAccount'),
|
||||
getFilters: (query, control) => {
|
||||
return {
|
||||
isGroup: 0,
|
||||
accountType: 'Receivable'
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'items',
|
||||
label: 'Items',
|
||||
fieldtype: 'Table',
|
||||
childtype: 'InvoiceItem',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
fieldname: 'netTotal',
|
||||
label: 'Net Total',
|
||||
fieldtype: 'Currency',
|
||||
formula: (doc) => doc.getSum('items', 'amount'),
|
||||
disabled: true,
|
||||
readOnly: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'taxes',
|
||||
label: 'Taxes',
|
||||
fieldtype: 'Table',
|
||||
childtype: 'TaxSummary',
|
||||
readOnly: 1,
|
||||
template: (doc, row) => {
|
||||
return `<div class='row'>
|
||||
<div class='col-6'></div>
|
||||
<div class='col-6'>
|
||||
<div class='row' v-for='row in value'>
|
||||
<div class='col-6'>{{row.account}} ({{row.rate}}%)</div>
|
||||
<div class='col-6 text-right'>
|
||||
{{frappe.format(row.amount, 'Currency')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'grandTotal',
|
||||
label: 'Grand Total',
|
||||
fieldtype: 'Currency',
|
||||
formula: (doc) => doc.getGrandTotal(),
|
||||
disabled: true,
|
||||
readOnly: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'outstandingAmount',
|
||||
label: 'Outstanding Amount',
|
||||
fieldtype: 'Currency',
|
||||
hidden: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'terms',
|
||||
label: 'Terms',
|
||||
fieldtype: 'Text'
|
||||
}
|
||||
],
|
||||
|
||||
layout: [
|
||||
// section 1
|
||||
{
|
||||
columns: [
|
||||
{ fields: ['customer', 'account'] },
|
||||
{ fields: ['date'] }
|
||||
]
|
||||
},
|
||||
|
||||
// section 2
|
||||
{
|
||||
columns: [
|
||||
{ fields: ['items'] }
|
||||
]
|
||||
},
|
||||
|
||||
// section 3
|
||||
{
|
||||
columns: [
|
||||
{ fields: ['netTotal', 'taxes', 'grandTotal'] }
|
||||
]
|
||||
},
|
||||
|
||||
// section 4
|
||||
{
|
||||
columns: [
|
||||
{ fields: ['terms'] }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
links: [
|
||||
utils.ledgerLink,
|
||||
{
|
||||
label: 'Make Payment',
|
||||
condition: form => form.doc.submitted && form.doc.outstandingAmount !== 0.0,
|
||||
action: async form => {
|
||||
const payment = await frappe.getNewDoc('Payment');
|
||||
payment.party = form.doc.customer;
|
||||
payment.account = form.doc.account;
|
||||
payment.for = [{ referenceType: form.doc.doctype, referenceName: form.doc.name, amount: form.doc.grandTotal }];
|
||||
payment.on('afterInsert', async () => {
|
||||
form.$formModal.close();
|
||||
|
||||
const _payment = await frappe.getDoc('Payment', payment.name);
|
||||
await _payment.submit();
|
||||
})
|
||||
await form.$formModal.open(payment);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
@ -1,72 +0,0 @@
|
||||
const BaseDocument = require('frappejs/model/document');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class Invoice extends BaseDocument {
|
||||
async getRowTax(row) {
|
||||
if (row.tax) {
|
||||
let tax = await this.getTax(row.tax);
|
||||
let taxAmount = [];
|
||||
for (let d of (tax.details || [])) {
|
||||
taxAmount.push({account: d.account, rate: d.rate, amount: row.amount * d.rate / 100});
|
||||
}
|
||||
return JSON.stringify(taxAmount);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
async getTax(tax) {
|
||||
if (!this._taxes) this._taxes = {};
|
||||
if (!this._taxes[tax]) this._taxes[tax] = await frappe.getDoc('Tax', tax);
|
||||
return this._taxes[tax];
|
||||
}
|
||||
|
||||
makeTaxSummary() {
|
||||
if (!this.taxes) this.taxes = [];
|
||||
|
||||
// reset tax amount
|
||||
this.taxes.map(d => { d.amount = 0; d.rate = 0; });
|
||||
|
||||
// calculate taxes
|
||||
for (let row of this.items) {
|
||||
if (row.taxAmount) {
|
||||
let taxAmount = JSON.parse(row.taxAmount);
|
||||
for (let rowTaxDetail of taxAmount) {
|
||||
let found = false;
|
||||
|
||||
// check if added in summary
|
||||
for (let taxDetail of this.taxes) {
|
||||
if (taxDetail.account === rowTaxDetail.account) {
|
||||
taxDetail.rate = rowTaxDetail.rate;
|
||||
taxDetail.amount += rowTaxDetail.amount;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// add new row
|
||||
if (!found) {
|
||||
this.taxes.push({
|
||||
account: rowTaxDetail.account,
|
||||
rate: rowTaxDetail.rate,
|
||||
amount: rowTaxDetail.amount
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear no taxes
|
||||
this.taxes = this.taxes.filter(d => d.amount);
|
||||
}
|
||||
|
||||
getGrandTotal() {
|
||||
this.makeTaxSummary();
|
||||
let grandTotal = this.netTotal;
|
||||
if (this.taxes) {
|
||||
for (let row of this.taxes) {
|
||||
grandTotal += row.amount;
|
||||
}
|
||||
}
|
||||
return grandTotal;
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<component :themeColor="color" :font="fontFamily" :is="invoiceTemplate" v-if="doc" :doc="doc"/>
|
||||
</template>
|
||||
<script>
|
||||
import InvoiceTemplate1 from '@/../models/doctype/Invoice/Templates/InvoiceTemplate1';
|
||||
import InvoiceTemplate2 from '@/../models/doctype/Invoice/Templates/InvoiceTemplate2';
|
||||
import InvoiceTemplate3 from '@/../models/doctype/Invoice/Templates/InvoiceTemplate3';
|
||||
|
||||
const invoiceTemplates = {
|
||||
'Basic I': InvoiceTemplate1,
|
||||
'Basic II': InvoiceTemplate2,
|
||||
'Modern': InvoiceTemplate3
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'InvoicePrint',
|
||||
props: ['doc', 'themeColor', 'template', 'font'],
|
||||
data() {
|
||||
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>
|
@ -1,28 +0,0 @@
|
||||
const Invoice = require('./InvoiceDocument');
|
||||
const LedgerPosting = require('../../../accounting/ledgerPosting');
|
||||
|
||||
module.exports = class InvoiceServer extends Invoice {
|
||||
getPosting() {
|
||||
let entries = new LedgerPosting({ reference: this, party: this.customer });
|
||||
entries.debit(this.account, this.grandTotal);
|
||||
|
||||
for (let item of this.items) {
|
||||
entries.credit(item.account, item.amount);
|
||||
}
|
||||
|
||||
if (this.taxes) {
|
||||
for (let tax of this.taxes) {
|
||||
entries.credit(tax.account, tax.amount);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
async afterSubmit() {
|
||||
await this.getPosting().post();
|
||||
}
|
||||
|
||||
async afterRevert() {
|
||||
await this.getPosting().postReverse();
|
||||
}
|
||||
};
|
@ -1,108 +0,0 @@
|
||||
<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>
|
@ -1,117 +0,0 @@
|
||||
<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>
|
@ -1,113 +0,0 @@
|
||||
<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>
|
@ -1,42 +0,0 @@
|
||||
module.exports = {
|
||||
name: "InvoiceSettings",
|
||||
label: "Invoice Settings",
|
||||
doctype: "DocType",
|
||||
isSingle: 1,
|
||||
isChild: 0,
|
||||
keywordFields: [],
|
||||
fields: [
|
||||
{
|
||||
fieldname: "numberSeries",
|
||||
label: "Number Series",
|
||||
fieldtype: "Link",
|
||||
target: "NumberSeries",
|
||||
required: 1,
|
||||
default: "INV"
|
||||
},
|
||||
{
|
||||
fieldname: "template",
|
||||
label: "Template",
|
||||
fieldtype: "Select",
|
||||
options: ["Basic I", "Basic II", "Modern"],
|
||||
required: 1,
|
||||
default: "Basic I"
|
||||
},
|
||||
{
|
||||
fieldname: "font",
|
||||
label: "Font",
|
||||
fieldtype: "Select",
|
||||
options: ["Montserrat", "Open Sans", "Oxygen", "Merriweather"],
|
||||
required: 1,
|
||||
default: "Montserrat"
|
||||
},
|
||||
{
|
||||
fieldname: "themeColor",
|
||||
label: "Theme Color",
|
||||
fieldtype: "Data",
|
||||
required: 1,
|
||||
default: "#000000",
|
||||
hidden: 1
|
||||
}
|
||||
]
|
||||
}
|
@ -2,11 +2,9 @@ module.exports = {
|
||||
name: 'Item',
|
||||
doctype: 'DocType',
|
||||
isSingle: 0,
|
||||
keywordFields: [
|
||||
'name',
|
||||
'description'
|
||||
],
|
||||
fields: [{
|
||||
keywordFields: ['name', 'description'],
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'name',
|
||||
label: 'Item Name',
|
||||
fieldtype: 'Data',
|
||||
@ -22,26 +20,40 @@ module.exports = {
|
||||
label: 'Unit',
|
||||
fieldtype: 'Select',
|
||||
default: 'No',
|
||||
options: [
|
||||
'No',
|
||||
'Kg',
|
||||
'Gram',
|
||||
'Hour',
|
||||
'Day'
|
||||
]
|
||||
options: ['No', 'Kg', 'Gram', 'Hour', 'Day']
|
||||
},
|
||||
{
|
||||
fieldname: 'incomeAccount',
|
||||
label: 'Income Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
required: 1
|
||||
required: 1,
|
||||
getFilters: query => {
|
||||
return {
|
||||
isGroup: 0,
|
||||
accountType: 'Income Account'
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'expenseAccount',
|
||||
label: 'Expense Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account'
|
||||
target: 'Account',
|
||||
required: 1,
|
||||
getFilters: query => {
|
||||
return {
|
||||
isGroup: 0,
|
||||
accountType: [
|
||||
'in',
|
||||
[
|
||||
'Cost of Goods Sold',
|
||||
'Expense Account',
|
||||
'Stock Received But Not Billed'
|
||||
]
|
||||
]
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'tax',
|
||||
@ -58,7 +70,8 @@ module.exports = {
|
||||
layout: [
|
||||
// section 1
|
||||
{
|
||||
columns: [{
|
||||
columns: [
|
||||
{
|
||||
fields: ['name', 'unit']
|
||||
},
|
||||
{
|
||||
@ -69,15 +82,18 @@ module.exports = {
|
||||
|
||||
// section 2
|
||||
{
|
||||
columns: [{
|
||||
fields: ['description']
|
||||
}]
|
||||
columns: [
|
||||
{
|
||||
fields: ['description']
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// section 3
|
||||
{
|
||||
title: 'Accounting',
|
||||
columns: [{
|
||||
columns: [
|
||||
{
|
||||
fields: ['incomeAccount', 'expenseAccount']
|
||||
},
|
||||
{
|
||||
@ -85,5 +101,49 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
links: [
|
||||
{
|
||||
label: 'New Sales Invoice',
|
||||
condition: form => !form.doc._notInserted,
|
||||
action: async form => {
|
||||
const invoice = await frappe.getNewDoc('SalesInvoice');
|
||||
invoice.items = [
|
||||
{
|
||||
item: form.doc.name,
|
||||
rate: form.doc.rate,
|
||||
tax: form.doc.tax
|
||||
}
|
||||
];
|
||||
invoice.on('afterInsert', async () => {
|
||||
form.$formModal.close();
|
||||
form.$router.push({
|
||||
path: `/edit/SalesInvoice/${invoice.name}`
|
||||
});
|
||||
});
|
||||
await form.$formModal.open(invoice);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'New Purchase Invoice',
|
||||
condition: form => !form.doc._notInserted,
|
||||
action: async form => {
|
||||
const invoice = await frappe.getNewDoc('PurchaseInvoice');
|
||||
invoice.items = [
|
||||
{
|
||||
item: form.doc.name,
|
||||
rate: form.doc.rate,
|
||||
tax: form.doc.tax
|
||||
}
|
||||
];
|
||||
invoice.on('afterInsert', async () => {
|
||||
form.$formModal.close();
|
||||
form.$router.push({
|
||||
path: `/edit/PurchaseInvoice/${invoice.name}`
|
||||
});
|
||||
});
|
||||
await form.$formModal.open(invoice);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -2,88 +2,80 @@ const frappe = require('frappejs');
|
||||
const utils = require('../../../accounting/utils');
|
||||
|
||||
module.exports = {
|
||||
name: "JournalEntry",
|
||||
doctype: "DocType",
|
||||
isSingle: 0,
|
||||
isChild: 0,
|
||||
isSubmittable: 1,
|
||||
keywordFields: ["name"],
|
||||
showTitle: true,
|
||||
settings: "JournalEntrySettings",
|
||||
fields: [
|
||||
{
|
||||
fieldname: "date",
|
||||
label: "Date",
|
||||
fieldtype: "Date"
|
||||
},
|
||||
{
|
||||
fieldname: "entryType",
|
||||
label: "Entry Type",
|
||||
fieldtype: "Select",
|
||||
options: [
|
||||
"Journal Entry",
|
||||
"Bank Entry",
|
||||
"Cash Entry",
|
||||
"Credit Card Entry",
|
||||
"Debit Note",
|
||||
"Credit Note",
|
||||
"Contra Entry",
|
||||
"Excise Entry",
|
||||
"Write Off Entry",
|
||||
"Opening Entry",
|
||||
"Depreciation Entry"
|
||||
],
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: "accounts",
|
||||
label: "Account Entries",
|
||||
fieldtype: "Table",
|
||||
childtype: "JournalEntryAccount",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
fieldname: "referenceNumber",
|
||||
label: "Reference Number",
|
||||
fieldtype: "Data",
|
||||
},
|
||||
{
|
||||
fieldname: "referenceDate",
|
||||
label: "Reference Date",
|
||||
fieldtype: "Date",
|
||||
},
|
||||
{
|
||||
fieldname: "userRemark",
|
||||
label: "User Remark",
|
||||
fieldtype: "Text",
|
||||
}
|
||||
],
|
||||
layout: [
|
||||
// section 1
|
||||
{
|
||||
columns: [
|
||||
{ fields: [ "date" ] },
|
||||
{ fields: [ "entryType" ] },
|
||||
]
|
||||
|
||||
},
|
||||
// section 2
|
||||
{
|
||||
columns: [
|
||||
{ fields: ["accounts"]},
|
||||
]
|
||||
},
|
||||
// section 3
|
||||
{
|
||||
columns: [
|
||||
{ fields: [ "referenceNumber"] },
|
||||
{ fields: [ "referenceDate"] }
|
||||
|
||||
]
|
||||
},
|
||||
// section 4
|
||||
{
|
||||
columns: [{ fields: [ "userRemark" ] }],
|
||||
}
|
||||
]
|
||||
}
|
||||
label: 'Journal Entry',
|
||||
name: 'JournalEntry',
|
||||
doctype: 'DocType',
|
||||
isSingle: 0,
|
||||
isChild: 0,
|
||||
isSubmittable: 1,
|
||||
keywordFields: ['name'],
|
||||
showTitle: true,
|
||||
settings: 'JournalEntrySettings',
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'entryType',
|
||||
label: 'Entry Type',
|
||||
fieldtype: 'Select',
|
||||
options: [
|
||||
'Select...',
|
||||
'Journal Entry',
|
||||
'Bank Entry',
|
||||
'Cash Entry',
|
||||
'Credit Card Entry',
|
||||
'Debit Note',
|
||||
'Credit Note',
|
||||
'Contra Entry',
|
||||
'Excise Entry',
|
||||
'Write Off Entry',
|
||||
'Opening Entry',
|
||||
'Depreciation Entry'
|
||||
],
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'date',
|
||||
label: 'Date',
|
||||
fieldtype: 'Date'
|
||||
},
|
||||
{
|
||||
fieldname: 'accounts',
|
||||
label: 'Account Entries',
|
||||
fieldtype: 'Table',
|
||||
childtype: 'JournalEntryAccount',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
fieldname: 'referenceNumber',
|
||||
label: 'Reference Number',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
{
|
||||
fieldname: 'referenceDate',
|
||||
label: 'Reference Date',
|
||||
fieldtype: 'Date'
|
||||
},
|
||||
{
|
||||
fieldname: 'userRemark',
|
||||
label: 'User Remark',
|
||||
fieldtype: 'Text'
|
||||
}
|
||||
],
|
||||
layout: [
|
||||
// section 1
|
||||
{
|
||||
columns: [{ fields: ['entryType'] }, { fields: ['date'] }]
|
||||
},
|
||||
// section 2
|
||||
{
|
||||
columns: [{ fields: ['accounts'] }]
|
||||
},
|
||||
// section 3
|
||||
{
|
||||
columns: [{ fields: ['referenceNumber'] }, { fields: ['referenceDate'] }]
|
||||
},
|
||||
// section 4
|
||||
{
|
||||
columns: [{ fields: ['userRemark'] }]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
7
models/doctype/JournalEntry/JournalEntryList.js
Normal file
7
models/doctype/JournalEntry/JournalEntryList.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { _ } from 'frappejs/utils';
|
||||
|
||||
export default {
|
||||
doctype: 'JournalEntry',
|
||||
title: _('Journal Entry'),
|
||||
columns: ['date', 'entryType']
|
||||
};
|
@ -1,33 +1,34 @@
|
||||
module.exports = {
|
||||
name: "JournalEntryAccount",
|
||||
doctype: "DocType",
|
||||
isSingle: 0,
|
||||
isChild: 1,
|
||||
keywordFields: [],
|
||||
layout: 'ratio',
|
||||
fields: [
|
||||
{
|
||||
"fieldname": "account",
|
||||
"label": "Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
"required": 1,
|
||||
getFilters: (query, control) => {
|
||||
return {
|
||||
keywords: ["like", query],
|
||||
isGroup: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "debit",
|
||||
"label": "Debit",
|
||||
"fieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "credit",
|
||||
"label": "Credit",
|
||||
"fieldtype": "Currency"
|
||||
}
|
||||
]
|
||||
}
|
||||
name: 'JournalEntryAccount',
|
||||
doctype: 'DocType',
|
||||
isSingle: 0,
|
||||
isChild: 1,
|
||||
keywordFields: [],
|
||||
layout: 'ratio',
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'account',
|
||||
label: 'Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
required: 1,
|
||||
getFilters: (query, control) => {
|
||||
if (query)
|
||||
return {
|
||||
keywords: ['like', query],
|
||||
isGroup: 0
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'debit',
|
||||
label: 'Debit',
|
||||
fieldtype: 'Currency'
|
||||
},
|
||||
{
|
||||
fieldname: 'credit',
|
||||
label: 'Credit',
|
||||
fieldtype: 'Currency'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -3,10 +3,8 @@ import { _ } from 'frappejs/utils';
|
||||
export default {
|
||||
doctype: 'Party',
|
||||
title: _('Customer'),
|
||||
columns: [
|
||||
'name'
|
||||
],
|
||||
columns: ['name', 'defaultAccount', 'address'],
|
||||
filters: {
|
||||
customer: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,54 +1,127 @@
|
||||
module.exports = {
|
||||
"name": "Party",
|
||||
"doctype": "DocType",
|
||||
"isSingle": 0,
|
||||
"istable": 0,
|
||||
"keywordFields": [
|
||||
"name"
|
||||
],
|
||||
"fields": [{
|
||||
"fieldname": "name",
|
||||
"label": "Name",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
fieldname: "address",
|
||||
label: "Address",
|
||||
fieldtype: "Link",
|
||||
target: "Address"
|
||||
},
|
||||
{
|
||||
fieldname: 'defaultAccount',
|
||||
label: 'Default Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
getFilters: (query, control) => {
|
||||
return {
|
||||
isGroup: 0,
|
||||
accountType: 'Receivable'
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "customer",
|
||||
"label": "Customer",
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier",
|
||||
"label": "Supplier",
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
],
|
||||
name: 'Party',
|
||||
label: 'Party',
|
||||
doctype: 'DocType',
|
||||
isSingle: 0,
|
||||
keywordFields: ['name'],
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'name',
|
||||
label: 'Name',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'address',
|
||||
label: 'Address',
|
||||
fieldtype: 'Link',
|
||||
target: 'Address'
|
||||
},
|
||||
{
|
||||
fieldname: 'defaultAccount',
|
||||
label: 'Default Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
getFilters: (query, doc) => {
|
||||
return {
|
||||
isGroup: 0,
|
||||
accountType: doc.customer === 1 ? 'Receivable' : 'Payable'
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'currency',
|
||||
label: 'Currency',
|
||||
fieldtype: 'Link',
|
||||
target: 'Currency',
|
||||
formula: async doc => {
|
||||
const { currency } = await frappe.getSingle('AccountingSettings');
|
||||
return currency;
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'customer',
|
||||
label: 'Customer',
|
||||
fieldtype: 'Check'
|
||||
},
|
||||
{
|
||||
fieldname: 'supplier',
|
||||
label: 'Supplier',
|
||||
fieldtype: 'Check'
|
||||
}
|
||||
],
|
||||
|
||||
links: [{
|
||||
label: 'Invoices',
|
||||
condition: (form) => form.doc.customer,
|
||||
action: form => {
|
||||
form.$router.push({
|
||||
path: `/report/sales-register?&customer=${form.doc.name}`
|
||||
});
|
||||
}
|
||||
}]
|
||||
}
|
||||
getFormTitle(doc) {
|
||||
if (doc.customer) return 'Customer';
|
||||
return 'Supplier';
|
||||
},
|
||||
|
||||
getListTitle(doc) {
|
||||
if (doc.customer) return 'Customer';
|
||||
return 'Supplier';
|
||||
},
|
||||
|
||||
links: [
|
||||
{
|
||||
label: 'New Sales Invoice',
|
||||
condition: form => form.doc.customer,
|
||||
action: async form => {
|
||||
const invoice = await frappe.getNewDoc('SalesInvoice');
|
||||
invoice.customer = form.doc.name;
|
||||
invoice.account = form.doc.defaultAccount;
|
||||
invoice.on('afterInsert', async () => {
|
||||
form.$formModal.close();
|
||||
form.$router.push({
|
||||
path: `/edit/SalesInvoice/${invoice.name}`
|
||||
});
|
||||
});
|
||||
await form.$formModal.open(invoice);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sales Invoices',
|
||||
condition: form => form.doc.customer,
|
||||
action: form => {
|
||||
form.$router.push({
|
||||
path: `/list/SalesInvoice?customer=${form.doc.name}`
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'New Purchase Invoice',
|
||||
condition: form => form.doc.supplier,
|
||||
action: async form => {
|
||||
const invoice = await frappe.getNewDoc('PurchaseInvoice');
|
||||
invoice.supplier = form.doc.name;
|
||||
invoice.account = form.doc.defaultAccount;
|
||||
invoice.on('afterInsert', async () => {
|
||||
form.$formModal.close();
|
||||
form.$router.push({
|
||||
path: `/edit/PurchaseInvoice/${invoice.name}`
|
||||
});
|
||||
});
|
||||
await form.$formModal.open(invoice);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Purchase Invoices',
|
||||
condition: form => form.doc.supplier,
|
||||
action: form => {
|
||||
form.$router.push({
|
||||
path: `/list/PurchaseInvoice?supplier=${form.doc.name}`
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
condition: form => form.doc.customer,
|
||||
action: async form => {
|
||||
const party = await frappe.getDoc('Party', form.doc.name);
|
||||
await party.delete();
|
||||
form.$router.push({
|
||||
path: `/list/Customer`
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
7
models/doctype/Party/PartyList.js
Normal file
7
models/doctype/Party/PartyList.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { _ } from 'frappejs/utils';
|
||||
|
||||
export default {
|
||||
doctype: 'Party',
|
||||
title: filters => (filters.customer ? 'Customer' : 'Supplier'),
|
||||
columns: ['name', 'defaultAccount', 'address']
|
||||
};
|
21
models/doctype/Party/PartyServer.js
Normal file
21
models/doctype/Party/PartyServer.js
Normal file
@ -0,0 +1,21 @@
|
||||
const BaseDocument = require('frappejs/model/document');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class PartyServer extends BaseDocument {
|
||||
beforeInsert() {
|
||||
if (this.customer && this.supplier) {
|
||||
frappe.call({
|
||||
method: 'show-dialog',
|
||||
args: {
|
||||
title: 'Invalid Entry',
|
||||
message: 'Select a single party type.'
|
||||
}
|
||||
});
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if (this.gstin && ['Unregistered', 'Consumer'].includes(this.gstType)) {
|
||||
this.gstin = '';
|
||||
}
|
||||
}
|
||||
};
|
@ -1,12 +1,21 @@
|
||||
const party = require('./Party')
|
||||
const party = require('./Party');
|
||||
|
||||
party.fields.splice(3, 0, { //insert at 3rd position
|
||||
party.fields.splice(3, 0, {
|
||||
//insert at 3rd position
|
||||
fieldname: 'gstin',
|
||||
label: 'GSTIN No.',
|
||||
fieldtype: 'Data',
|
||||
hidden: 0
|
||||
})
|
||||
party.fields.join()
|
||||
const newParty = party
|
||||
hidden: form => {
|
||||
return form.gstType === 'Registered Regular' ? 0 : 1;
|
||||
}
|
||||
});
|
||||
party.fields.splice(4, 0, {
|
||||
fieldname: 'gstType',
|
||||
label: 'GST Registration Type',
|
||||
fieldtype: 'Select',
|
||||
options: ['Unregistered', 'Registered Regular', 'Consumer']
|
||||
});
|
||||
party.fields.join();
|
||||
const newParty = party;
|
||||
|
||||
module.exports = newParty
|
||||
module.exports = newParty;
|
||||
|
10
models/doctype/Party/SupplierList.js
Normal file
10
models/doctype/Party/SupplierList.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { _ } from 'frappejs/utils';
|
||||
|
||||
export default {
|
||||
doctype: 'Party',
|
||||
title: _('Supplier'),
|
||||
columns: ['name', 'defaultAccount', 'address'],
|
||||
filters: {
|
||||
supplier: 1
|
||||
}
|
||||
};
|
@ -1,110 +1,165 @@
|
||||
const utils = require('../../../accounting/utils');
|
||||
|
||||
module.exports = {
|
||||
name: "Payment",
|
||||
label: "Payment",
|
||||
isSingle: 0,
|
||||
isChild: 0,
|
||||
isSubmittable: 1,
|
||||
keywordFields: [],
|
||||
settings: "PaymentSettings",
|
||||
fields: [{
|
||||
"fieldname": "date",
|
||||
"label": "Posting Date",
|
||||
"fieldtype": "Date",
|
||||
// default: (new Date()).toISOString()
|
||||
},
|
||||
{
|
||||
fieldname: "party",
|
||||
label: "Party",
|
||||
fieldtype: "Link",
|
||||
target: "Party",
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: "account",
|
||||
label: "Account",
|
||||
fieldtype: "Link",
|
||||
target: "Account",
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: "paymentAccount",
|
||||
label: "Payment Account",
|
||||
fieldtype: "Link",
|
||||
target: "Account",
|
||||
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",
|
||||
label: "Amount",
|
||||
fieldtype: "Currency",
|
||||
required: 1,
|
||||
disabled: true,
|
||||
formula: (doc) => doc.getSum('for', 'amount')
|
||||
},
|
||||
{
|
||||
fieldname: "writeoff",
|
||||
label: "Write Off / Refund",
|
||||
fieldtype: "Currency",
|
||||
},
|
||||
{
|
||||
fieldname: "for",
|
||||
label: "Payment For",
|
||||
fieldtype: "Table",
|
||||
childtype: "PaymentFor",
|
||||
required: 1
|
||||
name: 'Payment',
|
||||
label: 'Payment',
|
||||
isSingle: 0,
|
||||
isChild: 0,
|
||||
isSubmittable: 1,
|
||||
keywordFields: [],
|
||||
settings: 'PaymentSettings',
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'party',
|
||||
label: 'Party',
|
||||
fieldtype: 'Link',
|
||||
target: 'Party',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'date',
|
||||
label: 'Posting Date',
|
||||
fieldtype: 'Date',
|
||||
defaultValue: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
fieldname: 'account',
|
||||
label: 'From Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
required: 1,
|
||||
getFilters: (query, doc) => {
|
||||
if (doc.paymentType === 'Pay') {
|
||||
if (doc.paymentMethod === 'Cash') {
|
||||
return { accountType: 'Cash', isGroup: 0 };
|
||||
} else {
|
||||
return { accountType: ['in', ['Bank', 'Cash']], isGroup: 0 };
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
layout: [{
|
||||
columns: [{
|
||||
fields: ['date', 'party']
|
||||
},
|
||||
{
|
||||
fields: ['account', 'paymentAccount']
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
columns: [{
|
||||
fields: ['referenceId']
|
||||
}, {
|
||||
fields: ['referenceDate']
|
||||
},{
|
||||
fields: ['clearanceDate']
|
||||
}]
|
||||
},
|
||||
{
|
||||
columns: [{
|
||||
fields: ['for']
|
||||
}]
|
||||
},
|
||||
{
|
||||
columns: [{
|
||||
fields: ['amount', 'writeoff']
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'paymentType',
|
||||
label: 'Payment Type',
|
||||
fieldtype: 'Select',
|
||||
options: ['', 'Receive', 'Pay'],
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'paymentAccount',
|
||||
label: 'To Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
required: 1,
|
||||
getFilters: (query, doc) => {
|
||||
if (doc.paymentType === 'Receive') {
|
||||
if (doc.paymentMethod === 'Cash') {
|
||||
return { accountType: 'Cash', isGroup: 0 };
|
||||
} else {
|
||||
return { accountType: ['in', ['Bank', 'Cash']], isGroup: 0 };
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'paymentMethod',
|
||||
label: 'Payment Method',
|
||||
fieldtype: 'Select',
|
||||
options: ['', 'Cash', 'Cheque', 'Transfer'],
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'referenceId',
|
||||
label: 'Ref. / Cheque No.',
|
||||
fieldtype: 'Data',
|
||||
required: 1 // TODO: UNIQUE
|
||||
},
|
||||
{
|
||||
fieldname: 'referenceDate',
|
||||
label: 'Ref. Date',
|
||||
fieldtype: 'Date'
|
||||
},
|
||||
{
|
||||
fieldname: 'clearanceDate',
|
||||
label: 'Clearance Date',
|
||||
fieldtype: 'Date',
|
||||
hidden: doc => {
|
||||
return doc.paymentMethod === 'Cash' ? 1 : 0;
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'amount',
|
||||
label: 'Amount',
|
||||
fieldtype: 'Currency',
|
||||
required: 1,
|
||||
disabled: true,
|
||||
formula: doc => {
|
||||
return frappe.format(doc.getSum('for', 'amount'), 'Currency');
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'writeoff',
|
||||
label: 'Write Off / Refund',
|
||||
fieldtype: 'Currency'
|
||||
},
|
||||
{
|
||||
fieldname: 'for',
|
||||
label: 'Payment For',
|
||||
fieldtype: 'Table',
|
||||
childtype: 'PaymentFor',
|
||||
required: 1
|
||||
}
|
||||
],
|
||||
|
||||
links: [
|
||||
utils.ledgerLink
|
||||
]
|
||||
}
|
||||
layout: [
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fields: ['party', 'account']
|
||||
},
|
||||
{
|
||||
fields: ['date', 'paymentAccount']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fields: ['paymentMethod']
|
||||
},
|
||||
{
|
||||
fields: ['paymentType']
|
||||
},
|
||||
{
|
||||
fields: ['referenceId']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fields: ['referenceDate']
|
||||
},
|
||||
{
|
||||
fields: ['clearanceDate']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fields: ['for']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fields: ['amount', 'writeoff']
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
links: [utils.ledgerLink]
|
||||
};
|
||||
|
@ -7,24 +7,33 @@ export default {
|
||||
columns: [
|
||||
'party',
|
||||
{
|
||||
label: 'Payment',
|
||||
label: 'Status',
|
||||
fieldname: 'status',
|
||||
fieldtype: 'Select',
|
||||
size: 'small',
|
||||
options: ['Status...', 'Reconciled', 'Not Reconciled'],
|
||||
getValue(doc) {
|
||||
if (doc.submitted === 1 && doc.clearanceDate !== null) {
|
||||
if (
|
||||
doc.submitted === 1 &&
|
||||
(doc.clearanceDate !== null || doc.paymentMethod === 'Cash')
|
||||
) {
|
||||
return 'Reconciled';
|
||||
}
|
||||
return 'Not Reconciled';
|
||||
},
|
||||
getIndicator(doc) {
|
||||
if (doc.submitted === 1 && doc.clearanceDate !== null) {
|
||||
if (
|
||||
doc.submitted === 1 &&
|
||||
(doc.clearanceDate !== null || doc.paymentMethod === 'Cash')
|
||||
) {
|
||||
return indicators.GREEN;
|
||||
}
|
||||
return indicators.ORANGE;
|
||||
}
|
||||
},
|
||||
'account',
|
||||
'amount',
|
||||
'paymentType',
|
||||
'date',
|
||||
'clearanceDate',
|
||||
'name'
|
||||
'amount'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
@ -3,28 +3,83 @@ const frappe = require('frappejs');
|
||||
const LedgerPosting = require('../../../accounting/ledgerPosting');
|
||||
|
||||
module.exports = class PaymentServer extends BaseDocument {
|
||||
getPosting() {
|
||||
let entries = new LedgerPosting({reference: this, party: this.party});
|
||||
entries.debit(this.paymentAccount, this.amount);
|
||||
|
||||
for (let row of this.for) {
|
||||
entries.credit(this.account, row.amount, row.referenceType, row.referenceName);
|
||||
}
|
||||
|
||||
return entries;
|
||||
|
||||
async change({ changed }) {
|
||||
if (changed === 'for') {
|
||||
this.amount = 0;
|
||||
for (let paymentReference of this.for) {
|
||||
this.amount += frappe.parseNumber(paymentReference.amount);
|
||||
}
|
||||
this.amount = frappe.format(this.amount, 'Currency');
|
||||
}
|
||||
}
|
||||
|
||||
async afterSubmit() {
|
||||
await this.getPosting().post();
|
||||
for (let row of this.for) {
|
||||
if (row.referenceType === 'Invoice') {
|
||||
await frappe.db.setValue('Invoice', row.referenceName, 'outstandingAmount', 0.0);
|
||||
async getPosting() {
|
||||
let entries = new LedgerPosting({ reference: this, party: this.party });
|
||||
await entries.debit(this.paymentAccount, this.amount);
|
||||
for (let row of this.for) {
|
||||
await entries.credit(this.account, row.amount);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
async beforeSubmit() {
|
||||
if (this.for.length > 0)
|
||||
for (let row of this.for) {
|
||||
if (['SalesInvoice', 'PurchaseInvoice'].includes(row.referenceType)) {
|
||||
let { outstandingAmount, grandTotal } = await frappe.getDoc(
|
||||
row.referenceType,
|
||||
row.referenceName
|
||||
);
|
||||
if (outstandingAmount === null) {
|
||||
outstandingAmount = grandTotal;
|
||||
}
|
||||
if (
|
||||
0 >= frappe.parseNumber(this.amount) ||
|
||||
frappe.parseNumber(this.amount) >
|
||||
frappe.parseNumber(outstandingAmount)
|
||||
) {
|
||||
frappe.call({
|
||||
method: 'show-dialog',
|
||||
args: {
|
||||
title: 'Invalid Payment Entry',
|
||||
message: `Payment amount (${
|
||||
this.amount
|
||||
}) should be greater than 0 and less than Outstanding amount (${outstandingAmount})`
|
||||
}
|
||||
});
|
||||
throw new Error();
|
||||
} else {
|
||||
await frappe.db.setValue(
|
||||
row.referenceType,
|
||||
row.referenceName,
|
||||
'outstandingAmount',
|
||||
frappe.parseNumber(outstandingAmount) -
|
||||
frappe.parseNumber(this.amount)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
frappe.call({
|
||||
method: 'show-dialog',
|
||||
args: {
|
||||
title: 'Invalid Payment Entry',
|
||||
message: `No reference for the payment.`
|
||||
}
|
||||
});
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
async afterRevert() {
|
||||
await this.getPosting().postReverse();
|
||||
}
|
||||
}
|
||||
async afterSubmit() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.post();
|
||||
}
|
||||
|
||||
async afterRevert() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.postReverse();
|
||||
|
||||
// Maybe revert outstanding amount of invoice too?
|
||||
}
|
||||
};
|
||||
|
@ -1,28 +1,30 @@
|
||||
module.exports = {
|
||||
name: "PaymentFor",
|
||||
label: "Payment For",
|
||||
isSingle: 0,
|
||||
isChild: 1,
|
||||
keywordFields: [],
|
||||
fields: [
|
||||
{
|
||||
fieldname: "referenceType",
|
||||
label: "Reference Type",
|
||||
fieldtype: "Data",
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: "referenceName",
|
||||
label: "Reference Name",
|
||||
fieldtype: "DynamicLink",
|
||||
references: "referenceType",
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: "amount",
|
||||
label: "Amount",
|
||||
fieldtype: "Currency",
|
||||
required: 1
|
||||
},
|
||||
]
|
||||
}
|
||||
name: 'PaymentFor',
|
||||
label: 'Payment For',
|
||||
isSingle: 0,
|
||||
isChild: 1,
|
||||
keywordFields: [],
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'referenceType',
|
||||
label: 'Reference Type',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'referenceName',
|
||||
label: 'Reference Name',
|
||||
fieldtype: 'DynamicLink',
|
||||
references: 'referenceType',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'amount',
|
||||
label: 'Amount',
|
||||
fieldtype: 'Currency',
|
||||
formula: (row, doc) =>
|
||||
doc.getFrom(row.referenceType, row.referenceName, 'grandTotal'),
|
||||
required: 1
|
||||
}
|
||||
]
|
||||
};
|
||||
|
204
models/doctype/PurchaseInvoice/PurchaseInvoice.js
Normal file
204
models/doctype/PurchaseInvoice/PurchaseInvoice.js
Normal file
@ -0,0 +1,204 @@
|
||||
const frappe = require('frappejs');
|
||||
const utils = require('../../../accounting/utils');
|
||||
|
||||
module.exports = {
|
||||
name: 'PurchaseInvoice',
|
||||
doctype: 'DocType',
|
||||
label: 'Purchase Invoice',
|
||||
documentClass: require('./PurchaseInvoiceDocument'),
|
||||
isSingle: 0,
|
||||
isChild: 0,
|
||||
isSubmittable: 1,
|
||||
keywordFields: ['name', 'supplier'],
|
||||
settings: 'PurchaseInvoiceSettings',
|
||||
showTitle: true,
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'date',
|
||||
label: 'Date',
|
||||
fieldtype: 'Date',
|
||||
defaultValue: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
fieldname: 'supplier',
|
||||
label: 'Supplier',
|
||||
fieldtype: 'Link',
|
||||
target: 'Party',
|
||||
required: 1,
|
||||
getFilters: (query, control) => {
|
||||
if (!query) return { supplier: 1 };
|
||||
return {
|
||||
keywords: ['like', query],
|
||||
supplier: 1
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'account',
|
||||
label: 'Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
formula: doc => doc.getFrom('Party', doc.supplier, 'defaultAccount'),
|
||||
getFilters: (query, control) => {
|
||||
if (!query) return { isGroup: 0, accountType: 'Payable' };
|
||||
return {
|
||||
keywords: ['like', query],
|
||||
isGroup: 0,
|
||||
accountType: 'Payable'
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'currency',
|
||||
label: 'Customer Currency',
|
||||
fieldtype: 'Link',
|
||||
target: 'Currency',
|
||||
hidden: 1,
|
||||
formula: doc => doc.getFrom('Party', doc.supplier, 'currency')
|
||||
},
|
||||
{
|
||||
fieldname: 'exchangeRate',
|
||||
label: 'Exchange Rate',
|
||||
fieldtype: 'Float',
|
||||
placeholder: '1 USD = [?] INR',
|
||||
hidden: doc => !doc.isForeignTransaction()
|
||||
},
|
||||
{
|
||||
fieldname: 'items',
|
||||
label: 'Items',
|
||||
fieldtype: 'Table',
|
||||
childtype: 'PurchaseInvoiceItem',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
fieldname: 'baseNetTotal',
|
||||
label: 'Net Total (INR)',
|
||||
fieldtype: 'Currency',
|
||||
formula: async doc => await doc.getBaseNetTotal(),
|
||||
disabled: true,
|
||||
readOnly: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'netTotal',
|
||||
label: 'Net Total (USD)',
|
||||
fieldtype: 'Currency',
|
||||
hidden: doc => !doc.isForeignTransaction(),
|
||||
formula: async doc =>
|
||||
await doc.formatIntoCustomerCurrency(doc.getSum('items', 'amount')),
|
||||
disabled: true,
|
||||
readOnly: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'taxes',
|
||||
label: 'Taxes',
|
||||
fieldtype: 'Table',
|
||||
childtype: 'TaxSummary',
|
||||
readOnly: 1,
|
||||
template: (doc, row) => {
|
||||
return `<div class='row'>
|
||||
<div class='col-6'></div>
|
||||
<div class='col-6'>
|
||||
<div class='row' v-for='row in value'>
|
||||
<div class='col-6'>{{ row.account }} ({{row.rate}}%)</div>
|
||||
<div class='col-6 text-right'>
|
||||
{{ row.amount }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'baseGrandTotal',
|
||||
label: 'Grand Total (INR)',
|
||||
fieldtype: 'Currency',
|
||||
formula: async doc => await doc.getBaseGrandTotal(),
|
||||
disabled: true,
|
||||
readOnly: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'grandTotal',
|
||||
label: 'Grand Total (USD)',
|
||||
fieldtype: 'Currency',
|
||||
hidden: doc => !doc.isForeignTransaction(),
|
||||
formula: async doc => await doc.getGrandTotal(),
|
||||
disabled: true,
|
||||
readOnly: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'terms',
|
||||
label: 'Terms',
|
||||
fieldtype: 'Text'
|
||||
},
|
||||
{
|
||||
fieldname: 'outstandingAmount',
|
||||
label: 'Outstanding Amount',
|
||||
fieldtype: 'Currency',
|
||||
hidden: 1
|
||||
}
|
||||
],
|
||||
|
||||
layout: [
|
||||
// section 1
|
||||
{
|
||||
columns: [
|
||||
{ fields: ['supplier', 'account'] },
|
||||
{ fields: ['date', 'exchangeRate'] }
|
||||
]
|
||||
},
|
||||
|
||||
// section 2
|
||||
{
|
||||
columns: [{ fields: ['items'] }]
|
||||
},
|
||||
|
||||
// section 3
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fields: [
|
||||
'baseNetTotal',
|
||||
'netTotal',
|
||||
'taxes',
|
||||
'baseGrandTotal',
|
||||
'grandTotal'
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// section 4
|
||||
{
|
||||
columns: [{ fields: ['terms'] }]
|
||||
}
|
||||
],
|
||||
|
||||
links: [
|
||||
utils.ledgerLink,
|
||||
{
|
||||
label: 'Make Payment',
|
||||
condition: form =>
|
||||
form.doc.submitted && form.doc.outstandingAmount != 0.0,
|
||||
action: async form => {
|
||||
const payment = await frappe.getNewDoc('Payment');
|
||||
payment.paymentType = 'Pay';
|
||||
payment.party = form.doc.supplier;
|
||||
payment.paymentAccount = form.doc.account;
|
||||
payment.for = [
|
||||
{
|
||||
referenceType: form.doc.doctype,
|
||||
referenceName: form.doc.name,
|
||||
amount: form.doc.outstandingAmount
|
||||
}
|
||||
];
|
||||
payment.on('afterInsert', async () => {
|
||||
form.$formModal.close();
|
||||
form.$router.push({
|
||||
path: `/edit/Payment/${payment.name}`
|
||||
});
|
||||
});
|
||||
await form.$formModal.open(payment);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
@ -0,0 +1,4 @@
|
||||
const SalesInvoiceDocument = require('../SalesInvoice/SalesInvoiceDocument');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class PurchaseInvoice extends SalesInvoiceDocument {};
|
32
models/doctype/PurchaseInvoice/PurchaseInvoiceList.js
Normal file
32
models/doctype/PurchaseInvoice/PurchaseInvoiceList.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { _ } from 'frappejs/utils';
|
||||
import indicators from 'frappejs/ui/constants/indicators';
|
||||
|
||||
export default {
|
||||
doctype: 'PurchaseInvoice',
|
||||
title: _('Purchase Invoice'),
|
||||
columns: [
|
||||
'supplier',
|
||||
{
|
||||
label: 'Status',
|
||||
fieldname: 'status',
|
||||
fieldtype: 'Select',
|
||||
size: 'small',
|
||||
options: ['Status...', 'Paid', 'Pending'],
|
||||
getValue(doc) {
|
||||
if (doc.submitted === 1 && doc.outstandingAmount === 0.0) {
|
||||
return 'Paid';
|
||||
}
|
||||
return 'Pending';
|
||||
},
|
||||
getIndicator(doc) {
|
||||
if (doc.submitted === 1 && doc.outstandingAmount === 0.0) {
|
||||
return indicators.GREEN;
|
||||
}
|
||||
return indicators.ORANGE;
|
||||
}
|
||||
},
|
||||
'date',
|
||||
'grandTotal',
|
||||
'outstandingAmount'
|
||||
]
|
||||
};
|
72
models/doctype/PurchaseInvoice/PurchaseInvoiceServer.js
Normal file
72
models/doctype/PurchaseInvoice/PurchaseInvoiceServer.js
Normal file
@ -0,0 +1,72 @@
|
||||
const frappe = require('frappejs');
|
||||
const PurchaseInvoice = require('./PurchaseInvoiceDocument');
|
||||
const LedgerPosting = require('../../../accounting/ledgerPosting');
|
||||
|
||||
module.exports = class PurchaseInvoiceServer extends PurchaseInvoice {
|
||||
async getPosting() {
|
||||
let entries = new LedgerPosting({ reference: this, party: this.supplier });
|
||||
await entries.credit(this.account, this.baseGrandTotal);
|
||||
|
||||
for (let item of this.items) {
|
||||
const baseItemAmount = frappe.format(
|
||||
frappe.parseNumber(item.amount) * this.exchangeRate,
|
||||
'Currency'
|
||||
);
|
||||
await entries.debit(item.account, baseItemAmount);
|
||||
}
|
||||
|
||||
if (this.taxes) {
|
||||
for (let tax of this.taxes) {
|
||||
const baseTaxAmount = frappe.format(
|
||||
frappe.parseNumber(tax.amount) * this.exchangeRate,
|
||||
'Currency'
|
||||
);
|
||||
await entries.debit(tax.account, baseTaxAmount);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
async getPayments() {
|
||||
let payments = await frappe.db.getAll({
|
||||
doctype: 'PaymentFor',
|
||||
fields: ['parent'],
|
||||
filters: { referenceName: this.name },
|
||||
orderBy: 'name'
|
||||
});
|
||||
if (payments.length != 0) {
|
||||
return payments;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async beforeInsert() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.validateEntries();
|
||||
}
|
||||
|
||||
async afterSubmit() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.post();
|
||||
await frappe.db.setValue(
|
||||
'PurchaseInvoice',
|
||||
this.name,
|
||||
'outstandingAmount',
|
||||
this.baseGrandTotal
|
||||
);
|
||||
}
|
||||
|
||||
async afterRevert() {
|
||||
let paymentRefList = await this.getPayments();
|
||||
for (let paymentFor of paymentRefList) {
|
||||
const paymentReference = paymentFor.parent;
|
||||
const payment = await frappe.getDoc('Payment', paymentReference);
|
||||
const paymentEntries = await payment.getPosting();
|
||||
await paymentEntries.postReverse();
|
||||
// To set the payment status as unsubmitted.
|
||||
payment.revert();
|
||||
}
|
||||
const entries = await this.getPosting();
|
||||
await entries.postReverse();
|
||||
}
|
||||
};
|
77
models/doctype/PurchaseInvoiceItem/PurchaseInvoiceItem.js
Normal file
77
models/doctype/PurchaseInvoiceItem/PurchaseInvoiceItem.js
Normal file
@ -0,0 +1,77 @@
|
||||
module.exports = {
|
||||
name: 'PurchaseInvoiceItem',
|
||||
label: 'Purchase Invoice Item',
|
||||
doctype: 'DocType',
|
||||
isSingle: 0,
|
||||
isChild: 1,
|
||||
keywordFields: [],
|
||||
layout: 'ratio',
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'item',
|
||||
label: 'Item',
|
||||
fieldtype: 'Link',
|
||||
target: 'Item',
|
||||
required: 1,
|
||||
width: 2
|
||||
},
|
||||
{
|
||||
fieldname: 'description',
|
||||
label: 'Description',
|
||||
fieldtype: 'Text',
|
||||
formula: (row, doc) => doc.getFrom('Item', row.item, 'description'),
|
||||
hidden: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'quantity',
|
||||
label: 'Quantity',
|
||||
fieldtype: 'Float',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'rate',
|
||||
label: 'Rate',
|
||||
fieldtype: 'Currency',
|
||||
required: 1,
|
||||
formula: (row, doc) => doc.getFrom('Item', row.item, 'rate')
|
||||
},
|
||||
{
|
||||
fieldname: 'account',
|
||||
label: 'Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
required: 1,
|
||||
formula: (row, doc) => doc.getFrom('Item', row.item, 'expenseAccount')
|
||||
},
|
||||
{
|
||||
fieldname: 'tax',
|
||||
label: 'Tax',
|
||||
fieldtype: 'Link',
|
||||
target: 'Tax',
|
||||
formula: (row, doc) => {
|
||||
if (row.tax) return row.tax;
|
||||
return doc.getFrom('Item', row.item, 'tax');
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'amount',
|
||||
label: 'Amount',
|
||||
fieldtype: 'Currency',
|
||||
readOnly: 1,
|
||||
disabled: true,
|
||||
formula: async (row, doc) => {
|
||||
return await doc.formatIntoCustomerCurrency(
|
||||
row.quantity * frappe.parseNumber(row.rate)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'taxAmount',
|
||||
label: 'Tax Amount',
|
||||
hidden: 1,
|
||||
readOnly: 1,
|
||||
fieldtype: 'Text',
|
||||
formula: (row, doc) => doc.getRowTax(row)
|
||||
}
|
||||
]
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
name: 'PurchaseInvoiceSettings',
|
||||
label: 'Purchase Invoice Settings',
|
||||
doctype: 'DocType',
|
||||
isSingle: 1,
|
||||
isChild: 0,
|
||||
keywordFields: [],
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'numberSeries',
|
||||
label: 'Number Series',
|
||||
fieldtype: 'Link',
|
||||
target: 'NumberSeries',
|
||||
required: 1,
|
||||
default: 'PINV'
|
||||
}
|
||||
]
|
||||
};
|
@ -1,16 +1,20 @@
|
||||
const model = require('frappejs/model');
|
||||
const Bill = require('../Bill/Bill');
|
||||
const PurchaseInvoice = require('../PurchaseInvoice/PurchaseInvoice');
|
||||
|
||||
module.exports = model.extend(Bill, {
|
||||
name: "PurchaseOrder",
|
||||
label: "Purchase Order",
|
||||
settings: "PurchaseOrderSettings",
|
||||
module.exports = model.extend(
|
||||
PurchaseInvoice,
|
||||
{
|
||||
name: 'PurchaseOrder',
|
||||
label: 'Purchase Order',
|
||||
settings: 'PurchaseOrderSettings',
|
||||
fields: [
|
||||
{
|
||||
"fieldname": "items",
|
||||
"childtype": "PurchaseOrderItem"
|
||||
}
|
||||
{
|
||||
fieldname: 'items',
|
||||
childtype: 'PurchaseOrderItem'
|
||||
}
|
||||
]
|
||||
}, {
|
||||
},
|
||||
{
|
||||
skipFields: ['account']
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
const model = require('frappejs/model');
|
||||
const BillItem = require('../BillItem/BillItem');
|
||||
const PurchaseInvoiceItem = require('../PurchaseInvoiceItem/PurchaseInvoiceItem');
|
||||
|
||||
module.exports = model.extend(BillItem, {
|
||||
module.exports = model.extend(PurchaseInvoiceItem, {
|
||||
name: "PurchaseOrderItem"
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
const model = require('frappejs/model');
|
||||
const BillSettings = require('../BillSettings/BillSettings');
|
||||
const PurchaseInvoiceSettings = require('../PurchaseInvoiceSettings/PurchaseInvoiceSettings');
|
||||
|
||||
module.exports = model.extend(BillSettings, {
|
||||
module.exports = model.extend(PurchaseInvoiceSettings, {
|
||||
"name": "PurchaseOrderSettings",
|
||||
"label": "Purchase Order Settings",
|
||||
"fields": [
|
||||
|
@ -1,7 +1,7 @@
|
||||
const model = require('frappejs/model');
|
||||
const Invoice = require('../Invoice/Invoice');
|
||||
const SalesInvoice = require('../SalesInvoice/SalesInvoice');
|
||||
|
||||
const Quotation = model.extend(Invoice, {
|
||||
const Quotation = model.extend(SalesInvoice, {
|
||||
name: "Quotation",
|
||||
label: "Quotation",
|
||||
settings: "QuotationSettings",
|
||||
|
@ -1,3 +1,3 @@
|
||||
const InvoiceDocument = require('../Invoice/InvoiceDocument');
|
||||
const SalesInvoiceDocument = require('../SalesInvoice/SalesInvoiceDocument');
|
||||
|
||||
module.exports = class Quotation extends InvoiceDocument { }
|
||||
module.exports = class Quotation extends SalesInvoiceDocument {};
|
||||
|
@ -1,6 +1,6 @@
|
||||
const model = require('frappejs/model');
|
||||
const InvoiceItem = require('../InvoiceItem/InvoiceItem');
|
||||
const SalesInvoiceItem = require('../SalesInvoiceItem/SalesInvoiceItem');
|
||||
|
||||
module.exports = model.extend(InvoiceItem, {
|
||||
module.exports = model.extend(SalesInvoiceItem, {
|
||||
name: "QuotationItem"
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
const model = require('frappejs/model');
|
||||
const InvoiceSettings = require('../InvoiceSettings/InvoiceSettings');
|
||||
const SalesInvoiceSettings = require('../SalesInvoiceSettings/SalesInvoiceSettings');
|
||||
|
||||
module.exports = model.extend(InvoiceSettings, {
|
||||
module.exports = model.extend(SalesInvoiceSettings, {
|
||||
"name": "QuotationSettings",
|
||||
"label": "Quotation Settings",
|
||||
"fields": [
|
||||
|
211
models/doctype/SalesInvoice/SalesInvoice.js
Normal file
211
models/doctype/SalesInvoice/SalesInvoice.js
Normal file
@ -0,0 +1,211 @@
|
||||
const frappe = require('frappejs');
|
||||
const utils = require('../../../accounting/utils');
|
||||
|
||||
module.exports = {
|
||||
name: 'SalesInvoice',
|
||||
label: 'Sales Invoice',
|
||||
doctype: 'DocType',
|
||||
documentClass: require('./SalesInvoiceDocument'),
|
||||
print: {
|
||||
printFormat: 'Standard Invoice Format'
|
||||
},
|
||||
isSingle: 0,
|
||||
isChild: 0,
|
||||
isSubmittable: 1,
|
||||
keywordFields: ['name', 'customer'],
|
||||
settings: 'SalesInvoiceSettings',
|
||||
showTitle: true,
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'date',
|
||||
label: 'Date',
|
||||
fieldtype: 'Date',
|
||||
defaultValue: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
fieldname: 'customer',
|
||||
label: 'Customer',
|
||||
fieldtype: 'Link',
|
||||
target: 'Party',
|
||||
required: 1,
|
||||
getFilters: query => {
|
||||
if (query)
|
||||
return {
|
||||
keywords: ['like', query],
|
||||
customer: 1
|
||||
};
|
||||
|
||||
return {
|
||||
customer: 1
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'account',
|
||||
label: 'Account',
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
formula: doc => doc.getFrom('Party', doc.customer, 'defaultAccount'),
|
||||
getFilters: (query, control) => {
|
||||
if (!query) return { isGroup: 0, accountType: 'Receivable' };
|
||||
return {
|
||||
keywords: ['like', query],
|
||||
isGroup: 0,
|
||||
accountType: 'Receivable'
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'currency',
|
||||
label: 'Customer Currency',
|
||||
fieldtype: 'Link',
|
||||
target: 'Currency',
|
||||
hidden: 1,
|
||||
formula: doc => doc.getFrom('Party', doc.customer, 'currency')
|
||||
},
|
||||
{
|
||||
fieldname: 'exchangeRate',
|
||||
label: 'Exchange Rate',
|
||||
fieldtype: 'Float',
|
||||
placeholder: '1 USD = [?] INR',
|
||||
hidden: doc => !doc.isForeignTransaction()
|
||||
},
|
||||
{
|
||||
fieldname: 'items',
|
||||
label: 'Items',
|
||||
fieldtype: 'Table',
|
||||
childtype: 'SalesInvoiceItem',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
fieldname: 'baseNetTotal',
|
||||
label: 'Net Total (INR)',
|
||||
fieldtype: 'Currency',
|
||||
formula: async doc => await doc.getBaseNetTotal(),
|
||||
disabled: true,
|
||||
readOnly: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'netTotal',
|
||||
label: 'Net Total (USD)',
|
||||
fieldtype: 'Currency',
|
||||
hidden: doc => !doc.isForeignTransaction(),
|
||||
formula: async doc =>
|
||||
await doc.formatIntoCustomerCurrency(doc.getSum('items', 'amount')),
|
||||
disabled: true,
|
||||
readOnly: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'taxes',
|
||||
label: 'Taxes',
|
||||
fieldtype: 'Table',
|
||||
childtype: 'TaxSummary',
|
||||
readOnly: 1,
|
||||
template: (doc, row) => {
|
||||
return `<div class='row'>
|
||||
<div class='col-6'></div>
|
||||
<div class='col-6'>
|
||||
<div class='row' v-for='row in value'>
|
||||
<div class='col-6'>{{ row.account }} ({{row.rate}}%)</div>
|
||||
<div class='col-6 text-right'>
|
||||
{{ row.amount }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'baseGrandTotal',
|
||||
label: 'Grand Total (INR)',
|
||||
fieldtype: 'Currency',
|
||||
formula: async doc => await doc.getBaseGrandTotal(),
|
||||
disabled: true,
|
||||
readOnly: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'grandTotal',
|
||||
label: 'Grand Total (USD)',
|
||||
fieldtype: 'Currency',
|
||||
hidden: doc => !doc.isForeignTransaction(),
|
||||
formula: async doc => await doc.getGrandTotal(),
|
||||
disabled: true,
|
||||
readOnly: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'outstandingAmount',
|
||||
label: 'Outstanding Amount',
|
||||
fieldtype: 'Currency',
|
||||
hidden: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'terms',
|
||||
label: 'Terms',
|
||||
fieldtype: 'Text'
|
||||
}
|
||||
],
|
||||
|
||||
layout: [
|
||||
// section 1
|
||||
{
|
||||
columns: [
|
||||
{ fields: ['customer', 'account'] },
|
||||
{ fields: ['date', 'exchangeRate'] }
|
||||
]
|
||||
},
|
||||
|
||||
// section 2
|
||||
{
|
||||
columns: [{ fields: ['items'] }]
|
||||
},
|
||||
|
||||
// section 3
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
fields: [
|
||||
'baseNetTotal',
|
||||
'netTotal',
|
||||
'taxes',
|
||||
'baseGrandTotal',
|
||||
'grandTotal'
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// section 4
|
||||
{
|
||||
columns: [{ fields: ['terms'] }]
|
||||
}
|
||||
],
|
||||
|
||||
links: [
|
||||
utils.ledgerLink,
|
||||
{
|
||||
label: 'Make Payment',
|
||||
condition: form =>
|
||||
form.doc.submitted && form.doc.outstandingAmount != 0.0,
|
||||
action: async form => {
|
||||
const payment = await frappe.getNewDoc('Payment');
|
||||
payment.paymentType = 'Receive';
|
||||
payment.party = form.doc.customer;
|
||||
payment.account = form.doc.account;
|
||||
payment.for = [
|
||||
{
|
||||
referenceType: form.doc.doctype,
|
||||
referenceName: form.doc.name,
|
||||
amount: form.doc.outstandingAmount
|
||||
}
|
||||
];
|
||||
payment.on('afterInsert', async () => {
|
||||
form.$formModal.close();
|
||||
form.$router.push({
|
||||
path: `/edit/Payment/${payment.name}`
|
||||
});
|
||||
});
|
||||
await form.$formModal.open(payment);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
182
models/doctype/SalesInvoice/SalesInvoiceDocument.js
Normal file
182
models/doctype/SalesInvoice/SalesInvoiceDocument.js
Normal file
@ -0,0 +1,182 @@
|
||||
const BaseDocument = require('frappejs/model/document');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class SalesInvoice extends BaseDocument {
|
||||
async change({ changed }) {
|
||||
if (changed === 'items' || changed === 'exchangeRate') {
|
||||
const companyCurrency = frappe.AccountingSettings.currency;
|
||||
if (this.currency.length && this.currency !== companyCurrency) {
|
||||
for (let item of this.items) {
|
||||
if (item.rate && this.exchangeRate) {
|
||||
const itemRate = await this.getFrom('Item', item.item, 'rate');
|
||||
|
||||
item.rate = frappe.parseNumber(itemRate) / this.exchangeRate;
|
||||
if (item.quantity) {
|
||||
item.amount = item.rate * item.quantity;
|
||||
}
|
||||
item.amount = await this.formatIntoCustomerCurrency(item.amount);
|
||||
item.rate = await this.formatIntoCustomerCurrency(item.rate);
|
||||
}
|
||||
}
|
||||
this.netTotal = await this.formatIntoCustomerCurrency(this.netTotal);
|
||||
this.grandTotal = await this.formatIntoCustomerCurrency(
|
||||
this.grandTotal
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed === 'customer' || changed === 'supplier') {
|
||||
this.currency = await this.getFrom('Party', this[changed], 'currency');
|
||||
this.exchangeRate = await this.getExchangeRate();
|
||||
}
|
||||
}
|
||||
|
||||
async formatIntoCustomerCurrency(value) {
|
||||
const companyCurrency = frappe.AccountingSettings.currency;
|
||||
if (this.currency.length && this.currency !== companyCurrency) {
|
||||
const { numberFormat, symbol } = await this.getCustomerCurrencyInfo();
|
||||
return frappe.format(value, {
|
||||
fieldtype: 'Currency',
|
||||
currencyInfo: { numberFormat, symbol }
|
||||
});
|
||||
} else {
|
||||
return frappe.format(value, 'Currency');
|
||||
}
|
||||
}
|
||||
|
||||
isForeignTransaction() {
|
||||
return this.currency
|
||||
? this.currency !== frappe.AccountingSettings.currency
|
||||
? 1
|
||||
: 0
|
||||
: 0;
|
||||
}
|
||||
|
||||
async getCustomerCurrencyInfo() {
|
||||
if (this.numberFormat || this.symbol) {
|
||||
return { numberFormat: this.numberFormat, symbol: this.symbol };
|
||||
}
|
||||
const { numberFormat, symbol } = await frappe.getDoc(
|
||||
'Currency',
|
||||
this.currency
|
||||
);
|
||||
this.numberFormat = numberFormat;
|
||||
this.symbol = symbol;
|
||||
|
||||
return { numberFormat, symbol };
|
||||
}
|
||||
|
||||
async getExchangeRate() {
|
||||
const companyCurrency = frappe.AccountingSettings.currency;
|
||||
return this.currency === companyCurrency ? 1.0 : undefined;
|
||||
}
|
||||
|
||||
async getBaseNetTotal() {
|
||||
if (this.isForeignTransaction()) {
|
||||
return frappe.format(
|
||||
this.getSum('items', 'amount') * (this.exchangeRate || 0),
|
||||
'Currency'
|
||||
);
|
||||
} else {
|
||||
return await this.formatIntoCustomerCurrency(
|
||||
this.getSum('items', 'amount')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getRowTax(row) {
|
||||
if (row.tax) {
|
||||
let tax = await this.getTax(row.tax);
|
||||
let taxAmount = [];
|
||||
for (let d of tax.details || []) {
|
||||
const amt = (frappe.parseNumber(row.amount) * d.rate) / 100;
|
||||
taxAmount.push({
|
||||
account: d.account,
|
||||
rate: d.rate,
|
||||
amount: await this.formatIntoCustomerCurrency(amt)
|
||||
});
|
||||
}
|
||||
return JSON.stringify(taxAmount);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
async getTax(tax) {
|
||||
if (!this._taxes) this._taxes = {};
|
||||
if (!this._taxes[tax]) this._taxes[tax] = await frappe.getDoc('Tax', tax);
|
||||
return this._taxes[tax];
|
||||
}
|
||||
|
||||
async makeTaxSummary() {
|
||||
if (!this.taxes) this.taxes = [];
|
||||
|
||||
// reset tax amount
|
||||
this.taxes.map(d => {
|
||||
d.amount = 0;
|
||||
d.rate = 0;
|
||||
});
|
||||
|
||||
// calculate taxes
|
||||
for (let row of this.items) {
|
||||
if (row.taxAmount) {
|
||||
let taxAmount = JSON.parse(row.taxAmount);
|
||||
for (let rowTaxDetail of taxAmount) {
|
||||
let found = false;
|
||||
|
||||
// check if added in summary
|
||||
for (let taxDetail of this.taxes) {
|
||||
if (taxDetail.account === rowTaxDetail.account) {
|
||||
taxDetail.rate = rowTaxDetail.rate;
|
||||
taxDetail.amount =
|
||||
frappe.parseNumber(taxDetail.amount) +
|
||||
frappe.parseNumber(rowTaxDetail.amount);
|
||||
taxDetail.amount = await this.formatIntoCustomerCurrency(
|
||||
taxDetail.amount
|
||||
);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// add new row
|
||||
if (!found) {
|
||||
this.taxes.push({
|
||||
account: rowTaxDetail.account,
|
||||
rate: rowTaxDetail.rate,
|
||||
amount: rowTaxDetail.amount
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear no taxes
|
||||
this.taxes = this.taxes.filter(d => d.amount);
|
||||
}
|
||||
|
||||
async getGrandTotal() {
|
||||
await this.makeTaxSummary();
|
||||
let grandTotal = frappe.parseNumber(this.netTotal);
|
||||
if (this.taxes) {
|
||||
for (let row of this.taxes) {
|
||||
grandTotal += frappe.parseNumber(row.amount);
|
||||
}
|
||||
}
|
||||
grandTotal = Math.floor(grandTotal * 100) / 100;
|
||||
|
||||
return await this.formatIntoCustomerCurrency(grandTotal);
|
||||
}
|
||||
|
||||
async getBaseGrandTotal() {
|
||||
await this.makeTaxSummary();
|
||||
let baseGrandTotal = frappe.parseNumber(this.baseNetTotal);
|
||||
if (this.taxes) {
|
||||
for (let row of this.taxes) {
|
||||
baseGrandTotal += frappe.parseNumber(row.amount) * this.exchangeRate;
|
||||
}
|
||||
}
|
||||
baseGrandTotal = Math.floor(baseGrandTotal * 100) / 100;
|
||||
|
||||
return frappe.format(baseGrandTotal, 'Currency');
|
||||
}
|
||||
};
|
@ -2,12 +2,16 @@ import { _ } from 'frappejs/utils';
|
||||
import indicators from 'frappejs/ui/constants/indicators';
|
||||
|
||||
export default {
|
||||
doctype: 'Invoice',
|
||||
title: _('Invoice'),
|
||||
doctype: 'SalesInvoice',
|
||||
title: _('Sales Invoice'),
|
||||
columns: [
|
||||
'customer',
|
||||
{
|
||||
label: 'Status',
|
||||
fieldname: 'status',
|
||||
fieldtype: 'Select',
|
||||
size: 'small',
|
||||
options: ['Status..', 'Paid', 'Pending'],
|
||||
getValue(doc) {
|
||||
if (doc.submitted === 1 && doc.outstandingAmount === 0.0) {
|
||||
return 'Paid';
|
||||
@ -21,13 +25,8 @@ export default {
|
||||
return indicators.ORANGE;
|
||||
}
|
||||
},
|
||||
'grandTotal',
|
||||
'date',
|
||||
{
|
||||
label: 'INV #',
|
||||
getValue(doc) {
|
||||
return doc.name;
|
||||
}
|
||||
}
|
||||
'grandTotal',
|
||||
'outstandingAmount'
|
||||
]
|
||||
}
|
||||
};
|
102
models/doctype/SalesInvoice/SalesInvoicePrint.vue
Normal file
102
models/doctype/SalesInvoice/SalesInvoicePrint.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div>
|
||||
<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">
|
||||
<f-button primary @click="$emit('send', $refs.printComponent.innerHTML)">{{ _('Send') }}</f-button>
|
||||
<f-button secondary @click="toggleCustomizer">{{ _('Customize') }}</f-button>
|
||||
<f-button secondary @click="$emit('makePDF', $refs.printComponent.innerHTML)">{{ _('PDF') }}</f-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
<div v-if="showInvoiceCustomizer" class="col-3 mt-4 mx-auto">
|
||||
<invoice-customizer
|
||||
class="border"
|
||||
style="position: fixed"
|
||||
@closeInvoiceCustomizer="toggleCustomizer"
|
||||
@changeColor="changeColor($event)"
|
||||
@changeTemplate="changeTemplate($event)"
|
||||
@changeFont="changeFont($event)"
|
||||
@updateTemplateView="updateTemplateView"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-8 bg-white mt-4 mx-auto border shadow" ref="printComponent">
|
||||
<component
|
||||
:themeColor="themeColor"
|
||||
:font="font"
|
||||
:is="template"
|
||||
v-if="doc"
|
||||
:doc="doc"
|
||||
:key="usedForReRender"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import InvoiceTemplate1 from '@/../models/doctype/SalesInvoice/Templates/InvoiceTemplate1';
|
||||
import InvoiceTemplate2 from '@/../models/doctype/SalesInvoice/Templates/InvoiceTemplate2';
|
||||
import InvoiceTemplate3 from '@/../models/doctype/SalesInvoice/Templates/InvoiceTemplate3';
|
||||
import InvoiceCustomizer from '@/components/InvoiceCustomizer';
|
||||
|
||||
const invoiceTemplates = {
|
||||
'Basic I': InvoiceTemplate1,
|
||||
'Basic II': InvoiceTemplate2,
|
||||
Modern: InvoiceTemplate3
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'InvoicePrint',
|
||||
props: ['doc'],
|
||||
components: {
|
||||
InvoiceCustomizer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showInvoiceCustomizer: false,
|
||||
themeColor: undefined,
|
||||
template: undefined,
|
||||
font: undefined,
|
||||
usedForReRender: 0
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
await this.loadInvoice();
|
||||
},
|
||||
methods: {
|
||||
async loadInvoice() {
|
||||
await this.getTemplate();
|
||||
await this.getColor();
|
||||
await this.getFont();
|
||||
},
|
||||
async getTemplate() {
|
||||
let invoiceSettings = await frappe.getDoc('SalesInvoiceSettings');
|
||||
this.template = invoiceTemplates[invoiceSettings.template];
|
||||
},
|
||||
async getColor() {
|
||||
let invoiceSettings = await frappe.getDoc('SalesInvoiceSettings');
|
||||
this.themeColor = invoiceSettings.themeColor;
|
||||
},
|
||||
async getFont() {
|
||||
let invoiceSettings = await frappe.getDoc('SalesInvoiceSettings');
|
||||
this.font = invoiceSettings.font;
|
||||
},
|
||||
async toggleCustomizer() {
|
||||
await this.loadInvoice();
|
||||
this.showInvoiceCustomizer = !this.showInvoiceCustomizer;
|
||||
},
|
||||
changeColor(color) {
|
||||
this.themeColor = color;
|
||||
},
|
||||
changeTemplate(template) {
|
||||
this.template = invoiceTemplates[template];
|
||||
},
|
||||
changeFont(font) {
|
||||
this.font = font;
|
||||
},
|
||||
updateTemplateView() {
|
||||
this.usedForReRender += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
84
models/doctype/SalesInvoice/SalesInvoiceServer.js
Normal file
84
models/doctype/SalesInvoice/SalesInvoiceServer.js
Normal file
@ -0,0 +1,84 @@
|
||||
const SalesInvoice = require('./SalesInvoiceDocument');
|
||||
const LedgerPosting = require('../../../accounting/ledgerPosting');
|
||||
|
||||
module.exports = class SalesInvoiceServer extends SalesInvoice {
|
||||
async getPosting() {
|
||||
let entries = new LedgerPosting({ reference: this, party: this.customer });
|
||||
await entries.debit(this.account, this.baseGrandTotal);
|
||||
|
||||
if (this.isForeignTransaction()) {
|
||||
for (let item of this.items) {
|
||||
const baseItemAmount = frappe.format(
|
||||
frappe.parseNumber(item.amount) * this.exchangeRate,
|
||||
'Currency'
|
||||
);
|
||||
await entries.credit(item.account, baseItemAmount);
|
||||
}
|
||||
|
||||
if (this.taxes) {
|
||||
for (let tax of this.taxes) {
|
||||
const baseTaxAmount = frappe.format(
|
||||
frappe.parseNumber(tax.amount) * this.exchangeRate,
|
||||
'Currency'
|
||||
);
|
||||
await entries.credit(tax.account, baseTaxAmount);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
for (let item of this.items) {
|
||||
await entries.credit(item.account, item.amount);
|
||||
}
|
||||
|
||||
if (this.taxes) {
|
||||
for (let tax of this.taxes) {
|
||||
await entries.credit(tax.account, tax.amount);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
async getPayments() {
|
||||
let payments = await frappe.db.getAll({
|
||||
doctype: 'PaymentFor',
|
||||
fields: ['parent'],
|
||||
filters: { referenceName: this.name },
|
||||
orderBy: 'name'
|
||||
});
|
||||
if (payments.length != 0) {
|
||||
return payments;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async beforeInsert() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.validateEntries();
|
||||
}
|
||||
|
||||
async afterSubmit() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.post();
|
||||
await frappe.db.setValue(
|
||||
'SalesInvoice',
|
||||
this.name,
|
||||
'outstandingAmount',
|
||||
this.baseGrandTotal
|
||||
);
|
||||
}
|
||||
|
||||
async afterRevert() {
|
||||
let paymentRefList = await this.getPayments();
|
||||
for (let paymentFor of paymentRefList) {
|
||||
const paymentReference = paymentFor.parent;
|
||||
const payment = await frappe.getDoc('Payment', paymentReference);
|
||||
const paymentEntries = await payment.getPosting();
|
||||
await paymentEntries.postReverse();
|
||||
// To set the payment status as unsubmitted.
|
||||
payment.revert();
|
||||
}
|
||||
const entries = await this.getPosting();
|
||||
await entries.postReverse();
|
||||
}
|
||||
};
|
128
models/doctype/SalesInvoice/Templates/InvoiceTemplate1.vue
Normal file
128
models/doctype/SalesInvoice/Templates/InvoiceTemplate1.vue
Normal file
@ -0,0 +1,128 @@
|
||||
<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: 50%">{{ _("ITEM") }}</th>
|
||||
<th :style="$.hideBorderTop" class="text-left pl-0" style="width: 15%">{{ _("RATE") }}</th>
|
||||
<th :style="$.hideBorderTop" class="text-left" style="width: 10%">{{ _("QTY") }}</th>
|
||||
<th
|
||||
:style="$.hideBorderTop"
|
||||
class="text-right pr-1"
|
||||
style="width: 30%"
|
||||
>{{ _("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">{{ row.rate }}</td>
|
||||
<td class="text-left">{{ row.quantity }}</td>
|
||||
<td class="text-right pr-1">{{ row.amount }}</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">{{ doc.netTotal }}</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">{{ tax.amount }}</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;"
|
||||
>{{ doc.grandTotal }}</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>
|
132
models/doctype/SalesInvoice/Templates/InvoiceTemplate2.vue
Normal file
132
models/doctype/SalesInvoice/Templates/InvoiceTemplate2.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<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">{{ doc.grandTotal }}</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: 50%">{{ _("ITEM") }}</th>
|
||||
<th class="text-left pl-0" style="width: 15%">{{ _("RATE") }}</th>
|
||||
<th class="text-left" style="width: 10%">{{ _("QTY") }}</th>
|
||||
<th class="text-right pr-1" style="width: 30%">{{ _("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">{{ row.rate }}</td>
|
||||
<td class="text-left">{{ row.quantity }}</td>
|
||||
<td class="text-right pr-1">{{ row.amount }}</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">{{ doc.netTotal }}</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">{{ tax.amount }}</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">{{ doc.grandTotal }}</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>
|
145
models/doctype/SalesInvoice/Templates/InvoiceTemplate3.vue
Normal file
145
models/doctype/SalesInvoice/Templates/InvoiceTemplate3.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<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">{{ row.amount }}</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">{{ doc.netTotal }}</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">{{ tax.amount }}</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"
|
||||
>{{ doc.grandTotal }}</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>
|
@ -1,11 +1,12 @@
|
||||
module.exports = {
|
||||
name: 'InvoiceItem',
|
||||
name: 'SalesInvoiceItem',
|
||||
doctype: 'DocType',
|
||||
isSingle: 0,
|
||||
isChild: 1,
|
||||
keywordFields: [],
|
||||
layout: 'ratio',
|
||||
fields: [{
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'item',
|
||||
label: 'Item',
|
||||
fieldtype: 'Link',
|
||||
@ -39,6 +40,7 @@ module.exports = {
|
||||
hidden: 1,
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
required: 1,
|
||||
formula: (row, doc) => doc.getFrom('Item', row.item, 'incomeAccount')
|
||||
},
|
||||
{
|
||||
@ -57,7 +59,11 @@ module.exports = {
|
||||
fieldtype: 'Currency',
|
||||
readOnly: 1,
|
||||
disabled: true,
|
||||
formula: (row, doc) => row.quantity * row.rate
|
||||
formula: async (row, doc) => {
|
||||
return await doc.formatIntoCustomerCurrency(
|
||||
row.quantity * frappe.parseNumber(row.rate)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'taxAmount',
|
42
models/doctype/SalesInvoiceSettings/SalesInvoiceSettings.js
Normal file
42
models/doctype/SalesInvoiceSettings/SalesInvoiceSettings.js
Normal file
@ -0,0 +1,42 @@
|
||||
module.exports = {
|
||||
name: 'SalesInvoiceSettings',
|
||||
label: 'SalesInvoice Settings',
|
||||
doctype: 'DocType',
|
||||
isSingle: 1,
|
||||
isChild: 0,
|
||||
keywordFields: [],
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'numberSeries',
|
||||
label: 'Number Series',
|
||||
fieldtype: 'Link',
|
||||
target: 'NumberSeries',
|
||||
required: 1,
|
||||
default: 'SINV'
|
||||
},
|
||||
{
|
||||
fieldname: 'template',
|
||||
label: 'Template',
|
||||
fieldtype: 'Select',
|
||||
options: ['Basic I', 'Basic II', 'Modern'],
|
||||
required: 1,
|
||||
default: 'Basic I'
|
||||
},
|
||||
{
|
||||
fieldname: 'font',
|
||||
label: 'Font',
|
||||
fieldtype: 'Select',
|
||||
options: ['Montserrat', 'Open Sans', 'Oxygen', 'Merriweather'],
|
||||
required: 1,
|
||||
default: 'Montserrat'
|
||||
},
|
||||
{
|
||||
fieldname: 'themeColor',
|
||||
label: 'Theme Color',
|
||||
fieldtype: 'Data',
|
||||
required: 1,
|
||||
default: '#000000',
|
||||
hidden: 1
|
||||
}
|
||||
]
|
||||
};
|
@ -1,102 +1,135 @@
|
||||
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()
|
||||
},
|
||||
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: 'fullname',
|
||||
label: 'Name',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
|
||||
{
|
||||
fieldname: 'email',
|
||||
label: 'Email',
|
||||
fieldtype: 'Data',
|
||||
required: 1,
|
||||
inputType: 'email'
|
||||
},
|
||||
{
|
||||
fieldname: 'email',
|
||||
label: 'Email',
|
||||
fieldtype: 'Data',
|
||||
required: 1,
|
||||
inputType: 'email'
|
||||
},
|
||||
|
||||
{
|
||||
fieldname: 'companyName',
|
||||
label: 'Company Name',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'companyName',
|
||||
label: 'Company Name',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
|
||||
{
|
||||
fieldname: 'bankName',
|
||||
label: 'Bank 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: '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)
|
||||
{
|
||||
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
|
||||
},
|
||||
{
|
||||
fieldname: 'currency',
|
||||
label: 'Currency',
|
||||
fieldtype: 'Data',
|
||||
formula: doc => {
|
||||
return countryList[doc.country].currency;
|
||||
},
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'completed',
|
||||
label: 'Completed',
|
||||
fieldtype: 'Check',
|
||||
readonly: 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',
|
||||
'currency',
|
||||
'fiscalYearStart',
|
||||
'fiscalYearEnd'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
].filter(Boolean)
|
||||
}
|
||||
};
|
||||
|
41
models/doctype/Tax/RegionalChanges.js
Normal file
41
models/doctype/Tax/RegionalChanges.js
Normal file
@ -0,0 +1,41 @@
|
||||
module.exports = async function generateTaxes(country) {
|
||||
if (country === 'India') {
|
||||
const GSTs = {
|
||||
GST: [28, 18, 12, 6, 5, 3, 0.25, 0],
|
||||
IGST: [28, 18, 12, 6, 5, 3, 0.25, 0],
|
||||
'Exempt-GST': [0],
|
||||
'Exempt-IGST': [0]
|
||||
};
|
||||
let newTax = await frappe.getNewDoc('Tax');
|
||||
for (const type of Object.keys(GSTs)) {
|
||||
for (const percent of GSTs[type]) {
|
||||
if (type === 'GST') {
|
||||
await newTax.set({
|
||||
name: `${type}-${percent}`,
|
||||
details: [
|
||||
{
|
||||
account: 'CGST',
|
||||
rate: percent / 2
|
||||
},
|
||||
{
|
||||
account: 'SGST',
|
||||
rate: percent / 2
|
||||
}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
await newTax.set({
|
||||
name: `${type}-${percent}`,
|
||||
details: [
|
||||
{
|
||||
account: type.toString().split('-')[0],
|
||||
rate: percent
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
await newTax.insert();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -1,10 +1,7 @@
|
||||
import { _ } from 'frappejs/utils';
|
||||
import indicators from 'frappejs/ui/constants/indicators';
|
||||
|
||||
export default {
|
||||
doctype: 'Tax',
|
||||
title: _('Tax'),
|
||||
columns: [
|
||||
'name'
|
||||
]
|
||||
}
|
||||
columns: ['name']
|
||||
};
|
||||
|
0
models/doctype/Tax/TaxServer.js
Normal file
0
models/doctype/Tax/TaxServer.js
Normal file
@ -3,67 +3,72 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const countries = require('../../../fixtures/countryInfo.json');
|
||||
const standardCOA = require('../../../fixtures/verified/standardCOA.json');
|
||||
const accountFields = ['accountType', 'rootType', 'isGroup', 'account_type', 'root_type', 'is_group'];
|
||||
const accountFields = [
|
||||
'accountType',
|
||||
'rootType',
|
||||
'isGroup',
|
||||
'account_type',
|
||||
'root_type',
|
||||
'is_group'
|
||||
];
|
||||
|
||||
async function importAccounts(children, parent, rootType, rootAccount) {
|
||||
for (let accountName in children) {
|
||||
const child = children[accountName];
|
||||
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)
|
||||
}
|
||||
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 || child.account_type
|
||||
});
|
||||
|
||||
await doc.insert();
|
||||
|
||||
await importAccounts(child, accountName, rootType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function identifyIsGroup(child) {
|
||||
if (child.isGroup || child.is_group) {
|
||||
return child.isGroup || child.is_group;
|
||||
}
|
||||
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))
|
||||
const keys = Object.keys(child);
|
||||
const children = keys.filter(key => !accountFields.includes(key));
|
||||
|
||||
if (children.length) {
|
||||
return 1;
|
||||
}
|
||||
if (children.length) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function getCountryCOA(){
|
||||
const doc = await frappe.getDoc('AccountingSettings');
|
||||
const conCode = countries[doc.country].code;
|
||||
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;
|
||||
}
|
||||
try {
|
||||
const countryCoa = require('../../../fixtures/verified/' +
|
||||
conCode +
|
||||
'.json');
|
||||
return countryCoa.tree;
|
||||
} catch (e) {
|
||||
return standardCOA;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async function importCharts() {
|
||||
const chart = await getCountryCOA();
|
||||
await importAccounts(chart, '', '', true)
|
||||
}
|
||||
|
||||
const chart = await getCountryCOA();
|
||||
await importAccounts(chart, '', '', true);
|
||||
};
|
||||
|
100
models/index.js
100
models/index.js
@ -1,62 +1,68 @@
|
||||
module.exports = {
|
||||
models: {
|
||||
SetupWizard: require('./doctype/SetupWizard/SetupWizard'),
|
||||
Account: require('./doctype/Account/Account.js'),
|
||||
AccountingSettings: require('./doctype/AccountingSettings/AccountingSettings'),
|
||||
CompanySettings: require('./doctype/CompanySettings/CompanySettings'),
|
||||
AccountingLedgerEntry: require('./doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'),
|
||||
Party: require('./doctype/Party/Party.js'),
|
||||
models: {
|
||||
SetupWizard: require('./doctype/SetupWizard/SetupWizard'),
|
||||
DashboardSettings: require('./doctype/DashboardSettings/DashboardSettings'),
|
||||
DashboardChart: require('./doctype/DashboardChart/DashboardChart'),
|
||||
Currency: require('./doctype/Currency/Currency'),
|
||||
Color: require('./doctype/Color/Color'),
|
||||
Account: require('./doctype/Account/Account.js'),
|
||||
AccountingSettings: require('./doctype/AccountingSettings/AccountingSettings'),
|
||||
CompanySettings: require('./doctype/CompanySettings/CompanySettings'),
|
||||
AccountingLedgerEntry: require('./doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'),
|
||||
Party: require('./doctype/Party/Party.js'),
|
||||
|
||||
Payment: require('./doctype/Payment/Payment.js'),
|
||||
PaymentFor: require('./doctype/PaymentFor/PaymentFor.js'),
|
||||
PaymentSettings: require('./doctype/PaymentSettings/PaymentSettings.js'),
|
||||
Payment: require('./doctype/Payment/Payment.js'),
|
||||
PaymentFor: require('./doctype/PaymentFor/PaymentFor.js'),
|
||||
PaymentSettings: require('./doctype/PaymentSettings/PaymentSettings.js'),
|
||||
|
||||
Item: require('./doctype/Item/Item.js'),
|
||||
Item: require('./doctype/Item/Item.js'),
|
||||
|
||||
Invoice: require('./doctype/Invoice/Invoice.js'),
|
||||
InvoiceItem: require('./doctype/InvoiceItem/InvoiceItem.js'),
|
||||
InvoiceSettings: require('./doctype/InvoiceSettings/InvoiceSettings.js'),
|
||||
SalesInvoice: require('./doctype/SalesInvoice/SalesInvoice.js'),
|
||||
SalesInvoiceItem: require('./doctype/SalesInvoiceItem/SalesInvoiceItem.js'),
|
||||
SalesInvoiceSettings: require('./doctype/SalesInvoiceSettings/SalesInvoiceSettings.js'),
|
||||
|
||||
Bill: require('./doctype/Bill/Bill.js'),
|
||||
BillItem: require('./doctype/BillItem/BillItem.js'),
|
||||
BillSettings: require('./doctype/BillSettings/BillSettings.js'),
|
||||
PurchaseInvoice: require('./doctype/PurchaseInvoice/PurchaseInvoice.js'),
|
||||
PurchaseInvoiceItem: require('./doctype/PurchaseInvoiceItem/PurchaseInvoiceItem.js'),
|
||||
PurchaseInvoiceSettings: require('./doctype/PurchaseInvoiceSettings/PurchaseInvoiceSettings.js'),
|
||||
|
||||
Tax: require('./doctype/Tax/Tax.js'),
|
||||
TaxDetail: require('./doctype/TaxDetail/TaxDetail.js'),
|
||||
TaxSummary: require('./doctype/TaxSummary/TaxSummary.js'),
|
||||
Tax: require('./doctype/Tax/Tax.js'),
|
||||
TaxDetail: require('./doctype/TaxDetail/TaxDetail.js'),
|
||||
TaxSummary: require('./doctype/TaxSummary/TaxSummary.js'),
|
||||
|
||||
Address: require('./doctype/Address/Address.js'),
|
||||
Contact: require('./doctype/Contact/Contact.js'),
|
||||
GSTR3B: require('./doctype/GSTR3B/GSTR3B.js'),
|
||||
|
||||
JournalEntry: require('./doctype/JournalEntry/JournalEntry.js'),
|
||||
JournalEntryAccount: require('./doctype/JournalEntryAccount/JournalEntryAccount.js'),
|
||||
JournalEntrySettings: require('./doctype/JournalEntrySettings/JournalEntrySettings.js'),
|
||||
Address: require('./doctype/Address/Address.js'),
|
||||
Contact: require('./doctype/Contact/Contact.js'),
|
||||
|
||||
Quotation: require('./doctype/Quotation/Quotation.js'),
|
||||
QuotationItem: require('./doctype/QuotationItem/QuotationItem.js'),
|
||||
QuotationSettings: require('./doctype/QuotationSettings/QuotationSettings.js'),
|
||||
JournalEntry: require('./doctype/JournalEntry/JournalEntry.js'),
|
||||
JournalEntryAccount: require('./doctype/JournalEntryAccount/JournalEntryAccount.js'),
|
||||
JournalEntrySettings: require('./doctype/JournalEntrySettings/JournalEntrySettings.js'),
|
||||
|
||||
SalesOrder: require('./doctype/SalesOrder/SalesOrder.js'),
|
||||
SalesOrderItem: require('./doctype/SalesOrderItem/SalesOrderItem.js'),
|
||||
SalesOrderSettings: require('./doctype/SalesOrderSettings/SalesOrderSettings.js'),
|
||||
Quotation: require('./doctype/Quotation/Quotation.js'),
|
||||
QuotationItem: require('./doctype/QuotationItem/QuotationItem.js'),
|
||||
QuotationSettings: require('./doctype/QuotationSettings/QuotationSettings.js'),
|
||||
|
||||
Fulfillment: require('./doctype/Fulfillment/Fulfillment.js'),
|
||||
FulfillmentItem: require('./doctype/FulfillmentItem/FulfillmentItem.js'),
|
||||
FulfillmentSettings: require('./doctype/FulfillmentSettings/FulfillmentSettings.js'),
|
||||
SalesOrder: require('./doctype/SalesOrder/SalesOrder.js'),
|
||||
SalesOrderItem: require('./doctype/SalesOrderItem/SalesOrderItem.js'),
|
||||
SalesOrderSettings: require('./doctype/SalesOrderSettings/SalesOrderSettings.js'),
|
||||
|
||||
PurchaseOrder: require('./doctype/PurchaseOrder/PurchaseOrder.js'),
|
||||
PurchaseOrderItem: require('./doctype/PurchaseOrderItem/PurchaseOrderItem.js'),
|
||||
PurchaseOrderSettings: require('./doctype/PurchaseOrderSettings/PurchaseOrderSettings.js'),
|
||||
Fulfillment: require('./doctype/Fulfillment/Fulfillment.js'),
|
||||
FulfillmentItem: require('./doctype/FulfillmentItem/FulfillmentItem.js'),
|
||||
FulfillmentSettings: require('./doctype/FulfillmentSettings/FulfillmentSettings.js'),
|
||||
|
||||
PurchaseReceipt: require('./doctype/PurchaseReceipt/PurchaseReceipt.js'),
|
||||
PurchaseReceiptItem: require('./doctype/PurchaseReceiptItem/PurchaseReceiptItem.js'),
|
||||
PurchaseReceiptSettings: require('./doctype/PurchaseReceiptSettings/PurchaseReceiptSettings.js'),
|
||||
PurchaseOrder: require('./doctype/PurchaseOrder/PurchaseOrder.js'),
|
||||
PurchaseOrderItem: require('./doctype/PurchaseOrderItem/PurchaseOrderItem.js'),
|
||||
PurchaseOrderSettings: require('./doctype/PurchaseOrderSettings/PurchaseOrderSettings.js'),
|
||||
|
||||
Event: require('./doctype/Event/Event'),
|
||||
EventSchedule: require('./doctype/EventSchedule/EventSchedule'),
|
||||
EventSettings: require('./doctype/EventSettings/EventSettings'),
|
||||
PurchaseReceipt: require('./doctype/PurchaseReceipt/PurchaseReceipt.js'),
|
||||
PurchaseReceiptItem: require('./doctype/PurchaseReceiptItem/PurchaseReceiptItem.js'),
|
||||
PurchaseReceiptSettings: require('./doctype/PurchaseReceiptSettings/PurchaseReceiptSettings.js'),
|
||||
|
||||
Email: require('./doctype/Email/Email'),
|
||||
EmailAccount: require('./doctype/EmailAccount/EmailAccount'),
|
||||
}
|
||||
}
|
||||
Event: require('./doctype/Event/Event'),
|
||||
EventSchedule: require('./doctype/EventSchedule/EventSchedule'),
|
||||
EventSettings: require('./doctype/EventSettings/EventSettings'),
|
||||
|
||||
Email: require('./doctype/Email/Email'),
|
||||
EmailAccount: require('./doctype/EmailAccount/EmailAccount')
|
||||
}
|
||||
};
|
||||
|
9879
package-lock.json
generated
9879
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -20,7 +20,8 @@
|
||||
"dist/electron"
|
||||
],
|
||||
"dmg": {
|
||||
"contents": [{
|
||||
"contents": [
|
||||
{
|
||||
"x": 410,
|
||||
"y": 150,
|
||||
"type": "link",
|
||||
@ -58,10 +59,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-env": "^5.2.0",
|
||||
"frappejs": "github:frappe/frappejs",
|
||||
"file-saver": "^2.0.2",
|
||||
"frappe-charts": "^1.2.4",
|
||||
"frappejs": "https://github.com/thefalconx33/frappejs#test",
|
||||
"nodemailer": "^4.7.0",
|
||||
"popper.js": "^1.14.4",
|
||||
"vue-color": "^2.7.0",
|
||||
"vue-toasted": "^1.1.25"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,154 +1,170 @@
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class AccountsReceivablePayable {
|
||||
async run(reportType, { date }) {
|
||||
async run(reportType, { date }) {
|
||||
const rows = await getReceivablePayable({
|
||||
reportType,
|
||||
date
|
||||
});
|
||||
|
||||
const rows = await getReceivablePayable({
|
||||
reportType,
|
||||
date
|
||||
});
|
||||
|
||||
return { rows };
|
||||
}
|
||||
}
|
||||
return { rows };
|
||||
}
|
||||
};
|
||||
|
||||
async function getReceivablePayable({ reportType = 'Receivable', date }) {
|
||||
let entries = [];
|
||||
const debitOrCredit = reportType === 'Receivable' ? 'debit' : 'credit';
|
||||
const referenceType = reportType === 'Receivable' ? 'Invoice' : 'Bill';
|
||||
let entries = [];
|
||||
const debitOrCredit = reportType === 'Receivable' ? 'debit' : 'credit';
|
||||
const referenceType =
|
||||
reportType === 'Receivable' ? 'SalesInvoice' : 'PurchaseInvoice';
|
||||
|
||||
entries = await getLedgerEntries();
|
||||
const vouchers = await getVouchers();
|
||||
entries = await getLedgerEntries();
|
||||
const vouchers = await getVouchers();
|
||||
|
||||
const futureEntries = getFutureEntries();
|
||||
const returnEntries = getReturnEntries();
|
||||
const pdc = getPDC();
|
||||
const futureEntries = getFutureEntries();
|
||||
const returnEntries = getReturnEntries();
|
||||
const pdc = getPDC();
|
||||
|
||||
const validEntries = getValidEntries();
|
||||
const validEntries = getValidEntries();
|
||||
|
||||
let data = [];
|
||||
let data = [];
|
||||
|
||||
for (let entry of validEntries) {
|
||||
for (let entry of validEntries) {
|
||||
const { outStandingAmount, creditNoteAmount } = getOutstandingAmount(entry);
|
||||
|
||||
const { outStandingAmount, creditNoteAmount } = getOutstandingAmount(entry);
|
||||
console.log(outStandingAmount);
|
||||
|
||||
console.log(outStandingAmount);
|
||||
if (outStandingAmount > 0.1 / 10) {
|
||||
const row = {
|
||||
date: entry.date,
|
||||
party: entry.party
|
||||
};
|
||||
|
||||
if (outStandingAmount > 0.1 / 10) {
|
||||
const row = {
|
||||
date: entry.date,
|
||||
party: entry.party
|
||||
};
|
||||
// due date / bill date
|
||||
|
||||
// due date / bill date
|
||||
row.voucherType = entry.referenceType;
|
||||
row.voucherNo = entry.referenceName;
|
||||
|
||||
row.voucherType = entry.referenceType;
|
||||
row.voucherNo = entry.referenceName;
|
||||
// bill details
|
||||
|
||||
// bill details
|
||||
const invoicedAmount = entry[debitOrCredit] || 0;
|
||||
const paidAmount = invoicedAmount - outStandingAmount - creditNoteAmount;
|
||||
|
||||
const invoicedAmount = entry[debitOrCredit] || 0;
|
||||
const paidAmount = invoicedAmount - outStandingAmount - creditNoteAmount;
|
||||
Object.assign(row, {
|
||||
invoicedAmount,
|
||||
paidAmount,
|
||||
outStandingAmount,
|
||||
creditNoteAmount
|
||||
});
|
||||
|
||||
Object.assign(row, {
|
||||
invoicedAmount,
|
||||
paidAmount,
|
||||
outStandingAmount,
|
||||
creditNoteAmount
|
||||
});
|
||||
// ageing
|
||||
|
||||
// ageing
|
||||
data.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
data.push(row);
|
||||
return data;
|
||||
|
||||
// helpers
|
||||
|
||||
async function getVouchers() {
|
||||
return await frappe.db.getAll({
|
||||
doctype: referenceType,
|
||||
fields: ['name', 'date'],
|
||||
filters: {
|
||||
submitted: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getValidEntries() {
|
||||
return entries.filter(entry => {
|
||||
return (
|
||||
entry.date <= date &&
|
||||
entry.referenceType === referenceType &&
|
||||
entry[debitOrCredit] > 0
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function getOutstandingAmount(entry) {
|
||||
let paymentAmount = 0.0,
|
||||
creditNoteAmount = 0.0;
|
||||
let reverseDebitOrCredit = debitOrCredit === 'debit' ? 'credit' : 'debit';
|
||||
|
||||
for (let e of getEntriesFor(
|
||||
entry.party,
|
||||
entry.referenceType,
|
||||
entry.referenceName
|
||||
)) {
|
||||
if (e.date <= date) {
|
||||
const amount = e[reverseDebitOrCredit] - e[debitOrCredit];
|
||||
|
||||
if (!Object.keys(returnEntries).includes(e.referenceName)) {
|
||||
paymentAmount += amount;
|
||||
} else {
|
||||
creditNoteAmount += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
return {
|
||||
outStandingAmount:
|
||||
entry[debitOrCredit] -
|
||||
entry[reverseDebitOrCredit] -
|
||||
paymentAmount -
|
||||
creditNoteAmount,
|
||||
creditNoteAmount
|
||||
};
|
||||
}
|
||||
|
||||
// helpers
|
||||
function getEntriesFor(party, againstVoucherType, againstVoucher) {
|
||||
// TODO
|
||||
return [];
|
||||
}
|
||||
|
||||
async function getVouchers() {
|
||||
return await frappe.db.getAll({
|
||||
doctype: referenceType,
|
||||
fields: ['name', 'date'],
|
||||
filters: {
|
||||
submitted: 1
|
||||
}
|
||||
});
|
||||
function getFutureEntries() {
|
||||
return entries.filter(entry => entry.date > date);
|
||||
}
|
||||
|
||||
function getReturnEntries() {
|
||||
// TODO
|
||||
return {};
|
||||
}
|
||||
|
||||
function getPDC() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function getLedgerEntries() {
|
||||
if (entries.length) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
function getValidEntries() {
|
||||
return entries.filter(entry => {
|
||||
return (
|
||||
entry.date <= date &&
|
||||
entry.referenceType === referenceType && entry[debitOrCredit] > 0
|
||||
);
|
||||
});
|
||||
}
|
||||
const partyType = reportType === 'Receivable' ? 'customer' : 'supplier';
|
||||
const partyList = (await frappe.db.getAll({
|
||||
doctype: 'Party',
|
||||
filters: {
|
||||
[partyType]: 1
|
||||
}
|
||||
})).map(d => d.name);
|
||||
|
||||
function getOutstandingAmount(entry) {
|
||||
let paymentAmount = 0.0, creditNoteAmount = 0.0;
|
||||
let reverseDebitOrCredit = debitOrCredit === 'debit' ? 'credit' : 'debit';
|
||||
|
||||
for (let e of getEntriesFor(entry.party, entry.referenceType, entry.referenceName)) {
|
||||
if (e.date <= date) {
|
||||
const amount = e[reverseDebitOrCredit] - e[debitOrCredit];
|
||||
|
||||
if (!Object.keys(returnEntries).includes(e.referenceName)) {
|
||||
paymentAmount += amount;
|
||||
} else {
|
||||
creditNoteAmount += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
outStandingAmount: (entry[debitOrCredit] - entry[reverseDebitOrCredit]) - paymentAmount - creditNoteAmount,
|
||||
creditNoteAmount
|
||||
}
|
||||
}
|
||||
|
||||
function getEntriesFor(party, againstVoucherType, againstVoucher) {
|
||||
// TODO
|
||||
return []
|
||||
}
|
||||
|
||||
function getFutureEntries() {
|
||||
return entries.filter(entry => entry.date > date);
|
||||
}
|
||||
|
||||
function getReturnEntries() {
|
||||
// TODO
|
||||
return {};
|
||||
}
|
||||
|
||||
function getPDC() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function getLedgerEntries() {
|
||||
if (entries.length) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
const partyType = reportType === 'Receivable' ? 'customer': 'supplier'
|
||||
const partyList = (await frappe.db.getAll({
|
||||
doctype: 'Party',
|
||||
filters: {
|
||||
[partyType]: 1
|
||||
}
|
||||
})).map(d => d.name);
|
||||
|
||||
return await frappe.db.getAll({
|
||||
doctype: 'AccountingLedgerEntry',
|
||||
fields: ['name', 'date', 'account', 'party',
|
||||
'referenceType', 'referenceName',
|
||||
'sum(debit) as debit', 'sum(credit) as credit'],
|
||||
filters: {
|
||||
party: ['in', partyList]
|
||||
},
|
||||
groupBy: ['referenceType', 'referenceName', 'party'],
|
||||
orderBy: 'date'
|
||||
});
|
||||
}
|
||||
return await frappe.db.getAll({
|
||||
doctype: 'AccountingLedgerEntry',
|
||||
fields: [
|
||||
'name',
|
||||
'date',
|
||||
'account',
|
||||
'party',
|
||||
'referenceType',
|
||||
'referenceName',
|
||||
'sum(debit) as debit',
|
||||
'sum(credit) as credit'
|
||||
],
|
||||
filters: {
|
||||
party: ['in', partyList]
|
||||
},
|
||||
groupBy: ['referenceType', 'referenceName', 'party'],
|
||||
orderBy: 'date'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
46
reports/BalanceSheet/viewConfig.js
Normal file
46
reports/BalanceSheet/viewConfig.js
Normal file
@ -0,0 +1,46 @@
|
||||
module.exports = {
|
||||
title: 'Balance Sheet',
|
||||
method: 'balance-sheet',
|
||||
filterFields: [
|
||||
{
|
||||
fieldtype: 'Date',
|
||||
fieldname: 'toDate',
|
||||
size: 'small',
|
||||
placeholder: 'ToDate',
|
||||
label: 'To Date',
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
size: 'small',
|
||||
options: [
|
||||
'Select Period...',
|
||||
'Monthly',
|
||||
'Quarterly',
|
||||
'Half Yearly',
|
||||
'Yearly'
|
||||
],
|
||||
label: 'Periodicity',
|
||||
fieldname: 'periodicity',
|
||||
default: 'Monthly'
|
||||
}
|
||||
],
|
||||
getColumns(data) {
|
||||
const columns = [
|
||||
{ label: 'Account', fieldtype: 'Data', fieldname: 'account', width: 340 }
|
||||
];
|
||||
|
||||
if (data && data.columns) {
|
||||
const currencyColumns = data.columns;
|
||||
const columnDefs = currencyColumns.map(name => ({
|
||||
label: name,
|
||||
fieldname: name,
|
||||
fieldtype: 'Currency'
|
||||
}));
|
||||
|
||||
columns.push(...columnDefs);
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
};
|
@ -2,21 +2,32 @@ const frappe = require('frappejs');
|
||||
|
||||
class BankReconciliation {
|
||||
async run(params) {
|
||||
if (!Object.keys(params).length) return [];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
filters.paymentMethod = ['in', ['Cheque', 'Transfer']];
|
||||
|
||||
let data = await frappe.db.getAll({
|
||||
doctype: 'Payment',
|
||||
fields: ['date', 'account', 'paymentAccount', 'party', 'name', 'referenceDate','clearanceDate'],
|
||||
filters: filters,
|
||||
fields: [
|
||||
'date',
|
||||
'account',
|
||||
'paymentAccount',
|
||||
'party',
|
||||
'name',
|
||||
'referenceDate',
|
||||
'referenceId',
|
||||
'clearanceDate'
|
||||
],
|
||||
filters: filters
|
||||
});
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
@ -28,9 +39,10 @@ class BankReconciliation {
|
||||
account: data[i].paymentAccount,
|
||||
referenceName: data[i].name
|
||||
}
|
||||
})
|
||||
});
|
||||
data[i].credit = ledger[0].credit;
|
||||
data[i].debit = ledger[0].debit;
|
||||
data[i].debit = ledger[0].debit;
|
||||
data[i].referenceName = ledger[0].referenceName;
|
||||
data[i].referenceType = ledger[0].referenceType;
|
||||
}
|
||||
|
96
reports/BankReconciliation/BankReconciliationImport.js
Normal file
96
reports/BankReconciliation/BankReconciliationImport.js
Normal file
@ -0,0 +1,96 @@
|
||||
const csv2json = require('csvjson-csv2json');
|
||||
const frappe = require('frappejs');
|
||||
import Vue from 'vue';
|
||||
|
||||
export const fileImportHandler = (file, report) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const csv = reader.result;
|
||||
const json = csvToJsonHandler(csv);
|
||||
findMatchingReferences(json, report);
|
||||
};
|
||||
reader.readAsBinaryString(file);
|
||||
};
|
||||
|
||||
export const csvToJsonHandler = csv => {
|
||||
const json = csv2json(csv, { parseNumbers: true });
|
||||
return json;
|
||||
};
|
||||
|
||||
export const findMatchingReferences = async (json, report) => {
|
||||
const referenceField = Object.keys(json[0]).filter(field => {
|
||||
return field.toLowerCase().indexOf('ref') > -1 ? true : false;
|
||||
});
|
||||
const clearanceDateField = Object.keys(json[0]).filter(field => {
|
||||
return field.toLowerCase().indexOf('date') > -1 ? true : false;
|
||||
});
|
||||
const debitField = Object.keys(json[0]).filter(field => {
|
||||
return field.toLowerCase().indexOf('debit') > -1 ||
|
||||
field.toLowerCase().indexOf('deposit') > -1
|
||||
? true
|
||||
: false;
|
||||
});
|
||||
const creditField = Object.keys(json[0]).filter(field => {
|
||||
return field.toLowerCase().indexOf('credit') > -1 ||
|
||||
field.toLowerCase().indexOf('withdraw') > -1
|
||||
? true
|
||||
: false;
|
||||
});
|
||||
const balanceField = Object.keys(json[0]).filter(field => {
|
||||
return field.toLowerCase().indexOf('balance') > -1 ? true : false;
|
||||
});
|
||||
const references = json.map(row => {
|
||||
return row[referenceField];
|
||||
});
|
||||
const payments = await frappe.db.getAll({
|
||||
doctype: 'Payment',
|
||||
fields: ['*'],
|
||||
filters: {
|
||||
referenceId: ['in', references],
|
||||
paymentAccount: report.currentFilters.paymentAccount,
|
||||
clearanceDate: ['in', [null, undefined, '']]
|
||||
}
|
||||
});
|
||||
if (payments.length) {
|
||||
const entries = payments.map(payment => {
|
||||
const jsonEntry = json.filter(row => {
|
||||
return row[referenceField] === payment.referenceId;
|
||||
});
|
||||
return Object.assign(payment, jsonEntry[0]);
|
||||
});
|
||||
const normalizedEntries = entries.map(entry => {
|
||||
return {
|
||||
'Posting Date': frappe.format(entry.date, 'Date'),
|
||||
'Payment Entry': entry.name,
|
||||
'Ref/Cheq. ID': entry[referenceField],
|
||||
'Cr/Dr':
|
||||
frappe.parseNumber(entry[debitField]) > 0
|
||||
? entry[debitField] + ' Dr.'
|
||||
: entry[creditField] + ' Cr.',
|
||||
'Clearance Date': entry[clearanceDateField]
|
||||
};
|
||||
});
|
||||
report.$modal.show({
|
||||
modalProps: {
|
||||
title: `Validate Matching Entries`,
|
||||
noFooter: true
|
||||
},
|
||||
component: require('../../src/components/ReconciliationValidation')
|
||||
.default,
|
||||
props: {
|
||||
entries: normalizedEntries,
|
||||
afterReconcile: async () => {
|
||||
await report.getReportData(report.currentFilters);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frappe.call({
|
||||
method: 'show-dialog',
|
||||
args: {
|
||||
title: 'Message',
|
||||
message: 'No entries found with matching Ref / Cheque ID'
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
@ -2,31 +2,76 @@ const title = 'Bank Reconciliation';
|
||||
module.exports = {
|
||||
title: title,
|
||||
method: 'bank-reconciliation',
|
||||
filterFields: [{
|
||||
filterFields: [
|
||||
{
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
label: 'Payement Account',
|
||||
fieldname: 'paymentAccount'
|
||||
size: 'small',
|
||||
placeholder: 'Payment Account',
|
||||
label: 'Payment Account',
|
||||
fieldname: 'paymentAccount',
|
||||
getFilters: () => {
|
||||
return {
|
||||
accountType: 'Bank',
|
||||
isGroup: 0
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldtype: 'Link',
|
||||
target: 'Party',
|
||||
size: 'small',
|
||||
label: 'Party',
|
||||
placeholder: 'Party',
|
||||
fieldname: 'party'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Date',
|
||||
size: 'small',
|
||||
placeholder: 'From Date',
|
||||
label: 'From Date',
|
||||
fieldname: 'fromDate'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Date',
|
||||
size: 'small',
|
||||
placeholder: 'To Date',
|
||||
label: 'To Date',
|
||||
fieldname: 'toDate'
|
||||
}
|
||||
],
|
||||
linkFields: [
|
||||
{
|
||||
label: 'Reconcile',
|
||||
type: 'secondary',
|
||||
condition: report => report.currentFilters.paymentAccount,
|
||||
action: async report => {
|
||||
report.$modal.show({
|
||||
modalProps: {
|
||||
title: `Import Bank Account Statement`,
|
||||
noFooter: true
|
||||
},
|
||||
component: require('../../src/components/ImportWizard').default,
|
||||
props: {
|
||||
importHandler: require('./BankReconciliationImport')
|
||||
.fileImportHandler,
|
||||
report
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Clear Filters',
|
||||
type: 'secondary',
|
||||
action: async report => {
|
||||
await report.getReportData({});
|
||||
report.usedToReRender += 1;
|
||||
}
|
||||
}
|
||||
],
|
||||
getColumns() {
|
||||
return [{
|
||||
return [
|
||||
{
|
||||
label: 'Posting Date',
|
||||
fieldtype: 'Date',
|
||||
fieldname: 'date'
|
||||
@ -47,6 +92,16 @@ module.exports = {
|
||||
label: 'Balance',
|
||||
fieldtype: 'Currency'
|
||||
},
|
||||
{
|
||||
label: 'Ref/Cheque ID',
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'referenceId'
|
||||
},
|
||||
{
|
||||
label: 'Clearance Date',
|
||||
fieldtype: 'Date',
|
||||
fieldname: 'clearanceDate'
|
||||
},
|
||||
{
|
||||
label: 'Ref. Type',
|
||||
fieldtype: 'Data',
|
||||
@ -62,11 +117,7 @@ module.exports = {
|
||||
fieldtype: 'Date',
|
||||
fieldname: 'referenceDate'
|
||||
},
|
||||
{
|
||||
label: 'Clearance Date',
|
||||
fieldtype: 'Date',
|
||||
fieldname: 'clearanceDate'
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Party',
|
||||
fieldtype: 'Link'
|
||||
|
@ -3,249 +3,288 @@ const { DateTime } = require('luxon');
|
||||
const { unique } = require('frappejs/utils');
|
||||
|
||||
async function getData({
|
||||
rootType,
|
||||
balanceMustBe = 'Debit',
|
||||
fromDate,
|
||||
toDate,
|
||||
periodicity = 'Monthly',
|
||||
accumulateValues = false
|
||||
rootType,
|
||||
balanceMustBe = 'Debit',
|
||||
fromDate,
|
||||
toDate,
|
||||
periodicity = 'Monthly',
|
||||
accumulateValues = false
|
||||
}) {
|
||||
let accounts = await getAccounts(rootType);
|
||||
let fiscalYear = await getFiscalYear();
|
||||
let ledgerEntries = await getLedgerEntries(fromDate, toDate, accounts);
|
||||
let periodList = getPeriodList(fromDate, toDate, periodicity, fiscalYear);
|
||||
|
||||
let accounts = await getAccounts(rootType);
|
||||
let fiscalYear = await getFiscalYear();
|
||||
let ledgerEntries = await getLedgerEntries(fromDate, toDate, accounts);
|
||||
let periodList = getPeriodList(fromDate, toDate, periodicity, fiscalYear);
|
||||
for (let account of accounts) {
|
||||
const entries = ledgerEntries.filter(
|
||||
entry => entry.account === account.name
|
||||
);
|
||||
|
||||
for (let entry of entries) {
|
||||
let periodKey = getPeriodKey(entry.date, periodicity);
|
||||
|
||||
if (!account[periodKey]) {
|
||||
account[periodKey] = 0.0;
|
||||
}
|
||||
|
||||
const multiplier = balanceMustBe === 'Debit' ? 1 : -1;
|
||||
const value = (entry.debit - entry.credit) * multiplier;
|
||||
account[periodKey] += value;
|
||||
}
|
||||
}
|
||||
|
||||
if (accumulateValues) {
|
||||
periodList.forEach((periodKey, i) => {
|
||||
if (i === 0) return;
|
||||
const previousPeriodKey = periodList[i - 1];
|
||||
|
||||
for (let account of accounts) {
|
||||
if (!account[periodKey]) {
|
||||
account[periodKey] = 0.0;
|
||||
}
|
||||
account[periodKey] += account[previousPeriodKey] || 0.0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// calculate totalRow
|
||||
let totalRow = {
|
||||
account: `Total ${rootType} (${balanceMustBe})`
|
||||
};
|
||||
|
||||
periodList.forEach(periodKey => {
|
||||
if (!totalRow[periodKey]) {
|
||||
totalRow[periodKey] = 0.0;
|
||||
}
|
||||
|
||||
for (let account of accounts) {
|
||||
const entries = ledgerEntries.filter(entry => entry.account === account.name);
|
||||
|
||||
for (let entry of entries) {
|
||||
let periodKey = getPeriodKey(entry.date, periodicity);
|
||||
|
||||
if (!account[periodKey]) {
|
||||
account[periodKey] = 0.0;
|
||||
}
|
||||
|
||||
const multiplier = balanceMustBe === 'Debit' ? 1 : -1;
|
||||
const value = (entry.debit - entry.credit) * multiplier
|
||||
account[periodKey] += value;
|
||||
}
|
||||
totalRow[periodKey] += account[periodKey] || 0.0;
|
||||
}
|
||||
});
|
||||
|
||||
if (accumulateValues) {
|
||||
periodList.forEach((periodKey, i) => {
|
||||
if (i === 0) return;
|
||||
const previousPeriodKey = periodList[i - 1];
|
||||
|
||||
for (let account of accounts) {
|
||||
if (!account[periodKey]) {
|
||||
account[periodKey] = 0.0;
|
||||
}
|
||||
account[periodKey] += account[previousPeriodKey] || 0.0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// calculate totalRow
|
||||
let totalRow = {
|
||||
account: `Total ${rootType} (${balanceMustBe})`
|
||||
};
|
||||
|
||||
periodList.forEach((periodKey) => {
|
||||
if (!totalRow[periodKey]) {
|
||||
totalRow[periodKey] = 0.0;
|
||||
}
|
||||
|
||||
for (let account of accounts) {
|
||||
totalRow[periodKey] += account[periodKey] || 0.0;
|
||||
}
|
||||
});
|
||||
|
||||
return { accounts, totalRow, periodList };
|
||||
return { accounts, totalRow, periodList };
|
||||
}
|
||||
|
||||
async function getTrialBalance({ rootType, fromDate, toDate }) {
|
||||
let accounts = await getAccounts(rootType);
|
||||
let ledgerEntries = await getLedgerEntries(null, toDate, accounts);
|
||||
let accounts = await getAccounts(rootType);
|
||||
let ledgerEntries = await getLedgerEntries(null, toDate, accounts);
|
||||
|
||||
for (let account of accounts) {
|
||||
const accountEntries = ledgerEntries.filter(entry => entry.account === account.name);
|
||||
// opening
|
||||
const beforePeriodEntries = accountEntries.filter(entry => entry.date < fromDate);
|
||||
account.opening = beforePeriodEntries.reduce((acc, entry) => {
|
||||
return acc + (entry.debit - entry.credit);
|
||||
}, 0);
|
||||
for (let account of accounts) {
|
||||
const accountEntries = ledgerEntries.filter(
|
||||
entry => entry.account === account.name
|
||||
);
|
||||
// opening
|
||||
const beforePeriodEntries = accountEntries.filter(
|
||||
entry => entry.date < fromDate
|
||||
);
|
||||
account.opening = beforePeriodEntries.reduce((acc, entry) => {
|
||||
return acc + (entry.debit - entry.credit);
|
||||
}, 0);
|
||||
|
||||
if (account.opening >= 0) {
|
||||
account.openingDebit = account.opening;
|
||||
} else {
|
||||
account.openingCredit = -account.opening;
|
||||
}
|
||||
|
||||
// debit / credit
|
||||
const periodEntries = accountEntries.filter(entry => entry.date >= fromDate);
|
||||
account.debit = periodEntries.reduce((acc, entry) => acc + entry.debit, 0);
|
||||
account.credit = periodEntries.reduce((acc, entry) => acc + entry.credit, 0);
|
||||
|
||||
// closing
|
||||
account.closing = account.opening + account.debit - account.credit;
|
||||
|
||||
if (account.closing >= 0) {
|
||||
account.closingDebit = account.closing;
|
||||
} else {
|
||||
account.closingCredit = -account.closing;
|
||||
}
|
||||
if (account.opening >= 0) {
|
||||
account.openingDebit = account.opening;
|
||||
} else {
|
||||
account.openingCredit = -account.opening;
|
||||
}
|
||||
|
||||
return accounts;
|
||||
// debit / credit
|
||||
const periodEntries = accountEntries.filter(
|
||||
entry => entry.date >= fromDate && entry.date < toDate
|
||||
);
|
||||
account.debit = periodEntries.reduce((acc, entry) => acc + entry.debit, 0);
|
||||
account.credit = periodEntries.reduce(
|
||||
(acc, entry) => acc + entry.credit,
|
||||
0
|
||||
);
|
||||
|
||||
// closing
|
||||
account.closing = account.opening + account.debit - account.credit;
|
||||
|
||||
if (account.closing >= 0) {
|
||||
account.closingDebit = account.closing;
|
||||
} else {
|
||||
account.closingCredit = -account.closing;
|
||||
}
|
||||
|
||||
if (account.debit != 0 || account.credit != 0) {
|
||||
setParentEntry(account, account.parentAccount);
|
||||
}
|
||||
}
|
||||
|
||||
function setParentEntry(leafAccount, parentName) {
|
||||
for (let acc of accounts) {
|
||||
if (acc.name === parentName) {
|
||||
acc.debit += leafAccount.debit;
|
||||
acc.credit += leafAccount.credit;
|
||||
acc.closing = acc.opening + acc.debit - acc.credit;
|
||||
if (acc.closing >= 0) {
|
||||
acc.closingDebit = acc.closing;
|
||||
} else {
|
||||
acc.closingCredit = -acc.closing;
|
||||
}
|
||||
if (acc.parentAccount) {
|
||||
setParentEntry(leafAccount, acc.parentAccount);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
function getPeriodList(fromDate, toDate, periodicity, fiscalYear) {
|
||||
if (!fromDate) {
|
||||
fromDate = fiscalYear.start;
|
||||
}
|
||||
if (!fromDate) {
|
||||
fromDate = fiscalYear.start;
|
||||
}
|
||||
|
||||
let monthsToAdd = {
|
||||
'Monthly': 1,
|
||||
'Quarterly': 3,
|
||||
'Half Yearly': 6,
|
||||
'Yearly': 12
|
||||
}[periodicity];
|
||||
let monthsToAdd = {
|
||||
Monthly: 1,
|
||||
Quarterly: 3,
|
||||
'Half Yearly': 6,
|
||||
Yearly: 12
|
||||
}[periodicity];
|
||||
|
||||
let startDate = DateTime.fromISO(fromDate).startOf('month');
|
||||
let endDate = DateTime.fromISO(toDate).endOf('month');
|
||||
let curDate = startDate;
|
||||
let out = [];
|
||||
let startDate = DateTime.fromISO(fromDate).startOf('month');
|
||||
let endDate = DateTime.fromISO(toDate).endOf('month');
|
||||
let curDate = startDate;
|
||||
let out = [];
|
||||
|
||||
while (curDate <= endDate) {
|
||||
out.push(getPeriodKey(curDate, periodicity));
|
||||
curDate = curDate.plus({ months: monthsToAdd });
|
||||
}
|
||||
while (curDate <= endDate) {
|
||||
out.push(getPeriodKey(curDate, periodicity));
|
||||
curDate = curDate.plus({ months: monthsToAdd });
|
||||
}
|
||||
|
||||
return out;
|
||||
return out;
|
||||
}
|
||||
|
||||
function getPeriodKey(date, periodicity) {
|
||||
let key;
|
||||
let dateObj = DateTime.fromISO(date);
|
||||
let year = dateObj.year;
|
||||
let quarter = dateObj.quarter;
|
||||
let month = dateObj.month;
|
||||
let key;
|
||||
let dateObj = DateTime.fromISO(date);
|
||||
let year = dateObj.year;
|
||||
let quarter = dateObj.quarter;
|
||||
let month = dateObj.month;
|
||||
|
||||
let getKey = {
|
||||
'Monthly': () => `${dateObj.monthShort} ${year}`,
|
||||
'Quarterly': () => {
|
||||
return {
|
||||
1: `Jan ${year} - Mar ${year}`,
|
||||
2: `Apr ${year} - Jun ${year}`,
|
||||
3: `Jun ${year} - Sep ${year}`,
|
||||
4: `Oct ${year} - Dec ${year}`
|
||||
}[quarter]
|
||||
},
|
||||
'Half Yearly': () => {
|
||||
return {
|
||||
1: `Apr ${year} - Sep ${year}`,
|
||||
2: `Oct ${year} - Mar ${year}`
|
||||
}[[2, 3].includes(quarter) ? 1 : 2]
|
||||
},
|
||||
'Yearly': () => {
|
||||
if (month > 3) {
|
||||
return `${year} - ${year + 1}`
|
||||
}
|
||||
return `${year - 1} - ${year}`
|
||||
}
|
||||
}[periodicity];
|
||||
let getKey = {
|
||||
Monthly: () => `${dateObj.monthShort} ${year}`,
|
||||
Quarterly: () => {
|
||||
return {
|
||||
1: `Jan ${year} - Mar ${year}`,
|
||||
2: `Apr ${year} - Jun ${year}`,
|
||||
3: `Jun ${year} - Sep ${year}`,
|
||||
4: `Oct ${year} - Dec ${year}`
|
||||
}[quarter];
|
||||
},
|
||||
'Half Yearly': () => {
|
||||
return {
|
||||
1: `Apr ${year} - Sep ${year}`,
|
||||
2: `Oct ${year} - Mar ${year}`
|
||||
}[[2, 3].includes(quarter) ? 1 : 2];
|
||||
},
|
||||
Yearly: () => {
|
||||
if (month > 3) {
|
||||
return `${year} - ${year + 1}`;
|
||||
}
|
||||
return `${year - 1} - ${year}`;
|
||||
}
|
||||
}[periodicity];
|
||||
|
||||
return getKey();
|
||||
return getKey();
|
||||
}
|
||||
|
||||
function setIndentLevel(accounts, parentAccount, level) {
|
||||
if (!parentAccount) {
|
||||
// root
|
||||
parentAccount = null;
|
||||
level = 0;
|
||||
if (!parentAccount) {
|
||||
// root
|
||||
parentAccount = null;
|
||||
level = 0;
|
||||
}
|
||||
|
||||
accounts.forEach(account => {
|
||||
if (
|
||||
account.parentAccount === parentAccount &&
|
||||
account.indent === undefined
|
||||
) {
|
||||
account.indent = level;
|
||||
setIndentLevel(accounts, account.name, level + 1);
|
||||
}
|
||||
});
|
||||
|
||||
accounts.forEach(account => {
|
||||
if (account.parentAccount === parentAccount && account.indent === undefined) {
|
||||
account.indent = level;
|
||||
setIndentLevel(accounts, account.name, level + 1);
|
||||
}
|
||||
});
|
||||
|
||||
return accounts;
|
||||
return accounts;
|
||||
}
|
||||
|
||||
function sortAccounts(accounts) {
|
||||
let out = [];
|
||||
let pushed = {};
|
||||
let out = [];
|
||||
let pushed = {};
|
||||
|
||||
pushToOut(null);
|
||||
pushToOut(null);
|
||||
|
||||
function pushToOut(parentAccount) {
|
||||
accounts.forEach(account => {
|
||||
if (account.parentAccount === parentAccount && !pushed[account.name]) {
|
||||
out.push(account);
|
||||
pushed[account.name] = 1;
|
||||
function pushToOut(parentAccount) {
|
||||
accounts.forEach(account => {
|
||||
if (account.parentAccount === parentAccount && !pushed[account.name]) {
|
||||
out.push(account);
|
||||
pushed[account.name] = 1;
|
||||
|
||||
pushToOut(account.name);
|
||||
}
|
||||
})
|
||||
}
|
||||
pushToOut(account.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
return out;
|
||||
}
|
||||
|
||||
async function getLedgerEntries(fromDate, toDate, accounts) {
|
||||
const dateFilter = () => {
|
||||
const before = ['<=', toDate];
|
||||
const after = ['>=', fromDate];
|
||||
if (fromDate) {
|
||||
return [...after, ...before];
|
||||
}
|
||||
return before;
|
||||
const dateFilter = () => {
|
||||
const before = ['<=', toDate];
|
||||
const after = ['>=', fromDate];
|
||||
if (fromDate) {
|
||||
return [...after, ...before];
|
||||
}
|
||||
return before;
|
||||
};
|
||||
|
||||
const ledgerEntries = await frappe.db.getAll({
|
||||
doctype: 'AccountingLedgerEntry',
|
||||
fields: ['account', 'debit', 'credit', 'date'],
|
||||
filters: {
|
||||
account: ['in', accounts.map(d => d.name)],
|
||||
date: dateFilter()
|
||||
}
|
||||
});
|
||||
const ledgerEntries = await frappe.db.getAll({
|
||||
doctype: 'AccountingLedgerEntry',
|
||||
fields: ['account', 'debit', 'credit', 'date'],
|
||||
filters: {
|
||||
account: ['in', accounts.map(d => d.name)],
|
||||
date: dateFilter()
|
||||
}
|
||||
});
|
||||
|
||||
return ledgerEntries;
|
||||
return ledgerEntries;
|
||||
}
|
||||
|
||||
async function getAccounts(rootType) {
|
||||
let accounts = await frappe.db.getAll({
|
||||
doctype: 'Account',
|
||||
fields: ['name', 'parentAccount'],
|
||||
filters: {
|
||||
rootType
|
||||
}
|
||||
});
|
||||
let accounts = await frappe.db.getAll({
|
||||
doctype: 'Account',
|
||||
fields: ['name', 'parentAccount'],
|
||||
filters: {
|
||||
rootType
|
||||
}
|
||||
});
|
||||
|
||||
accounts = setIndentLevel(accounts);
|
||||
accounts = sortAccounts(accounts);
|
||||
accounts = setIndentLevel(accounts);
|
||||
accounts = sortAccounts(accounts);
|
||||
|
||||
accounts.forEach(account => {
|
||||
account.account = account.name;
|
||||
});
|
||||
accounts.forEach(account => {
|
||||
account.account = account.name;
|
||||
});
|
||||
|
||||
return accounts;
|
||||
return accounts;
|
||||
}
|
||||
|
||||
async function getFiscalYear() {
|
||||
let { fiscalYearStart, fiscalYearEnd } = await frappe.getSingle('AccountingSettings');
|
||||
return {
|
||||
start: fiscalYearStart,
|
||||
end: fiscalYearEnd
|
||||
};
|
||||
let { fiscalYearStart, fiscalYearEnd } = await frappe.getSingle(
|
||||
'AccountingSettings'
|
||||
);
|
||||
return {
|
||||
start: fiscalYearStart,
|
||||
end: fiscalYearEnd
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getData,
|
||||
getTrialBalance
|
||||
}
|
||||
getData,
|
||||
getTrialBalance
|
||||
};
|
||||
|
@ -2,6 +2,8 @@ const frappe = require('frappejs');
|
||||
|
||||
class GeneralLedger {
|
||||
async run(params) {
|
||||
if (!Object.keys(params).length) return [];
|
||||
|
||||
const filters = {};
|
||||
if (params.account) filters.account = params.account;
|
||||
if (params.party) filters.party = params.party;
|
||||
@ -12,13 +14,67 @@ class GeneralLedger {
|
||||
if (params.toDate) filters.date.push('<=', params.toDate);
|
||||
if (params.fromDate) filters.date.push('>=', params.fromDate);
|
||||
}
|
||||
|
||||
let data = await frappe.db.getAll({
|
||||
doctype: 'AccountingLedgerEntry',
|
||||
fields: ['date', 'account', 'party', 'referenceType', 'referenceName', 'debit', 'credit'],
|
||||
fields: [
|
||||
'date',
|
||||
'account',
|
||||
'party',
|
||||
'referenceType',
|
||||
'referenceName',
|
||||
'debit',
|
||||
'credit'
|
||||
],
|
||||
filters: filters
|
||||
});
|
||||
|
||||
return data;
|
||||
return this.appendOpeningEntry(data);
|
||||
}
|
||||
appendOpeningEntry(data) {
|
||||
let glEntries = [];
|
||||
let balance = 0,
|
||||
debitTotal = 0,
|
||||
creditTotal = 0;
|
||||
|
||||
glEntries.push({
|
||||
date: '',
|
||||
account: '<b>Opening</b>',
|
||||
party: '',
|
||||
debit: 0,
|
||||
credit: 0,
|
||||
balance: 0,
|
||||
referenceType: '',
|
||||
referenceName: ''
|
||||
});
|
||||
for (let entry of data) {
|
||||
balance += entry.debit > 0 ? entry.debit : -entry.credit;
|
||||
debitTotal += entry.debit;
|
||||
creditTotal += entry.credit;
|
||||
entry.balance = balance;
|
||||
glEntries.push(entry);
|
||||
}
|
||||
glEntries.push({
|
||||
date: '',
|
||||
account: '<b>Total</b>',
|
||||
party: '',
|
||||
debit: debitTotal,
|
||||
credit: creditTotal,
|
||||
balance: balance,
|
||||
referenceType: '',
|
||||
referenceName: ''
|
||||
});
|
||||
glEntries.push({
|
||||
date: '',
|
||||
account: '<b>Closing</b>',
|
||||
party: '',
|
||||
debit: debitTotal,
|
||||
credit: creditTotal,
|
||||
balance: balance,
|
||||
referenceType: '',
|
||||
referenceName: ''
|
||||
});
|
||||
return glEntries;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,9 +5,10 @@ module.exports = class GeneralLedgerView extends ReportPage {
|
||||
constructor() {
|
||||
super({
|
||||
title: frappe._('General Ledger'),
|
||||
filterFields: [{
|
||||
filterFields: [
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
options: ['', 'Invoice', 'Payment'],
|
||||
options: ['', 'SalesInvoice', 'Payment'],
|
||||
label: 'Reference Type',
|
||||
fieldname: 'referenceType'
|
||||
},
|
||||
@ -42,7 +43,8 @@ module.exports = class GeneralLedgerView extends ReportPage {
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
return [{
|
||||
return [
|
||||
{
|
||||
label: 'Date',
|
||||
fieldtype: 'Date'
|
||||
},
|
||||
|
@ -1,15 +1,19 @@
|
||||
const title = 'General Ledger';
|
||||
module.exports = {
|
||||
title: title,
|
||||
method: 'general-ledger',
|
||||
filterFields: [{
|
||||
let title = 'General Ledger';
|
||||
|
||||
const viewConfig = {
|
||||
title,
|
||||
filterFields: [
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
options: ['', 'Invoice', 'Payment'],
|
||||
options: ['Select...', 'SalesInvoice', 'Payment', 'PurchaseInvoice'],
|
||||
size: 'small',
|
||||
label: 'Reference Type',
|
||||
fieldname: 'referenceType'
|
||||
},
|
||||
{
|
||||
fieldtype: 'DynamicLink',
|
||||
size: 'small',
|
||||
placeholder: 'Reference Name',
|
||||
references: 'referenceType',
|
||||
label: 'Reference Name',
|
||||
fieldname: 'referenceName'
|
||||
@ -17,6 +21,8 @@ module.exports = {
|
||||
{
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
size: 'small',
|
||||
placeholder: 'Account',
|
||||
label: 'Account',
|
||||
fieldname: 'account'
|
||||
},
|
||||
@ -24,21 +30,70 @@ module.exports = {
|
||||
fieldtype: 'Link',
|
||||
target: 'Party',
|
||||
label: 'Party',
|
||||
size: 'small',
|
||||
placeholder: 'Party',
|
||||
fieldname: 'party'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Date',
|
||||
size: 'small',
|
||||
placeholder: 'From Date',
|
||||
label: 'From Date',
|
||||
fieldname: 'fromDate'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Date',
|
||||
size: 'small',
|
||||
placeholder: 'To Date',
|
||||
label: 'To Date',
|
||||
fieldname: 'toDate'
|
||||
}
|
||||
],
|
||||
method: 'general-ledger',
|
||||
linkFields: [
|
||||
{
|
||||
label: 'Clear Filters',
|
||||
type: 'secondary',
|
||||
action: async report => {
|
||||
await report.getReportData({});
|
||||
report.usedToReRender += 1;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Export',
|
||||
type: 'primary',
|
||||
action: async report => {
|
||||
async function getReportDetails() {
|
||||
let [rows, columns] = await report.getReportData(
|
||||
report.currentFilters
|
||||
);
|
||||
let columnData = columns.map(column => {
|
||||
return {
|
||||
id: column.id,
|
||||
content: column.content,
|
||||
checked: true
|
||||
};
|
||||
});
|
||||
return {
|
||||
title: title,
|
||||
rows: rows,
|
||||
columnData: columnData
|
||||
};
|
||||
}
|
||||
report.$modal.show({
|
||||
modalProps: {
|
||||
title: `Export ${title}`,
|
||||
noFooter: true
|
||||
},
|
||||
component: require('../../src/components/ExportWizard').default,
|
||||
props: await getReportDetails()
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
getColumns() {
|
||||
return [{
|
||||
return [
|
||||
{
|
||||
label: 'Date',
|
||||
fieldtype: 'Date'
|
||||
},
|
||||
@ -77,3 +132,5 @@ module.exports = {
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = viewConfig;
|
||||
|
68
reports/GoodsAndServiceTax/BaseGSTR.js
Normal file
68
reports/GoodsAndServiceTax/BaseGSTR.js
Normal file
@ -0,0 +1,68 @@
|
||||
const frappe = require('frappejs');
|
||||
|
||||
class BaseGSTR {
|
||||
async getCompleteReport(gstrType, filters) {
|
||||
if (['GSTR-1', 'GSTR-2'].includes(gstrType)) {
|
||||
let entries = await frappe.db.getAll({
|
||||
doctype: gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice',
|
||||
filters
|
||||
});
|
||||
|
||||
let tableData = [];
|
||||
for (let entry of entries) {
|
||||
entry.doctype = gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice';
|
||||
const row = await this.getRow(entry);
|
||||
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;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getRow(ledgerEntry) {
|
||||
let row = {};
|
||||
ledgerEntry = await frappe.getDoc(ledgerEntry.doctype, ledgerEntry.name);
|
||||
let party = await frappe.getDoc(
|
||||
'Party',
|
||||
ledgerEntry.customer || ledgerEntry.supplier
|
||||
);
|
||||
if (party.address) {
|
||||
let addressDetails = await frappe.getDoc('Address', party.address);
|
||||
row.place = addressDetails.state || '';
|
||||
}
|
||||
row.gstin = party.gstin;
|
||||
row.partyName = ledgerEntry.customer || ledgerEntry.supplier;
|
||||
row.invNo = ledgerEntry.name;
|
||||
row.invDate = ledgerEntry.date;
|
||||
row.rate = 0;
|
||||
row.inState = true;
|
||||
row.reverseCharge = !party.gstin ? 'Y' : 'N';
|
||||
ledgerEntry.taxes.forEach(tax => {
|
||||
row.rate += tax.rate;
|
||||
const taxAmt = (tax.rate * ledgerEntry.netTotal) / 100;
|
||||
if (tax.account === 'IGST') row.igstAmt = taxAmt;
|
||||
if (tax.account === 'IGST') row.inState = false;
|
||||
if (tax.account === 'CGST') row.cgstAmt = taxAmt;
|
||||
if (tax.account === 'SGST') row.sgstAmt = taxAmt;
|
||||
if (tax.account === 'Nil Rated') row.nilRated = true;
|
||||
if (tax.account === 'Exempt') row.exempt = true;
|
||||
if (tax.account === 'Non GST') row.nonGST = true;
|
||||
});
|
||||
row.invAmt = ledgerEntry.grandTotal;
|
||||
row.taxVal = ledgerEntry.netTotal;
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseGSTR;
|
156
reports/GoodsAndServiceTax/BaseViewConfig.js
Normal file
156
reports/GoodsAndServiceTax/BaseViewConfig.js
Normal file
@ -0,0 +1,156 @@
|
||||
module.exports = {
|
||||
filterFields: [
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
label: 'Transfer Type',
|
||||
fieldname: 'transferType',
|
||||
options: [
|
||||
'',
|
||||
'B2B',
|
||||
'B2C-Large',
|
||||
'B2C-Small',
|
||||
'Nil Rated, Exempted and Non GST supplies'
|
||||
],
|
||||
size: 'small'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Data',
|
||||
label: 'Place',
|
||||
size: 'small',
|
||||
placeholder: 'Place',
|
||||
fieldname: 'place'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Date',
|
||||
label: 'From Date',
|
||||
size: 'small',
|
||||
placeholder: 'From Date',
|
||||
fieldname: 'fromDate'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Date',
|
||||
label: 'To Date',
|
||||
size: 'small',
|
||||
placeholder: 'To Date',
|
||||
fieldname: 'toDate'
|
||||
}
|
||||
],
|
||||
linkFields: [
|
||||
{
|
||||
label: 'Export',
|
||||
type: 'primary',
|
||||
action: async report => {
|
||||
async function getReportDetails() {
|
||||
let [rows, columns] = await report.getReportData(
|
||||
report.currentFilters
|
||||
);
|
||||
let columnData = columns.map(column => {
|
||||
return {
|
||||
id: column.id,
|
||||
content: column.content,
|
||||
checked: true
|
||||
};
|
||||
});
|
||||
return {
|
||||
title: title,
|
||||
rows: rows,
|
||||
columnData: columnData
|
||||
};
|
||||
}
|
||||
report.$modal.show({
|
||||
modalProps: {
|
||||
title: `Export ${title}`,
|
||||
noFooter: true
|
||||
},
|
||||
component: require('../../src/components/ExportWizard').default,
|
||||
props: await getReportDetails()
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Clear Filters',
|
||||
type: 'secondary',
|
||||
action: async report => {
|
||||
await report.getReportData({});
|
||||
report.usedToReRender += 1;
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
getColumns() {
|
||||
return [
|
||||
{
|
||||
label: 'GSTIN No.',
|
||||
fieldname: 'gstin',
|
||||
fieldtype: 'Data',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'partyName',
|
||||
label: 'Party',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: 'Invoice No.',
|
||||
fieldname: 'invNo',
|
||||
fieldtype: 'Data',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: 'Invoice Value',
|
||||
fieldname: 'invAmt',
|
||||
fieldtype: 'Currency',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: 'Invoice Date',
|
||||
fieldname: 'invDate',
|
||||
fieldtype: 'Date',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: 'Place of supply',
|
||||
fieldname: 'place',
|
||||
fieldtype: 'Data',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: 'Rate',
|
||||
fieldname: 'rate',
|
||||
fieldtype: 'Data',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
label: 'Taxable Value',
|
||||
fieldname: 'taxVal',
|
||||
fieldtype: 'Currency',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: 'Reverse Chrg.',
|
||||
fieldname: 'reverseCharge',
|
||||
fieldtype: 'Data',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
label: 'Intergrated Tax',
|
||||
fieldname: 'igstAmt',
|
||||
fieldtype: 'Currency',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: 'Central Tax',
|
||||
fieldname: 'cgstAmt',
|
||||
fieldtype: 'Currency',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: 'State Tax',
|
||||
fieldname: 'sgstAmt',
|
||||
fieldtype: 'Currency',
|
||||
width: 100
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
28
reports/GoodsAndServiceTax/GSTR1.js
Normal file
28
reports/GoodsAndServiceTax/GSTR1.js
Normal file
@ -0,0 +1,28 @@
|
||||
const BaseGSTR = require('./BaseGSTR');
|
||||
|
||||
class GSTR1 extends BaseGSTR {
|
||||
async run(params) {
|
||||
if (!Object.keys(params).length) return [];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const data = await this.getCompleteReport('GSTR-1', filters);
|
||||
|
||||
const conditions = {
|
||||
B2B: row => row.gstin,
|
||||
'B2C-Large': row => !row.gstin && !row.inState && row.invAmt >= 250000,
|
||||
'B2C-Small': row =>
|
||||
!row.gstin && (row.inState || (row.inState && row.invAmt < 250000))
|
||||
};
|
||||
|
||||
if (!params.transferType) return data;
|
||||
return data.filter(row => conditions[params.transferType](row));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GSTR1;
|
9
reports/GoodsAndServiceTax/GSTR1View.js
Normal file
9
reports/GoodsAndServiceTax/GSTR1View.js
Normal file
@ -0,0 +1,9 @@
|
||||
const title = 'GSTR 1';
|
||||
const baseConfig = require('./BaseViewConfig');
|
||||
module.exports = {
|
||||
title: title,
|
||||
method: 'gstr-1',
|
||||
filterFields: baseConfig.filterFields,
|
||||
linkFields: baseConfig.linkFields,
|
||||
getColumns: baseConfig.getColumns
|
||||
};
|
28
reports/GoodsAndServiceTax/GSTR2.js
Normal file
28
reports/GoodsAndServiceTax/GSTR2.js
Normal file
@ -0,0 +1,28 @@
|
||||
const BaseGSTR = require('./BaseGSTR');
|
||||
|
||||
class GSTR2 extends BaseGSTR {
|
||||
async run(params) {
|
||||
if (!Object.keys(params).length) return [];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const data = await this.getCompleteReport('GSTR-2', filters);
|
||||
|
||||
const conditions = {
|
||||
B2B: row => row.gstin,
|
||||
'B2C-Large': row => !row.gstin && !row.inState && row.invAmt >= 250000,
|
||||
'B2C-Small': row =>
|
||||
!row.gstin && (row.inState || (row.inState && row.invAmt < 250000))
|
||||
};
|
||||
|
||||
if (!params.transferType) return data;
|
||||
return data.filter(row => conditions[params.transferType](row));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GSTR2;
|
9
reports/GoodsAndServiceTax/GSTR2View.js
Normal file
9
reports/GoodsAndServiceTax/GSTR2View.js
Normal file
@ -0,0 +1,9 @@
|
||||
const title = 'GSTR 2';
|
||||
const baseConfig = require('./BaseViewConfig');
|
||||
module.exports = {
|
||||
title: title,
|
||||
method: 'gstr-2',
|
||||
filterFields: baseConfig.filterFields,
|
||||
linkFields: baseConfig.linkFields,
|
||||
getColumns: baseConfig.getColumns
|
||||
};
|
@ -1,59 +0,0 @@
|
||||
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;
|
@ -1,74 +0,0 @@
|
||||
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'
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
@ -1,69 +0,0 @@
|
||||
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'
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user