Merge branch 'develop' into suplier_portal_fixes

This commit is contained in:
Anurag Mishra 2021-03-15 16:21:55 +05:30 committed by GitHub
commit 5e475f85b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
342 changed files with 10102 additions and 7218 deletions

46
.github/helper/install.sh vendored Normal file
View File

@ -0,0 +1,46 @@
#!/bin/bash
set -e
cd ~ || exit
sudo apt-get install redis-server
sudo apt install nodejs
sudo apt install npm
pip install frappe-bench
git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
mkdir ~/frappe-bench/sites/test_site
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
sudo chmod o+x /usr/local/bin/wkhtmltopdf
sudo apt-get install libcups2-dev
cd ~/frappe-bench || exit
sed -i 's/watch:/# watch:/g' Procfile
sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
bench get-app erpnext "${GITHUB_WORKSPACE}"
bench start &
bench --site test_site reinstall --yes

View File

@ -1,4 +1,6 @@
{
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "test_frappe",
"db_password": "test_frappe",
"auto_email_id": "test@example.com",

96
.github/workflows/ci-tests.yml vendored Normal file
View File

@ -0,0 +1,96 @@
name: CI
on:
pull_request:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- TYPE: "server"
JOB_NAME: "Server"
RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --coverage
- TYPE: "patch"
JOB_NAME: "Patch"
RUN_COMMAND: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate
name: ${{ matrix.JOB_NAME }}
services:
mysql:
image: mariadb:10.3
env:
MYSQL_ALLOW_EMPTY_PASSWORD: YES
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.6
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- name: Run Tests
run: ${{ matrix.RUN_COMMAND }}
env:
TYPE: ${{ matrix.TYPE }}
- name: Coverage
if: matrix.TYPE == 'server'
run: |
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
cd ${GITHUB_WORKSPACE}
pip install coveralls==2.2.0
pip install coverage==4.5.4
coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}

View File

@ -1,69 +0,0 @@
language: python
dist: trusty
git:
depth: 1
cache:
- pip
addons:
hosts: test_site
mariadb: 10.3
jobs:
include:
- name: "Python 3.6 Server Side Test"
python: 3.6
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 3.6 Patch Test"
python: 3.6
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
install:
- cd ~
- nvm install 10
- pip install frappe-bench
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench
- mkdir ~/frappe-bench/sites/test_site
- cp -r $TRAVIS_BUILD_DIR/.travis/site_config.json ~/frappe-bench/sites/test_site/
- mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
- mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- mysql -u root -e "CREATE DATABASE test_frappe"
- mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
- mysql -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
- mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
- mysql -u root -e "FLUSH PRIVILEGES"
- wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
- tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
- sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
- sudo chmod o+x /usr/local/bin/wkhtmltopdf
- sudo apt-get install libcups2-dev
- cd ~/frappe-bench
- sed -i 's/watch:/# watch:/g' Procfile
- sed -i 's/schedule:/# schedule:/g' Procfile
- sed -i 's/socketio:/# socketio:/g' Procfile
- sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
- bench get-app erpnext $TRAVIS_BUILD_DIR
- bench start &
- bench --site test_site reinstall --yes
after_script:
- pip install coverage==4.5.4
- pip install python-coveralls
- coveralls -b apps/erpnext -d ../../sites/.coverage

View File

