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

AccountsReceivablePayable first cut

This commit is contained in:
Faris Ansari 2018-04-29 14:37:59 +05:30
parent a5b4b81051
commit 55d4058b02
6 changed files with 292 additions and 22 deletions

View File

@ -33,6 +33,8 @@ module.exports = {
frappe.desk.menu.addItem('Trial Balance', '#report/trial-balance');
frappe.desk.menu.addItem('Sales Register', '#report/sales-register');
frappe.desk.menu.addItem('Purchase Register', '#report/purchase-register');
frappe.desk.menu.addItem('Accounts Receivable', '#report/accounts-receivable');
frappe.desk.menu.addItem('Accounts Payable', '#report/accounts-payable');
frappe.router.default = '#tree/Account';

View File

@ -0,0 +1,41 @@
const frappe = require('frappejs');
const ReportPage = require('frappejs/client/desk/reportpage');
const { DateTime } = require('luxon');
module.exports = class AccountsReceivableView extends ReportPage {
constructor(opts) {
super({
title: frappe._('Accounts Payable'),
filterFields: [
{fieldtype: 'Date', fieldname: 'date', label: 'As on Date', required: 1},
]
});
this.method = 'accounts-payable';
this.datatableOptions = {
layout: 'fixed'
}
}
async setDefaultFilterValues() {
const today = DateTime.local();
this.filters.setValue('date', today.toISODate());
this.run();
}
getRowsForDataTable(data) {
return data.rows || [];
}
getColumns() {
const columns = [
{ label: 'Posting Date', fieldtype: 'Data', fieldname: 'date' },
{ label: 'Supplier', fieldtype: 'Data', fieldname: 'party' },
{ label: 'Voucher Type', fieldtype: 'Data', fieldname: 'voucherType' },
{ label: 'Voucher No', fieldtype: 'Data', fieldname: 'voucherNo' },
{ label: 'Due Date', fieldtype: 'Data', fieldname: 'dueDate' },
];
return columns;
}
}

View File

@ -0,0 +1,155 @@
const frappe = require('frappejs');
module.exports = class AccountsReceivablePayable {
async run(reportType, { date }) {
const rows = await getReceivablePayable({
reportType,
date
});
return { rows };
}
}
async function getReceivablePayable({ reportType = 'Receivable', date }) {
let entries = [];
const debitOrCredit = reportType === 'Receivable' ? 'debit' : 'credit';
const referenceType = reportType === 'Receivable' ? 'Invoice' : 'Bill';
entries = await getLedgerEntries();
const vouchers = await getVouchers();
const futureEntries = getFutureEntries();
const returnEntries = getReturnEntries();
const pdc = getPDC();
const validEntries = getValidEntries();
let data = [];
for (let entry of validEntries) {
// console.log(entry);
const { outStandingAmount, creditNoteAmount } = getOutstandingAmount(entry);
console.log(outStandingAmount);
if (outStandingAmount > 0.1 / 10) {
const row = {
date: entry.date,
party: entry.party
};
// due date / bill date
row.voucherType = entry.referenceType;
row.voucherNo = entry.referenceName;
// bill details
const invoicedAmount = entry[debitOrCredit] || 0;
const paidAmount = invoicedAmount - outStandingAmount - creditNoteAmount;
Object.assign(row, {
invoicedAmount,
paidAmount,
outStandingAmount,
creditNoteAmount
});
// ageing
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 {
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'
});
}
}

View File

@ -0,0 +1,41 @@
const frappe = require('frappejs');
const ReportPage = require('frappejs/client/desk/reportpage');
const { DateTime } = require('luxon');
module.exports = class AccountsReceivableView extends ReportPage {
constructor(opts) {
super({
title: frappe._('Accounts Receivable'),
filterFields: [
{fieldtype: 'Date', fieldname: 'date', label: 'As on Date', required: 1},
]
});
this.method = 'accounts-receivable';
this.datatableOptions = {
layout: 'fixed'
}
}
async setDefaultFilterValues() {
const today = DateTime.local();
this.filters.setValue('date', today.toISODate());
this.run();
}
getRowsForDataTable(data) {
return data.rows || [];
}
getColumns() {
const columns = [
{ label: 'Posting Date', fieldtype: 'Data', fieldname: 'date' },
{ label: 'Customer', fieldtype: 'Data', fieldname: 'party' },
{ label: 'Voucher Type', fieldtype: 'Data', fieldname: 'voucherType' },
{ label: 'Voucher No', fieldtype: 'Data', fieldname: 'voucherNo' },
{ label: 'Due Date', fieldtype: 'Data', fieldname: 'dueDate' },
];
return columns;
}
}

View File

@ -5,37 +5,52 @@ const BalanceSheet = require('./BalanceSheet/BalanceSheet');
const TrialBalance = require('./TrialBalance/TrialBalance');
const SalesRegister = require('./SalesRegister/SalesRegister');
const PurchaseRegister = require('./PurchaseRegister/PurchaseRegister');
const AccountsReceivablePayable = require('./AccountsReceivablePayable/AccountsReceivablePayable');
// called on server side
function registerReportMethods() {
frappe.registerMethod({
method: 'general-ledger',
handler: getReportData(GeneralLedger)
const reports = [
{
method: 'general-ledger',
class: GeneralLedger
},
{
method: 'profit-and-loss',
class: ProfitAndLoss
},
{
method: 'balance-sheet',
class: BalanceSheet
},
{
method: 'trial-balance',
class: TrialBalance
},
{
method: 'sales-register',
class: SalesRegister
},
{
method: 'purchase-register',
class: PurchaseRegister
},
];
reports.forEach(report => {
frappe.registerMethod({
method: report.method,
handler: getReportData(report.class)
});
});
frappe.registerMethod({
method: 'profit-and-loss',
handler: getReportData(ProfitAndLoss)
method: 'accounts-receivable',
handler: args => new AccountsReceivablePayable().run('Receivable', args)
});
frappe.registerMethod({
method: 'balance-sheet',
handler: getReportData(BalanceSheet)
});
frappe.registerMethod({
method: 'trial-balance',
handler: getReportData(TrialBalance)
});
frappe.registerMethod({
method: 'sales-register',
handler: getReportData(SalesRegister)
});
frappe.registerMethod({
method: 'purchase-register',
handler: getReportData(PurchaseRegister)
method: 'accounts-payable',
handler: args => new AccountsReceivablePayable().run('Payable', args)
});
}

View File

@ -5,6 +5,8 @@ const BalanceSheetView = require('./BalanceSheet/BalanceSheetView');
const TrialBalanceView = require('./TrialBalance/TrialBalanceView');
const SalesRegisterView = require('./SalesRegister/SalesRegisterView');
const PurchaseRegisterView = require('./PurchaseRegister/PurchaseRegisterView');
const AccountsReceivableView = require('./AccountsReceivablePayable/AccountsReceivableView');
const AccountsPayableView = require('./AccountsReceivablePayable/AccountsPayableView');
// called on client side
function registerReportRoutes() {
@ -49,6 +51,20 @@ function registerReportRoutes() {
}
await frappe.views.PurchaseRegister.show(params);
});
frappe.router.add('report/accounts-receivable', async (params) => {
if (!frappe.views.AccountsReceivable) {
frappe.views.AccountsReceivable = new AccountsReceivableView();
}
await frappe.views.AccountsReceivable.show(params);
});
frappe.router.add('report/accounts-payable', async (params) => {
if (!frappe.views.AccountsPayable) {
frappe.views.AccountsPayable = new AccountsPayableView();
}
await frappe.views.AccountsPayable.show(params);
});
}
module.exports = registerReportRoutes;