@ -109,7 +109,7 @@ def get_region(company=None):
'''
if company or frappe.flags.company:
return frappe.get_cached_value('Company',
company or frappe.flags.company, 'country')
company or frappe.flags.company, 'country')
elif frappe.flags.country:
return frappe.flags.country
else:

View File

@ -63,17 +63,21 @@
"Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
"account_number": "1371"
},
"Abziehbare VSt. 7%": {
"account_number": "1571"
},
"Abziehbare VSt. 19%": {
"account_number": "1576"
},
"Abziehbare VStr. nach \u00a713b UStG 19%": {
"account_number": "1577"
},
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
"account_number": "3120"
"Abziehbare Vorsteuer": {
"account_type": "Tax",
"is_group": 1,
"Abziehbare Vorsteuer 7%": {
"account_number": "1571"
},
"Abziehbare Vorsteuer 19%": {
"account_number": "1576"
},
"Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
"account_number": "1577"
},
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
"account_number": "3120"
}
}
},
"III. Wertpapiere": {
@ -196,6 +200,7 @@
},
"Umsatzsteuer": {
"is_group": 1,
"account_type": "Tax",
"Umsatzsteuer 7%": {
"account_number": "1771"
},

View File

@ -292,18 +292,21 @@
"Umsatzsteuerforderungen fr\u00fchere Jahre": {}
},
"Sonstige Verm\u00f6gensgegenst\u00e4nde oder sonstige Verbindlichkeiten": {
"Abziehbare Vorsteuer": {},
"Abziehbare Vorsteuer 16%": {},
"Abziehbare Vorsteuer 19%": {},
"Abziehbare Vorsteuer 7%": {},
"Abziehbare Vorsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Unsatzsteuerlager": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 16%": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19%": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG ": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {},
"Abziehbare Vorsteuer": {
"account_type": "Tax",
"is_group": 1,
"Abziehbare Vorsteuer 16%": {},
"Abziehbare Vorsteuer 19%": {},
"Abziehbare Vorsteuer 7%": {},
"Abziehbare Vorsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Unsatzsteuerlager": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 16%": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19%": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG ": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {}
},
"Aufl\u00f6sung Vorsteuer aus Vorjahr \u00a7 4/3 EStG": {},
"Aufzuteilende Vorsteuer": {},
"Aufzuteilende Vorsteuer 16%": {},
@ -673,23 +676,26 @@
"Sonstige Verrechnungskonten (Interimskonto)": {
"account_type": "Stock Received But Not Billed"
},
"Umsatzsteuer": {},
"Umsatzsteuer 16%": {},
"Umsatzsteuer 19%": {},
"Umsatzsteuer 7%": {},
"Umsatzsteuer Vorjahr": {},
"Umsatzsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Umsatzsteuerlager": {},
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen": {},
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen 19%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 16%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 19%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug": {},
"Umsatzsteuer fr\u00fchere Jahre": {},
"Umsatzsteuer laufendes Jahr": {},
"Umsatzsteuer nach \u00a713b UStG": {},
"Umsatzsteuer nach \u00a713b UStG 16%": {},
"Umsatzsteuer nach \u00a713b UStG 19%": {},
"Umsatzsteuer": {
"account_type": "Tax",
"is_group": 1,
"Umsatzsteuer 16%": {},
"Umsatzsteuer 19%": {},
"Umsatzsteuer 7%": {},
"Umsatzsteuer Vorjahr": {},
"Umsatzsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Umsatzsteuerlager": {},
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen": {},
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen 19%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 16%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 19%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug": {},
"Umsatzsteuer fr\u00fchere Jahre": {},
"Umsatzsteuer laufendes Jahr": {},
"Umsatzsteuer nach \u00a713b UStG": {},
"Umsatzsteuer nach \u00a713b UStG 16%": {},
"Umsatzsteuer nach \u00a713b UStG 19%": {}
},
"Umsatzsteuer- Vorauszahlungen": {},
"Umsatzsteuer- Vorauszahlungen 1/11": {},
"Verbindlichkeiten aus Lohn- und Kirchensteuer": {}

View File

@ -659,6 +659,7 @@
},
"Abziehbare Vorsteuer (Gruppe)": {
"is_group": 1,
"account_type": "Tax",
"Abziehbare Vorsteuer": {
"account_number": "1400"
},

View File

@ -43,11 +43,11 @@ class AccountingDimension(Document):
if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
else:
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self)
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue='long')
def on_trash(self):
if frappe.flags.in_test:
delete_accounting_dimension(doc=self)
delete_accounting_dimension(doc=self, queue='long')
else:
frappe.enqueue(delete_accounting_dimension, doc=self)
@ -58,8 +58,13 @@ class AccountingDimension(Document):
if not self.fieldname:
self.fieldname = scrub(self.label)
def make_dimension_in_accounting_doctypes(doc):
doclist = get_doctypes_with_dimensions()
def on_update(self):
frappe.flags.accounting_dimensions = None
def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist:
doclist = get_doctypes_with_dimensions()
doc_count = len(get_accounting_dimensions())
count = 0
@ -79,13 +84,13 @@ def make_dimension_in_accounting_doctypes(doc):
"owner": "Administrator"
}
if doctype == "Budget":
add_dimension_to_budget_doctype(df, doc)
else:
meta = frappe.get_meta(doctype, cached=False)
fieldnames = [d.fieldname for d in meta.get("fields")]
meta = frappe.get_meta(doctype, cached=False)
fieldnames = [d.fieldname for d in meta.get("fields")]
if df['fieldname'] not in fieldnames:
if df['fieldname'] not in fieldnames:
if doctype == "Budget":
add_dimension_to_budget_doctype(df.copy(), doc)
else:
create_custom_field(doctype, df)
count += 1
@ -175,23 +180,17 @@ def toggle_disabling(doc):
frappe.clear_cache(doctype=doctype)
def get_doctypes_with_dimensions():
doclist = ["GL Entry", "Sales Invoice", "POS Invoice", "Purchase Invoice", "Payment Entry", "Asset",
"Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
"Sales Invoice Item", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"]
return doclist
return frappe.get_hooks("accounting_dimension_doctypes")
def get_accounting_dimensions(as_list=True):
accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"])
if frappe.flags.accounting_dimensions is None:
frappe.flags.accounting_dimensions = frappe.get_all("Accounting Dimension",
fields=["label", "fieldname", "disabled", "document_type"])
if as_list:
return [d.fieldname for d in accounting_dimensions]
return [d.fieldname for d in frappe.flags.accounting_dimensions]
else:
return accounting_dimensions
return frappe.flags.accounting_dimensions
def get_checks_for_pl_and_bs_accounts():
dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs

View File

@ -86,6 +86,7 @@
},
{
"default": "0",
"description": "Setting the account as a Company Account is necessary for Bank Reconciliation",
"fieldname": "is_company_account",
"fieldtype": "Check",
"label": "Is Company Account"
@ -207,7 +208,7 @@
}
],
"links": [],
"modified": "2020-07-17 13:59:50.795412",
"modified": "2020-10-23 16:48:06.303658",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account",

View File

@ -0,0 +1,163 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.provide("erpnext.accounts.bank_reconciliation");
frappe.ui.form.on("Bank Reconciliation Tool", {
setup: function (frm) {
frm.set_query("bank_account", function () {
return {
filters: {
company: ["in", frm.doc.company],
'is_company_account': 1
},
};
});
},
refresh: function (frm) {
frappe.require("assets/js/bank-reconciliation-tool.min.js", () =>
frm.trigger("make_reconciliation_tool")
);
frm.upload_statement_button = frm.page.set_secondary_action(
__("Upload Bank Statement"),
() =>
frappe.call({
method:
"erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement",
args: {
dt: frm.doc.doctype,
dn: frm.doc.name,
company: frm.doc.company,
bank_account: frm.doc.bank_account,
},
callback: function (r) {
if (!r.exc) {
var doc = frappe.model.sync(r.message);
frappe.set_route(
"Form",
doc[0].doctype,
doc[0].name
);
}
},
})
);
},
after_save: function (frm) {
frm.trigger("make_reconciliation_tool");
},
bank_account: function (frm) {
frappe.db.get_value(
"Bank Account",
frm.bank_account,
"account",
(r) => {
frappe.db.get_value(
"Account",
r.account,
"account_currency",
(r) => {
frm.currency = r.account_currency;
}
);
}
);
frm.trigger("get_account_opening_balance");
},
bank_statement_from_date: function (frm) {
frm.trigger("get_account_opening_balance");
},
make_reconciliation_tool(frm) {
frm.get_field("reconciliation_tool_cards").$wrapper.empty();
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
frm.trigger("get_cleared_balance").then(() => {
if (
frm.doc.bank_account &&
frm.doc.bank_statement_from_date &&
frm.doc.bank_statement_to_date &&
frm.doc.bank_statement_closing_balance
) {
frm.trigger("render_chart");
frm.trigger("render");
frappe.utils.scroll_to(
frm.get_field("reconciliation_tool_cards").$wrapper,
true,
30
);
}
});
}
},
get_account_opening_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_from_date) {
frappe.call({
method:
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {
bank_account: frm.doc.bank_account,
till_date: frm.doc.bank_statement_from_date,
},
callback: (response) => {
frm.set_value("account_opening_balance", response.message);
},
});
}
},
get_cleared_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
return frappe.call({
method:
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {
bank_account: frm.doc.bank_account,
till_date: frm.doc.bank_statement_to_date,
},
callback: (response) => {
frm.cleared_balance = response.message;
},
});
}
},
render_chart(frm) {
frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager(
{
$reconciliation_tool_cards: frm.get_field(
"reconciliation_tool_cards"
).$wrapper,
bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance,
cleared_balance: frm.cleared_balance,
currency: frm.currency,
}
);
},
render(frm) {
if (frm.doc.bank_account) {
frm.bank_reconciliation_data_table_manager = new erpnext.accounts.bank_reconciliation.DataTableManager(
{
company: frm.doc.company,
bank_account: frm.doc.bank_account,
$reconciliation_tool_dt: frm.get_field(
"reconciliation_tool_dt"
).$wrapper,
$no_bank_transactions: frm.get_field(
"no_bank_transactions"
).$wrapper,
bank_statement_from_date: frm.doc.bank_statement_from_date,
bank_statement_to_date: frm.doc.bank_statement_to_date,
bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance,
cards_manager: frm.cards_manager,
}
);
}
},
});

View File

@ -0,0 +1,113 @@
{
"actions": [],
"creation": "2020-12-02 10:13:02.148040",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"bank_account",
"column_break_1",
"bank_statement_from_date",
"bank_statement_to_date",
"column_break_2",
"account_opening_balance",
"bank_statement_closing_balance",
"section_break_1",
"reconciliation_tool_cards",
"reconciliation_tool_dt",
"no_bank_transactions"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"fieldname": "bank_account",
"fieldtype": "Link",
"label": "Bank Account",
"options": "Bank Account"
},
{
"fieldname": "column_break_1",
"fieldtype": "Column Break"
},
{
"depends_on": "eval: doc.bank_account",
"fieldname": "bank_statement_from_date",
"fieldtype": "Date",
"label": "Bank Statement From Date"
},
{
"depends_on": "eval: doc.bank_statement_from_date",
"fieldname": "bank_statement_to_date",
"fieldtype": "Date",
"label": "Bank Statement To Date"
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"depends_on": "eval: doc.bank_statement_from_date",
"fieldname": "account_opening_balance",
"fieldtype": "Currency",
"label": "Account Opening Balance",
"options": "Currency",
"read_only": 1
},
{
"depends_on": "eval: doc.bank_statement_to_date",
"fieldname": "bank_statement_closing_balance",
"fieldtype": "Currency",
"label": "Bank Statement Closing Balance",
"options": "Currency"
},
{
"depends_on": "eval: doc.bank_statement_closing_balance",
"fieldname": "section_break_1",
"fieldtype": "Section Break",
"label": "Reconcile"
},
{
"fieldname": "reconciliation_tool_cards",
"fieldtype": "HTML"
},
{
"fieldname": "reconciliation_tool_dt",
"fieldtype": "HTML"
},
{
"fieldname": "no_bank_transactions",
"fieldtype": "HTML",
"options": "<div class=\"text-muted text-center\">No Matching Bank Transactions Found</div>"
}
],
"hide_toolbar": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-02-02 01:35:53.043578",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation Tool",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -0,0 +1,452 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import json
import frappe
from frappe.model.document import Document
from frappe import _
from frappe.utils import flt
from erpnext import get_company_currency
from erpnext.accounts.utils import get_balance_on
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import get_entries, get_amounts_not_reflected_in_system
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_paid_amount
class BankReconciliationTool(Document):
pass
@frappe.whitelist()
def get_bank_transactions(bank_account, from_date = None, to_date = None):
# returns bank transactions for a bank account
filters = []
filters.append(['bank_account', '=', bank_account])
filters.append(['docstatus', '=', 1])
filters.append(['unallocated_amount', '>', 0])
if to_date:
filters.append(['date', '<=', to_date])
if from_date:
filters.append(['date', '>=', from_date])
transactions = frappe.get_all(
'Bank Transaction',
fields = ['date', 'deposit', 'withdrawal', 'currency',
'description', 'name', 'bank_account', 'company',
'unallocated_amount', 'reference_number', 'party_type', 'party'],
filters = filters
)
return transactions
@frappe.whitelist()
def get_account_balance(bank_account, till_date):
# returns account balance till the specified date
account = frappe.db.get_value('Bank Account', bank_account, 'account')
filters = frappe._dict({
"account": account,
"report_date": till_date,
"include_pos_transactions": 1
})
data = get_entries(filters)
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
total_debit, total_credit = 0,0
for d in data:
total_debit += flt(d.debit)
total_credit += flt(d.credit)
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
bank_bal = flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) \
+ amounts_not_reflected_in_system
return bank_bal
@frappe.whitelist()
def update_bank_transaction(bank_transaction_name, reference_number, party_type=None, party=None):
# updates bank transaction based on the new parameters provided by the user from Vouchers
bank_transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
bank_transaction.reference_number = reference_number
bank_transaction.party_type = party_type
bank_transaction.party = party
bank_transaction.save()
return frappe.db.get_all('Bank Transaction',
filters={
'name': bank_transaction_name
},
fields=['date', 'deposit', 'withdrawal', 'currency',
'description', 'name', 'bank_account', 'company',
'unallocated_amount', 'reference_number',
'party_type', 'party'],
)[0]
@frappe.whitelist()
def create_journal_entry_bts( bank_transaction_name, reference_number=None, reference_date=None, posting_date=None, entry_type=None,
second_account=None, mode_of_payment=None, party_type=None, party=None, allow_edit=None):
# Create a new journal entry based on the bank transaction
bank_transaction = frappe.db.get_values(
"Bank Transaction", bank_transaction_name,
fieldname=["name", "deposit", "withdrawal", "bank_account"] ,
as_dict=True
)[0]
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
account_type = frappe.db.get_value("Account", second_account, "account_type")
if account_type in ["Receivable", "Payable"]:
if not (party_type and party):
frappe.throw(_("Party Type and Party is required for Receivable / Payable account {0}").format( second_account))
accounts = []
# Multi Currency?
accounts.append({
"account": second_account,
"credit_in_account_currency": bank_transaction.deposit
if bank_transaction.deposit > 0
else 0,
"debit_in_account_currency":bank_transaction.withdrawal
if bank_transaction.withdrawal > 0
else 0,
"party_type":party_type,
"party":party,
})
accounts.append({
"account": company_account,
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal
if bank_transaction.withdrawal > 0
else 0,
"debit_in_account_currency":bank_transaction.deposit
if bank_transaction.deposit > 0
else 0,
})
company = frappe.get_value("Account", company_account, "company")
journal_entry_dict = {
"voucher_type" : entry_type,
"company" : company,
"posting_date" : posting_date,
"cheque_date" : reference_date,
"cheque_no" : reference_number,
"mode_of_payment" : mode_of_payment
}
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.update(journal_entry_dict)
journal_entry.set("accounts", accounts)
if allow_edit:
return journal_entry
journal_entry.insert()
journal_entry.submit()
if bank_transaction.deposit > 0:
paid_amount = bank_transaction.deposit
else:
paid_amount = bank_transaction.withdrawal
vouchers = json.dumps([{
"payment_doctype":"Journal Entry",
"payment_name":journal_entry.name,
"amount":paid_amount}])
return reconcile_vouchers(bank_transaction.name, vouchers)
@frappe.whitelist()
def create_payment_entry_bts( bank_transaction_name, reference_number=None, reference_date=None, party_type=None, party=None, posting_date=None,
mode_of_payment=None, project=None, cost_center=None, allow_edit=None):
# Create a new payment entry based on the bank transaction
bank_transaction = frappe.db.get_values(
"Bank Transaction", bank_transaction_name,
fieldname=["name", "unallocated_amount", "deposit", "bank_account"] ,
as_dict=True
)[0]
paid_amount = bank_transaction.unallocated_amount
payment_type = "Receive" if bank_transaction.deposit > 0 else "Pay"
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
company = frappe.get_value("Account", company_account, "company")
payment_entry_dict = {
"company" : company,
"payment_type" : payment_type,
"reference_no" : reference_number,
"reference_date" : reference_date,
"party_type" : party_type,
"party" : party,
"posting_date" : posting_date,
"paid_amount": paid_amount,
"received_amount": paid_amount
}
payment_entry = frappe.new_doc("Payment Entry")
payment_entry.update(payment_entry_dict)
if mode_of_payment:
payment_entry.mode_of_payment = mode_of_payment
if project:
payment_entry.project = project
if cost_center:
payment_entry.cost_center = cost_center
if payment_type == "Receive":
payment_entry.paid_to = company_account
else:
payment_entry.paid_from = company_account
payment_entry.validate()
if allow_edit:
return payment_entry
payment_entry.insert()
payment_entry.submit()
vouchers = json.dumps([{
"payment_doctype":"Payment Entry",
"payment_name":payment_entry.name,
"amount":paid_amount}])
return reconcile_vouchers(bank_transaction.name, vouchers)
@frappe.whitelist()
def reconcile_vouchers(bank_transaction_name, vouchers):
# updated clear date of all the vouchers based on the bank transaction
vouchers = json.loads(vouchers)
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
if transaction.unallocated_amount == 0:
frappe.throw(_("This bank transaction is already fully reconciled"))
total_amount = 0
for voucher in vouchers:
voucher['payment_entry'] = frappe.get_doc(voucher['payment_doctype'], voucher['payment_name'])
total_amount += get_paid_amount(frappe._dict({
'payment_document': voucher['payment_doctype'],
'payment_entry': voucher['payment_name'],
}), transaction.currency)
if total_amount > transaction.unallocated_amount:
frappe.throw(_("The Sum Total of Amounts of All Selected Vouchers Should be Less than the Unallocated Amount of the Bank Transaction"))
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
for voucher in vouchers:
gl_entry = frappe.db.get_value("GL Entry", dict(account=account, voucher_type=voucher['payment_doctype'], voucher_no=voucher['payment_name']), ['credit', 'debit'], as_dict=1)
gl_amount, transaction_amount = (gl_entry.credit, transaction.deposit) if gl_entry.credit > 0 else (gl_entry.debit, transaction.withdrawal)
allocated_amount = gl_amount if gl_amount >= transaction_amount else transaction_amount
transaction.append("payment_entries", {
"payment_document": voucher['payment_entry'].doctype,
"payment_entry": voucher['payment_entry'].name,
"allocated_amount": allocated_amount
})
transaction.save()
transaction.update_allocations()
return frappe.get_doc("Bank Transaction", bank_transaction_name)
@frappe.whitelist()
def get_linked_payments(bank_transaction_name, document_types = None):
# get all matching payments for a bank transaction
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
bank_account = frappe.db.get_values(
"Bank Account",
transaction.bank_account,
["account", "company"],
as_dict=True)[0]
(account, company) = (bank_account.account, bank_account.company)
matching = check_matching(account, company, transaction, document_types)
return matching
def check_matching(bank_account, company, transaction, document_types):
# combine all types of vocuhers
subquery = get_queries(bank_account, company, transaction, document_types)
filters = {
"amount": transaction.unallocated_amount,
"payment_type" : "Receive" if transaction.deposit > 0 else "Pay",
"reference_no": transaction.reference_number,
"party_type": transaction.party_type,
"party": transaction.party,
"bank_account": bank_account
}
matching_vouchers = []
for query in subquery:
matching_vouchers.extend(
frappe.db.sql(query, filters,)
)
return sorted(matching_vouchers, key = lambda x: x[0], reverse=True) if matching_vouchers else []
def get_queries(bank_account, company, transaction, document_types):
# get queries to get matching vouchers
amount_condition = "=" if "exact_match" in document_types else "<="
account_from_to = "paid_to" if transaction.deposit > 0 else "paid_from"
queries = []
if "payment_entry" in document_types:
pe_amount_matching = get_pe_matching_query(amount_condition, account_from_to, transaction)
queries.extend([pe_amount_matching])
if "journal_entry" in document_types:
je_amount_matching = get_je_matching_query(amount_condition, transaction)
queries.extend([je_amount_matching])
if transaction.deposit > 0 and "sales_invoice" in document_types:
si_amount_matching = get_si_matching_query(amount_condition)
queries.extend([si_amount_matching])
if transaction.withdrawal > 0:
if "purchase_invoice" in document_types:
pi_amount_matching = get_pi_matching_query(amount_condition)
queries.extend([pi_amount_matching])
if "expense_claim" in document_types:
ec_amount_matching = get_ec_matching_query(bank_account, company, amount_condition)
queries.extend([ec_amount_matching])
return queries
def get_pe_matching_query(amount_condition, account_from_to, transaction):
# get matching payment entries query
if transaction.deposit > 0:
currency_field = "paid_to_account_currency as currency"
else:
currency_field = "paid_from_account_currency as currency"
return f"""
SELECT
(CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END
+ CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Payment Entry' as doctype,
name,
paid_amount,
reference_no,
reference_date,
party,
party_type,
posting_date,
{currency_field}
FROM
`tabPayment Entry`
WHERE
paid_amount {amount_condition} %(amount)s
AND docstatus = 1
AND payment_type IN (%(payment_type)s, 'Internal Transfer')
AND ifnull(clearance_date, '') = ""
AND {account_from_to} = %(bank_account)s
"""
def get_je_matching_query(amount_condition, transaction):
# get matching journal entry query
cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
return f"""
SELECT
(CASE WHEN je.cheque_no=%(reference_no)s THEN 1 ELSE 0 END
+ 1) AS rank ,
'Journal Entry' as doctype,
je.name,
jea.{cr_or_dr}_in_account_currency as paid_amount,
je.cheque_no as reference_no,
je.cheque_date as reference_date,
je.pay_to_recd_from as party,
jea.party_type,
je.posting_date,
jea.account_currency as currency
FROM
`tabJournal Entry Account` as jea
JOIN
`tabJournal Entry` as je
ON
jea.parent = je.name
WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00')
AND jea.account = %(bank_account)s
AND jea.{cr_or_dr}_in_account_currency {amount_condition} %(amount)s
AND je.docstatus = 1
"""
def get_si_matching_query(amount_condition):
# get matchin sales invoice query
return f"""
SELECT
( CASE WHEN si.customer = %(party)s THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Sales Invoice' as doctype,
si.name,
sip.amount as paid_amount,
'' as reference_no,
'' as reference_date,
si.customer as party,
'Customer' as party_type,
si.posting_date,
si.currency
FROM
`tabSales Invoice Payment` as sip
JOIN
`tabSales Invoice` as si
ON
sip.parent = si.name
WHERE (sip.clearance_date is null or sip.clearance_date='0000-00-00')
AND sip.account = %(bank_account)s
AND sip.amount {amount_condition} %(amount)s
AND si.docstatus = 1
"""
def get_pi_matching_query(amount_condition):
# get matching purchase invoice query
return f"""
SELECT
( CASE WHEN supplier = %(party)s THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Purchase Invoice' as doctype,
name,
paid_amount,
'' as reference_no,
'' as reference_date,
supplier as party,
'Supplier' as party_type,
posting_date,
currency
FROM
`tabPurchase Invoice`
WHERE
paid_amount {amount_condition} %(amount)s
AND docstatus = 1
AND is_paid = 1
AND ifnull(clearance_date, '') = ""
AND cash_bank_account = %(bank_account)s
"""
def get_ec_matching_query(bank_account, company, amount_condition):
# get matching Expense Claim query
mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account",
filters={"default_account": bank_account}, fields=["parent"])]
mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
company_currency = get_company_currency(company)
return f"""
SELECT
( CASE WHEN employee = %(party)s THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Expense Claim' as doctype,
name,
total_sanctioned_amount as paid_amount,
'' as reference_no,
'' as reference_date,
employee as party,
'Employee' as party_type,
posting_date,
'{company_currency}' as currency
FROM
`tabExpense Claim`
WHERE
total_sanctioned_amount {amount_condition} %(amount)s
AND docstatus = 1
AND is_paid = 1
AND ifnull(clearance_date, '') = ""
AND mode_of_payment in {mode_of_payments}
"""

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestBankReconciliationTool(unittest.TestCase):
pass

View File

@ -0,0 +1,3 @@
.warnings .warning {
margin-bottom: 40px;
}

View File

@ -0,0 +1,574 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Bank Statement Import", {
setup(frm) {
frappe.realtime.on("data_import_refresh", ({ data_import }) => {
frm.import_in_progress = false;
if (data_import !== frm.doc.name) return;
frappe.model.clear_doc("Bank Statement Import", frm.doc.name);
frappe.model
.with_doc("Bank Statement Import", frm.doc.name)
.then(() => {
frm.refresh();
});
});
frappe.realtime.on("data_import_progress", (data) => {
frm.import_in_progress = true;
if (data.data_import !== frm.doc.name) {
return;
}
let percent = Math.floor((data.current * 100) / data.total);
let seconds = Math.floor(data.eta);
let minutes = Math.floor(data.eta / 60);
let eta_message =
// prettier-ignore
seconds < 60
? __('About {0} seconds remaining', [seconds])
: minutes === 1
? __('About {0} minute remaining', [minutes])
: __('About {0} minutes remaining', [minutes]);
let message;
if (data.success) {
let message_args = [data.current, data.total, eta_message];
message =
frm.doc.import_type === "Insert New Records"
? __("Importing {0} of {1}, {2}", message_args)
: __("Updating {0} of {1}, {2}", message_args);
}
if (data.skipping) {
message = __(
"Skipping {0} of {1}, {2}",
[
data.current,
data.total,
eta_message,
]
);
}
frm.dashboard.show_progress(
__("Import Progress"),
percent,
message
);
frm.page.set_indicator(__("In Progress"), "orange");
// hide progress when complete
if (data.current === data.total) {
setTimeout(() => {
frm.dashboard.hide();
frm.refresh();
}, 2000);
}
});
frm.set_query("reference_doctype", () => {
return {
filters: {
name: ["in", frappe.boot.user.can_import],
},
};
});
frm.get_field("import_file").df.options = {
restrictions: {
allowed_file_types: [".csv", ".xls", ".xlsx"],
},
};
frm.has_import_file = () => {
return frm.doc.import_file || frm.doc.google_sheets_url;
};
},
refresh(frm) {
frm.page.hide_icon_group();
frm.trigger("update_indicators");
frm.trigger("import_file");
frm.trigger("show_import_log");
frm.trigger("show_import_warnings");
frm.trigger("toggle_submit_after_import");
frm.trigger("show_import_status");
frm.trigger("show_report_error_button");
if (frm.doc.status === "Partial Success") {
frm.add_custom_button(__("Export Errored Rows"), () =>
frm.trigger("export_errored_rows")
);
}
if (frm.doc.status.includes("Success")) {
frm.add_custom_button(
__("Go to {0} List", [frm.doc.reference_doctype]),
() => frappe.set_route("List", frm.doc.reference_doctype)
);
}
},
onload_post_render(frm) {
frm.trigger("update_primary_action");
},
update_primary_action(frm) {
if (frm.is_dirty()) {
frm.enable_save();
return;
}
frm.disable_save();
if (frm.doc.status !== "Success") {
if (!frm.is_new() && frm.has_import_file()) {
let label =
frm.doc.status === "Pending"
? __("Start Import")
: __("Retry");
frm.page.set_primary_action(label, () =>
frm.events.start_import(frm)
);
} else {
frm.page.set_primary_action(__("Save"), () => frm.save());
}
}
},
update_indicators(frm) {
const indicator = frappe.get_indicator(frm.doc);
if (indicator) {
frm.page.set_indicator(indicator[0], indicator[1]);
} else {
frm.page.clear_indicator();
}
},
show_import_status(frm) {
let import_log = JSON.parse(frm.doc.import_log || "[]");
let successful_records = import_log.filter((log) => log.success);
let failed_records = import_log.filter((log) => !log.success);
if (successful_records.length === 0) return;
let message;
if (failed_records.length === 0) {
let message_args = [successful_records.length];
if (frm.doc.import_type === "Insert New Records") {
message =
successful_records.length > 1
? __("Successfully imported {0} records.", message_args)
: __("Successfully imported {0} record.", message_args);
} else {
message =
successful_records.length > 1
? __("Successfully updated {0} records.", message_args)
: __("Successfully updated {0} record.", message_args);
}
} else {
let message_args = [successful_records.length, import_log.length];
if (frm.doc.import_type === "Insert New Records") {
message =
successful_records.length > 1
? __(
"Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args
)
: __(
"Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args
);
} else {
message =
successful_records.length > 1
? __(
"Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args
)
: __(
"Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args
);
}
}
frm.dashboard.set_headline(message);
},
show_report_error_button(frm) {
if (frm.doc.status === "Error") {
frappe.db
.get_list("Error Log", {
filters: { method: frm.doc.name },
fields: ["method", "error"],
order_by: "creation desc",
limit: 1,
})
.then((result) => {
if (result.length > 0) {
frm.add_custom_button("Report Error", () => {
let fake_xhr = {
responseText: JSON.stringify({
exc: result[0].error,
}),
};
frappe.request.report_error(fake_xhr, {});
});
}
});
}
},
start_import(frm) {
frm.call({
method: "form_start_import",
args: { data_import: frm.doc.name },
btn: frm.page.btn_primary,
}).then((r) => {
if (r.message === true) {
frm.disable_save();
}
});
},
download_template() {
let method =
"/api/method/frappe.core.doctype.data_import.data_import.download_template";
open_url_post(method, {
doctype: "Bank Transaction",
export_records: "5_records",
export_fields: {
"Bank Transaction": [
"date",
"deposit",
"withdrawal",
"description",
"reference_number",
],
},
});
},
reference_doctype(frm) {
frm.trigger("toggle_submit_after_import");
},
toggle_submit_after_import(frm) {
frm.toggle_display("submit_after_import", false);
let doctype = frm.doc.reference_doctype;
if (doctype) {
frappe.model.with_doctype(doctype, () => {
let meta = frappe.get_meta(doctype);
frm.toggle_display("submit_after_import", meta.is_submittable);
});
}
},
google_sheets_url(frm) {
if (!frm.is_dirty()) {
frm.trigger("import_file");
} else {
frm.trigger("update_primary_action");
}
},
refresh_google_sheet(frm) {
frm.trigger("import_file");
},
import_file(frm) {
frm.toggle_display("section_import_preview", frm.has_import_file());
if (!frm.has_import_file()) {
frm.get_field("import_preview").$wrapper.empty();
return;
} else {
frm.trigger("update_primary_action");
}
// load import preview
frm.get_field("import_preview").$wrapper.empty();
$('<span class="text-muted">')
.html(__("Loading import file..."))
.appendTo(frm.get_field("import_preview").$wrapper);
frm.call({
method: "get_preview_from_template",
args: {
data_import: frm.doc.name,
import_file: frm.doc.import_file,
google_sheets_url: frm.doc.google_sheets_url,
},
error_handlers: {
TimestampMismatchError() {
// ignore this error
},
},
}).then((r) => {
let preview_data = r.message;
frm.events.show_import_preview(frm, preview_data);
frm.events.show_import_warnings(frm, preview_data);
});
},
// method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template',
show_import_preview(frm, preview_data) {
let import_log = JSON.parse(frm.doc.import_log || "[]");
if (
frm.import_preview &&
frm.import_preview.doctype === frm.doc.reference_doctype
) {
frm.import_preview.preview_data = preview_data;
frm.import_preview.import_log = import_log;
frm.import_preview.refresh();
return;
}
frappe.require("/assets/js/data_import_tools.min.js", () => {
frm.import_preview = new frappe.data_import.ImportPreview({
wrapper: frm.get_field("import_preview").$wrapper,
doctype: frm.doc.reference_doctype,
preview_data,
import_log,
frm,
events: {
remap_column(changed_map) {
let template_options = JSON.parse(
frm.doc.template_options || "{}"
);
template_options.column_to_field_map =
template_options.column_to_field_map || {};
Object.assign(
template_options.column_to_field_map,
changed_map
);
frm.set_value(
"template_options",
JSON.stringify(template_options)
);
frm.save().then(() => frm.trigger("import_file"));
},
},
});
});
},
export_errored_rows(frm) {
open_url_post(
"/api/method/frappe.core.doctype.data_import.data_import.download_errored_template",
{
data_import_name: frm.doc.name,
}
);
},
show_import_warnings(frm, preview_data) {
let columns = preview_data.columns;
let warnings = JSON.parse(frm.doc.template_warnings || "[]");
warnings = warnings.concat(preview_data.warnings || []);
frm.toggle_display("import_warnings_section", warnings.length > 0);
if (warnings.length === 0) {
frm.get_field("import_warnings").$wrapper.html("");
return;
}
// group warnings by row
let warnings_by_row = {};
let other_warnings = [];
for (let warning of warnings) {
if (warning.row) {
warnings_by_row[warning.row] =
warnings_by_row[warning.row] || [];
warnings_by_row[warning.row].push(warning);
} else {
other_warnings.push(warning);
}
}
let html = "";
html += Object.keys(warnings_by_row)
.map((row_number) => {
let message = warnings_by_row[row_number]
.map((w) => {
if (w.field) {
let label =
w.field.label +
(w.field.parent !== frm.doc.reference_doctype
? ` (${w.field.parent})`
: "");
return `<li>${label}: ${w.message}</li>`;
}
return `<li>${w.message}</li>`;
})
.join("");
return `
<div class="warning" data-row="${row_number}">
<h5 class="text-uppercase">${__("Row {0}", [row_number])}</h5>
<div class="body"><ul>${message}</ul></div>
</div>
`;
})
.join("");
html += other_warnings
.map((warning) => {
let header = "";
if (warning.col) {
let column_number = `<span class="text-uppercase">${__(
"Column {0}",
[warning.col]
)}</span>`;
let column_header = columns[warning.col].header_title;
header = `${column_number} (${column_header})`;
}
return `
<div class="warning" data-col="${warning.col}">
<h5>${header}</h5>
<div class="body">${warning.message}</div>
</div>
`;
})
.join("");
frm.get_field("import_warnings").$wrapper.html(`
<div class="row">
<div class="col-sm-10 warnings">${html}</div>
</div>
`);
},
show_failed_logs(frm) {
frm.trigger("show_import_log");
},
show_import_log(frm) {
let import_log = JSON.parse(frm.doc.import_log || "[]");
let logs = import_log;
frm.toggle_display("import_log", false);
frm.toggle_display("import_log_section", logs.length > 0);
if (logs.length === 0) {
frm.get_field("import_log_preview").$wrapper.empty();
return;
}
let rows = logs
.map((log) => {
let html = "";
if (log.success) {
if (frm.doc.import_type === "Insert New Records") {
html = __(
"Successfully imported {0}", [
`<span class="underline">${frappe.utils.get_form_link(
frm.doc.reference_doctype,
log.docname,
true
)}<span>`,
]
);
} else {
html = __(
"Successfully updated {0}", [
`<span class="underline">${frappe.utils.get_form_link(
frm.doc.reference_doctype,
log.docname,
true
)}<span>`,
]
);
}
} else {
let messages = log.messages
.map(JSON.parse)
.map((m) => {
let title = m.title
? `<strong>${m.title}</strong>`
: "";
let message = m.message
? `<div>${m.message}</div>`
: "";
return title + message;
})
.join("");
let id = frappe.dom.get_unique_id();
html = `${messages}
<button class="btn btn-default btn-xs" type="button" data-toggle="collapse" data-target="#${id}" aria-expanded="false" aria-controls="${id}" style="margin-top: 15px;">
${__("Show Traceback")}
</button>
<div class="collapse" id="${id}" style="margin-top: 15px;">
<div class="well">
<pre>${log.exception}</pre>
</div>
</div>`;
}
let indicator_color = log.success ? "green" : "red";
let title = log.success ? __("Success") : __("Failure");
if (frm.doc.show_failed_logs && log.success) {
return "";
}
return `<tr>
<td>${log.row_indexes.join(", ")}</td>
<td>
<div class="indicator ${indicator_color}">${title}</div>
</td>
<td>
${html}
</td>
</tr>`;
})
.join("");
if (!rows && frm.doc.show_failed_logs) {
rows = `<tr><td class="text-center text-muted" colspan=3>
${__("No failed logs")}
</td></tr>`;
}
frm.get_field("import_log_preview").$wrapper.html(`
<table class="table table-bordered">
<tr class="text-muted">
<th width="10%">${__("Row Number")}</th>
<th width="10%">${__("Status")}</th>
<th width="80%">${__("Message")}</th>
</tr>
${rows}
</table>
`);
},
show_missing_link_values(frm, missing_link_values) {
let can_be_created_automatically = missing_link_values.every(
(d) => d.has_one_mandatory_field
);
let html = missing_link_values
.map((d) => {
let doctype = d.doctype;
let values = d.missing_values;
return `
<h5>${doctype}</h5>
<ul>${values.map((v) => `<li>${v}</li>`).join("")}</ul>
`;
})
.join("");
if (can_be_created_automatically) {
// prettier-ignore
let message = __('There are some linked records which needs to be created before we can import your file. Do you want to create the following missing records automatically?');
frappe.confirm(message + html, () => {
frm.call("create_missing_link_values", {
missing_link_values,
}).then((r) => {
let records = r.message;
frappe.msgprint(__(
"Created {0} records successfully.", [
records.length,
]
));
});
});
} else {
frappe.msgprint(
// prettier-ignore
__('The following records needs to be created before we can import your file.') + html
);
}
},
});

View File

@ -0,0 +1,227 @@
{
"actions": [],
"autoname": "format:Bank Statement Import on {creation}",
"beta": 1,
"creation": "2019-08-04 14:16:08.318714",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"bank_account",
"bank",
"column_break_4",
"google_sheets_url",
"refresh_google_sheet",
"html_5",
"import_file",
"download_template",
"status",
"template_options",
"import_warnings_section",
"template_warnings",
"import_warnings",
"section_import_preview",
"import_preview",
"import_log_section",
"import_log",
"show_failed_logs",
"import_log_preview",
"reference_doctype",
"import_type",
"submit_after_import",
"mute_emails"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1,
"set_only_once": 1
},
{
"fieldname": "bank_account",
"fieldtype": "Link",
"label": "Bank Account",
"options": "Bank Account",
"reqd": 1,
"set_only_once": 1
},
{
"depends_on": "eval:doc.bank_account",
"fetch_from": "bank_account.bank",
"fieldname": "bank",
"fieldtype": "Link",
"label": "Bank",
"options": "Bank",
"read_only": 1,
"set_only_once": 1
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "download_template",
"fieldtype": "Button",
"label": "Download Template"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "import_file",
"fieldtype": "Attach",
"in_list_view": 1,
"label": "Import File"
},
{
"fieldname": "import_preview",
"fieldtype": "HTML",
"label": "Import Preview"
},
{
"fieldname": "section_import_preview",
"fieldtype": "Section Break",
"label": "Preview"
},
{
"fieldname": "template_options",
"fieldtype": "Code",
"hidden": 1,
"label": "Template Options",
"options": "JSON",
"read_only": 1
},
{
"fieldname": "import_log",
"fieldtype": "Code",
"label": "Import Log",
"options": "JSON"
},
{
"fieldname": "import_log_section",
"fieldtype": "Section Break",
"label": "Import Log"
},
{
"fieldname": "import_log_preview",
"fieldtype": "HTML",
"label": "Import Log Preview"
},
{
"default": "Pending",
"fieldname": "status",
"fieldtype": "Select",
"hidden": 1,
"label": "Status",
"options": "Pending\nSuccess\nPartial Success\nError",
"read_only": 1
},
{
"fieldname": "template_warnings",
"fieldtype": "Code",
"hidden": 1,
"label": "Template Warnings",
"options": "JSON"
},
{
"fieldname": "import_warnings_section",
"fieldtype": "Section Break",
"label": "Import File Errors and Warnings"
},
{
"fieldname": "import_warnings",
"fieldtype": "HTML",
"label": "Import Warnings"
},
{
"default": "0",
"fieldname": "show_failed_logs",
"fieldtype": "Check",
"label": "Show Failed Logs"
},
{
"depends_on": "eval:!doc.__islocal && !doc.import_file",
"fieldname": "html_5",
"fieldtype": "HTML",
"options": "<h5 class=\"text-muted uppercase\">Or</h5>"
},
{
"depends_on": "eval:!doc.__islocal && !doc.import_file\n",
"description": "Must be a publicly accessible Google Sheets URL",
"fieldname": "google_sheets_url",
"fieldtype": "Data",
"label": "Import from Google Sheets"
},
{
"depends_on": "eval:doc.google_sheets_url && !doc.__unsaved",
"fieldname": "refresh_google_sheet",
"fieldtype": "Button",
"label": "Refresh Google Sheet"
},
{
"default": "Bank Transaction",
"fieldname": "reference_doctype",
"fieldtype": "Link",
"hidden": 1,
"in_list_view": 1,
"label": "Document Type",
"options": "DocType",
"reqd": 1,
"set_only_once": 1
},
{
"default": "Insert New Records",
"fieldname": "import_type",
"fieldtype": "Select",
"hidden": 1,
"in_list_view": 1,
"label": "Import Type",
"options": "\nInsert New Records\nUpdate Existing Records",
"reqd": 1,
"set_only_once": 1
},
{
"default": "1",
"fieldname": "submit_after_import",
"fieldtype": "Check",
"hidden": 1,
"label": "Submit After Import",
"set_only_once": 1
},
{
"default": "1",
"fieldname": "mute_emails",
"fieldtype": "Check",
"hidden": 1,
"label": "Don't Send Emails",
"set_only_once": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
}
],
"hide_toolbar": 1,
"links": [],
"modified": "2021-02-10 19:29:59.027325",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Import",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import csv
import json
import re
import openpyxl
from openpyxl.styles import Font
from openpyxl.utils import get_column_letter
from six import string_types
import frappe
from frappe.core.doctype.data_import.importer import Importer, ImportFile
from frappe.utils.background_jobs import enqueue
from frappe.utils.xlsxutils import handle_html, ILLEGAL_CHARACTERS_RE
from frappe import _
from frappe.core.doctype.data_import.data_import import DataImport
class BankStatementImport(DataImport):
def __init__(self, *args, **kwargs):
super(BankStatementImport, self).__init__(*args, **kwargs)
def validate(self):
doc_before_save = self.get_doc_before_save()
if (
not (self.import_file or self.google_sheets_url)
or (doc_before_save and doc_before_save.import_file != self.import_file)
or (doc_before_save and doc_before_save.google_sheets_url != self.google_sheets_url)
):
template_options_dict = {}
column_to_field_map = {}
bank = frappe.get_doc("Bank", self.bank)
for i in bank.bank_transaction_mapping:
column_to_field_map[i.file_field] = i.bank_transaction_field
template_options_dict["column_to_field_map"] = column_to_field_map
self.template_options = json.dumps(template_options_dict)
self.template_warnings = ""
self.validate_import_file()
self.validate_google_sheets_url()
def start_import(self):
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.utils.scheduler import is_scheduler_inactive
if is_scheduler_inactive() and not frappe.flags.in_test:
frappe.throw(
_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive")
)
enqueued_jobs = [d.get("job_name") for d in get_info()]
if self.name not in enqueued_jobs:
enqueue(
start_import,
queue="default",
timeout=6000,
event="data_import",
job_name=self.name,
data_import=self.name,
bank_account=self.bank_account,
import_file_path=self.import_file,
bank=self.bank,
template_options=self.template_options,
now=frappe.conf.developer_mode or frappe.flags.in_test,
)
return True
return False
@frappe.whitelist()
def get_preview_from_template(data_import, import_file=None, google_sheets_url=None):
return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template(
import_file, google_sheets_url
)
@frappe.whitelist()
def form_start_import(data_import):
return frappe.get_doc("Bank Statement Import", data_import).start_import()
@frappe.whitelist()
def download_errored_template(data_import_name):
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
data_import.export_errored_rows()
def start_import(data_import, bank_account, import_file_path, bank, template_options):
"""This method runs in background job"""
update_mapping_db(bank, template_options)
data_import = frappe.get_doc("Bank Statement Import", data_import)
import_file = ImportFile("Bank Transaction", file = import_file_path, import_type="Insert New Records")
data = import_file.raw_data
add_bank_account(data, bank_account)
write_files(import_file, data)
try:
i = Importer(data_import.reference_doctype, data_import=data_import)
i.import_data()
except Exception:
frappe.db.rollback()
data_import.db_set("status", "Error")
frappe.log_error(title=data_import.name)
finally:
frappe.flags.in_import = False
frappe.publish_realtime("data_import_refresh", {"data_import": data_import.name})
def update_mapping_db(bank, template_options):
bank = frappe.get_doc("Bank", bank)
for d in bank.bank_transaction_mapping:
d.delete()
for d in json.loads(template_options)["column_to_field_map"].items():
bank.append("bank_transaction_mapping", {"bank_transaction_field": d[1] ,"file_field": d[0]} )
bank.save()
def add_bank_account(data, bank_account):
bank_account_loc = None
if "Bank Account" not in data[0]:
data[0].append("Bank Account")
else:
for loc, header in enumerate(data[0]):
if header == "Bank Account":
bank_account_loc = loc
for row in data[1:]:
if bank_account_loc:
row[bank_account_loc] = bank_account
else:
row.append(bank_account)
def write_files(import_file, data):
full_file_path = import_file.file_doc.get_full_path()
parts = import_file.file_doc.get_extension()
extension = parts[1]
extension = extension.lstrip(".")
if extension == "csv":
with open(full_file_path, 'w', newline='') as file:
writer = csv.writer(file)
writer.writerows(data)
elif extension == "xlsx" or "xls":
write_xlsx(data, "trans", file_path = full_file_path)
def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
# from xlsx utils with changes
column_widths = column_widths or []
if wb is None:
wb = openpyxl.Workbook(write_only=True)
ws = wb.create_sheet(sheet_name, 0)
for i, column_width in enumerate(column_widths):
if column_width:
ws.column_dimensions[get_column_letter(i + 1)].width = column_width
row1 = ws.row_dimensions[1]
row1.font = Font(name='Calibri', bold=True)
for row in data:
clean_row = []
for item in row:
if isinstance(item, string_types) and (sheet_name not in ['Data Import Template', 'Data Export']):
value = handle_html(item)
else:
value = item
if isinstance(item, string_types) and next(ILLEGAL_CHARACTERS_RE.finditer(value), None):
# Remove illegal characters from the string
value = re.sub(ILLEGAL_CHARACTERS_RE, '', value)
clean_row.append(value)
ws.append(clean_row)
wb.save(file_path)
return True
@frappe.whitelist()
def upload_bank_statement(**args):
args = frappe._dict(args)
bsi = frappe.new_doc("Bank Statement Import")
if args.company:
bsi.update({
"company": args.company,
})
if args.bank_account:
bsi.update({
"bank_account": args.bank_account
})
return bsi

View File

@ -0,0 +1,36 @@
let imports_in_progress = [];
frappe.listview_settings['Bank Statement Import'] = {
onload(listview) {
frappe.realtime.on('data_import_progress', data => {
if (!imports_in_progress.includes(data.data_import)) {
imports_in_progress.push(data.data_import);
}
});
frappe.realtime.on('data_import_refresh', data => {
imports_in_progress = imports_in_progress.filter(
d => d !== data.data_import
);
listview.refresh();
});
},
get_indicator: function(doc) {
var colors = {
'Pending': 'orange',
'Not Started': 'orange',
'Partial Success': 'orange',
'Success': 'green',
'In Progress': 'orange',
'Error': 'red'
};
let status = doc.status;
if (imports_in_progress.includes(doc.name)) {
status = 'In Progress';
}
if (status == 'Pending') {
status = 'Not Started';
}
return [__(status), colors[status], 'status,=,' + doc.status];
},
hide_name_column: true
};

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestBankStatementImport(unittest.TestCase):
pass

View File

@ -1,8 +0,0 @@
// Copyright (c) 2017, sathishpy@gmail.com and contributors
// For license information, please see license.txt
frappe.ui.form.on('Bank Statement Settings', {
refresh: function(frm) {
}
});

View File

@ -1,272 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"beta": 0,
"creation": "2017-11-13 13:38:10.863592",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Bank Account",
"length": 0,
"no_copy": 0,
"options": "Bank",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "'%d/%m/%Y'",
"fieldname": "date_format",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Date Format",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "statement_header_mapping",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Statement Header Mapping",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "header_items",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Statement Headers",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Settings Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "transaction_data_mapping",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Transaction Data Mapping",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mapped_items",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Mapped Items",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Transaction Settings Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-04-07 18:57:04.048423",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class BankStatementSettings(Document):
def autoname(self):
self.name = self.bank + "-Statement-Settings"

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Bank Statement Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Statement Settings
() => frappe.tests.make('Bank Statement Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestBankStatementSettings(unittest.TestCase):
pass

View File

@ -1,101 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-01-08 00:16:42.762980",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mapped_header",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Mapped Header",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stmt_header",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Bank Header",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-01-08 00:19:14.841134",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Settings Item",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,100 +0,0 @@
// Copyright (c) 2017, sathishpy@gmail.com and contributors
// For license information, please see license.txt
frappe.ui.form.on('Bank Statement Transaction Entry', {
setup: function(frm) {
frm.events.account_filters(frm)
frm.events.invoice_filter(frm)
},
refresh: function(frm) {
frm.set_df_property("bank_account", "read_only", frm.doc.__islocal ? 0 : 1);
frm.set_df_property("from_date", "read_only", frm.doc.__islocal ? 0 : 1);
frm.set_df_property("to_date", "read_only", frm.doc.__islocal ? 0 : 1);
},
invoke_doc_function(frm, method) {
frappe.call({
doc: frm.doc,
method: method,
callback: function(r) {
if(!r.exe) {
frm.refresh_fields();
}
}
});
},
account_filters: function(frm) {
frm.fields_dict['bank_account'].get_query = function(doc, dt, dn) {
return {
filters:[
["Account", "account_type", "in", ["Bank"]]
]
}
};
frm.fields_dict['receivable_account'].get_query = function(doc, dt, dn) {
return {
filters: {"account_type": "Receivable"}
}
};
frm.fields_dict['payable_account'].get_query = function(doc, dt, dn) {
return {
filters: {"account_type": "Payable"}
}
};
},
invoice_filter: function(frm) {
frm.set_query("invoice", "payment_invoice_items", function(doc, cdt, cdn) {
let row = locals[cdt][cdn]
if (row.party_type == "Customer") {
return {
filters:[[row.invoice_type, "customer", "in", [row.party]],
[row.invoice_type, "status", "!=", "Cancelled" ],
[row.invoice_type, "posting_date", "<", row.transaction_date ],
[row.invoice_type, "outstanding_amount", ">", 0 ]]
}
} else if (row.party_type == "Supplier") {
return {
filters:[[row.invoice_type, "supplier", "in", [row.party]],
[row.invoice_type, "status", "!=", "Cancelled" ],
[row.invoice_type, "posting_date", "<", row.transaction_date ],
[row.invoice_type, "outstanding_amount", ">", 0 ]]
}
}
});
},
match_invoices: function(frm) {
frm.events.invoke_doc_function(frm, "populate_matching_invoices");
},
create_payments: function(frm) {
frm.events.invoke_doc_function(frm, "create_payment_entries");
},
submit_payments: function(frm) {
frm.events.invoke_doc_function(frm, "submit_payment_entries");
},
});
frappe.ui.form.on('Bank Statement Transaction Invoice Item', {
party_type: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (row.party_type == "Customer") {
row.invoice_type = "Sales Invoice";
} else if (row.party_type == "Supplier") {
row.invoice_type = "Purchase Invoice";
} else if (row.party_type == "Account") {
row.invoice_type = "Journal Entry";
}
refresh_field("invoice_type", row.name, "payment_invoice_items");
},
invoice_type: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (row.invoice_type == "Purchase Invoice") {
row.party_type = "Supplier";
} else if (row.invoice_type == "Sales Invoice") {
row.party_type = "Customer";
}
refresh_field("party_type", row.name, "payment_invoice_items");
}
});

View File

@ -1,792 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"beta": 0,
"creation": "2017-11-07 13:48:13.123185",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Bank Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "From Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "To Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank_settings",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Bank Statement Settings",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Settings",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Bank",
"length": 0,
"no_copy": 0,
"options": "Bank",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "receivable_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Receivable Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payable_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payable Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank_statement",
"fieldtype": "Attach",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Bank Statement",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Bank Transaction Entries",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "new_transaction_items",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "New Transactions",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Transaction Payment Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.new_transaction_items && doc.new_transaction_items.length",
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "match_invoices",
"fieldtype": "Button",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Match Transaction to Invoices",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_14",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "create_payments",
"fieldtype": "Button",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Create New Payment/Journal Entry",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_16",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "submit_payments",
"fieldtype": "Button",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submit/Reconcile Payments",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.new_transaction_items && doc.new_transaction_items.length",
"fieldname": "section_break_18",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Matching Invoices",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payment_invoice_items",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Invoice Items",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Transaction Invoice Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reconciled_transactions",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reconciled Transactions",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reconciled_transaction_items",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reconciled Transactions",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Transaction Payment Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Bank Statement Transaction Entry",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-09-14 18:04:44.170455",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Transaction Entry",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,443 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from erpnext.accounts.utils import get_outstanding_invoices
from frappe.utils import nowdate
from datetime import datetime
import csv, os, re, io
import difflib
import copy
class BankStatementTransactionEntry(Document):
def autoname(self):
self.name = self.bank_account + "-" + self.from_date + "-" + self.to_date
if self.bank:
mapper_name = self.bank + "-Statement-Settings"
if not frappe.db.exists("Bank Statement Settings", mapper_name):
self.create_settings(self.bank)
self.bank_settings = mapper_name
def create_settings(self, bank):
mapper = frappe.new_doc("Bank Statement Settings")
mapper.bank = bank
mapper.date_format = "%Y-%m-%d"
mapper.bank_account = self.bank_account
for header in ["Date", "Particulars", "Withdrawals", "Deposits", "Balance"]:
header_item = mapper.append("header_items", {})
header_item.mapped_header = header_item.stmt_header = header
mapper.save()
def on_update(self):
if (not self.bank_statement):
self.reconciled_transaction_items = self.new_transaction_items = []
return
if len(self.new_transaction_items + self.reconciled_transaction_items) == 0:
self.populate_payment_entries()
else:
self.match_invoice_to_payment()
def validate(self):
if not self.new_transaction_items:
self.populate_payment_entries()
def get_statement_headers(self):
if not self.bank_settings:
frappe.throw(_("Bank Data mapper doesn't exist"))
mapper_doc = frappe.get_doc("Bank Statement Settings", self.bank_settings)
headers = {entry.mapped_header:entry.stmt_header for entry in mapper_doc.header_items}
return headers
def populate_payment_entries(self):
if self.bank_statement is None: return
file_url = self.bank_statement
if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0):
frappe.throw(_("Transactions already retreived from the statement"))
date_format = frappe.get_value("Bank Statement Settings", self.bank_settings, "date_format")
if (date_format is None):
date_format = '%Y-%m-%d'
if self.bank_settings:
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
statement_headers = self.get_statement_headers()
transactions = get_transaction_entries(file_url, statement_headers)
for entry in transactions:
date = entry[statement_headers["Date"]].strip()
#print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"]))
if (not date): continue
transaction_date = datetime.strptime(date, date_format).date()
if (self.from_date and transaction_date < datetime.strptime(self.from_date, '%Y-%m-%d').date()): continue
if (self.to_date and transaction_date > datetime.strptime(self.to_date, '%Y-%m-%d').date()): continue
bank_entry = self.append('new_transaction_items', {})
bank_entry.transaction_date = transaction_date
bank_entry.description = entry[statement_headers["Particulars"]]
mapped_item = next((entry for entry in mapped_items if entry.mapping_type == "Transaction" and frappe.safe_decode(entry.bank_data.lower()) in frappe.safe_decode(bank_entry.description.lower())), None)
if (mapped_item is not None):
bank_entry.party_type = mapped_item.mapped_data_type
bank_entry.party = mapped_item.mapped_data
else:
bank_entry.party_type = "Supplier" if not entry[statement_headers["Deposits"]].strip() else "Customer"
party_list = frappe.get_all(bank_entry.party_type, fields=["name"])
parties = [party.name for party in party_list]
matches = difflib.get_close_matches(frappe.safe_decode(bank_entry.description.lower()), parties, 1, 0.4)
if len(matches) > 0: bank_entry.party = matches[0]
bank_entry.amount = -float(entry[statement_headers["Withdrawals"]]) if not entry[statement_headers["Deposits"]].strip() else float(entry[statement_headers["Deposits"]])
self.map_unknown_transactions()
self.map_transactions_on_journal_entry()
def map_transactions_on_journal_entry(self):
for entry in self.new_transaction_items:
vouchers = frappe.db.sql("""select name, posting_date from `tabJournal Entry`
where posting_date='{0}' and total_credit={1} and cheque_no='{2}' and docstatus != 2
""".format(entry.transaction_date, abs(entry.amount), frappe.safe_decode(entry.description)), as_dict=True)
if (len(vouchers) == 1):
entry.reference_name = vouchers[0].name
def populate_matching_invoices(self):
self.payment_invoice_items = []
self.map_unknown_transactions()
added_invoices = []
for entry in self.new_transaction_items:
if (not entry.party or entry.party_type == "Account"): continue
account = self.receivable_account if entry.party_type == "Customer" else self.payable_account
invoices = get_outstanding_invoices(entry.party_type, entry.party, account)
transaction_date = datetime.strptime(entry.transaction_date, "%Y-%m-%d").date()
outstanding_invoices = [invoice for invoice in invoices if invoice.posting_date <= transaction_date]
amount = abs(entry.amount)
matching_invoices = [invoice for invoice in outstanding_invoices if invoice.outstanding_amount == amount]
sorted(outstanding_invoices, key=lambda k: k['posting_date'])
for e in (matching_invoices + outstanding_invoices):
added = next((inv for inv in added_invoices if inv == e.get('voucher_no')), None)
if (added is not None): continue
ent = self.append('payment_invoice_items', {})
ent.transaction_date = entry.transaction_date
ent.payment_description = frappe.safe_decode(entry.description)
ent.party_type = entry.party_type
ent.party = entry.party
ent.invoice = e.get('voucher_no')
added_invoices += [ent.invoice]
ent.invoice_type = "Sales Invoice" if entry.party_type == "Customer" else "Purchase Invoice"
ent.invoice_date = e.get('posting_date')
ent.outstanding_amount = e.get('outstanding_amount')
ent.allocated_amount = min(float(e.get('outstanding_amount')), amount)
amount -= float(e.get('outstanding_amount'))
if (amount <= 5): break
self.match_invoice_to_payment()
self.populate_matching_vouchers()
self.map_transactions_on_journal_entry()
def match_invoice_to_payment(self):
added_payments = []
for entry in self.new_transaction_items:
if (not entry.party or entry.party_type == "Account"): continue
entry.account = self.receivable_account if entry.party_type == "Customer" else self.payable_account
amount = abs(entry.amount)
payment, matching_invoices = None, []
for inv_entry in self.payment_invoice_items:
if (inv_entry.payment_description != frappe.safe_decode(entry.description) or inv_entry.transaction_date != entry.transaction_date): continue
if (inv_entry.party != entry.party): continue
matching_invoices += [inv_entry.invoice_type + "|" + inv_entry.invoice]
payment = get_payments_matching_invoice(inv_entry.invoice, entry.amount, entry.transaction_date)
doc = frappe.get_doc(inv_entry.invoice_type, inv_entry.invoice)
inv_entry.invoice_date = doc.posting_date
inv_entry.outstanding_amount = doc.outstanding_amount
inv_entry.allocated_amount = min(float(doc.outstanding_amount), amount)
amount -= inv_entry.allocated_amount
if (amount < 0): break
amount = abs(entry.amount)
if (payment is None):
order_doctype = "Sales Order" if entry.party_type=="Customer" else "Purchase Order"
from erpnext.controllers.accounts_controller import get_advance_payment_entries
payment_entries = get_advance_payment_entries(entry.party_type, entry.party, entry.account, order_doctype, against_all_orders=True)
payment_entries += self.get_matching_payments(entry.party, amount, entry.transaction_date)
payment = next((payment for payment in payment_entries if payment.amount == amount and payment not in added_payments), None)
if (payment is None):
print("Failed to find payments for {0}:{1}".format(entry.party, amount))
continue
added_payments += [payment]
entry.reference_type = payment.reference_type
entry.reference_name = payment.reference_name
entry.mode_of_payment = "Wire Transfer"
entry.outstanding_amount = min(amount, 0)
if (entry.payment_reference is None):
entry.payment_reference = frappe.safe_decode(entry.description)
entry.invoices = ",".join(matching_invoices)
#print("Matching payment is {0}:{1}".format(entry.reference_type, entry.reference_name))
def get_matching_payments(self, party, amount, pay_date):
query = """select 'Payment Entry' as reference_type, name as reference_name, paid_amount as amount
from `tabPayment Entry` where party='{0}' and paid_amount={1} and posting_date='{2}' and docstatus != 2
""".format(party, amount, pay_date)
matching_payments = frappe.db.sql(query, as_dict=True)
return matching_payments
def map_unknown_transactions(self):
for entry in self.new_transaction_items:
if (entry.party): continue
inv_type = "Sales Invoice" if (entry.amount > 0) else "Purchase Invoice"
party_type = "customer" if (entry.amount > 0) else "supplier"
query = """select posting_date, name, {0}, outstanding_amount
from `tab{1}` where ROUND(outstanding_amount)={2} and posting_date < '{3}'
""".format(party_type, inv_type, round(abs(entry.amount)), entry.transaction_date)
invoices = frappe.db.sql(query, as_dict = True)
if(len(invoices) > 0):
entry.party = invoices[0].get(party_type)
def populate_matching_vouchers(self):
for entry in self.new_transaction_items:
if (not entry.party or entry.reference_name): continue
print("Finding matching voucher for {0}".format(frappe.safe_decode(entry.description)))
amount = abs(entry.amount)
invoices = []
vouchers = get_matching_journal_entries(self.from_date, self.to_date, entry.party, self.bank_account, amount)
if len(vouchers) == 0: continue
for voucher in vouchers:
added = next((entry.invoice for entry in self.payment_invoice_items if entry.invoice == voucher.voucher_no), None)
if (added):
print("Found voucher {0}".format(added))
continue
print("Adding voucher {0} {1} {2}".format(voucher.voucher_no, voucher.posting_date, voucher.debit))
ent = self.append('payment_invoice_items', {})
ent.invoice_date = voucher.posting_date
ent.invoice_type = "Journal Entry"
ent.invoice = voucher.voucher_no
ent.payment_description = frappe.safe_decode(entry.description)
ent.allocated_amount = max(voucher.debit, voucher.credit)
invoices += [ent.invoice_type + "|" + ent.invoice]
entry.reference_type = "Journal Entry"
entry.mode_of_payment = "Wire Transfer"
entry.reference_name = ent.invoice
#entry.account = entry.party
entry.invoices = ",".join(invoices)
break
def create_payment_entries(self):
for payment_entry in self.new_transaction_items:
if (not payment_entry.party): continue
if (payment_entry.reference_name): continue
print("Creating payment entry for {0}".format(frappe.safe_decode(payment_entry.description)))
if (payment_entry.party_type == "Account"):
payment = self.create_journal_entry(payment_entry)
invoices = [payment.doctype + "|" + payment.name]
payment_entry.invoices = ",".join(invoices)
else:
payment = self.create_payment_entry(payment_entry)
invoices = [entry.reference_doctype + "|" + entry.reference_name for entry in payment.references if entry is not None]
payment_entry.invoices = ",".join(invoices)
payment_entry.mode_of_payment = payment.mode_of_payment
payment_entry.account = self.receivable_account if payment_entry.party_type == "Customer" else self.payable_account
payment_entry.reference_name = payment.name
payment_entry.reference_type = payment.doctype
frappe.msgprint(_("Successfully created payment entries"))
def create_payment_entry(self, pe):
payment = frappe.new_doc("Payment Entry")
payment.posting_date = pe.transaction_date
payment.payment_type = "Receive" if pe.party_type == "Customer" else "Pay"
payment.mode_of_payment = "Wire Transfer"
payment.party_type = pe.party_type
payment.party = pe.party
payment.paid_to = self.bank_account if pe.party_type == "Customer" else self.payable_account
payment.paid_from = self.receivable_account if pe.party_type == "Customer" else self.bank_account
payment.paid_amount = payment.received_amount = abs(pe.amount)
payment.reference_no = pe.description
payment.reference_date = pe.transaction_date
payment.save()
for inv_entry in self.payment_invoice_items:
if (pe.description != inv_entry.payment_description or pe.transaction_date != inv_entry.transaction_date): continue
if (pe.party != inv_entry.party): continue
reference = payment.append("references", {})
reference.reference_doctype = inv_entry.invoice_type
reference.reference_name = inv_entry.invoice
reference.allocated_amount = inv_entry.allocated_amount
print ("Adding invoice {0} {1}".format(reference.reference_name, reference.allocated_amount))
payment.setup_party_account_field()
payment.set_missing_values()
#payment.set_exchange_rate()
#payment.set_amounts()
#print("Created payment entry {0}".format(payment.as_dict()))
payment.save()
return payment
def create_journal_entry(self, pe):
je = frappe.new_doc("Journal Entry")
je.is_opening = "No"
je.voucher_type = "Bank Entry"
je.cheque_no = pe.description
je.cheque_date = pe.transaction_date
je.remark = pe.description
je.posting_date = pe.transaction_date
if (pe.amount < 0):
je.append("accounts", {"account": pe.party, "debit_in_account_currency": abs(pe.amount)})
je.append("accounts", {"account": self.bank_account, "credit_in_account_currency": abs(pe.amount)})
else:
je.append("accounts", {"account": pe.party, "credit_in_account_currency": pe.amount})
je.append("accounts", {"account": self.bank_account, "debit_in_account_currency": pe.amount})
je.save()
return je
def update_payment_entry(self, payment):
lst = []
invoices = payment.invoices.strip().split(',')
if (len(invoices) == 0): return
amount = float(abs(payment.amount))
for invoice_entry in invoices:
if (not invoice_entry.strip()): continue
invs = invoice_entry.split('|')
invoice_type, invoice = invs[0], invs[1]
outstanding_amount = frappe.get_value(invoice_type, invoice, 'outstanding_amount')
lst.append(frappe._dict({
'voucher_type': payment.reference_type,
'voucher_no' : payment.reference_name,
'against_voucher_type' : invoice_type,
'against_voucher' : invoice,
'account' : payment.account,
'party_type': payment.party_type,
'party': frappe.get_value("Payment Entry", payment.reference_name, "party"),
'unadjusted_amount' : float(amount),
'allocated_amount' : min(outstanding_amount, amount)
}))
amount -= outstanding_amount
if lst:
from erpnext.accounts.utils import reconcile_against_document
try:
reconcile_against_document(lst)
except:
frappe.throw(_("Exception occurred while reconciling {0}").format(payment.reference_name))
def submit_payment_entries(self):
for payment in self.new_transaction_items:
if payment.reference_name is None: continue
doc = frappe.get_doc(payment.reference_type, payment.reference_name)
if doc.docstatus == 1:
if (payment.reference_type == "Journal Entry"): continue
if doc.unallocated_amount == 0: continue
print("Reconciling payment {0}".format(payment.reference_name))
self.update_payment_entry(payment)
else:
print("Submitting payment {0}".format(payment.reference_name))
if (payment.reference_type == "Payment Entry"):
if (payment.payment_reference):
doc.reference_no = payment.payment_reference
doc.mode_of_payment = payment.mode_of_payment
doc.save()
doc.submit()
self.move_reconciled_entries()
self.populate_matching_invoices()
def move_reconciled_entries(self):
idx = 0
while idx < len(self.new_transaction_items):
entry = self.new_transaction_items[idx]
try:
print("Checking transaction {0}: {2} in {1} entries".format(idx, len(self.new_transaction_items), frappe.safe_decode(entry.description)))
except UnicodeEncodeError:
pass
idx += 1
if entry.reference_name is None: continue
doc = frappe.get_doc(entry.reference_type, entry.reference_name)
if doc.docstatus == 1 and (entry.reference_type == "Journal Entry" or doc.unallocated_amount == 0):
self.remove(entry)
rc_entry = self.append('reconciled_transaction_items', {})
dentry = entry.as_dict()
dentry.pop('idx', None)
rc_entry.update(dentry)
idx -= 1
def get_matching_journal_entries(from_date, to_date, account, against, amount):
query = """select voucher_no, posting_date, account, against, debit_in_account_currency as debit, credit_in_account_currency as credit
from `tabGL Entry`
where posting_date between '{0}' and '{1}' and account = '{2}' and against = '{3}' and debit = '{4}'
""".format(from_date, to_date, account, against, amount)
jv_entries = frappe.db.sql(query, as_dict=True)
#print("voucher query:{0}\n Returned {1} entries".format(query, len(jv_entries)))
return jv_entries
def get_payments_matching_invoice(invoice, amount, pay_date):
query = """select pe.name as reference_name, per.reference_doctype as reference_type, per.outstanding_amount, per.allocated_amount
from `tabPayment Entry Reference` as per JOIN `tabPayment Entry` as pe on pe.name = per.parent
where per.reference_name='{0}' and (posting_date='{1}' or reference_date='{1}') and pe.docstatus != 2
""".format(invoice, pay_date)
payments = frappe.db.sql(query, as_dict=True)
if (len(payments) == 0): return
payment = next((payment for payment in payments if payment.allocated_amount == amount), payments[0])
#Hack: Update the reference type which is set to invoice type
payment.reference_type = "Payment Entry"
return payment
def is_headers_present(headers, row):
for header in headers:
if header not in row:
return False
return True
def get_header_index(headers, row):
header_index = {}
for header in headers:
if header in row:
header_index[header] = row.index(header)
return header_index
def get_transaction_info(headers, header_index, row):
transaction = {}
for header in headers:
transaction[header] = row[header_index[header]]
if (transaction[header] == None):
transaction[header] = ""
return transaction
def get_transaction_entries(file_url, headers):
header_index = {}
rows, transactions = [], []
if (file_url.lower().endswith("xlsx")):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(file_url=file_url)
elif (file_url.lower().endswith("csv")):
from frappe.utils.csvutils import read_csv_content
_file = frappe.get_doc("File", {"file_url": file_url})
filepath = _file.get_full_path()
with open(filepath,'rb') as csvfile:
rows = read_csv_content(csvfile.read())
elif (file_url.lower().endswith("xls")):
filename = file_url.split("/")[-1]
rows = get_rows_from_xls_file(filename)
else:
frappe.throw(_("Only .csv and .xlsx files are supported currently"))
stmt_headers = headers.values()
for row in rows:
if len(row) == 0 or row[0] == None or not row[0]: continue
#print("Processing row {0}".format(row))
if header_index:
transaction = get_transaction_info(stmt_headers, header_index, row)
transactions.append(transaction)
elif is_headers_present(stmt_headers, row):
header_index = get_header_index(stmt_headers, row)
return transactions
def get_rows_from_xls_file(filename):
_file = frappe.get_doc("File", {"file_name": filename})
filepath = _file.get_full_path()
import xlrd
book = xlrd.open_workbook(filepath)
sheets = book.sheets()
rows = []
for row in range(1, sheets[0].nrows):
row_values = []
for col in range(1, sheets[0].ncols):
row_values.append(sheets[0].cell_value(row, col))
rows.append(row_values)
return rows

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Bank Statement Transaction Entry", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Statement Transaction Entry
() => frappe.tests.make('Bank Statement Transaction Entry', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestBankStatementTransactionEntry(unittest.TestCase):
pass

View File

@ -1,365 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-11-07 13:58:53.827058",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "transaction_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Transaction Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 4,
"fieldname": "payment_description",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "party_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Party Type",
"length": 0,
"no_copy": 0,
"options": "Customer\nSupplier\nAccount",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "party",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Party",
"length": 0,
"no_copy": 0,
"options": "party_type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "invoice_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Invoice Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "invoice_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Invoice Type",
"length": 0,
"no_copy": 0,
"options": "Sales Invoice\nPurchase Invoice\nJournal Entry",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "invoice",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "invoice",
"length": 0,
"no_copy": 0,
"options": "invoice_type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 1,
"fieldname": "outstanding_amount",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Outstanding Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 1,
"fieldname": "allocated_amount",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Allocated Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-09-14 19:03:30.949831",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Transaction Invoice Item",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@ -1,494 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-11-07 14:03:05.651413",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 1,
"fieldname": "transaction_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Transaction Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 4,
"fieldname": "description",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 1,
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 1,
"fieldname": "party_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Party Type",
"length": 0,
"no_copy": 0,
"options": "Customer\nSupplier\nAccount",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "party",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Party",
"length": 0,
"no_copy": 0,
"options": "party_type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference Type",
"length": 0,
"no_copy": 0,
"options": "Payment Entry\nJournal Entry",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mode_of_payment",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Mode of Payment",
"length": 0,
"no_copy": 0,
"options": "Mode of Payment",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "outstanding_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "outstanding_amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Reference Name",
"length": 0,
"no_copy": 0,
"options": "reference_type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payment_reference",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Reference",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "invoices",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Invoices",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-11-15 19:18:52.876221",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Transaction Payment Item",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,8 +0,0 @@
// Copyright (c) 2017, sathishpy@gmail.com and contributors
// For license information, please see license.txt
frappe.ui.form.on('Bank Statement Settings', {
refresh: function(frm) {
}
});

View File

@ -1,266 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"beta": 0,
"creation": "2017-11-13 13:38:10.863592",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Bank Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "'%d/%m/%Y'",
"fieldname": "date_format",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Date Format",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "statement_header_mapping",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Statement Header Mapping",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "header_items",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Statement Headers",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Settings Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "transaction_data_mapping",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Transaction Data Mapping",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mapped_items",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Mapped Items",
"length": 0,
"no_copy": 0,
"options": "Bank Statement Transaction Settings Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-01-12 10:34:32.840487",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class BankStatementSettings(Document):
def autoname(self):
self.name = self.bank_account + "-Mappings"

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Bank Statement Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Statement Settings
() => frappe.tests.make('Bank Statement Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, sathishpy@gmail.com and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestBankStatementSettings(unittest.TestCase):
pass

View File

@ -1,166 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-11-13 13:42:00.335432",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Transaction",
"fieldname": "mapping_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Mapping Type",
"length": 0,
"no_copy": 0,
"options": "Transaction",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank_data",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Bank Data",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Account",
"fieldname": "mapped_data_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Mapped Data Type",
"length": 0,
"no_copy": 0,
"options": "Account\nCustomer\nSupplier\nAccount",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mapped_data",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Mapped Data",
"length": 0,
"no_copy": 0,
"options": "mapped_data_type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-01-08 00:13:49.973501",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Transaction Settings Item",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,32 +1,70 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Bank Transaction', {
frappe.ui.form.on("Bank Transaction", {
onload(frm) {
frm.set_query('payment_document', 'payment_entries', function() {
frm.set_query("payment_document", "payment_entries", function () {
return {
"filters": {
"name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]]
}
filters: {
name: [
"in",
[
"Payment Entry",
"Journal Entry",
"Sales Invoice",
"Purchase Invoice",
"Expense Claim",
],
],
},
};
});
}
},
bank_account: function (frm) {
set_bank_statement_filter(frm);
},
setup: function (frm) {
frm.set_query("party_type", function () {
return {
filters: {
name: ["in", Object.keys(frappe.boot.party_account_types)],
},
};
});
},
});
frappe.ui.form.on('Bank Transaction Payments', {
payment_entries_remove: function(frm, cdt, cdn) {
frappe.ui.form.on("Bank Transaction Payments", {
payment_entries_remove: function (frm, cdt, cdn) {
update_clearance_date(frm, cdt, cdn);
}
},
});
const update_clearance_date = (frm, cdt, cdn) => {
if (frm.doc.docstatus === 1) {
frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment',
{doctype: cdt, docname: cdn})
.then(e => {
frappe
.xcall(
"erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment",
{ doctype: cdt, docname: cdn }
)
.then((e) => {
if (e == "success") {
frappe.show_alert({message:__("Document {0} successfully uncleared", [e]), indicator:'green'});
frappe.show_alert({
message: __("Document {0} successfully uncleared", [e]),
indicator: "green",
});
}
});
}
};
};
function set_bank_statement_filter(frm) {
frm.set_query("bank_statement", function () {
return {
filters: {
bank_account: frm.doc.bank_account,
},
};
});
}

View File

@ -1,833 +1,245 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"actions": [],
"allow_import": 1,
"allow_rename": 0,
"autoname": "naming_series:",
"beta": 0,
"creation": "2018-10-22 18:19:02.784533",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"date",
"column_break_2",
"status",
"bank_account",
"company",
"section_break_4",
"deposit",
"withdrawal",
"column_break_7",
"currency",
"section_break_10",
"description",
"section_break_14",
"reference_number",
"transaction_id",
"payment_entries",
"section_break_18",
"allocated_amount",
"amended_from",
"column_break_17",
"unallocated_amount",
"party_section",
"party_type",
"party"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "ACC-BTN-.YYYY.-",
"fetch_if_empty": 0,
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Series",
"length": 0,
"no_copy": 1,
"options": "ACC-BTN-.YYYY.-",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
"set_only_once": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Date"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Pending",
"fetch_if_empty": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "\nPending\nSettled\nUnreconciled\nReconciled",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "\nPending\nSettled\nUnreconciled\nReconciled"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "bank_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Bank Account",
"length": 0,
"no_copy": 0,
"options": "Bank Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Bank Account"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_from": "bank_account.company",
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "debit",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Debit",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "credit",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Credit",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "currency",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Currency",
"length": 0,
"no_copy": 0,
"options": "Currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Currency"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Description"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_14",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"allow_on_submit": 1,
"fieldname": "reference_number",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference Number",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Reference Number"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "transaction_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Transaction ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payment_entries",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Entries",
"length": 0,
"no_copy": 0,
"options": "Bank Transaction Payments",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Bank Transaction Payments"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_18",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allocated Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Allocated Amount"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Bank Transaction",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_17",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "unallocated_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Unallocated Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Unallocated Amount"
},
{
"fieldname": "party_section",
"fieldtype": "Section Break",
"label": "Payment From / To"
},
{
"allow_on_submit": 1,
"fieldname": "party_type",
"fieldtype": "Link",
"label": "Party Type",
"options": "DocType"
},
{
"allow_on_submit": 1,
"fieldname": "party",
"fieldtype": "Dynamic Link",
"label": "Party",
"options": "party_type"
},
{
"fieldname": "deposit",
"oldfieldname": "debit",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Deposit"
},
{
"fieldname": "withdrawal",
"oldfieldname": "credit",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Withdrawal"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-05-11 05:27:55.244721",
"links": [],
"modified": "2020-12-30 19:40:54.221070",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "date",
"sort_order": "DESC",
"title_field": "bank_account",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
"track_changes": 1
}

View File

@ -11,7 +11,7 @@ from frappe import _
class BankTransaction(StatusUpdater):
def after_insert(self):
self.unallocated_amount = abs(flt(self.credit) - flt(self.debit))
self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit))
def on_submit(self):
self.clear_linked_payment_entries()
@ -30,13 +30,13 @@ class BankTransaction(StatusUpdater):
if allocated_amount:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount))
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)) - flt(allocated_amount))
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit)) - flt(allocated_amount))
else:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0)
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)))
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit)))
amount = self.debit or self.credit
amount = self.deposit or self.withdrawal
if amount == self.allocated_amount:
frappe.db.set_value(self.doctype, self.name, "status", "Reconciled")
@ -44,18 +44,11 @@ class BankTransaction(StatusUpdater):
def clear_linked_payment_entries(self):
for payment_entry in self.payment_entries:
allocated_amount = get_total_allocated_amount(payment_entry)
paid_amount = get_paid_amount(payment_entry, self.currency)
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
self.clear_simple_entry(payment_entry)
if paid_amount and allocated_amount:
if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount):
frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).").format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount)))
else:
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
self.clear_simple_entry(payment_entry)
elif payment_entry.payment_document == "Sales Invoice":
self.clear_sales_invoice(payment_entry)
elif payment_entry.payment_document == "Sales Invoice":
self.clear_sales_invoice(payment_entry)
def clear_simple_entry(self, payment_entry):
frappe.db.set_value(payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", self.date)
@ -112,3 +105,4 @@ def unclear_reference_payment(doctype, docname):
frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None)
return doc.payment_entry

View File

@ -5,10 +5,11 @@ from __future__ import unicode_literals
import frappe
import unittest
import json
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import reconcile_vouchers, get_linked_payments
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
test_dependencies = ["Item", "Cost Center"]
@ -17,7 +18,7 @@ class TestBankTransaction(unittest.TestCase):
def setUp(self):
make_pos_profile()
add_transactions()
add_payments()
add_vouchers()
def tearDown(self):
for bt in frappe.get_all("Bank Transaction"):
@ -38,14 +39,18 @@ class TestBankTransaction(unittest.TestCase):
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"))
linked_payments = get_linked_payments(bank_transaction.name)
self.assertTrue(linked_payments[0].party == "Conrad Electronic")
linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
self.assertTrue(linked_payments[0][6] == "Conrad Electronic")
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
def test_reconcile(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
reconcile(bank_transaction.name, "Payment Entry", payment.name)
vouchers = json.dumps([{
"payment_doctype":"Payment Entry",
"payment_name":payment.name,
"amount":bank_transaction.unallocated_amount}])
reconcile_vouchers(bank_transaction.name, vouchers)
unallocated_amount = frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount")
self.assertTrue(unallocated_amount == 0)
@ -53,45 +58,40 @@ class TestBankTransaction(unittest.TestCase):
clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
self.assertTrue(clearance_date is not None)
# Check if ERPNext can correctly fetch a linked payment based on the party
def test_linked_payments_based_on_party(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"))
linked_payments = get_linked_payments(bank_transaction.name)
self.assertTrue(len(linked_payments)==1)
# Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
def test_debit_credit_output(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
linked_payments = get_linked_payments(bank_transaction.name)
self.assertTrue(linked_payments[0].payment_type == "Pay")
linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
print(linked_payments)
self.assertTrue(linked_payments[0][3])
# Check error if already reconciled
def test_already_reconciled(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
reconcile(bank_transaction.name, "Payment Entry", payment.name)
vouchers = json.dumps([{
"payment_doctype":"Payment Entry",
"payment_name":payment.name,
"amount":bank_transaction.unallocated_amount}])
reconcile_vouchers(bank_transaction.name, vouchers)
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name)
# Raise an error if creditor transaction vs creditor payment
def test_invalid_creditor_reconcilation(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio"))
payment = frappe.get_doc("Payment Entry", dict(party="Conrad Electronic", paid_amount=690))
self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name)
# Raise an error if debitor transaction vs debitor payment
def test_invalid_debitor_reconcilation(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
payment = frappe.get_doc("Payment Entry", dict(party="Fayva", paid_amount=109080))
self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name)
vouchers = json.dumps([{
"payment_doctype":"Payment Entry",
"payment_name":payment.name,
"amount":bank_transaction.unallocated_amount}])
self.assertRaises(frappe.ValidationError, reconcile_vouchers, bank_transaction_name=bank_transaction.name, vouchers=vouchers)
# Raise an error if debitor transaction vs debitor payment
def test_clear_sales_invoice(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio"))
payment = frappe.get_doc("Sales Invoice", dict(customer="Fayva", status=["=", "Paid"]))
reconcile(bank_transaction.name, "Sales Invoice", payment.name)
vouchers = json.dumps([{
"payment_doctype":"Sales Invoice",
"payment_name":payment.name,
"amount":bank_transaction.unallocated_amount}])
reconcile_vouchers(bank_transaction.name, vouchers=vouchers)
self.assertEqual(frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0)
self.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None)
@ -126,7 +126,7 @@ def add_transactions():
"doctype": "Bank Transaction",
"description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
"date": "2018-10-23",
"debit": 1200,
"deposit": 1200,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
@ -136,7 +136,7 @@ def add_transactions():
"doctype": "Bank Transaction",
"description":"1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G",
"date": "2018-10-23",
"debit": 1700,
"deposit": 1700,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
@ -146,7 +146,7 @@ def add_transactions():
"doctype": "Bank Transaction",
"description":"Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic",
"date": "2018-10-26",
"debit": 690,
"withdrawal": 690,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
@ -156,7 +156,7 @@ def add_transactions():
"doctype": "Bank Transaction",
"description":"Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07",
"date": "2018-10-27",
"debit": 3900,
"deposit": 3900,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
@ -166,7 +166,7 @@ def add_transactions():
"doctype": "Bank Transaction",
"description":"I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio",
"date": "2018-10-27",
"credit": 109080,
"withdrawal": 109080,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
@ -174,7 +174,7 @@ def add_transactions():
frappe.flags.test_bank_transactions_created = True
def add_payments():
def add_vouchers():
if frappe.flags.test_payments_created:
return
@ -192,6 +192,7 @@ def add_payments():
pass
pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Conrad Oct 18"
pe.reference_date = "2018-10-24"
@ -242,10 +243,15 @@ def add_payments():
except frappe.DuplicateEntryError:
pass
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900)
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save =1)
pi.cash_bank_account = "_Test Bank - _TC"
pi.insert()
pi.submit()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28"
pe.paid_amount = 690
pe.received_amount = 690
pe.insert()
pe.submit()
@ -295,4 +301,4 @@ def add_payments():
si.save()
si.submit()
frappe.flags.test_payments_created = True
frappe.flags.test_payments_created = True

View File

@ -22,9 +22,10 @@ def validate_company(company):
'allow_account_creation_against_child_company'])
if parent_company and (not allow_account_creation_against_child_company):
frappe.throw(_("""{0} is a child company. Please import accounts against parent company
or enable {1} in company master""").format(frappe.bold(company),
frappe.bold('Allow Account Creation Against Child Company')), title='Wrong Company')
msg = _("{} is a child company. ").format(frappe.bold(company))
msg += _("Please import accounts against parent company or enable {} in company master.").format(
frappe.bold('Allow Account Creation Against Child Company'))
frappe.throw(msg, title=_('Wrong Company'))
if frappe.db.get_all('GL Entry', {"company": company}, "name", limit=1):
return False
@ -74,7 +75,9 @@ def generate_data_from_csv(file_doc, as_dict=False):
if as_dict:
data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
else:
if not row[1]: row[1] = row[0]
if not row[1]:
row[1] = row[0]
row[3] = row[2]
data.append(row)
# convert csv data
@ -96,7 +99,9 @@ def generate_data_from_excel(file_doc, extension, as_dict=False):
if as_dict:
data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
else:
if not row[1]: row[1] = row[0]
if not row[1]:
row[1] = row[0]
row[3] = row[2]
data.append(row)
return data
@ -147,7 +152,13 @@ def build_forest(data):
from frappe import _
for row in data:
account_name, parent_account = row[0:2]
account_name, parent_account, account_number, parent_account_number = row[0:4]
if account_number:
account_name = "{} - {}".format(account_number, account_name)
if parent_account_number:
parent_account_number = cstr(parent_account_number).strip()
parent_account = "{} - {}".format(parent_account_number, parent_account)
if parent_account == account_name == child:
return [parent_account]
elif account_name == child:
@ -159,20 +170,23 @@ def build_forest(data):
charts_map, paths = {}, []
line_no = 3
line_no = 2
error_messages = []
for i in data:
account_name, dummy, account_number, is_group, account_type, root_type = i
account_name, parent_account, account_number, parent_account_number, is_group, account_type, root_type = i
if not account_name:
error_messages.append("Row {0}: Please enter Account Name".format(line_no))
if account_number:
account_number = cstr(account_number).strip()
account_name = "{} - {}".format(account_number, account_name)
charts_map[account_name] = {}
if cint(is_group) == 1: charts_map[account_name]["is_group"] = is_group
if account_type: charts_map[account_name]["account_type"] = account_type
if root_type: charts_map[account_name]["root_type"] = root_type
if account_number: charts_map[account_name]["account_number"] = account_number
path = return_parent(data, account_name)[::-1]
paths.append(path) # List of path is created
line_no += 1
@ -221,7 +235,7 @@ def download_template(file_type, template_type):
def get_template(template_type):
fields = ["Account Name", "Parent Account", "Account Number", "Is Group", "Account Type", "Root Type"]
fields = ["Account Name", "Parent Account", "Account Number", "Parent Account Number", "Is Group", "Account Type", "Root Type"]
writer = UnicodeWriter()
writer.writerow(fields)
@ -241,23 +255,23 @@ def get_template(template_type):
def get_sample_template(writer):
template = [
["Application Of Funds(Assets)", "", "", 1, "", "Asset"],
["Sources Of Funds(Liabilities)", "", "", 1, "", "Liability"],
["Equity", "", "", 1, "", "Equity"],
["Expenses", "", "", 1, "", "Expense"],
["Income", "", "", 1, "", "Income"],
["Bank Accounts", "Application Of Funds(Assets)", "", 1, "Bank", "Asset"],
["Cash In Hand", "Application Of Funds(Assets)", "", 1, "Cash", "Asset"],
["Stock Assets", "Application Of Funds(Assets)", "", 1, "Stock", "Asset"],
["Cost Of Goods Sold", "Expenses", "", 0, "Cost of Goods Sold", "Expense"],
["Asset Depreciation", "Expenses", "", 0, "Depreciation", "Expense"],
["Fixed Assets", "Application Of Funds(Assets)", "", 0, "Fixed Asset", "Asset"],
["Accounts Payable", "Sources Of Funds(Liabilities)", "", 0, "Payable", "Liability"],
["Accounts Receivable", "Application Of Funds(Assets)", "", 1, "Receivable", "Asset"],
["Stock Expenses", "Expenses", "", 0, "Stock Adjustment", "Expense"],
["Sample Bank", "Bank Accounts", "", 0, "Bank", "Asset"],
["Cash", "Cash In Hand", "", 0, "Cash", "Asset"],
["Stores", "Stock Assets", "", 0, "Stock", "Asset"],
["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"],
["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"],
["Equity", "", "", "", 1, "", "Equity"],
["Expenses", "", "", "", 1, "", "Expense"],
["Income", "", "", "", 1, "", "Income"],
["Bank Accounts", "Application Of Funds(Assets)", "", "", 1, "Bank", "Asset"],
["Cash In Hand", "Application Of Funds(Assets)", "", "", 1, "Cash", "Asset"],
["Stock Assets", "Application Of Funds(Assets)", "", "", 1, "Stock", "Asset"],
["Cost Of Goods Sold", "Expenses", "", "", 0, "Cost of Goods Sold", "Expense"],
["Asset Depreciation", "Expenses", "", "", 0, "Depreciation", "Expense"],
["Fixed Assets", "Application Of Funds(Assets)", "", "", 0, "Fixed Asset", "Asset"],
["Accounts Payable", "Sources Of Funds(Liabilities)", "", "", 0, "Payable", "Liability"],
["Accounts Receivable", "Application Of Funds(Assets)", "", "", 1, "Receivable", "Asset"],
["Stock Expenses", "Expenses", "", "", 0, "Stock Adjustment", "Expense"],
["Sample Bank", "Bank Accounts", "", "", 0, "Bank", "Asset"],
["Cash", "Cash In Hand", "", "", 0, "Cash", "Asset"],
["Stores", "Stock Assets", "", "", 0, "Stock", "Asset"],
]
for row in template:

View File

@ -27,30 +27,30 @@ class GLEntry(Document):
def validate(self):
self.flags.ignore_submit_comment = True
self.check_mandatory()
self.validate_and_set_fiscal_year()
self.pl_must_have_cost_center()
self.validate_cost_center()
if not self.flags.from_repost:
self.check_mandatory()
self.validate_cost_center()
self.check_pl_account()
self.validate_party()
self.validate_currency()
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
if not from_repost:
def on_update(self):
adv_adj = self.flags.adv_adj
if not self.flags.from_repost:
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()
validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
validate_balance_type(self.account, adv_adj)
# Update outstanding amt on against voucher
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
and self.against_voucher and update_outstanding == 'Yes' and not from_repost:
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher)
# Update outstanding amt on against voucher
if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees']
and self.against_voucher and self.flags.update_outstanding == 'Yes'):
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher)
def check_mandatory(self):
mandatory = ['account','voucher_type','voucher_no','company']
@ -58,7 +58,7 @@ class GLEntry(Document):
if not self.get(k):
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
account_type = frappe.db.get_value("Account", self.account, "account_type")
account_type = frappe.get_cached_value("Account", self.account, "account_type")
if not (self.party_type and self.party):
if account_type == "Receivable":
frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}")
@ -73,7 +73,7 @@ class GLEntry(Document):
.format(self.voucher_type, self.voucher_no, self.account))
def pl_must_have_cost_center(self):
if frappe.db.get_value("Account", self.account, "report_type") == "Profit and Loss":
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.")
.format(self.voucher_type, self.voucher_no, self.account))
@ -140,25 +140,16 @@ class GLEntry(Document):
.format(self.voucher_type, self.voucher_no, self.account, self.company))
def validate_cost_center(self):
if not hasattr(self, "cost_center_company"):
self.cost_center_company = {}
if not self.cost_center: return
def _get_cost_center_company():
if not self.cost_center_company.get(self.cost_center):
self.cost_center_company[self.cost_center] = frappe.db.get_value(
"Cost Center", self.cost_center, "company")
is_group, company = frappe.get_cached_value('Cost Center',
self.cost_center, ['is_group', 'company'])
return self.cost_center_company[self.cost_center]
def _check_is_group():
return cint(frappe.get_cached_value('Cost Center', self.cost_center, 'is_group'))
if self.cost_center and _get_cost_center_company() != self.company:
if company != self.company:
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
and self.cost_center and _check_is_group():
if (self.voucher_type != 'Period Closing Voucher' and is_group):
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
@ -184,7 +175,6 @@ class GLEntry(Document):
if not self.fiscal_year:
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
def validate_balance_type(account, adv_adj=False):
if not adv_adj and account:
balance_must_be = frappe.db.get_value("Account", account, "balance_must_be")
@ -250,7 +240,7 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
def validate_frozen_account(account, adv_adj=None):
frozen_account = frappe.db.get_value("Account", account, "freeze_account")
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
if frozen_account == 'Yes' and not adv_adj:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,
'frozen_accounts_modifier')

View File

@ -1,7 +1,7 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
"creation": "2018-11-22 22:45:00.370913",
"doctype": "DocType",
"document_type": "Setup",
@ -20,8 +20,7 @@
"in_list_view": 1,
"label": "Title",
"no_copy": 1,
"reqd": 1,
"unique": 1
"reqd": 1
},
{
"fieldname": "taxes",
@ -33,12 +32,14 @@
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
}
],
"modified": "2020-09-18 17:26:09.703215",
"links": [],
"modified": "2021-03-08 19:50:21.416513",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Item Tax Template",
@ -81,5 +82,6 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "title",
"track_changes": 1
}

View File

@ -11,6 +11,11 @@ class ItemTaxTemplate(Document):
def validate(self):
self.validate_tax_accounts()
def autoname(self):
if self.company and self.title:
abbr = frappe.get_cached_value('Company', self.company, 'abbr')
self.name = '{0} - {1}'.format(self.title, abbr)
def validate_tax_accounts(self):
"""Check whether Tax Rate is not entered twice for same Tax Type"""
check_list = []

View File

@ -102,7 +102,7 @@ class JournalEntry(AccountsController):
if account_currency == previous_account_currency:
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
def validate_stock_accounts(self):
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
for account in stock_accounts:
@ -229,11 +229,11 @@ class JournalEntry(AccountsController):
if d.reference_type=="Journal Entry":
account_root_type = frappe.db.get_value("Account", d.account, "root_type")
if account_root_type == "Asset" and flt(d.debit) > 0:
frappe.throw(_("For {0}, only credit accounts can be linked against another debit entry")
.format(d.account))
frappe.throw(_("Row #{0}: For {1}, you can select reference document only if account gets credited")
.format(d.idx, d.account))
elif account_root_type == "Liability" and flt(d.credit) > 0:
frappe.throw(_("For {0}, only debit accounts can be linked against another credit entry")
.format(d.account))
frappe.throw(_("Row #{0}: For {1}, you can select reference document only if account gets debited")
.format(d.idx, d.account))
if d.reference_name == self.name:
frappe.throw(_("You can not enter current voucher in 'Against Journal Entry' column"))
@ -1077,4 +1077,4 @@ def make_reverse_journal_entry(source_name, target_doc=None):
},
}, target_doc)
return doclist
return doclist

View File

@ -12,7 +12,7 @@ class ModeofPayment(Document):
self.validate_accounts()
self.validate_repeating_companies()
self.validate_pos_mode_of_payment()
def validate_repeating_companies(self):
"""Error when Same Company is entered multiple times in accounts"""
accounts_list = []
@ -31,10 +31,10 @@ class ModeofPayment(Document):
def validate_pos_mode_of_payment(self):
if not self.enabled:
pos_profiles = frappe.db.sql("""SELECT sip.parent FROM `tabSales Invoice Payment` sip
pos_profiles = frappe.db.sql("""SELECT sip.parent FROM `tabSales Invoice Payment` sip
WHERE sip.parenttype = 'POS Profile' and sip.mode_of_payment = %s""", (self.name))
pos_profiles = list(map(lambda x: x[0], pos_profiles))
if pos_profiles:
message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \
Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode."

View File

@ -198,6 +198,7 @@ def start_import(invoices):
try:
publish(idx, len(invoices), d.doctype)
doc = frappe.get_doc(d)
doc.flags.ignore_mandatory = True
doc.insert()
doc.submit()
frappe.db.commit()

View File

@ -92,14 +92,16 @@ frappe.ui.form.on('Payment Entry', {
});
frm.set_query("reference_doctype", "references", function() {
if (frm.doc.party_type=="Customer") {
if (frm.doc.party_type == "Customer") {
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
} else if (frm.doc.party_type=="Supplier") {
} else if (frm.doc.party_type == "Supplier") {
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
} else if (frm.doc.party_type=="Employee") {
} else if (frm.doc.party_type == "Employee") {
var doctypes = ["Expense Claim", "Journal Entry"];
} else if (frm.doc.party_type=="Student") {
} else if (frm.doc.party_type == "Student") {
var doctypes = ["Fees"];
} else if (frm.doc.party_type == "Donor") {
var doctypes = ["Donation"];
} else {
var doctypes = ["Journal Entry"];
}
@ -128,7 +130,7 @@ frappe.ui.form.on('Payment Entry', {
const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company};
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning', 'Donation'];
if (in_list(party_type_doctypes, child.reference_doctype)) {
filters[doc.party_type.toLowerCase()] = doc.party;
@ -281,7 +283,7 @@ frappe.ui.form.on('Payment Entry', {
let party_types = Object.keys(frappe.boot.party_account_types);
if(frm.doc.party_type && !party_types.includes(frm.doc.party_type)){
frm.set_value("party_type", "");
frappe.throw(__("Party can only be one of "+ party_types.join(", ")));
frappe.throw(__("Party can only be one of {0}", [party_types.join(", ")]));
}
frm.set_query("party", function() {
@ -705,7 +707,8 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
) {
if(total_positive_outstanding > total_negative_outstanding)
if (!frm.doc.paid_amount)
@ -748,7 +751,8 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
) {
if(total_positive_outstanding_including_order > paid_amount) {
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
@ -905,6 +909,12 @@ frappe.ui.form.on('Payment Entry', {
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
return false;
}
if (frm.doc.party_type == "Donor" && row.reference_doctype != "Donation") {
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
frappe.msgprint(__("Row #{0}: Reference Document Type must be Donation", [row.idx]));
return false;
}
}
if (row) {

View File

@ -536,7 +536,8 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Title",
"print_hide": 1
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "party",
@ -588,7 +589,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-10-30 13:56:20.007336",
"modified": "2021-03-08 13:05:16.958866",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
@ -632,4 +633,4 @@
"sort_order": "DESC",
"title_field": "title",
"track_changes": 1
}
}

View File

@ -72,6 +72,7 @@ class PaymentEntry(AccountsController):
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_expense_claim()
self.update_donation()
self.update_payment_schedule()
self.set_status()
@ -82,6 +83,7 @@ class PaymentEntry(AccountsController):
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_expense_claim()
self.update_donation(cancel=1)
self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1)
self.set_payment_req_status()
@ -242,9 +244,11 @@ class PaymentEntry(AccountsController):
elif self.party_type == "Supplier":
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
elif self.party_type == "Employee":
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance")
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity")
elif self.party_type == "Shareholder":
valid_reference_doctypes = ("Journal Entry")
elif self.party_type == "Donor":
valid_reference_doctypes = ("Donation")
for d in self.get("references"):
if not d.allocated_amount:
@ -455,6 +459,10 @@ class PaymentEntry(AccountsController):
.format(total_negative_outstanding), InvalidPaymentEntry)
def set_title(self):
if frappe.flags.in_import and self.title:
# do not set title dynamically if title exists during data import.
return
if self.payment_type in ("Receive", "Pay"):
self.title = self.party
else:
@ -604,7 +612,7 @@ class PaymentEntry(AccountsController):
if self.payment_type in ("Receive", "Pay") and self.party:
for d in self.get("references"):
if d.allocated_amount \
and d.reference_doctype in ("Sales Order", "Purchase Order", "Employee Advance"):
and d.reference_doctype in ("Sales Order", "Purchase Order", "Employee Advance", "Gratuity"):
frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid()
def update_expense_claim(self):
@ -614,6 +622,13 @@ class PaymentEntry(AccountsController):
doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc, self.name)
def update_donation(self, cancel=0):
if self.payment_type == "Receive" and self.party_type == "Donor" and self.party:
for d in self.get("references"):
if d.reference_doctype=="Donation" and d.reference_name:
is_paid = 0 if cancel else 1
frappe.db.set_value("Donation", d.reference_name, "paid", is_paid)
def on_recurring(self, reference_doc, auto_repeat_doc):
self.reference_no = reference_doc.name
self.reference_date = nowdate()
@ -913,6 +928,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
total_amount = ref_doc.get("grand_total")
exchange_rate = 1
outstanding_amount = ref_doc.get("outstanding_amount")
elif reference_doctype == "Donation":
total_amount = ref_doc.get("amount")
exchange_rate = 1
elif reference_doctype == "Dunning":
total_amount = ref_doc.get("dunning_amount")
exchange_rate = 1
@ -932,6 +950,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
exchange_rate = ref_doc.get("exchange_rate")
if party_account_currency != ref_doc.currency:
total_amount = flt(total_amount) * flt(exchange_rate)
elif ref_doc.doctype == "Gratuity":
total_amount = ref_doc.amount
if not total_amount:
if party_account_currency == company_currency:
total_amount = ref_doc.base_grand_total
@ -955,6 +975,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
if party_account_currency == company_currency:
exchange_rate = 1
elif reference_doctype == "Gratuity":
outstanding_amount = ref_doc.amount - flt(ref_doc.paid_amount)
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
else:
@ -996,7 +1018,7 @@ def get_amounts_based_on_ref_doc(reference_doctype, ref_doc, party_account_curre
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc)
if not total_amount:
total_amount, exchange_rate = get_total_amount_exchange_rate_base_on_currency(
party_account_currency, company_currency, ref_doc)
@ -1160,10 +1182,12 @@ def set_party_type(dt):
party_type = "Customer"
elif dt in ("Purchase Invoice", "Purchase Order"):
party_type = "Supplier"
elif dt in ("Expense Claim", "Employee Advance"):
elif dt in ("Expense Claim", "Employee Advance", "Gratuity"):
party_type = "Employee"
elif dt in ("Fees"):
elif dt == "Fees":
party_type = "Student"
elif dt == "Donation":
party_type = "Donor"
return party_type
def set_party_account(dt, dn, doc, party_type):
@ -1177,6 +1201,8 @@ def set_party_account(dt, dn, doc, party_type):
party_account = doc.advance_account
elif dt == "Expense Claim":
party_account = doc.payable_account
elif dt == "Gratuity":
party_account = doc.payable_account
else:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
return party_account
@ -1189,7 +1215,7 @@ def set_party_account_currency(dt, party_account, doc):
return party_account_currency
def set_payment_type(dt, doc):
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
if (dt in ("Sales Order", "Donation") or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
payment_type = "Receive"
else:
@ -1222,6 +1248,12 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
elif dt == "Dunning":
grand_total = doc.grand_total
outstanding_amount = doc.grand_total
elif dt == "Donation":
grand_total = doc.amount
outstanding_amount = doc.amount
elif dt == "Gratuity":
grand_total = doc.amount
outstanding_amount = flt(doc.amount) - flt(doc.paid_amount)
else:
if party_account_currency == doc.company_currency:
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
@ -1326,4 +1358,4 @@ def make_payment_order(source_name, target_doc=None):
}, target_doc, set_missing_values)
return doclist
return doclist

View File

@ -179,10 +179,18 @@ class POSInvoice(SalesInvoice):
if d.get("serial_no"):
serial_nos = get_serial_nos(d.serial_no)
for sr in serial_nos:
serial_no_exists = frappe.db.exists("POS Invoice Item", {
"parent": self.return_against,
"serial_no": ["like", d.get("serial_no")]
})
serial_no_exists = frappe.db.sql("""
SELECT name
FROM `tabPOS Invoice Item`
WHERE
parent = %s
and (serial_no = %s
or serial_no like %s
or serial_no like %s
or serial_no like %s
)
""", (self.return_against, sr, sr+'\n%', '%\n'+sr, '%\n'+sr+'\n%'))
if not serial_no_exists:
bold_return_against = frappe.bold(self.return_against)
bold_serial_no = frappe.bold(sr)
@ -190,7 +198,7 @@ class POSInvoice(SalesInvoice):
_("Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {}")
.format(d.idx, bold_serial_no, bold_return_against)
)
def validate_non_stock_items(self):
for d in self.get("items"):
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
@ -292,7 +300,7 @@ class POSInvoice(SalesInvoice):
if not self.get('payments') and not for_validate:
update_multi_mode_option(self, profile)
if self.is_return and not for_validate:
add_return_modes(self, profile)

View File

@ -99,10 +99,10 @@ class TestPOSInvoice(unittest.TestCase):
item_row = inv.get("items")[0]
add_items = [
(54, '_Test Account Excise Duty @ 12'),
(288, '_Test Account Excise Duty @ 15'),
(144, '_Test Account Excise Duty @ 20'),
(430, '_Test Item Tax Template 1')
(54, '_Test Account Excise Duty @ 12 - _TC'),
(288, '_Test Account Excise Duty @ 15 - _TC'),
(144, '_Test Account Excise Duty @ 20 - _TC'),
(430, '_Test Item Tax Template 1 - _TC')
]
for qty, item_tax_template in add_items:
item_row_copy = copy.deepcopy(item_row)
@ -198,6 +198,65 @@ class TestPOSInvoice(unittest.TestCase):
self.assertEqual(pos_return.get('payments')[0].amount, -500)
self.assertEqual(pos_return.get('payments')[1].amount, -500)
def test_pos_return_for_serialized_item(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
se = make_serialized_item(company='_Test Company',
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
pos.get("items")[0].serial_no = serial_nos[0]
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 1000, 'default': 1})
pos.insert()
pos.submit()
pos_return = make_sales_return(pos.name)
pos_return.insert()
pos_return.submit()
self.assertEqual(pos_return.get('items')[0].serial_no, serial_nos[0])
def test_partial_pos_returns(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
se = make_serialized_item(company='_Test Company',
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
item=se.get("items")[0].item_code, qty=2, rate=1000, do_not_save=1)
pos.get("items")[0].serial_no = serial_nos[0] + "\n" + serial_nos[1]
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 1000, 'default': 1})
pos.insert()
pos.submit()
pos_return1 = make_sales_return(pos.name)
# partial return 1
pos_return1.get('items')[0].qty = -1
pos_return1.get('items')[0].serial_no = serial_nos[0]
pos_return1.insert()
pos_return1.submit()
# partial return 2
pos_return2 = make_sales_return(pos.name)
self.assertEqual(pos_return2.get('items')[0].qty, -1)
self.assertEqual(pos_return2.get('items')[0].serial_no, serial_nos[1])
def test_pos_change_amount(self):
pos = create_pos_invoice(company= "_Test Company", debit_to="Debtors - _TC",
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,

View File

@ -87,6 +87,7 @@
"edit_references",
"sales_order",
"so_detail",
"pos_invoice_item",
"column_break_74",
"delivery_note",
"dn_detail",
@ -790,11 +791,20 @@
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "pos_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "POS Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-07-22 13:40:34.418346",
"modified": "2021-01-04 17:34:49.924531",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",

View File

@ -29,7 +29,7 @@ class POSInvoiceMergeLog(Document):
for d in self.pos_invoices:
status, docstatus, is_return, return_against = frappe.db.get_value(
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
bold_pos_invoice = frappe.bold(d.pos_invoice)
bold_status = frappe.bold(status)
if docstatus != 1:
@ -58,7 +58,7 @@ class POSInvoiceMergeLog(Document):
sales_invoice, credit_note = "", ""
if sales:
sales_invoice = self.process_merging_into_sales_invoice(sales)
if returns:
credit_note = self.process_merging_into_credit_note(returns)
@ -74,7 +74,7 @@ class POSInvoiceMergeLog(Document):
def process_merging_into_sales_invoice(self, data):
sales_invoice = self.get_new_sales_invoice()
sales_invoice = self.merge_pos_invoice_into(sales_invoice, data)
sales_invoice.is_consolidated = 1
@ -98,19 +98,19 @@ class POSInvoiceMergeLog(Document):
self.consolidated_credit_note = credit_note.name
return credit_note.name
def merge_pos_invoice_into(self, invoice, data):
items, payments, taxes = [], [], []
loyalty_amount_sum, loyalty_points_sum = 0, 0
for doc in data:
map_doc(doc, invoice, table_map={ "doctype": invoice.doctype })
if doc.redeem_loyalty_points:
invoice.loyalty_redemption_account = doc.loyalty_redemption_account
invoice.loyalty_redemption_cost_center = doc.loyalty_redemption_cost_center
loyalty_points_sum += doc.loyalty_points
loyalty_amount_sum += doc.loyalty_amount
for item in doc.get('items'):
found = False
for i in items:
@ -118,12 +118,13 @@ class POSInvoiceMergeLog(Document):
i.uom == item.uom and i.net_rate == item.net_rate):
found = True
i.qty = i.qty + item.qty
if not found:
item.rate = item.net_rate
item.price_list_rate = 0
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
items.append(si_item)
for tax in doc.get('taxes'):
found = False
for t in taxes:
@ -162,7 +163,7 @@ class POSInvoiceMergeLog(Document):
invoice.ignore_pricing_rule = 1
return invoice
def get_new_sales_invoice(self):
sales_invoice = frappe.new_doc('Sales Invoice')
sales_invoice.customer = self.customer
@ -194,7 +195,7 @@ def get_all_unconsolidated_invoices():
}
pos_invoices = frappe.db.get_all('POS Invoice', filters=filters,
fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer'])
return pos_invoices
def get_invoice_customer_map(pos_invoices):
@ -204,7 +205,7 @@ def get_invoice_customer_map(pos_invoices):
customer = invoice.get('customer')
pos_invoice_customer_map.setdefault(customer, [])
pos_invoice_customer_map[customer].append(invoice)
return pos_invoice_customer_map
def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):

View File

@ -20,15 +20,16 @@ class POSOpeningEntry(StatusUpdater):
if not cint(frappe.db.get_value("User", self.user, "enabled")):
frappe.throw(_("User {} is disabled. Please select valid user/cashier").format(self.user))
def validate_payment_method_account(self):
invalid_modes = []
for d in self.balance_details:
account = frappe.db.get_value("Mode of Payment Account",
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
if not account:
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
if d.mode_of_payment:
account = frappe.db.get_value("Mode of Payment Account",
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
if not account:
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
if invalid_modes:
if invalid_modes == 1:
msg = _("Please set default Cash or Bank account in Mode of Payment {}")

View File

@ -357,7 +357,6 @@
"reqd": 1
},
{
"depends_on": "eval: doc.selling == 1",
"fieldname": "margin",
"fieldtype": "Section Break",
"label": "Margin"
@ -565,7 +564,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2020-12-04 00:36:24.698219",
"modified": "2021-03-01 23:18:38.717613",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@ -136,7 +136,7 @@ class PricingRule(Document):
for d in self.items:
max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount")
if max_discount and flt(self.discount_percentage) > flt(max_discount):
throw(_("Max discount allowed for item: {0} is {1}%").format(self.item_code, max_discount))
throw(_("Max discount allowed for item: {0} is {1}%").format(d.item_code, max_discount))
def validate_price_list_with_currency(self):
if self.currency and self.for_price_list:

View File

@ -58,6 +58,7 @@
"rejected_warehouse",
"col_break_warehouse",
"set_from_warehouse",
"supplier_warehouse",
"is_subcontracted",
"items_section",
"update_stock",
@ -1350,7 +1351,7 @@
"options": "Company"
},
{
"depends_on": "eval:doc.update_stock && (doc.is_subcontracted==\"Yes\" || doc.is_internal_supplier)",
"depends_on": "eval:doc.update_stock && doc.is_internal_supplier",
"description": "Sets 'From Warehouse' in each row of the items table.",
"fieldname": "set_from_warehouse",
"fieldtype": "Link",
@ -1360,13 +1361,24 @@
"print_hide": 1,
"print_width": "50px",
"width": "50px"
},
{
"depends_on": "eval:doc.update_stock && doc.is_subcontracted==\"Yes\"",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
"no_copy": 1,
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
"width": "50px"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2020-12-26 20:49:03.305063",
"modified": "2021-03-09 21:12:30.422084",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -968,7 +968,7 @@ class PurchaseInvoice(BuyingController):
# base_rounding_adjustment may become zero due to small precision
# eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2
# then base_rounding_adjustment becomes zero and error is thrown in GL Entry
if self.rounding_adjustment and self.base_rounding_adjustment:
if not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment:
round_off_account, round_off_cost_center = \
get_round_off_account_and_cost_center(self.company)

View File

@ -456,7 +456,7 @@ class TestPurchaseInvoice(unittest.TestCase):
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
@ -1031,7 +1031,7 @@ def make_purchase_invoice_against_cost_center(**args):
pi.is_return = args.is_return
pi.credit_to = args.return_against or "Creditors - _TC"
pi.is_subcontracted = args.is_subcontracted or "No"
if args.supplier_warehouse:
if args.supplier_warehouse:
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
pi.append("items", {

View File

@ -18,7 +18,7 @@
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "_Test Item Home Desktop 100",
"item_name": "_Test Item Home Desktop 100",
"item_tax_template": "_Test Account Excise Duty @ 10",
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
"parentfield": "items",
"qty": 10,
"rate": 50,

View File

@ -28,10 +28,16 @@
"stock_qty",
"sec_break1",
"price_list_rate",
"discount_percentage",
"discount_amount",
"col_break3",
"base_price_list_rate",
"section_break_26",
"margin_type",
"margin_rate_or_amount",
"rate_with_margin",
"column_break_30",
"discount_percentage",
"discount_amount",
"base_rate_with_margin",
"sec_break2",
"rate",
"amount",
@ -789,6 +795,7 @@
"fieldname": "stock_uom_rate",
"fieldtype": "Currency",
"label": "Rate of Stock UOM",
"no_copy": 1,
"options": "currency",
"read_only": 1
},
@ -799,12 +806,54 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "section_break_26",
"fieldtype": "Section Break",
"label": "Discount and Margin"
},
{
"depends_on": "price_list_rate",
"fieldname": "margin_type",
"fieldtype": "Select",
"label": "Margin Type",
"options": "\nPercentage\nAmount",
"print_hide": 1
},
{
"depends_on": "eval:doc.margin_type && doc.price_list_rate",
"fieldname": "margin_rate_or_amount",
"fieldtype": "Float",
"label": "Margin Rate or Amount",
"print_hide": 1
},
{
"depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
"fieldname": "rate_with_margin",
"fieldtype": "Currency",
"label": "Rate With Margin",
"options": "currency",
"read_only": 1
},
{
"fieldname": "column_break_30",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
"fieldname": "base_rate_with_margin",
"fieldtype": "Currency",
"label": "Rate With Margin (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-01-30 21:43:21.488258",
"modified": "2021-02-23 00:59:52.614805",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@ -695,6 +695,7 @@ frappe.ui.form.on('Sales Invoice', {
refresh_field(['timesheets'])
}
})
frm.refresh();
},
onload: function(frm) {
@ -810,6 +811,65 @@ frappe.ui.form.on('Sales Invoice', {
},
refresh: function(frm) {
if (frm.doc.project) {
frm.add_custom_button(__('Fetch Timesheet'), function() {
let d = new frappe.ui.Dialog({
title: __('Fetch Timesheet'),
fields: [
{
"label" : "From",
"fieldname": "from_time",
"fieldtype": "Date",
"reqd": 1,
},
{
fieldtype: 'Column Break',
fieldname: 'col_break_1',
},
{
"label" : "To",
"fieldname": "to_time",
"fieldtype": "Date",
"reqd": 1,
}
],
primary_action: function() {
let data = d.get_values();
frappe.call({
method: "erpnext.projects.doctype.timesheet.timesheet.get_projectwise_timesheet_data",
args: {
from_time: data.from_time,
to_time: data.to_time,
project: frm.doc.project
},
callback: function(r) {
if(!r.exc) {
if(r.message.length > 0) {
frm.clear_table('timesheets')
r.message.forEach((d) => {
frm.add_child('timesheets',{
'time_sheet': d.parent,
'billing_hours': d.billing_hours,
'billing_amount': d.billing_amt,
'timesheet_detail': d.name
});
});
frm.refresh_field('timesheets')
}
else {
frappe.msgprint(__('No Timesheet Found.'))
}
d.hide();
}
}
});
},
primary_action_label: __('Get Timesheets')
});
d.show();
})
}
if (frappe.boot.active_domains.includes("Healthcare")) {
frm.set_df_property("patient", "hidden", 0);
frm.set_df_property("patient_name", "hidden", 0);

View File

@ -21,6 +21,7 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from frappe.model.utils import get_fetch_values
from frappe.contacts.doctype.address.address import get_address_display
@ -75,6 +76,8 @@ class SalesInvoice(SellingController):
if not self.is_pos:
self.so_dn_required()
self.set_tax_withholding()
self.validate_proj_cust()
self.validate_pos_return()
@ -153,6 +156,32 @@ class SalesInvoice(SellingController):
if cost_center_company != self.company:
frappe.throw(_("Row #{0}: Cost Center {1} does not belong to company {2}").format(frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company)))
def set_tax_withholding(self):
tax_withholding_details = get_party_tax_withholding_details(self)
if not tax_withholding_details:
return
accounts = []
tax_withholding_account = tax_withholding_details.get("account_head")
for d in self.taxes:
if d.account_head == tax_withholding_account:
d.update(tax_withholding_details)
accounts.append(d.account_head)
if not accounts or tax_withholding_account not in accounts:
self.append("taxes", tax_withholding_details)
to_remove = [d for d in self.taxes
if not d.tax_amount and d.charge_type == "Actual" and d.account_head == tax_withholding_account]
for d in to_remove:
self.remove(d)
# calculate totals again after applying TDS
self.calculate_taxes_and_totals()
def before_save(self):
set_account_for_mode_of_payment(self)
@ -1030,7 +1059,8 @@ class SalesInvoice(SellingController):
)
def make_gle_for_rounding_adjustment(self, gl_entries):
if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment:
if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment \
and not self.is_internal_transfer():
round_off_account, round_off_cost_center = \
get_round_off_account_and_cost_center(self.company)

View File

@ -148,7 +148,7 @@
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "_Test Item Home Desktop 100",
"item_name": "_Test Item Home Desktop 100",
"item_tax_template": "_Test Account Excise Duty @ 10",
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
"parentfield": "items",
"price_list_rate": 50,
"qty": 10,
@ -276,7 +276,7 @@
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "_Test Item Home Desktop 100",
"item_name": "_Test Item Home Desktop 100",
"item_tax_template": "_Test Account Excise Duty @ 10",
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
"parentfield": "items",
"price_list_rate": 62.5,
"qty": 10,

View File

@ -405,10 +405,10 @@ class TestSalesInvoice(unittest.TestCase):
item_row = si.get("items")[0]
add_items = [
(54, '_Test Account Excise Duty @ 12'),
(288, '_Test Account Excise Duty @ 15'),
(144, '_Test Account Excise Duty @ 20'),
(430, '_Test Item Tax Template 1')
(54, '_Test Account Excise Duty @ 12 - _TC'),
(288, '_Test Account Excise Duty @ 15 - _TC'),
(144, '_Test Account Excise Duty @ 20 - _TC'),
(430, '_Test Item Tax Template 1 - _TC')
]
for qty, item_tax_template in add_items:
item_row_copy = copy.deepcopy(item_row)
@ -2077,14 +2077,14 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
item.save()
item.append("taxes", {
"item_tax_template": "_Test Item Tax Template 1",
"item_tax_template": "_Test Item Tax Template 1 - _TC",
"valid_from": add_days(nowdate(), 1)
})
item.save()
sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1)
sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1"
sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1 - _TC"
self.assertRaises(frappe.ValidationError, sales_invoice.save)
item.taxes = []

View File

@ -818,6 +818,7 @@
"fieldname": "stock_uom_rate",
"fieldtype": "Currency",
"label": "Rate of Stock UOM",
"no_copy": 1,
"options": "currency",
"read_only": 1
}
@ -825,7 +826,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-01-30 21:42:37.796771",
"modified": "2021-02-23 01:05:22.123527",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@ -10,6 +10,14 @@ frappe.ui.form.on('Subscription', {
}
}
});
frm.set_query('cost_center', function() {
return {
filters: {
company: frm.doc.company
}
};
});
},
refresh: function(frm) {

View File

@ -7,9 +7,10 @@
"engine": "InnoDB",
"field_order": [
"party_type",
"status",
"cb_1",
"party",
"cb_1",
"company",
"status",
"subscription_period",
"start_date",
"end_date",
@ -44,80 +45,107 @@
{
"allow_on_submit": 1,
"fieldname": "cb_1",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"no_copy": 1,
"options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "subscription_period",
"fieldtype": "Section Break",
"label": "Subscription Period"
"label": "Subscription Period",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "cancelation_date",
"fieldtype": "Date",
"label": "Cancelation Date",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "trial_period_start",
"fieldtype": "Date",
"label": "Trial Period Start Date",
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.trial_period_start",
"fieldname": "trial_period_end",
"fieldtype": "Date",
"label": "Trial Period End Date",
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "current_invoice_start",
"fieldtype": "Date",
"label": "Current Invoice Start Date",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "current_invoice_end",
"fieldtype": "Date",
"label": "Current Invoice End Date",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"description": "Number of days that the subscriber has to pay invoices generated by this subscription",
"fieldname": "days_until_due",
"fieldtype": "Int",
"label": "Days Until Due"
"label": "Days Until Due",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "cancel_at_period_end",
"fieldtype": "Check",
"label": "Cancel At End Of Period"
"label": "Cancel At End Of Period",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "generate_invoice_at_period_start",
"fieldtype": "Check",
"label": "Generate Invoice At Beginning Of Period"
"label": "Generate Invoice At Beginning Of Period",
"show_days": 1,
"show_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "sb_4",
"fieldtype": "Section Break",
"label": "Plans"
"label": "Plans",
"show_days": 1,
"show_seconds": 1
},
{
"allow_on_submit": 1,
@ -125,62 +153,84 @@
"fieldtype": "Table",
"label": "Plans",
"options": "Subscription Plan Detail",
"reqd": 1
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)",
"fieldname": "sb_1",
"fieldtype": "Section Break",
"label": "Taxes"
"label": "Taxes",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "sb_2",
"fieldtype": "Section Break",
"label": "Discounts"
"label": "Discounts",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "apply_additional_discount",
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total"
"options": "\nGrand Total\nNet Total",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "cb_2",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Percent",
"label": "Additional DIscount Percentage"
"label": "Additional DIscount Percentage",
"show_days": 1,
"show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "additional_discount_amount",
"fieldtype": "Currency",
"label": "Additional DIscount Amount"
"label": "Additional DIscount Amount",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.invoices",
"fieldname": "sb_3",
"fieldtype": "Section Break",
"label": "Invoices"
"label": "Invoices",
"show_days": 1,
"show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "invoices",
"fieldtype": "Table",
"label": "Invoices",
"options": "Subscription Invoice"
"options": "Subscription Invoice",
"show_days": 1,
"show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
"label": "Accounting Dimensions",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "party_type",
@ -188,7 +238,9 @@
"label": "Party Type",
"options": "DocType",
"reqd": 1,
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "party",
@ -197,21 +249,27 @@
"label": "Party",
"options": "party_type",
"reqd": 1,
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.party_type === 'Customer'",
"fieldname": "sales_tax_template",
"fieldtype": "Link",
"label": "Sales Taxes and Charges Template",
"options": "Sales Taxes and Charges Template"
"options": "Sales Taxes and Charges Template",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.party_type === 'Supplier'",
"fieldname": "purchase_tax_template",
"fieldtype": "Link",
"label": "Purchase Taxes and Charges Template",
"options": "Purchase Taxes and Charges Template"
"options": "Purchase Taxes and Charges Template",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
@ -219,36 +277,55 @@
"fieldname": "follow_calendar_months",
"fieldtype": "Check",
"label": "Follow Calendar Months",
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
"fieldname": "generate_new_invoices_past_due_date",
"fieldtype": "Check",
"label": "Generate New Invoices Past Due Date"
"label": "Generate New Invoices Past Due Date",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "end_date",
"fieldtype": "Date",
"label": "Subscription End Date",
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Subscription Start Date",
"set_only_once": 1
"set_only_once": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
"options": "Cost Center",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"show_days": 1,
"show_seconds": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-06-25 10:52:52.265105",
"modified": "2021-02-09 15:44:20.024789",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
@ -5,12 +6,13 @@
from __future__ import unicode_literals
import frappe
import erpnext
from frappe import _
from frappe.model.document import Document
from frappe.utils.data import nowdate, getdate, cstr, cint, add_days, date_diff, get_last_day, add_to_date, flt
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
from erpnext import get_default_company
class Subscription(Document):
def before_insert(self):
@ -243,6 +245,7 @@ class Subscription(Document):
self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval())
self.validate_end_date()
self.validate_to_follow_calendar_months()
self.cost_center = erpnext.get_default_cost_center(self.get('company'))
def validate_trial_period(self):
"""
@ -304,6 +307,14 @@ class Subscription(Document):
doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice'
invoice = frappe.new_doc(doctype)
# For backward compatibility
# Earlier subscription didn't had any company field
company = self.get('company') or get_default_company()
if not company:
frappe.throw(_("Company is mandatory was generating invoice. Please set default company in Global Defaults"))
invoice.company = company
invoice.set_posting_time = 1
invoice.posting_date = self.current_invoice_start if self.generate_invoice_at_period_start \
else self.current_invoice_end
@ -330,6 +341,7 @@ class Subscription(Document):
# for that reason
items_list = self.get_items_from_plans(self.plans, prorate)
for item in items_list:
item['cost_center'] = self.cost_center
invoice.append('items', item)
# Taxes
@ -380,7 +392,8 @@ class Subscription(Document):
Returns the `Item`s linked to `Subscription Plan`
"""
if prorate:
prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start)
prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start,
self.generate_invoice_at_period_start)
items = []
party = self.party
@ -583,10 +596,13 @@ def get_calendar_months(billing_interval):
return calendar_months
def get_prorata_factor(period_end, period_start):
diff = flt(date_diff(nowdate(), period_start) + 1)
plan_days = flt(date_diff(period_end, period_start) + 1)
prorate_factor = diff / plan_days
def get_prorata_factor(period_end, period_start, is_prepaid):
if is_prepaid:
prorate_factor = 1
else:
diff = flt(date_diff(nowdate(), period_start) + 1)
plan_days = flt(date_diff(period_end, period_start) + 1)
prorate_factor = diff / plan_days
return prorate_factor

View File

@ -321,7 +321,8 @@ class TestSubscription(unittest.TestCase):
self.assertEqual(
flt(
get_prorata_factor(subscription.current_invoice_end, subscription.current_invoice_start),
get_prorata_factor(subscription.current_invoice_end, subscription.current_invoice_start,
subscription.generate_invoice_at_period_start),
2),
flt(prorate_factor, 2)
)
@ -561,9 +562,7 @@ class TestSubscription(unittest.TestCase):
current_inv = subscription.get_current_invoice()
self.assertEqual(current_inv.status, "Unpaid")
diff = flt(date_diff(nowdate(), subscription.current_invoice_start) + 1)
plan_days = flt(date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1)
prorate_factor = flt(diff / plan_days)
prorate_factor = 1
self.assertEqual(flt(current_inv.grand_total, 2), flt(prorate_factor * 900, 2))

View File

@ -13,21 +13,28 @@
"fieldname": "document_type",
"fieldtype": "Link",
"label": "Document Type ",
"no_copy": 1,
"options": "DocType",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "invoice",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Invoice",
"no_copy": 1,
"options": "document_type",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-06-01 22:23:54.462718",
"modified": "2021-02-09 15:43:32.026233",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Invoice",

View File

@ -11,15 +11,18 @@
],
"fields": [
{
"allow_in_quick_entry": 1,
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1,
"unique": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-08-30 19:41:25.783852",
"modified": "2021-03-03 11:50:38.748872",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Category",

View File

@ -12,37 +12,62 @@ from erpnext.accounts.utils import get_fiscal_year
class TaxWithholdingCategory(Document):
pass
def get_party_tax_withholding_details(ref_doc, tax_withholding_category=None):
def get_party_details(inv):
party_type, party = '', ''
if inv.doctype == 'Sales Invoice':
party_type = 'Customer'
party = inv.customer
else:
party_type = 'Supplier'
party = inv.supplier
return party_type, party
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
pan_no = ''
suppliers = []
parties = []
party_type, party = get_party_details(inv)
if not tax_withholding_category:
tax_withholding_category, pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, ['tax_withholding_category', 'pan'])
tax_withholding_category, pan_no = frappe.db.get_value(party_type, party, ['tax_withholding_category', 'pan'])
if not tax_withholding_category:
return
# if tax_withholding_category passed as an argument but not pan_no
if not pan_no:
pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, 'pan')
pan_no = frappe.db.get_value(party_type, party, 'pan')
# Get others suppliers with the same PAN No
if pan_no:
suppliers = [d.name for d in frappe.get_all('Supplier', fields=['name'], filters={'pan': pan_no})]
parties = frappe.get_all(party_type, filters={ 'pan': pan_no }, pluck='name')
if not suppliers:
suppliers.append(ref_doc.supplier)
if not parties:
parties.append(party)
fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company)
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
fy = get_fiscal_year(ref_doc.posting_date, company=ref_doc.company)
tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company)
if not tax_details:
frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
.format(tax_withholding_category, ref_doc.company))
.format(tax_withholding_category, inv.company))
tds_amount = get_tds_amount(suppliers, ref_doc.net_total, ref_doc.company,
tax_details, fy, ref_doc.posting_date, pan_no)
if party_type == 'Customer' and not tax_details.cumulative_threshold:
# TCS is only chargeable on sum of invoiced value
frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.')
.format(tax_withholding_category, inv.company, party))
tax_row = get_tax_row(tax_details, tds_amount)
tax_amount, tax_deducted = get_tax_amount(
party_type, parties,
inv, tax_details,
fiscal_year, pan_no
)
if party_type == 'Supplier':
tax_row = get_tax_row_for_tds(tax_details, tax_amount)
else:
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
return tax_row
@ -69,147 +94,254 @@ def get_tax_withholding_rates(tax_withholding, fiscal_year):
frappe.throw(_("No Tax Withholding data found for the current Fiscal Year."))
def get_tax_row(tax_details, tds_amount):
return {
def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted):
row = {
"category": "Total",
"add_deduct_tax": "Deduct",
"charge_type": "Actual",
"account_head": tax_details.account_head,
"tax_amount": tax_amount,
"description": tax_details.description,
"tax_amount": tds_amount
"account_head": tax_details.account_head
}
def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_details, posting_date, pan_no=None):
fiscal_year, year_start_date, year_end_date = fiscal_year_details
tds_amount = 0
tds_deducted = 0
if tax_deducted:
# TCS already deducted on previous invoices
# So, TCS will be calculated by 'Previous Row Total'
def _get_tds(amount, rate):
if amount <= 0:
return 0
return amount * rate / 100
ldc_name = frappe.db.get_value('Lower Deduction Certificate',
{
'pan_no': pan_no,
'fiscal_year': fiscal_year
}, 'name')
ldc = ''
if ldc_name:
ldc = frappe.get_doc('Lower Deduction Certificate', ldc_name)
entries = frappe.db.sql("""
select voucher_no, credit
from `tabGL Entry`
where company = %s and
party in %s and fiscal_year=%s and credit > 0
and is_opening = 'No'
""", (company, tuple(suppliers), fiscal_year), as_dict=1)
vouchers = [d.voucher_no for d in entries]
advance_vouchers = get_advance_vouchers(suppliers, fiscal_year=fiscal_year, company=company)
tds_vouchers = vouchers + advance_vouchers
if tds_vouchers:
tds_deducted = frappe.db.sql("""
SELECT sum(credit) FROM `tabGL Entry`
WHERE
account=%s and fiscal_year=%s and credit > 0
and voucher_no in ({0})""". format(','.join(['%s'] * len(tds_vouchers))),
((tax_details.account_head, fiscal_year) + tuple(tds_vouchers)))
tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0
if tds_deducted:
if ldc:
limit_consumed = frappe.db.get_value('Purchase Invoice',
{
'supplier': ('in', suppliers),
'apply_tds': 1,
'docstatus': 1
}, 'sum(net_total)')
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total,
ldc.certificate_limit):
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
taxes_excluding_tcs = [d for d in inv.taxes if d.account_head != tax_details.account_head]
if taxes_excluding_tcs:
# chargeable amount is the total amount after other charges are applied
row.update({
"charge_type": "On Previous Row Total",
"row_id": len(taxes_excluding_tcs),
"rate": tax_details.rate
})
else:
tds_amount = _get_tds(net_total, tax_details.rate)
else:
supplier_credit_amount = frappe.get_all('Purchase Invoice',
fields = ['sum(net_total)'],
filters = {'name': ('in', vouchers), 'docstatus': 1, "apply_tds": 1}, as_list=1)
# if only TCS is to be charged, then net total is chargeable amount
row.update({
"charge_type": "On Net Total",
"rate": tax_details.rate
})
supplier_credit_amount = (supplier_credit_amount[0][0]
if supplier_credit_amount and supplier_credit_amount[0][0] else 0)
return row
jv_supplier_credit_amt = frappe.get_all('Journal Entry Account',
fields = ['sum(credit_in_account_currency)'],
filters = {
'parent': ('in', vouchers), 'docstatus': 1,
'party': ('in', suppliers),
'reference_type': ('not in', ['Purchase Invoice'])
}, as_list=1)
def get_tax_row_for_tds(tax_details, tax_amount):
return {
"category": "Total",
"charge_type": "Actual",
"tax_amount": tax_amount,
"add_deduct_tax": "Deduct",
"description": tax_details.description,
"account_head": tax_details.account_head
}
supplier_credit_amount += (jv_supplier_credit_amt[0][0]
if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0)
def get_lower_deduction_certificate(fiscal_year, pan_no):
ldc_name = frappe.db.get_value('Lower Deduction Certificate', { 'pan_no': pan_no, 'fiscal_year': fiscal_year }, 'name')
if ldc_name:
return frappe.get_doc('Lower Deduction Certificate', ldc_name)
supplier_credit_amount += net_total
def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
fiscal_year = fiscal_year_details[0]
debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date)
supplier_credit_amount -= debit_note_amount
vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
taxable_vouchers = vouchers + advance_vouchers
if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold)
or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)):
tax_deducted = 0
if taxable_vouchers:
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total,
ldc.certificate_limit):
tds_amount = get_ltds_amount(supplier_credit_amount, 0, ldc.certificate_limit, ldc.rate,
tax_details)
tax_amount = 0
posting_date = inv.posting_date
if party_type == 'Supplier':
ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
if tax_deducted:
net_total = inv.net_total
if ldc:
tax_amount = get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total)
else:
tds_amount = _get_tds(supplier_credit_amount, tax_details.rate)
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
else:
tax_amount = get_tds_amount(
ldc, parties, inv, tax_details,
fiscal_year_details, tax_deducted, vouchers
)
elif party_type == 'Customer':
if tax_deducted:
# if already TCS is charged, then amount will be calculated based on 'Previous Row Total'
tax_amount = 0
else:
# if no TCS has been charged in FY,
# then chargeable value is "prev invoices + advances" value which cross the threshold
tax_amount = get_tcs_amount(
parties, inv, tax_details,
fiscal_year_details, vouchers, advance_vouchers
)
return tax_amount, tax_deducted
def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'):
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
filters = {
dr_or_cr: ['>', 0],
'company': company,
'party_type': party_type,
'party': ['in', parties],
'fiscal_year': fiscal_year,
'is_opening': 'No',
'is_cancelled': 0
}
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""]
def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None, to_date=None, party_type='Supplier'):
# for advance vouchers, debit and credit is reversed
dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit'
filters = {
dr_or_cr: ['>', 0],
'is_opening': 'No',
'is_cancelled': 0,
'party_type': party_type,
'party': ['in', parties],
'against_voucher': ['is', 'not set']
}
if fiscal_year:
filters['fiscal_year'] = fiscal_year
if company:
filters['company'] = company
if from_date and to_date:
filters['posting_date'] = ['between', (from_date, to_date)]
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""]
def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details):
# check if TDS / TCS account is already charged on taxable vouchers
filters = {
'is_cancelled': 0,
'credit': ['>', 0],
'fiscal_year': fiscal_year,
'account': tax_details.account_head,
'voucher_no': ['in', taxable_vouchers],
}
field = "sum(credit)"
return frappe.db.get_value('GL Entry', filters, field) or 0.0
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
tds_amount = 0
supp_credit_amt = frappe.db.get_value('Purchase Invoice', {
'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1
}, 'sum(net_total)') or 0.0
supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
'parent': ('in', vouchers), 'docstatus': 1,
'party': ('in', parties), 'reference_type': ('!=', 'Purchase Invoice')
}, 'sum(credit_in_account_currency)') or 0.0
supp_credit_amt += supp_jv_credit_amt
supp_credit_amt += inv.net_total
debit_note_amount = get_debit_note_amount(parties, fiscal_year_details, inv.company)
supp_credit_amt -= debit_note_amount
threshold = tax_details.get('threshold', 0)
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
if ((threshold and supp_credit_amt >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
if ldc and is_valid_certificate(
ldc.valid_from, ldc.valid_upto,
inv.posting_date, tax_deducted,
inv.net_total, ldc.certificate_limit
):
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
else:
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
return tds_amount
def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=None, to_date=None):
condition = "fiscal_year=%s" % fiscal_year
def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv_vouchers):
tcs_amount = 0
fiscal_year, _, _ = fiscal_year_details
# sum of debit entries made from sales invoices
invoiced_amt = frappe.db.get_value('GL Entry', {
'is_cancelled': 0,
'party': ['in', parties],
'company': inv.company,
'voucher_no': ['in', vouchers],
}, 'sum(debit)') or 0.0
# sum of credit entries made from PE / JV with unset 'against voucher'
advance_amt = frappe.db.get_value('GL Entry', {
'is_cancelled': 0,
'party': ['in', parties],
'company': inv.company,
'voucher_no': ['in', adv_vouchers],
}, 'sum(credit)') or 0.0
# sum of credit entries made from sales invoice
credit_note_amt = frappe.db.get_value('GL Entry', {
'is_cancelled': 0,
'credit': ['>', 0],
'party': ['in', parties],
'fiscal_year': fiscal_year,
'company': inv.company,
'voucher_type': 'Sales Invoice',
}, 'sum(credit)') or 0.0
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
current_invoice_total = get_invoice_total_without_tcs(inv, tax_details)
total_invoiced_amt = current_invoice_total + invoiced_amt + advance_amt - credit_note_amt
if ((cumulative_threshold and total_invoiced_amt >= cumulative_threshold)):
chargeable_amt = total_invoiced_amt - cumulative_threshold
tcs_amount = chargeable_amt * tax_details.rate / 100 if chargeable_amt > 0 else 0
return tcs_amount
def get_invoice_total_without_tcs(inv, tax_details):
tcs_tax_row = [d for d in inv.taxes if d.account_head == tax_details.account_head]
tcs_tax_row_amount = tcs_tax_row[0].base_tax_amount if tcs_tax_row else 0
return inv.grand_total - tcs_tax_row_amount
def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total):
tds_amount = 0
limit_consumed = frappe.db.get_value('Purchase Invoice', {
'supplier': ('in', parties),
'apply_tds': 1,
'docstatus': 1
}, 'sum(net_total)')
if is_valid_certificate(
ldc.valid_from, ldc.valid_upto,
posting_date, limit_consumed,
net_total, ldc.certificate_limit
):
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
return tds_amount
def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
_, year_start_date, year_end_date = fiscal_year_details
filters = {
'supplier': ['in', suppliers],
'is_return': 1,
'docstatus': 1,
'posting_date': ['between', (year_start_date, year_end_date)]
}
fields = ['abs(sum(net_total)) as net_total']
if company:
condition += "and company =%s" % (company)
if from_date and to_date:
condition += "and posting_date between %s and %s" % (from_date, to_date)
filters['company'] = company
## Appending the same supplier again if length of suppliers list is 1
## since tuple of single element list contains None, For example ('Test Supplier 1', )
## and the below query fails
if len(suppliers) == 1:
suppliers.append(suppliers[0])
return frappe.db.sql_list("""
select distinct voucher_no
from `tabGL Entry`
where party in %s and %s and debit > 0
and is_opening = 'No'
""", (tuple(suppliers), condition)) or []
def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
condition = "and 1=1"
if company:
condition = " and company=%s " % company
if len(suppliers) == 1:
suppliers.append(suppliers[0])
return flt(frappe.db.sql("""
select abs(sum(net_total))
from `tabPurchase Invoice`
where supplier in %s and is_return=1 and docstatus=1
and posting_date between %s and %s %s
""", (tuple(suppliers), year_start_date, year_end_date, condition)))
return frappe.get_all('Purchase Invoice', filters, fields)[0].get('net_total') or 0.0
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
if current_amount < (certificate_limit - deducted_amount):
@ -227,4 +359,4 @@ def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount,
certificate_limit > deducted_amount):
valid = True
return valid
return valid

View File

@ -9,7 +9,7 @@ from frappe.utils import today
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
test_dependencies = ["Supplier Group"]
test_dependencies = ["Supplier Group", "Customer Group"]
class TestTaxWithholdingCategory(unittest.TestCase):
@classmethod
@ -18,6 +18,9 @@ class TestTaxWithholdingCategory(unittest.TestCase):
create_records()
create_tax_with_holding_category()
def tearDown(self):
cancel_invoices()
def test_cumulative_threshold_tds(self):
frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS")
invoices = []
@ -128,9 +131,59 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in invoices:
d.cancel()
def test_cumulative_threshold_tcs(self):
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
invoices = []
# create invoices for lower than single threshold tax rate
for _ in range(2):
si = create_sales_invoice(customer = "Test TCS Customer")
si.submit()
invoices.append(si)
# create another invoice whose total when added to previously created invoice,
# surpasses cumulative threshhold
si = create_sales_invoice(customer = "Test TCS Customer", rate=12000)
si.submit()
# assert tax collection on total invoice amount created until now
tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC'])
self.assertEqual(tcs_charged, 200)
self.assertEqual(si.grand_total, 12200)
invoices.append(si)
# TCS is already collected once, so going forward system will collect TCS on every invoice
si = create_sales_invoice(customer = "Test TCS Customer", rate=5000)
si.submit()
tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC'])
self.assertEqual(tcs_charged, 500)
invoices.append(si)
#delete invoices to avoid clashing
for d in invoices:
d.cancel()
def cancel_invoices():
purchase_invoices = frappe.get_all("Purchase Invoice", {
'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']],
'docstatus': 1
}, pluck="name")
sales_invoices = frappe.get_all("Sales Invoice", {
'customer': 'Test TCS Customer',
'docstatus': 1
}, pluck="name")
for d in purchase_invoices:
frappe.get_doc('Purchase Invoice', d).cancel()
for d in sales_invoices:
frappe.get_doc('Sales Invoice', d).cancel()
def create_purchase_invoice(**args):
# return sales invoice doc object
item = frappe.get_doc('Item', {'item_name': 'TDS Item'})
item = frappe.db.get_value('Item', {'item_name': 'TDS Item'}, "name")
args = frappe._dict(args)
pi = frappe.get_doc({
@ -145,7 +198,7 @@ def create_purchase_invoice(**args):
"taxes": [],
"items": [{
'doctype': 'Purchase Invoice Item',
'item_code': item.name,
'item_code': item,
'qty': args.qty or 1,
'rate': args.rate or 10000,
'cost_center': 'Main - _TC',
@ -156,6 +209,33 @@ def create_purchase_invoice(**args):
pi.save()
return pi
def create_sales_invoice(**args):
# return sales invoice doc object
item = frappe.db.get_value('Item', {'item_name': 'TCS Item'}, "name")
args = frappe._dict(args)
si = frappe.get_doc({
"doctype": "Sales Invoice",
"posting_date": today(),
"customer": args.customer,
"company": '_Test Company',
"taxes_and_charges": "",
"currency": "INR",
"debit_to": "Debtors - _TC",
"taxes": [],
"items": [{
'doctype': 'Sales Invoice Item',
'item_code': item,
'qty': args.qty or 1,
'rate': args.rate or 10000,
'cost_center': 'Main - _TC',
'expense_account': 'Cost of Goods Sold - _TC'
}]
})
si.save()
return si
def create_records():
# create a new suppliers
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
@ -168,7 +248,17 @@ def create_records():
"doctype": "Supplier",
}).insert()
# create an item
for name in ['Test TCS Customer']:
if frappe.db.exists('Customer', name):
continue
frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": name,
"doctype": "Customer"
}).insert()
# create item
if not frappe.db.exists('Item', "TDS Item"):
frappe.get_doc({
"doctype": "Item",
@ -178,7 +268,16 @@ def create_records():
"is_stock_item": 0,
}).insert()
# create an account
if not frappe.db.exists('Item', "TCS Item"):
frappe.get_doc({
"doctype": "Item",
"item_code": "TCS Item",
"item_name": "TCS Item",
"item_group": "All Item Groups",
"is_stock_item": 1
}).insert()
# create tds account
if not frappe.db.exists("Account", "TDS - _TC"):
frappe.get_doc({
'doctype': 'Account',
@ -189,6 +288,17 @@ def create_records():
'root_type': 'Asset'
}).insert()
# create tcs account
if not frappe.db.exists("Account", "TCS - _TC"):
frappe.get_doc({
'doctype': 'Account',
'company': '_Test Company',
'account_name': 'TCS',
'parent_account': 'Duties and Taxes - _TC',
'report_type': 'Balance Sheet',
'root_type': 'Liability'
}).insert()
def create_tax_with_holding_category():
fiscal_year = get_fiscal_year(today(), company="_Test Company")[0]
@ -210,6 +320,23 @@ def create_tax_with_holding_category():
}]
}).insert()
if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TCS"):
frappe.get_doc({
"doctype": "Tax Withholding Category",
"name": "Cumulative Threshold TCS",
"category_name": "10% TCS",
"rates": [{
'fiscal_year': fiscal_year,
'tax_withholding_rate': 10,
'single_threshold': 0,
'cumulative_threshold': 30000.00
}],
"accounts": [{
'company': '_Test Company',
'account': 'TCS - _TC'
}]
}).insert()
# Single thresold
if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"):
frappe.get_doc({

View File

@ -44,9 +44,9 @@ def validate_accounting_period(gl_map):
frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}")
.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
def process_gl_map(gl_map, merge_entries=True):
def process_gl_map(gl_map, merge_entries=True, precision=None):
if merge_entries:
gl_map = merge_similar_entries(gl_map)
gl_map = merge_similar_entries(gl_map, precision)
for entry in gl_map:
# toggle debit, credit if negative entry
if flt(entry.debit) < 0:
@ -69,7 +69,7 @@ def process_gl_map(gl_map, merge_entries=True):
return gl_map
def merge_similar_entries(gl_map):
def merge_similar_entries(gl_map, precision=None):
merged_gl_map = []
accounting_dimensions = get_accounting_dimensions()
for entry in gl_map:
@ -88,7 +88,9 @@ def merge_similar_entries(gl_map):
company = gl_map[0].company if gl_map else erpnext.get_default_company()
company_currency = erpnext.get_company_currency(company)
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
if not precision:
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
# filter zero debit and credit entries
merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map)
@ -132,8 +134,8 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
gle.update(args)
gle.flags.ignore_permissions = 1
gle.flags.from_repost = from_repost
gle.insert()
gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
gle.flags.adv_adj = adv_adj
gle.flags.update_outstanding = update_outstanding or 'Yes'
gle.submit()
if not from_repost:
@ -194,7 +196,7 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
if not round_off_gle:
for k in ["voucher_type", "voucher_no", "company",
"posting_date", "remarks", "is_opening"]:
"posting_date", "remarks"]:
round_off_gle[k] = gl_map[0][k]
round_off_gle.update({
@ -206,6 +208,7 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
"cost_center": round_off_cost_center,
"party_type": None,
"party": None,
"is_opening": "No",
"against_voucher_type": None,
"against_voucher": None
})

View File

@ -1,583 +0,0 @@
frappe.provide("erpnext.accounts");
frappe.pages['bank-reconciliation'].on_page_load = function(wrapper) {
new erpnext.accounts.bankReconciliation(wrapper);
}
erpnext.accounts.bankReconciliation = class BankReconciliation {
constructor(wrapper) {
this.page = frappe.ui.make_app_page({
parent: wrapper,
title: __("Bank Reconciliation"),
single_column: true
});
this.parent = wrapper;
this.page = this.parent.page;
this.check_plaid_status();
this.make();
}
make() {
const me = this;
me.$main_section = $(`<div class="reconciliation page-main-content"></div>`).appendTo(me.page.main);
const empty_state = __("Upload a bank statement, link or reconcile a bank account")
me.$main_section.append(`<div class="flex justify-center align-center text-muted"
style="height: 50vh; display: flex;"><h5 class="text-muted">${empty_state}</h5></div>`)
me.page.add_field({
fieldtype: 'Link',
label: __('Company'),
fieldname: 'company',
options: "Company",
onchange: function() {
if (this.value) {
me.company = this.value;
} else {
me.company = null;
me.bank_account = null;
}
}
})
me.page.add_field({
fieldtype: 'Link',
label: __('Bank Account'),
fieldname: 'bank_account',
options: "Bank Account",
get_query: function() {
if(!me.company) {
frappe.throw(__("Please select company first"));
return
}
return {
filters: {
"company": me.company
}
}
},
onchange: function() {
if (this.value) {
me.bank_account = this.value;
me.add_actions();
} else {
me.bank_account = null;
me.page.hide_actions_menu();
}
}
})
}
check_plaid_status() {
const me = this;
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
if (r && r.enabled === "1") {
me.plaid_status = "active"
} else {
me.plaid_status = "inactive"
}
})
}
add_actions() {
const me = this;
me.page.show_menu()
me.page.add_menu_item(__("Upload a statement"), function() {
me.clear_page_content();
new erpnext.accounts.bankTransactionUpload(me);
}, true)
if (me.plaid_status==="active") {
me.page.add_menu_item(__("Synchronize this account"), function() {
me.clear_page_content();
new erpnext.accounts.bankTransactionSync(me);
}, true)
}
me.page.add_menu_item(__("Reconcile this account"), function() {
me.clear_page_content();
me.make_reconciliation_tool();
}, true)
}
clear_page_content() {
const me = this;
$(me.page.body).find('.frappe-list').remove();
me.$main_section.empty();
}
make_reconciliation_tool() {
const me = this;
frappe.model.with_doctype("Bank Transaction", () => {
erpnext.accounts.ReconciliationList = new erpnext.accounts.ReconciliationTool({
parent: me.parent,
doctype: "Bank Transaction"
});
})
}
}
erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
constructor(parent) {
this.parent = parent;
this.data = [];
const assets = [
"/assets/frappe/css/frappe-datatable.css",
"/assets/frappe/js/lib/clusterize.min.js",
"/assets/frappe/js/lib/Sortable.min.js",
"/assets/frappe/js/lib/frappe-datatable.js"
];
frappe.require(assets, () => {
this.make();
});
}
make() {
const me = this;
new frappe.ui.FileUploader({
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
allow_multiple: 0,
on_success: function(attachment, r) {
if (!r.exc && r.message) {
me.data = r.message;
me.setup_transactions_dom();
me.create_datatable();
me.add_primary_action();
}
}
})
}
setup_transactions_dom() {
const me = this;
me.parent.$main_section.append('<div class="transactions-table"></div>');
}
create_datatable() {
try {
this.datatable = new DataTable('.transactions-table', {
columns: this.data.columns,
data: this.data.data
})
}
catch(err) {
let msg = __("Your file could not be processed. It should be a standard CSV or XLSX file with headers in the first row.");
frappe.throw(msg)
}
}
add_primary_action() {
const me = this;
me.parent.page.set_primary_action(__("Submit"), function() {
me.add_bank_entries()
}, null, __("Creating bank entries..."))
}
add_bank_entries() {
const me = this;
frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.create_bank_entries',
{columns: this.datatable.datamanager.columns, data: this.datatable.datamanager.data, bank_account: me.parent.bank_account}
).then((result) => {
let result_title = result.errors == 0 ? __("{0} bank transaction(s) created", [result.success]) : __("{0} bank transaction(s) created and {1} errors", [result.success, result.errors])
let result_msg = `
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5>
</div>`
me.parent.page.clear_primary_action();
me.parent.$main_section.empty();
me.parent.$main_section.append(result_msg);
if (result.errors == 0) {
frappe.show_alert({message:__("All bank transactions have been created"), indicator:'green'});
} else {
frappe.show_alert({message:__("Please check the error log for details about the import errors"), indicator:'red'});
}
})
}
}
erpnext.accounts.bankTransactionSync = class bankTransactionSync {
constructor(parent) {
this.parent = parent;
this.data = [];
this.init_config()
}
init_config() {
const me = this;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration')
.then(result => {
me.plaid_env = result.plaid_env;
me.client_name = result.client_name;
me.link_token = result.link_token;
me.sync_transactions();
})
}
sync_transactions() {
const me = this;
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (r) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
bank: r.bank,
bank_account: me.parent.bank_account,
freeze: true
})
.then((result) => {
let result_title = (result && result.length > 0)
? __("{0} bank transaction(s) created", [result.length])
: __("This bank account is already synchronized");
let result_msg = `
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5>
</div>`
this.parent.$main_section.append(result_msg)
frappe.show_alert({ message: __("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator: 'green' });
})
})
}
}
erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.views.BaseList {
constructor(opts) {
super(opts);
this.show();
}
setup_defaults() {
super.setup_defaults();
this.page_title = __("Bank Reconciliation");
this.doctype = 'Bank Transaction';
this.fields = ['date', 'description', 'debit', 'credit', 'currency']
}
setup_view() {
this.render_header();
}
setup_side_bar() {
//
}
make_standard_filters() {
//
}
freeze() {
this.$result.find('.list-count').html(`<span>${__('Refreshing')}...</span>`);
}
get_args() {
const args = super.get_args();
return Object.assign({}, args, {
...args.filters.push(["Bank Transaction", "docstatus", "=", 1],
["Bank Transaction", "unallocated_amount", ">", 0])
});
}
update_data(r) {
let data = r.message || [];
if (this.start === 0) {
this.data = data;
} else {
this.data = this.data.concat(data);
}
}
render() {
const me = this;
this.$result.find('.list-row-container').remove();
$('[data-fieldname="name"]').remove();
me.data.map((value) => {
const row = $('<div class="list-row-container">').data("data", value).appendTo(me.$result).get(0);
new erpnext.accounts.ReconciliationRow(row, value);
})
}
render_header() {
const me = this;
if ($(this.wrapper).find('.transaction-header').length === 0) {
me.$result.append(frappe.render_template("bank_transaction_header"));
}
}
}
erpnext.accounts.ReconciliationRow = class ReconciliationRow {
constructor(row, data) {
this.data = data;
this.row = row;
this.make();
this.bind_events();
}
make() {
$(this.row).append(frappe.render_template("bank_transaction_row", this.data))
}
bind_events() {
const me = this;
$(me.row).on('click', '.clickable-section', function() {
me.bank_entry = $(this).attr("data-name");
me.show_dialog($(this).attr("data-name"));
})
$(me.row).on('click', '.new-reconciliation', function() {
me.bank_entry = $(this).attr("data-name");
me.show_dialog($(this).attr("data-name"));
})
$(me.row).on('click', '.new-payment', function() {
me.bank_entry = $(this).attr("data-name");
me.new_payment();
})
$(me.row).on('click', '.new-invoice', function() {
me.bank_entry = $(this).attr("data-name");
me.new_invoice();
})
$(me.row).on('click', '.new-expense', function() {
me.bank_entry = $(this).attr("data-name");
me.new_expense();
})
}
new_payment() {
const me = this;
const paid_amount = me.data.credit > 0 ? me.data.credit : me.data.debit;
const payment_type = me.data.credit > 0 ? "Receive": "Pay";
const party_type = me.data.credit > 0 ? "Customer": "Supplier";
frappe.new_doc("Payment Entry", {"payment_type": payment_type, "paid_amount": paid_amount,
"party_type": party_type, "paid_from": me.data.bank_account})
}
new_invoice() {
const me = this;
const invoice_type = me.data.credit > 0 ? "Sales Invoice" : "Purchase Invoice";
frappe.new_doc(invoice_type)
}
new_expense() {
frappe.new_doc("Expense Claim")
}
show_dialog(data) {
const me = this;
frappe.db.get_value("Bank Account", me.data.bank_account, "account", (r) => {
me.gl_account = r.account;
})
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
{ bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") }
).then((result) => {
me.make_dialog(result)
})
}
make_dialog(data) {
const me = this;
me.selected_payment = null;
const fields = [
{
fieldtype: 'Section Break',
fieldname: 'section_break_1',
label: __('Automatic Reconciliation')
},
{
fieldtype: 'HTML',
fieldname: 'payment_proposals'
},
{
fieldtype: 'Section Break',
fieldname: 'section_break_2',
label: __('Search for a payment')
},
{
fieldtype: 'Link',
fieldname: 'payment_doctype',
options: 'DocType',
label: 'Payment DocType',
get_query: () => {
return {
filters : {
"name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]]
}
}
},
},
{
fieldtype: 'Column Break',
fieldname: 'column_break_1',
},
{
fieldtype: 'Dynamic Link',
fieldname: 'payment_entry',
options: 'payment_doctype',
label: 'Payment Document',
get_query: () => {
let dt = this.dialog.fields_dict.payment_doctype.value;
if (dt === "Payment Entry") {
return {
query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.payment_entry_query",
filters : {
"bank_account": this.data.bank_account,
"company": this.data.company
}
}
} else if (dt === "Journal Entry") {
return {
query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.journal_entry_query",
filters : {
"bank_account": this.data.bank_account,
"company": this.data.company
}
}
} else if (dt === "Sales Invoice") {
return {
query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.sales_invoices_query"
}
} else if (dt === "Purchase Invoice") {
return {
filters : [
["Purchase Invoice", "ifnull(clearance_date, '')", "=", ""],
["Purchase Invoice", "docstatus", "=", 1],
["Purchase Invoice", "company", "=", this.data.company]
]
}
} else if (dt === "Expense Claim") {
return {
filters : [
["Expense Claim", "ifnull(clearance_date, '')", "=", ""],
["Expense Claim", "docstatus", "=", 1],
["Expense Claim", "company", "=", this.data.company]
]
}
}
},
onchange: function() {
if (me.selected_payment !== this.value) {
me.selected_payment = this.value;
me.display_payment_details(this);
}
}
},
{
fieldtype: 'Section Break',
fieldname: 'section_break_3'
},
{
fieldtype: 'HTML',
fieldname: 'payment_details'
},
];
me.dialog = new frappe.ui.Dialog({
title: __("Choose a corresponding payment"),
fields: fields,
size: "large"
});
const proposals_wrapper = me.dialog.fields_dict.payment_proposals.$wrapper;
if (data && data.length > 0) {
proposals_wrapper.append(frappe.render_template("linked_payment_header"));
data.map(value => {
proposals_wrapper.append(frappe.render_template("linked_payment_row", value))
})
} else {
const empty_data_msg = __("ERPNext could not find any matching payment entry")
proposals_wrapper.append(`<div class="text-center"><h5 class="text-muted">${empty_data_msg}</h5></div>`)
}
$(me.dialog.body).on('click', '.reconciliation-btn', (e) => {
const payment_entry = $(e.target).attr('data-name');
const payment_doctype = $(e.target).attr('data-doctype');
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.reconcile',
{bank_transaction: me.bank_entry, payment_doctype: payment_doctype, payment_name: payment_entry})
.then((result) => {
setTimeout(function(){
erpnext.accounts.ReconciliationList.refresh();
}, 2000);
me.dialog.hide();
})
})
me.dialog.show();
}
display_payment_details(event) {
const me = this;
if (event.value) {
let dt = me.dialog.fields_dict.payment_doctype.value;
me.dialog.fields_dict['payment_details'].$wrapper.empty();
frappe.db.get_doc(dt, event.value)
.then(doc => {
let displayed_docs = []
let payment = []
if (dt === "Payment Entry") {
payment.currency = doc.payment_type == "Receive" ? doc.paid_to_account_currency : doc.paid_from_account_currency;
payment.doctype = dt
payment.posting_date = doc.posting_date;
payment.party = doc.party;
payment.reference_no = doc.reference_no;
payment.reference_date = doc.reference_date;
payment.paid_amount = doc.paid_amount;
payment.name = doc.name;
displayed_docs.push(payment);
} else if (dt === "Journal Entry") {
doc.accounts.forEach(payment => {
if (payment.account === me.gl_account) {
payment.doctype = dt;
payment.posting_date = doc.posting_date;
payment.party = doc.pay_to_recd_from;
payment.reference_no = doc.cheque_no;
payment.reference_date = doc.cheque_date;
payment.currency = payment.account_currency;
payment.paid_amount = payment.credit > 0 ? payment.credit : payment.debit;
payment.name = doc.name;
displayed_docs.push(payment);
}
})
} else if (dt === "Sales Invoice") {
doc.payments.forEach(payment => {
if (payment.clearance_date === null || payment.clearance_date === "") {
payment.doctype = dt;
payment.posting_date = doc.posting_date;
payment.party = doc.customer;
payment.reference_no = doc.remarks;
payment.currency = doc.currency;
payment.paid_amount = payment.amount;
payment.name = doc.name;
displayed_docs.push(payment);
}
})
}
const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper;
details_wrapper.append(frappe.render_template("linked_payment_header"));
displayed_docs.forEach(payment => {
details_wrapper.append(frappe.render_template("linked_payment_row", payment));
})
})
}
}
}

View File

@ -1,29 +0,0 @@
{
"content": null,
"creation": "2018-11-24 12:03:14.646669",
"docstatus": 0,
"doctype": "Page",
"idx": 0,
"modified": "2018-11-24 12:03:14.646669",
"modified_by": "Administrator",
"module": "Accounts",
"name": "bank-reconciliation",
"owner": "Administrator",
"page_name": "bank-reconciliation",
"roles": [
{
"role": "System Manager"
},
{
"role": "Accounts Manager"
},
{
"role": "Accounts User"
}
],
"script": null,
"standard": "Yes",
"style": null,
"system_page": 0,
"title": "Bank Reconciliation"
}

View File

@ -1,369 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
import difflib
from frappe.utils import flt
from six import iteritems
from erpnext import get_company_currency
@frappe.whitelist()
def reconcile(bank_transaction, payment_doctype, payment_name):
transaction = frappe.get_doc("Bank Transaction", bank_transaction)
payment_entry = frappe.get_doc(payment_doctype, payment_name)
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name))
if payment_doctype == "Payment Entry" and payment_entry.unallocated_amount > transaction.unallocated_amount:
frappe.throw(_("The unallocated amount of Payment Entry {0} is greater than the Bank Transaction's unallocated amount").format(payment_name))
if transaction.unallocated_amount == 0:
frappe.throw(_("This bank transaction is already fully reconciled"))
if transaction.credit > 0 and gl_entry.credit > 0:
frappe.throw(_("The selected payment entry should be linked with a debtor bank transaction"))
if transaction.debit > 0 and gl_entry.debit > 0:
frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction"))
add_payment_to_transaction(transaction, payment_entry, gl_entry)
return 'reconciled'
def add_payment_to_transaction(transaction, payment_entry, gl_entry):
gl_amount, transaction_amount = (gl_entry.credit, transaction.debit) if gl_entry.credit > 0 else (gl_entry.debit, transaction.credit)
allocated_amount = gl_amount if gl_amount <= transaction_amount else transaction_amount
transaction.append("payment_entries", {
"payment_document": payment_entry.doctype,
"payment_entry": payment_entry.name,
"allocated_amount": allocated_amount
})
transaction.save()
transaction.update_allocations()
@frappe.whitelist()
def get_linked_payments(bank_transaction):
transaction = frappe.get_doc("Bank Transaction", bank_transaction)
bank_account = frappe.db.get_values("Bank Account", transaction.bank_account, ["account", "company"], as_dict=True)
# Get all payment entries with a matching amount
amount_matching = check_matching_amount(bank_account[0].account, bank_account[0].company, transaction)
# Get some data from payment entries linked to a corresponding bank transaction
description_matching = get_matching_descriptions_data(bank_account[0].company, transaction)
if amount_matching:
return check_amount_vs_description(amount_matching, description_matching)
elif description_matching:
description_matching = filter(lambda x: not x.get('clearance_date'), description_matching)
if not description_matching:
return []
return sorted(list(description_matching), key = lambda x: x["posting_date"], reverse=True)
else:
return []
def check_matching_amount(bank_account, company, transaction):
payments = []
amount = transaction.credit if transaction.credit > 0 else transaction.debit
payment_type = "Receive" if transaction.credit > 0 else "Pay"
account_from_to = "paid_to" if transaction.credit > 0 else "paid_from"
currency_field = "paid_to_account_currency as currency" if transaction.credit > 0 else "paid_from_account_currency as currency"
payment_entries = frappe.get_all("Payment Entry", fields=["'Payment Entry' as doctype", "name", "paid_amount", "payment_type", "reference_no", "reference_date",
"party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)],
["docstatus", "=", "1"], ["payment_type", "=", [payment_type, "Internal Transfer"]], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]])
jea_side = "debit" if transaction.credit > 0 else "credit"
journal_entries = frappe.db.sql(f"""
SELECT
'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
jea.account_currency as currency, je.pay_to_recd_from as party, je.cheque_date as reference_date,
jea.{jea_side}_in_account_currency as paid_amount
FROM
`tabJournal Entry Account` as jea
JOIN
`tabJournal Entry` as je
ON
jea.parent = je.name
WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00')
AND
jea.account = %(bank_account)s
AND
jea.{jea_side}_in_account_currency like %(txt)s
AND
je.docstatus = 1
""", {
'bank_account': bank_account,
'txt': '%%%s%%' % amount
}, as_dict=True)
if transaction.credit > 0:
sales_invoices = frappe.db.sql("""
SELECT
'Sales Invoice' as doctype, si.name, si.customer as party,
si.posting_date, sip.amount as paid_amount
FROM
`tabSales Invoice Payment` as sip
JOIN
`tabSales Invoice` as si
ON
sip.parent = si.name
WHERE
(sip.clearance_date is null or sip.clearance_date='0000-00-00')
AND
sip.account = %s
AND
sip.amount like %s
AND
si.docstatus = 1
""", (bank_account, amount), as_dict=True)
else:
sales_invoices = []
if transaction.debit > 0:
purchase_invoices = frappe.get_all("Purchase Invoice",
fields = ["'Purchase Invoice' as doctype", "name", "paid_amount", "supplier as party", "posting_date", "currency"],
filters=[
["paid_amount", "like", "{0}%".format(amount)],
["docstatus", "=", "1"],
["is_paid", "=", "1"],
["ifnull(clearance_date, '')", "=", ""],
["cash_bank_account", "=", "{0}".format(bank_account)]
]
)
mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account",
filters={"default_account": bank_account}, fields=["parent"])]
company_currency = get_company_currency(company)
expense_claims = frappe.get_all("Expense Claim",
fields=["'Expense Claim' as doctype", "name", "total_sanctioned_amount as paid_amount",
"employee as party", "posting_date", "'{0}' as currency".format(company_currency)],
filters=[
["total_sanctioned_amount", "like", "{0}%".format(amount)],
["docstatus", "=", "1"],
["is_paid", "=", "1"],
["ifnull(clearance_date, '')", "=", ""],
["mode_of_payment", "in", "{0}".format(tuple(mode_of_payments))]
]
)
else:
purchase_invoices = expense_claims = []
for data in [payment_entries, journal_entries, sales_invoices, purchase_invoices, expense_claims]:
if data:
payments.extend(data)
return payments
def get_matching_descriptions_data(company, transaction):
if not transaction.description :
return []
bank_transactions = frappe.db.sql("""
SELECT
bt.name, bt.description, bt.date, btp.payment_document, btp.payment_entry
FROM
`tabBank Transaction` as bt
LEFT JOIN
`tabBank Transaction Payments` as btp
ON
bt.name = btp.parent
WHERE
bt.allocated_amount > 0
AND
bt.docstatus = 1
""", as_dict=True)
selection = []
for bank_transaction in bank_transactions:
if bank_transaction.description:
seq=difflib.SequenceMatcher(lambda x: x == " ", transaction.description, bank_transaction.description)
if seq.ratio() > 0.6:
bank_transaction["ratio"] = seq.ratio()
selection.append(bank_transaction)
document_types = set([x["payment_document"] for x in selection])
links = {}
for document_type in document_types:
links[document_type] = [x["payment_entry"] for x in selection if x["payment_document"]==document_type]
data = []
company_currency = get_company_currency(company)
for key, value in iteritems(links):
if key == "Payment Entry":
data.extend(frappe.get_all("Payment Entry", filters=[["name", "in", value]],
fields=["'Payment Entry' as doctype", "posting_date", "party", "reference_no",
"reference_date", "paid_amount", "paid_to_account_currency as currency", "clearance_date"]))
if key == "Journal Entry":
journal_entries = frappe.get_all("Journal Entry", filters=[["name", "in", value]],
fields=["name", "'Journal Entry' as doctype", "posting_date",
"pay_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date",
"total_credit as paid_amount", "clearance_date"])
for journal_entry in journal_entries:
journal_entry_accounts = frappe.get_all("Journal Entry Account", filters={"parenttype": journal_entry["doctype"], "parent": journal_entry["name"]}, fields=["account_currency"])
journal_entry["currency"] = journal_entry_accounts[0]["account_currency"] if journal_entry_accounts else company_currency
data.extend(journal_entries)
if key == "Sales Invoice":
data.extend(frappe.get_all("Sales Invoice", filters=[["name", "in", value]], fields=["'Sales Invoice' as doctype", "posting_date", "customer_name as party", "paid_amount", "currency"]))
if key == "Purchase Invoice":
data.extend(frappe.get_all("Purchase Invoice", filters=[["name", "in", value]], fields=["'Purchase Invoice' as doctype", "posting_date", "supplier_name as party", "paid_amount", "currency"]))
if key == "Expense Claim":
expense_claims = frappe.get_all("Expense Claim", filters=[["name", "in", value]], fields=["'Expense Claim' as doctype", "posting_date", "employee_name as party", "total_amount_reimbursed as paid_amount"])
data.extend([dict(x,**{"currency": company_currency}) for x in expense_claims])
return data
def check_amount_vs_description(amount_matching, description_matching):
result = []
if description_matching:
for am_match in amount_matching:
for des_match in description_matching:
if des_match.get("clearance_date"):
continue
if am_match["party"] == des_match["party"]:
if am_match not in result:
result.append(am_match)
continue
if "reference_no" in am_match and "reference_no" in des_match:
# Sequence Matcher does not handle None as input
am_reference = am_match["reference_no"] or ""
des_reference = des_match["reference_no"] or ""
if difflib.SequenceMatcher(lambda x: x == " ", am_reference, des_reference).ratio() > 70:
if am_match not in result:
result.append(am_match)
if result:
return sorted(result, key = lambda x: x["posting_date"], reverse=True)
else:
return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True)
else:
return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True)
def get_matching_transactions_payments(description_matching):
payments = [x["payment_entry"] for x in description_matching]
payment_by_ratio = {x["payment_entry"]: x["ratio"] for x in description_matching}
if payments:
reference_payment_list = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date",
"party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["name", "in", payments]])
return sorted(reference_payment_list, key=lambda x: payment_by_ratio[x["name"]])
else:
return []
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def payment_entry_query(doctype, txt, searchfield, start, page_len, filters):
account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account")
if not account:
return
return frappe.db.sql("""
SELECT
name, party, paid_amount, received_amount, reference_no
FROM
`tabPayment Entry`
WHERE
(clearance_date is null or clearance_date='0000-00-00')
AND (paid_from = %(account)s or paid_to = %(account)s)
AND (name like %(txt)s or party like %(txt)s)
AND docstatus = 1
ORDER BY
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name
LIMIT
%(start)s, %(page_len)s""",
{
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len,
'account': account
}
)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def journal_entry_query(doctype, txt, searchfield, start, page_len, filters):
account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account")
return frappe.db.sql("""
SELECT
jea.parent, je.pay_to_recd_from,
if(jea.debit_in_account_currency > 0, jea.debit_in_account_currency, jea.credit_in_account_currency)
FROM
`tabJournal Entry Account` as jea
LEFT JOIN
`tabJournal Entry` as je
ON
jea.parent = je.name
WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00')
AND
jea.account = %(account)s
AND
(jea.parent like %(txt)s or je.pay_to_recd_from like %(txt)s)
AND
je.docstatus = 1
ORDER BY
if(locate(%(_txt)s, jea.parent), locate(%(_txt)s, jea.parent), 99999),
jea.parent
LIMIT
%(start)s, %(page_len)s""",
{
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len,
'account': account
}
)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""
SELECT
sip.parent, si.customer, sip.amount, sip.mode_of_payment
FROM
`tabSales Invoice Payment` as sip
LEFT JOIN
`tabSales Invoice` as si
ON
sip.parent = si.name
WHERE
(sip.clearance_date is null or sip.clearance_date='0000-00-00')
AND
(sip.parent like %(txt)s or si.customer like %(txt)s)
ORDER BY
if(locate(%(_txt)s, sip.parent), locate(%(_txt)s, sip.parent), 99999),
sip.parent
LIMIT
%(start)s, %(page_len)s""",
{
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len
}
)

View File

@ -1,21 +0,0 @@
<div class="transaction-header">
<div class="level list-row list-row-head text-muted small">
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Date") }}
</div>
<div class="col-xs-11 col-sm-4 ellipsis list-subject">
{{ __("Description") }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Debit") }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Credit") }}
</div>
<div class="col-sm-1 ellipsis hidden-xs">
{{ __("Currency") }}
</div>
<div class="col-sm-1 ellipsis">
</div>
</div>
</div>

View File

@ -1,36 +0,0 @@
<div class="list-row transaction-item">
<div>
<div class="clickable-section" data-name={{ name }}>
<div class="col-sm-2 ellipsis hidden-xs">
{%= frappe.datetime.str_to_user(date) %}
</div>
<div class="col-xs-8 col-sm-4 ellipsis list-subject">
{{ description }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{%= format_currency(debit, currency) %}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{%= format_currency(credit, currency) %}
</div>
<div class="col-sm-1 ellipsis hidden-xs">
{{ currency }}
</div>
</div>
<div class="col-xs-3 col-sm-1">
<div class="btn-group">
<a class="dropdown-toggle btn btn-default btn-xs" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span>Actions </span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu reports-dropdown" style="max-height: 300px; overflow-y: auto; right: 0px; left: auto;">
<li><a class="new-reconciliation" data-name={{ name }}>{{ __("Reconcile") }}</a></li>
<li class="divider"></li>
<li><a class="new-payment" data-name={{ name }}>{{ __("New Payment") }}</a></li>
<li><a class="new-invoice" data-name={{ name }}>{{ __("New Invoice") }}</a></li>
<li><a class="new-expense" data-name={{ name }}>{{ __("New Expense") }}</a></li>
</ul>
</div>
</div>
</div>
</div>

View File

@ -1,21 +0,0 @@
<div class="transaction-header">
<div class="level list-row list-row-head text-muted small">
<div class="col-xs-3 col-sm-2 ellipsis">
{{ __("Payment Name") }}
</div>
<div class="col-xs-3 col-sm-2 ellipsis">
{{ __("Reference Date") }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Amount") }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Party") }}
</div>
<div class="col-xs-3 col-sm-2 ellipsis">
{{ __("Reference Number") }}
</div>
<div class="col-xs-2 col-sm-2">
</div>
</div>
</div>

View File

@ -1,36 +0,0 @@
<div class="list-row">
<div>
<div class="col-xs-3 col-sm-2 ellipsis">
{{ name }}
</div>
<div class="col-xs-3 col-sm-2 ellipsis">
{% if (typeof reference_date !== "undefined") %}
{%= frappe.datetime.str_to_user(reference_date) %}
{% else %}
{% if (typeof posting_date !== "undefined") %}
{%= frappe.datetime.str_to_user(posting_date) %}
{% endif %}
{% endif %}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ format_currency(paid_amount, currency) }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{% if (typeof party !== "undefined") %}
{{ party }}
{% endif %}
</div>
<div class="col-xs-3 col-sm-2 ellipsis">
{% if (typeof reference_no !== "undefined") %}
{{ reference_no }}
{% else %}
{{ "" }}
{% endif %}
</div>
<div class="col-xs-2 col-sm-2">
<div class="text-right margin-bottom">
<button class="btn btn-primary btn-xs reconciliation-btn" data-doctype="{{ doctype }}" data-name="{{ name }}">{{ __("Reconcile") }}</button>
</div>
</div>
</div>
</div>

View File

@ -617,6 +617,7 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur
FROM `tabGL Entry`
WHERE
party_type = %s and against_voucher is null
and is_cancelled = 0
and {1} GROUP BY party"""
.format(("credit") if party_type == "Customer" else "debit", cond) , party_type)

View File

@ -22,8 +22,8 @@
</p>
</div>
{% endif %}
<h5 class="font-bold" style="margin-top: 0px;">1. Transaction Details</h5>
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
<h5 class="font-bold" style="margin-left: 15px; margin-top: 0px;">1. Transaction Details</h5>
<div class="col-xs-8 column-break">
<div class="row data-field">
<div class="col-xs-4"><label>IRN</label></div>
@ -54,8 +54,8 @@
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
</div>
</div>
<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">2. Party Details</h5>
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
<h5 class="font-bold" style="margin-left: 15px; margin-bottom: 0px;">2. Party Details</h5>
{%- set seller = einvoice.SellerDtls -%}
<div class="col-xs-6 column-break">
<h5 style="margin-bottom: 5px;">Seller</h5>
@ -89,7 +89,7 @@
</div>
</div>
<div style="overflow-x: auto;">
<h5 class="font-bold" style="margin-bottom: 0px;">3. Item Details</h5>
<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">3. Item Details</h5>
<table class="table table-bordered">
<thead>
<tr>

View File

@ -258,7 +258,7 @@
{% } %}
{% } else { %}
{% if(data[i]["party"]|| "&nbsp;") { %}
{% if((data[i]["party"]) != __("'Total'")) { %}
{% if(!data[i]["is_total_row"]) { %}
<td>
{% if(!(filters.customer || filters.supplier)) { %}
{%= data[i]["party"] %}

View File

@ -240,8 +240,7 @@ def get_company_currency(filters=None):
def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters):
for entries in gl_entries_by_account.values():
for entry in entries:
key = entry.account_number or entry.account_name
d = accounts_by_name.get(key)
d = accounts_by_name.get(entry.account_name)
if d:
for company in companies:
# check if posting date is within the period
@ -256,7 +255,8 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
"""accumulate children's values in parent accounts"""
for d in reversed(accounts):
if d.parent_account:
account = d.parent_account.split(' - ')[0].strip()
account = d.parent_account_name
if not accounts_by_name.get(account):
continue
@ -267,16 +267,34 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
accounts_by_name[account]["opening_balance"] = \
accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
def get_account_heads(root_type, companies, filters):
accounts = get_accounts(root_type, filters)
if not accounts:
return None, None
accounts = update_parent_account_names(accounts)
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
return accounts, accounts_by_name
def update_parent_account_names(accounts):
"""Update parent_account_name in accounts list.
parent_name is `name` of parent account which could have other prefix
of account_number and suffix of company abbr. This function adds key called
`parent_account_name` which does not have such prefix/suffix.
"""
name_to_account_map = { d.name : d.account_name for d in accounts }
for account in accounts:
if account.parent_account:
account["parent_account_name"] = name_to_account_map[account.parent_account]
return accounts
def get_companies(filters):
companies = {}
all_companies = get_subsidiary_companies(filters.get('company'))
@ -381,9 +399,9 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
convert_to_presentation_currency(gl_entries, currency_info, filters.get('company'))
for entry in gl_entries:
key = entry.account_number or entry.account_name
validate_entries(key, entry, accounts_by_name, accounts)
gl_entries_by_account.setdefault(key, []).append(entry)
account_name = entry.account_name
validate_entries(account_name, entry, accounts_by_name, accounts)
gl_entries_by_account.setdefault(account_name, []).append(entry)
return gl_entries_by_account
@ -452,8 +470,7 @@ def filter_accounts(accounts, depth=10):
parent_children_map = {}
accounts_by_name = {}
for d in accounts:
key = d.account_number or d.account_name
accounts_by_name[key] = d
accounts_by_name[d.account_name] = d
parent_children_map.setdefault(d.parent_account or None, []).append(d)
filtered_accounts = []

View File

@ -129,6 +129,9 @@ def get_gl_entries(filters, accounting_dimensions):
order_by_statement = "order by posting_date, account, creation"
if filters.get("include_dimensions"):
order_by_statement = "order by posting_date, creation"
if filters.get("group_by") == _("Group by Voucher"):
order_by_statement = "order by posting_date, voucher_type, voucher_no"
@ -142,7 +145,9 @@ def get_gl_entries(filters, accounting_dimensions):
distributed_cost_center_query = ""
if filters and filters.get('cost_center'):
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit,
credit*(DCC_allocation.percentage_allocation/100) as credit,
debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
distributed_cost_center_query = """
@ -200,7 +205,7 @@ def get_gl_entries(filters, accounting_dimensions):
def get_conditions(filters):
conditions = []
if filters.get("account"):
if filters.get("account") and not filters.get("include_dimensions"):
lft, rgt = frappe.db.get_value("Account", filters["account"], ["lft", "rgt"])
conditions.append("""account in (select name from tabAccount
where lft>=%s and rgt<=%s and docstatus<2)""" % (lft, rgt))
@ -245,17 +250,19 @@ def get_conditions(filters):
if match_conditions:
conditions.append(match_conditions)
accounting_dimensions = get_accounting_dimensions(as_list=False)
if filters.get("include_dimensions"):
accounting_dimensions = get_accounting_dimensions(as_list=False)
if accounting_dimensions:
for dimension in accounting_dimensions:
if filters.get(dimension.fieldname):
if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'):
filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type,
filters.get(dimension.fieldname))
conditions.append("{0} in %({0})s".format(dimension.fieldname))
else:
conditions.append("{0} in (%({0})s)".format(dimension.fieldname))
if accounting_dimensions:
for dimension in accounting_dimensions:
if not dimension.disabled:
if filters.get(dimension.fieldname):
if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'):
filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type,
filters.get(dimension.fieldname))
conditions.append("{0} in %({0})s".format(dimension.fieldname))
else:
conditions.append("{0} in (%({0})s)".format(dimension.fieldname))
return "and {}".format(" and ".join(conditions)) if conditions else ""

View File

@ -55,7 +55,7 @@ def get_result(filters):
except IndexError:
account = []
total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account,
filters.company, filters.from_date, filters.to_date)
filters.company, filters.from_date, filters.to_date, filters.fiscal_year)
if total_invoiced_amount or tds_deducted:
row = [supplier.pan, supplier.name]
@ -68,7 +68,7 @@ def get_result(filters):
return out
def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date):
def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, fiscal_year):
''' calculate total invoice amount and total tds deducted for given supplier '''
entries = frappe.db.sql("""
@ -94,7 +94,9 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date):
""".format(', '.join(["'%s'" % d for d in vouchers])),
(account, from_date, to_date, company))[0][0])
debit_note_amount = get_debit_note_amount([supplier], from_date, to_date, company=company)
date_range_filter = [fiscal_year, from_date, to_date]
debit_note_amount = get_debit_note_amount([supplier], date_range_filter, company=company)
total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount

View File

@ -897,18 +897,18 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
gle = get_voucherwise_gl_entries(stock_vouchers, posting_date)
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit")) or 2
gle = get_voucherwise_gl_entries(stock_vouchers, posting_date)
for voucher_type, voucher_no in stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
else:
@ -954,16 +954,17 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
return gl_entries
def compare_existing_and_expected_gle(existing_gle, expected_gle):
def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
matched = True
for entry in expected_gle:
account_existed = False
for e in existing_gle:
if entry.account == e.account:
account_existed = True
if entry.account == e.account and entry.against_account == e.against_account \
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \
and (entry.debit != e.debit or entry.credit != e.credit):
if (entry.account == e.account and entry.against_account == e.against_account
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
and ( flt(entry.debit, precision) != flt(e.debit, precision) or
flt(entry.credit, precision) != flt(e.credit, precision))):
matched = False
break
if not account_existed:

View File

@ -1061,7 +1061,7 @@
"type": "Link"
}
],
"modified": "2020-12-01 13:38:35.349024",
"modified": "2021-03-04 00:38:35.349024",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
@ -1071,7 +1071,7 @@
"pin_to_top": 0,
"shortcuts": [
{
"label": "Chart Of Accounts",
"label": "Chart of Accounts",
"link_to": "Account",
"type": "DocType"
},
@ -1116,4 +1116,4 @@
"type": "Dashboard"
}
]
}
}

View File

@ -19,7 +19,6 @@
],
"fields": [
{
"depends_on": "eval:!doc.asset_category_name",
"fieldname": "asset_category_name",
"fieldtype": "Data",
"in_list_view": 1,
@ -67,7 +66,7 @@
}
],
"links": [],
"modified": "2021-01-22 12:31:14.425319",
"modified": "2021-02-24 15:05:38.621803",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Category",

View File

@ -96,7 +96,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2020-10-13 12:00:23.276329",
"modified": "2021-03-02 17:34:04.190677",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
@ -113,5 +113,6 @@
}
],
"sort_field": "modified",
"sort_order": "DESC"
}
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -231,12 +231,12 @@ class TestPurchaseOrder(unittest.TestCase):
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
new_item_with_tax.append("taxes", {
"item_tax_template": "Test Update Items Template",
"item_tax_template": "Test Update Items Template - _TC",
"valid_from": nowdate()
})
new_item_with_tax.save()
tax_template = "_Test Account Excise Duty @ 10"
tax_template = "_Test Account Excise Duty @ 10 - _TC"
item = "_Test Item Home Desktop 100"
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
item_doc = frappe.get_doc("Item", item)
@ -287,7 +287,7 @@ class TestPurchaseOrder(unittest.TestCase):
po.cancel()
po.delete()
new_item_with_tax.delete()
frappe.get_doc("Item Tax Template", "Test Update Items Template").delete()
frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete()
def test_update_child_uom_conv_factor_change(self):
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")

View File

@ -27,11 +27,17 @@
"stock_qty",
"sec_break1",
"price_list_rate",
"last_purchase_rate",
"col_break3",
"base_price_list_rate",
"discount_and_margin_section",
"margin_type",
"margin_rate_or_amount",
"rate_with_margin",
"column_break_28",
"discount_percentage",
"discount_amount",
"col_break3",
"last_purchase_rate",
"base_price_list_rate",
"base_rate_with_margin",
"sec_break2",
"rate",
"amount",
@ -733,15 +739,59 @@
"fieldname": "stock_uom_rate",
"fieldtype": "Currency",
"label": "Rate of Stock UOM",
"no_copy": 1,
"options": "currency",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "discount_and_margin_section",
"fieldtype": "Section Break",
"label": "Discount and Margin"
},
{
"depends_on": "price_list_rate",
"fieldname": "margin_type",
"fieldtype": "Select",
"label": "Margin Type",
"options": "\nPercentage\nAmount",
"print_hide": 1
},
{
"depends_on": "eval:doc.margin_type && doc.price_list_rate",
"fieldname": "margin_rate_or_amount",
"fieldtype": "Float",
"label": "Margin Rate or Amount",
"print_hide": 1
},
{
"depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
"fieldname": "rate_with_margin",
"fieldtype": "Currency",
"label": "Rate With Margin",
"options": "currency",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "column_break_28",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
"fieldname": "base_rate_with_margin",
"fieldtype": "Currency",
"label": "Rate With Margin (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-01-30 21:44:41.816974",
"modified": "2021-02-23 01:00:27.132705",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

Some files were not shown because too many files have changed in this diff Show More