Merge branch 'develop' of https://github.com/frappe/erpnext into balancing-accounting-dimensions
This commit is contained in:
commit
f999b75ed6
65
.eslintrc
65
.eslintrc
@ -2,65 +2,32 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"node": true,
|
"node": true,
|
||||||
"es6": true
|
"es2022": true
|
||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 11,
|
|
||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
"extends": "eslint:recommended",
|
||||||
"rules": {
|
"rules": {
|
||||||
"indent": [
|
"indent": "off",
|
||||||
"error",
|
"brace-style": "off",
|
||||||
"tab",
|
"no-mixed-spaces-and-tabs": "off",
|
||||||
{ "SwitchCase": 1 }
|
"no-useless-escape": "off",
|
||||||
],
|
"space-unary-ops": ["error", { "words": true }],
|
||||||
"brace-style": [
|
"linebreak-style": "off",
|
||||||
"error",
|
"quotes": ["off"],
|
||||||
"1tbs"
|
"semi": "off",
|
||||||
],
|
"camelcase": "off",
|
||||||
"space-unary-ops": [
|
"no-unused-vars": "off",
|
||||||
"error",
|
"no-console": ["warn"],
|
||||||
{ "words": true }
|
"no-extra-boolean-cast": ["off"],
|
||||||
],
|
"no-control-regex": ["off"]
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"unix"
|
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"off"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"warn",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"camelcase": [
|
|
||||||
"off"
|
|
||||||
],
|
|
||||||
"no-unused-vars": [
|
|
||||||
"warn"
|
|
||||||
],
|
|
||||||
"no-redeclare": [
|
|
||||||
"warn"
|
|
||||||
],
|
|
||||||
"no-console": [
|
|
||||||
"warn"
|
|
||||||
],
|
|
||||||
"no-extra-boolean-cast": [
|
|
||||||
"off"
|
|
||||||
],
|
|
||||||
"no-control-regex": [
|
|
||||||
"off"
|
|
||||||
],
|
|
||||||
"space-before-blocks": "warn",
|
|
||||||
"keyword-spacing": "warn",
|
|
||||||
"comma-spacing": "warn",
|
|
||||||
"key-spacing": "warn"
|
|
||||||
},
|
},
|
||||||
"root": true,
|
"root": true,
|
||||||
"globals": {
|
"globals": {
|
||||||
"frappe": true,
|
"frappe": true,
|
||||||
"Vue": true,
|
"Vue": true,
|
||||||
|
"SetVueGlobals": true,
|
||||||
"erpnext": true,
|
"erpnext": true,
|
||||||
"hub": true,
|
"hub": true,
|
||||||
"$": true,
|
"$": true,
|
||||||
@ -97,8 +64,10 @@
|
|||||||
"is_null": true,
|
"is_null": true,
|
||||||
"in_list": true,
|
"in_list": true,
|
||||||
"has_common": true,
|
"has_common": true,
|
||||||
|
"posthog": true,
|
||||||
"has_words": true,
|
"has_words": true,
|
||||||
"validate_email": true,
|
"validate_email": true,
|
||||||
|
"open_web_template_values_editor": true,
|
||||||
"get_number_format": true,
|
"get_number_format": true,
|
||||||
"format_number": true,
|
"format_number": true,
|
||||||
"format_currency": true,
|
"format_currency": true,
|
||||||
|
9
.github/workflows/linters.yml
vendored
9
.github/workflows/linters.yml
vendored
@ -9,21 +9,22 @@ jobs:
|
|||||||
name: linters
|
name: linters
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python 3.10
|
- name: Set up Python 3.10
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
|
cache: pip
|
||||||
|
|
||||||
- name: Install and Run Pre-commit
|
- name: Install and Run Pre-commit
|
||||||
uses: pre-commit/action@v2.0.3
|
uses: pre-commit/action@v3.0.0
|
||||||
|
|
||||||
- name: Download Semgrep rules
|
- name: Download Semgrep rules
|
||||||
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
||||||
|
|
||||||
- name: Download semgrep
|
- name: Download semgrep
|
||||||
run: pip install semgrep==0.97.0
|
run: pip install semgrep
|
||||||
|
|
||||||
- name: Run Semgrep rules
|
- name: Run Semgrep rules
|
||||||
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
|
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
|
||||||
|
38
.github/workflows/release_notes.yml
vendored
Normal file
38
.github/workflows/release_notes.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# This action:
|
||||||
|
#
|
||||||
|
# 1. Generates release notes using github API.
|
||||||
|
# 2. Strips unnecessary info like chore/style etc from notes.
|
||||||
|
# 3. Updates release info.
|
||||||
|
|
||||||
|
# This action needs to be maintained on all branches that do releases.
|
||||||
|
|
||||||
|
name: 'Release Notes'
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag_name:
|
||||||
|
description: 'Tag of release like v13.0.0'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
regen-notes:
|
||||||
|
name: 'Regenerate release notes'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Update notes
|
||||||
|
run: |
|
||||||
|
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/generate-notes -f tag_name=$RELEASE_TAG | jq -r '.body' | sed -E '/^\* (chore|ci|test|docs|style)/d' )
|
||||||
|
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/tags/$RELEASE_TAG | jq -r '.id')
|
||||||
|
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/$RELEASE_ID -f body="$NEW_NOTES"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }}
|
8
.github/workflows/server-tests-mariadb.yml
vendored
8
.github/workflows/server-tests-mariadb.yml
vendored
@ -7,11 +7,9 @@ on:
|
|||||||
- '**.css'
|
- '**.css'
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '**.html'
|
- '**.html'
|
||||||
push:
|
schedule:
|
||||||
branches: [ develop ]
|
# Run everday at midnight UTC / 5:30 IST
|
||||||
paths-ignore:
|
- cron: "0 0 * * *"
|
||||||
- '**.js'
|
|
||||||
- '**.md'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
user:
|
user:
|
||||||
|
@ -16,8 +16,26 @@ repos:
|
|||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-ast
|
- id: check-ast
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||||
|
rev: v8.44.0
|
||||||
|
hooks:
|
||||||
|
- id: eslint
|
||||||
|
types_or: [javascript]
|
||||||
|
args: ['--quiet']
|
||||||
|
# Ignore any files that might contain jinja / bundles
|
||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
erpnext/public/dist/.*|
|
||||||
|
cypress/.*|
|
||||||
|
.*node_modules.*|
|
||||||
|
.*boilerplate.*|
|
||||||
|
erpnext/public/js/controllers/.*|
|
||||||
|
erpnext/templates/pages/order.js|
|
||||||
|
erpnext/templates/includes/.*
|
||||||
|
)$
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 5.0.4
|
rev: 6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies: [
|
additional_dependencies: [
|
||||||
|
@ -4,18 +4,19 @@
|
|||||||
"creation": "2020-07-17 11:25:34.593061",
|
"creation": "2020-07-17 11:25:34.593061",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Dashboard Chart",
|
"doctype": "Dashboard Chart",
|
||||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
|
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||||
"filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}",
|
"filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_public": 1,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-22 12:24:49.144210",
|
"modified": "2023-07-19 13:13:13.307073",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Budget Variance",
|
"name": "Budget Variance",
|
||||||
"number_of_groups": 0,
|
"number_of_groups": 0,
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"report_name": "Budget Variance Report",
|
"report_name": "Budget Variance Report",
|
||||||
|
"roles": [],
|
||||||
"timeseries": 0,
|
"timeseries": 0,
|
||||||
"type": "Bar",
|
"type": "Bar",
|
||||||
"use_report_chart": 1,
|
"use_report_chart": 1,
|
||||||
|
@ -4,18 +4,19 @@
|
|||||||
"creation": "2020-07-17 11:25:34.448572",
|
"creation": "2020-07-17 11:25:34.448572",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Dashboard Chart",
|
"doctype": "Dashboard Chart",
|
||||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
|
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||||
"filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}",
|
"filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_public": 1,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-22 12:33:48.888943",
|
"modified": "2023-07-19 13:08:56.470390",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Profit and Loss",
|
"name": "Profit and Loss",
|
||||||
"number_of_groups": 0,
|
"number_of_groups": 0,
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"report_name": "Profit and Loss Statement",
|
"report_name": "Profit and Loss Statement",
|
||||||
|
"roles": [],
|
||||||
"timeseries": 0,
|
"timeseries": 0,
|
||||||
"type": "Bar",
|
"type": "Bar",
|
||||||
"use_report_chart": 1,
|
"use_report_chart": 1,
|
||||||
|
@ -79,8 +79,8 @@ frappe.ui.form.on('Account', {
|
|||||||
frm.add_custom_button(__('General Ledger'), function () {
|
frm.add_custom_button(__('General Ledger'), function () {
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
"account": frm.doc.name,
|
"account": frm.doc.name,
|
||||||
"from_date": frappe.sys_defaults.year_start_date,
|
"from_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
"to_date": frappe.sys_defaults.year_end_date,
|
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||||
"company": frm.doc.company
|
"company": frm.doc.company
|
||||||
};
|
};
|
||||||
frappe.set_route("query-report", "General Ledger");
|
frappe.set_route("query-report", "General Ledger");
|
||||||
|
@ -194,8 +194,8 @@ frappe.treeview_settings["Account"] = {
|
|||||||
click: function(node, btn) {
|
click: function(node, btn) {
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
"account": node.label,
|
"account": node.label,
|
||||||
"from_date": frappe.sys_defaults.year_start_date,
|
"from_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
"to_date": frappe.sys_defaults.year_end_date,
|
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||||
"company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value()
|
"company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value()
|
||||||
};
|
};
|
||||||
frappe.set_route("query-report", "General Ledger");
|
frappe.set_route("query-report", "General Ledger");
|
||||||
|
@ -14,10 +14,8 @@ class AccountClosingBalance(Document):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def make_closing_entries(closing_entries, voucher_name):
|
def make_closing_entries(closing_entries, voucher_name, company, closing_date):
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
accounting_dimensions = get_accounting_dimensions()
|
||||||
company = closing_entries[0].get("company")
|
|
||||||
closing_date = closing_entries[0].get("closing_date")
|
|
||||||
|
|
||||||
previous_closing_entries = get_previous_closing_entries(
|
previous_closing_entries = get_previous_closing_entries(
|
||||||
company, closing_date, accounting_dimensions
|
company, closing_date, accounting_dimensions
|
||||||
|
@ -281,6 +281,12 @@ def get_dimensions(with_cost_center_and_project=False):
|
|||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(with_cost_center_and_project, str):
|
||||||
|
if with_cost_center_and_project.lower().strip() == "true":
|
||||||
|
with_cost_center_and_project = True
|
||||||
|
else:
|
||||||
|
with_cost_center_and_project = False
|
||||||
|
|
||||||
if with_cost_center_and_project:
|
if with_cost_center_and_project:
|
||||||
dimension_filters.extend(
|
dimension_filters.extend(
|
||||||
[
|
[
|
||||||
|
@ -20,5 +20,11 @@ frappe.ui.form.on('Accounting Period', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frm.set_query("document_type", "closed_documents", () => {
|
||||||
|
return {
|
||||||
|
query: "erpnext.controllers.queries.get_doctypes_for_closing",
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -11,6 +11,10 @@ class OverlapError(frappe.ValidationError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ClosedAccountingPeriod(frappe.ValidationError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AccountingPeriod(Document):
|
class AccountingPeriod(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_overlap()
|
self.validate_overlap()
|
||||||
@ -65,3 +69,42 @@ class AccountingPeriod(Document):
|
|||||||
"closed_documents",
|
"closed_documents",
|
||||||
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
|
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_accounting_period_on_doc_save(doc, method=None):
|
||||||
|
if doc.doctype == "Bank Clearance":
|
||||||
|
return
|
||||||
|
elif doc.doctype == "Asset":
|
||||||
|
if doc.is_existing_asset:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
date = doc.available_for_use_date
|
||||||
|
elif doc.doctype == "Asset Repair":
|
||||||
|
date = doc.completion_date
|
||||||
|
else:
|
||||||
|
date = doc.posting_date
|
||||||
|
|
||||||
|
ap = frappe.qb.DocType("Accounting Period")
|
||||||
|
cd = frappe.qb.DocType("Closed Document")
|
||||||
|
|
||||||
|
accounting_period = (
|
||||||
|
frappe.qb.from_(ap)
|
||||||
|
.from_(cd)
|
||||||
|
.select(ap.name)
|
||||||
|
.where(
|
||||||
|
(ap.name == cd.parent)
|
||||||
|
& (ap.company == doc.company)
|
||||||
|
& (cd.closed == 1)
|
||||||
|
& (cd.document_type == doc.doctype)
|
||||||
|
& (date >= ap.start_date)
|
||||||
|
& (date <= ap.end_date)
|
||||||
|
)
|
||||||
|
).run(as_dict=1)
|
||||||
|
|
||||||
|
if accounting_period:
|
||||||
|
frappe.throw(
|
||||||
|
_("You cannot create a {0} within the closed Accounting Period {1}").format(
|
||||||
|
doc.doctype, frappe.bold(accounting_period[0]["name"])
|
||||||
|
),
|
||||||
|
ClosedAccountingPeriod,
|
||||||
|
)
|
||||||
|
@ -6,9 +6,11 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import add_months, nowdate
|
from frappe.utils import add_months, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
|
from erpnext.accounts.doctype.accounting_period.accounting_period import (
|
||||||
|
ClosedAccountingPeriod,
|
||||||
|
OverlapError,
|
||||||
|
)
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
|
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
test_dependencies = ["Item"]
|
||||||
|
|
||||||
@ -33,9 +35,9 @@ class TestAccountingPeriod(unittest.TestCase):
|
|||||||
ap1.save()
|
ap1.save()
|
||||||
|
|
||||||
doc = create_sales_invoice(
|
doc = create_sales_invoice(
|
||||||
do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
|
do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
|
||||||
)
|
)
|
||||||
self.assertRaises(ClosedAccountingPeriod, doc.submit)
|
self.assertRaises(ClosedAccountingPeriod, doc.save)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for d in frappe.get_all("Accounting Period"):
|
for d in frappe.get_all("Accounting Period"):
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
"closing_settings_tab",
|
"closing_settings_tab",
|
||||||
"period_closing_settings_section",
|
"period_closing_settings_section",
|
||||||
"acc_frozen_upto",
|
"acc_frozen_upto",
|
||||||
|
"ignore_account_closing_balance",
|
||||||
"column_break_25",
|
"column_break_25",
|
||||||
"frozen_accounts_modifier",
|
"frozen_accounts_modifier",
|
||||||
"tab_break_dpet",
|
"tab_break_dpet",
|
||||||
@ -406,6 +407,13 @@
|
|||||||
"fieldname": "enable_fuzzy_matching",
|
"fieldname": "enable_fuzzy_matching",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Fuzzy Matching"
|
"label": "Enable Fuzzy Matching"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ",
|
||||||
|
"fieldname": "ignore_account_closing_balance",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Ignore Account Closing Balance"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@ -413,7 +421,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-15 16:35:45.123456",
|
"modified": "2023-07-27 15:05:34.000264",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
@ -14,21 +14,32 @@ from erpnext.stock.utils import check_pending_reposting
|
|||||||
|
|
||||||
|
|
||||||
class AccountsSettings(Document):
|
class AccountsSettings(Document):
|
||||||
def on_update(self):
|
|
||||||
frappe.clear_cache()
|
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
frappe.db.set_default(
|
old_doc = self.get_doc_before_save()
|
||||||
"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)
|
clear_cache = False
|
||||||
)
|
|
||||||
|
|
||||||
frappe.db.set_default(
|
if old_doc.add_taxes_from_item_tax_template != self.add_taxes_from_item_tax_template:
|
||||||
"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
|
frappe.db.set_default(
|
||||||
)
|
"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)
|
||||||
|
)
|
||||||
|
clear_cache = True
|
||||||
|
|
||||||
|
if old_doc.enable_common_party_accounting != self.enable_common_party_accounting:
|
||||||
|
frappe.db.set_default(
|
||||||
|
"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
|
||||||
|
)
|
||||||
|
clear_cache = True
|
||||||
|
|
||||||
self.validate_stale_days()
|
self.validate_stale_days()
|
||||||
self.enable_payment_schedule_in_print()
|
|
||||||
self.validate_pending_reposts()
|
if old_doc.show_payment_schedule_in_print != self.show_payment_schedule_in_print:
|
||||||
|
self.enable_payment_schedule_in_print()
|
||||||
|
|
||||||
|
if old_doc.acc_frozen_upto != self.acc_frozen_upto:
|
||||||
|
self.validate_pending_reposts()
|
||||||
|
|
||||||
|
if clear_cache:
|
||||||
|
frappe.clear_cache()
|
||||||
|
|
||||||
def validate_stale_days(self):
|
def validate_stale_days(self):
|
||||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||||
|
@ -102,7 +102,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onScriptLoaded(me) {
|
onScriptLoaded(me) {
|
||||||
me.linkHandler = Plaid.create({
|
me.linkHandler = Plaid.create({ // eslint-disable-line no-undef
|
||||||
env: me.plaid_env,
|
env: me.plaid_env,
|
||||||
token: me.token,
|
token: me.token,
|
||||||
onSuccess: me.plaid_success
|
onSuccess: me.plaid_success
|
||||||
|
@ -70,7 +70,7 @@ frappe.ui.form.on('Cost Center', {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
primary_action: function() {
|
primary_action: function() {
|
||||||
var data = d.get_values();
|
let data = d.get_values();
|
||||||
if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) {
|
if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) {
|
||||||
d.hide();
|
d.hide();
|
||||||
return;
|
return;
|
||||||
@ -91,8 +91,8 @@ frappe.ui.form.on('Cost Center', {
|
|||||||
if(r.message) {
|
if(r.message) {
|
||||||
frappe.set_route("Form", "Cost Center", r.message);
|
frappe.set_route("Form", "Cost Center", r.message);
|
||||||
} else {
|
} else {
|
||||||
me.frm.set_value("cost_center_name", data.cost_center_name);
|
frm.set_value("cost_center_name", data.cost_center_name);
|
||||||
me.frm.set_value("cost_center_number", data.cost_center_number);
|
frm.set_value("cost_center_number", data.cost_center_number);
|
||||||
}
|
}
|
||||||
d.hide();
|
d.hide();
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("Dunning", {
|
frappe.ui.form.on("Dunning", {
|
||||||
setup: function (frm) {
|
setup: function (frm) {
|
||||||
frm.set_query("sales_invoice", () => {
|
frm.set_query("sales_invoice", "overdue_payments", () => {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
company: frm.doc.company,
|
company: frm.doc.company,
|
||||||
|
customer: frm.doc.customer,
|
||||||
outstanding_amount: [">", 0],
|
outstanding_amount: [">", 0],
|
||||||
status: "Overdue"
|
status: "Overdue"
|
||||||
},
|
},
|
||||||
@ -22,14 +23,24 @@ frappe.ui.form.on("Dunning", {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
frm.set_query("cost_center", () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
is_group: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("contact_person", erpnext.queries.contact_query);
|
||||||
|
frm.set_query("customer_address", erpnext.queries.address_query);
|
||||||
|
frm.set_query("company_address", erpnext.queries.company_address_query);
|
||||||
|
|
||||||
|
// cannot add rows manually, only via button "Fetch Overdue Payments"
|
||||||
|
frm.set_df_property("overdue_payments", "cannot_add_rows", true);
|
||||||
},
|
},
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1);
|
frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1);
|
||||||
frm.set_df_property(
|
|
||||||
"sales_invoice",
|
|
||||||
"read_only",
|
|
||||||
frm.doc.__islocal ? 0 : 1
|
|
||||||
);
|
|
||||||
if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") {
|
if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") {
|
||||||
frm.add_custom_button(__("Resolve"), () => {
|
frm.add_custom_button(__("Resolve"), () => {
|
||||||
frm.set_value("status", "Resolved");
|
frm.set_value("status", "Resolved");
|
||||||
@ -40,42 +51,111 @@ frappe.ui.form.on("Dunning", {
|
|||||||
__("Payment"),
|
__("Payment"),
|
||||||
function () {
|
function () {
|
||||||
frm.events.make_payment_entry(frm);
|
frm.events.make_payment_entry(frm);
|
||||||
},__("Create")
|
}, __("Create")
|
||||||
);
|
);
|
||||||
frm.page.set_inner_btn_group_as_primary(__("Create"));
|
frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(frm.doc.docstatus > 0) {
|
if (frm.doc.docstatus === 0) {
|
||||||
frm.add_custom_button(__('Ledger'), function() {
|
frm.add_custom_button(__("Fetch Overdue Payments"), () => {
|
||||||
frappe.route_options = {
|
erpnext.utils.map_current_doc({
|
||||||
"voucher_no": frm.doc.name,
|
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
|
||||||
"from_date": frm.doc.posting_date,
|
source_doctype: "Sales Invoice",
|
||||||
"to_date": frm.doc.posting_date,
|
date_field: "due_date",
|
||||||
"company": frm.doc.company,
|
target: frm,
|
||||||
"show_cancelled_entries": frm.doc.docstatus === 2
|
setters: {
|
||||||
};
|
customer: frm.doc.customer || undefined,
|
||||||
frappe.set_route("query-report", "General Ledger");
|
},
|
||||||
}, __('View'));
|
get_query_filters: {
|
||||||
|
docstatus: 1,
|
||||||
|
status: "Overdue",
|
||||||
|
company: frm.doc.company
|
||||||
|
},
|
||||||
|
allow_child_item_selection: true,
|
||||||
|
child_fieldname: "payment_schedule",
|
||||||
|
child_columns: ["due_date", "outstanding"],
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frappe.dynamic_link = { doc: frm.doc, fieldname: 'customer', doctype: 'Customer' };
|
||||||
|
|
||||||
|
frm.toggle_display("customer_name", (frm.doc.customer_name && frm.doc.customer_name !== frm.doc.customer));
|
||||||
},
|
},
|
||||||
overdue_days: function (frm) {
|
// When multiple companies are set up. in case company name is changed set default company address
|
||||||
frappe.db.get_value(
|
company: function (frm) {
|
||||||
"Dunning Type",
|
if (frm.doc.company) {
|
||||||
{
|
frappe.call({
|
||||||
start_day: ["<", frm.doc.overdue_days],
|
method: "erpnext.setup.doctype.company.company.get_default_company_address",
|
||||||
end_day: [">=", frm.doc.overdue_days],
|
args: { name: frm.doc.company, existing_address: frm.doc.company_address || "" },
|
||||||
},
|
debounce: 2000,
|
||||||
"dunning_type",
|
callback: function (r) {
|
||||||
(r) => {
|
frm.set_value("company_address", r && r.message || "");
|
||||||
if (r) {
|
}
|
||||||
frm.set_value("dunning_type", r.dunning_type);
|
});
|
||||||
} else {
|
|
||||||
frm.set_value("dunning_type", "");
|
if (frm.fields_dict.currency) {
|
||||||
frm.set_value("rate_of_interest", "");
|
const company_currency = erpnext.get_currency(frm.doc.company);
|
||||||
frm.set_value("dunning_fee", "");
|
|
||||||
|
if (!frm.doc.currency) {
|
||||||
|
frm.set_value("currency", company_currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frm.doc.currency == company_currency) {
|
||||||
|
frm.set_value("conversion_rate", 1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
const company_doc = frappe.get_doc(":Company", frm.doc.company);
|
||||||
|
if (company_doc.default_letter_head) {
|
||||||
|
if (frm.fields_dict.letter_head) {
|
||||||
|
frm.set_value("letter_head", company_doc.default_letter_head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currency: function (frm) {
|
||||||
|
// this.set_dynamic_labels();
|
||||||
|
const company_currency = erpnext.get_currency(frm.doc.company);
|
||||||
|
// Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
|
||||||
|
if (frm.doc.currency && frm.doc.currency !== company_currency) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.setup.utils.get_exchange_rate",
|
||||||
|
args: {
|
||||||
|
transaction_date: frm.doc.posting_date,
|
||||||
|
from_currency: frm.doc.currency,
|
||||||
|
to_currency: company_currency,
|
||||||
|
args: "for_selling"
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __("Fetching exchange rates ..."),
|
||||||
|
callback: function(r) {
|
||||||
|
const exchange_rate = flt(r.message);
|
||||||
|
if (exchange_rate != frm.doc.conversion_rate) {
|
||||||
|
frm.set_value("conversion_rate", exchange_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
frm.trigger("conversion_rate");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customer: (frm) => {
|
||||||
|
erpnext.utils.get_party_details(frm);
|
||||||
|
},
|
||||||
|
conversion_rate: function (frm) {
|
||||||
|
if (frm.doc.currency === erpnext.get_currency(frm.doc.company)) {
|
||||||
|
frm.set_value("conversion_rate", 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make read only if Accounts Settings doesn't allow stale rates
|
||||||
|
frm.set_df_property("conversion_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
|
||||||
|
},
|
||||||
|
customer_address: function (frm) {
|
||||||
|
erpnext.utils.get_address_display(frm, "customer_address");
|
||||||
|
},
|
||||||
|
company_address: function (frm) {
|
||||||
|
erpnext.utils.get_address_display(frm, "company_address");
|
||||||
},
|
},
|
||||||
dunning_type: function (frm) {
|
dunning_type: function (frm) {
|
||||||
frm.trigger("get_dunning_letter_text");
|
frm.trigger("get_dunning_letter_text");
|
||||||
@ -87,7 +167,7 @@ frappe.ui.form.on("Dunning", {
|
|||||||
if (frm.doc.dunning_type) {
|
if (frm.doc.dunning_type) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method:
|
method:
|
||||||
"erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text",
|
"erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text",
|
||||||
args: {
|
args: {
|
||||||
dunning_type: frm.doc.dunning_type,
|
dunning_type: frm.doc.dunning_type,
|
||||||
language: frm.doc.language,
|
language: frm.doc.language,
|
||||||
@ -106,49 +186,62 @@ frappe.ui.form.on("Dunning", {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
due_date: function (frm) {
|
|
||||||
frm.trigger("calculate_overdue_days");
|
|
||||||
},
|
|
||||||
posting_date: function (frm) {
|
posting_date: function (frm) {
|
||||||
frm.trigger("calculate_overdue_days");
|
frm.trigger("calculate_overdue_days");
|
||||||
},
|
},
|
||||||
rate_of_interest: function (frm) {
|
rate_of_interest: function (frm) {
|
||||||
frm.trigger("calculate_interest_and_amount");
|
frm.trigger("calculate_interest");
|
||||||
},
|
|
||||||
outstanding_amount: function (frm) {
|
|
||||||
frm.trigger("calculate_interest_and_amount");
|
|
||||||
},
|
|
||||||
interest_amount: function (frm) {
|
|
||||||
frm.trigger("calculate_interest_and_amount");
|
|
||||||
},
|
},
|
||||||
dunning_fee: function (frm) {
|
dunning_fee: function (frm) {
|
||||||
frm.trigger("calculate_interest_and_amount");
|
frm.trigger("calculate_totals");
|
||||||
},
|
},
|
||||||
sales_invoice: function (frm) {
|
overdue_payments_add: function (frm) {
|
||||||
frm.trigger("calculate_overdue_days");
|
frm.trigger("calculate_totals");
|
||||||
|
},
|
||||||
|
overdue_payments_remove: function (frm) {
|
||||||
|
frm.trigger("calculate_totals");
|
||||||
},
|
},
|
||||||
calculate_overdue_days: function (frm) {
|
calculate_overdue_days: function (frm) {
|
||||||
if (frm.doc.posting_date && frm.doc.due_date) {
|
frm.doc.overdue_payments.forEach((row) => {
|
||||||
const overdue_days = moment(frm.doc.posting_date).diff(
|
if (frm.doc.posting_date && row.due_date) {
|
||||||
frm.doc.due_date,
|
const overdue_days = moment(frm.doc.posting_date).diff(
|
||||||
"days"
|
row.due_date,
|
||||||
);
|
"days"
|
||||||
frm.set_value("overdue_days", overdue_days);
|
);
|
||||||
}
|
frappe.model.set_value(row.doctype, row.name, "overdue_days", overdue_days);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
calculate_interest_and_amount: function (frm) {
|
calculate_interest: function (frm) {
|
||||||
const interest_per_year = frm.doc.outstanding_amount * frm.doc.rate_of_interest / 100;
|
frm.doc.overdue_payments.forEach((row) => {
|
||||||
const interest_amount = flt((interest_per_year * cint(frm.doc.overdue_days)) / 365 || 0, precision('interest_amount'));
|
const interest_per_day = frm.doc.rate_of_interest / 100 / 365;
|
||||||
const dunning_amount = flt(interest_amount + frm.doc.dunning_fee, precision('dunning_amount'));
|
const interest = flt((interest_per_day * row.overdue_days * row.outstanding), precision("interest", row));
|
||||||
const grand_total = flt(frm.doc.outstanding_amount + dunning_amount, precision('grand_total'));
|
frappe.model.set_value(row.doctype, row.name, "interest", interest);
|
||||||
frm.set_value("interest_amount", interest_amount);
|
});
|
||||||
frm.set_value("dunning_amount", dunning_amount);
|
},
|
||||||
frm.set_value("grand_total", grand_total);
|
calculate_totals: function (frm) {
|
||||||
|
const total_interest = frm.doc.overdue_payments
|
||||||
|
.reduce((prev, cur) => prev + cur.interest, 0);
|
||||||
|
const total_outstanding = frm.doc.overdue_payments
|
||||||
|
.reduce((prev, cur) => prev + cur.outstanding, 0);
|
||||||
|
const dunning_amount = total_interest + frm.doc.dunning_fee;
|
||||||
|
const base_dunning_amount = dunning_amount * frm.doc.conversion_rate;
|
||||||
|
const grand_total = total_outstanding + dunning_amount;
|
||||||
|
|
||||||
|
function setWithPrecison(field, value) {
|
||||||
|
frm.set_value(field, flt(value, precision(field)));
|
||||||
|
}
|
||||||
|
|
||||||
|
setWithPrecison("total_outstanding", total_outstanding);
|
||||||
|
setWithPrecison("total_interest", total_interest);
|
||||||
|
setWithPrecison("dunning_amount", dunning_amount);
|
||||||
|
setWithPrecison("base_dunning_amount", base_dunning_amount);
|
||||||
|
setWithPrecison("grand_total", grand_total);
|
||||||
},
|
},
|
||||||
make_payment_entry: function (frm) {
|
make_payment_entry: function (frm) {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
method:
|
method:
|
||||||
"erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry",
|
"erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry",
|
||||||
args: {
|
args: {
|
||||||
dt: frm.doc.doctype,
|
dt: frm.doc.doctype,
|
||||||
dn: frm.doc.name,
|
dn: frm.doc.name,
|
||||||
@ -160,3 +253,9 @@ frappe.ui.form.on("Dunning", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Overdue Payment", {
|
||||||
|
interest: function (frm) {
|
||||||
|
frm.trigger("calculate_totals");
|
||||||
|
}
|
||||||
|
});
|
@ -2,49 +2,60 @@
|
|||||||
"actions": [],
|
"actions": [],
|
||||||
"allow_events_in_timeline": 1,
|
"allow_events_in_timeline": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
|
"beta": 1,
|
||||||
"creation": "2019-07-05 16:34:31.013238",
|
"creation": "2019-07-05 16:34:31.013238",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"title",
|
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"sales_invoice",
|
|
||||||
"customer",
|
"customer",
|
||||||
"customer_name",
|
"customer_name",
|
||||||
"outstanding_amount",
|
|
||||||
"currency",
|
|
||||||
"conversion_rate",
|
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"company",
|
"company",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"posting_time",
|
"posting_time",
|
||||||
"due_date",
|
"status",
|
||||||
"overdue_days",
|
"section_break_9",
|
||||||
|
"currency",
|
||||||
|
"column_break_11",
|
||||||
|
"conversion_rate",
|
||||||
"address_and_contact_section",
|
"address_and_contact_section",
|
||||||
|
"customer_address",
|
||||||
"address_display",
|
"address_display",
|
||||||
|
"contact_person",
|
||||||
"contact_display",
|
"contact_display",
|
||||||
|
"column_break_16",
|
||||||
|
"company_address",
|
||||||
|
"company_address_display",
|
||||||
"contact_mobile",
|
"contact_mobile",
|
||||||
"contact_email",
|
"contact_email",
|
||||||
"column_break_18",
|
|
||||||
"company_address_display",
|
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"dunning_type",
|
"dunning_type",
|
||||||
"dunning_fee",
|
|
||||||
"column_break_8",
|
"column_break_8",
|
||||||
"rate_of_interest",
|
"rate_of_interest",
|
||||||
"interest_amount",
|
|
||||||
"section_break_12",
|
"section_break_12",
|
||||||
"dunning_amount",
|
"overdue_payments",
|
||||||
"grand_total",
|
"section_break_28",
|
||||||
"income_account",
|
"total_interest",
|
||||||
|
"dunning_fee",
|
||||||
"column_break_17",
|
"column_break_17",
|
||||||
"status",
|
"dunning_amount",
|
||||||
"printing_setting_section",
|
"base_dunning_amount",
|
||||||
|
"section_break_32",
|
||||||
|
"spacer",
|
||||||
|
"column_break_33",
|
||||||
|
"total_outstanding",
|
||||||
|
"grand_total",
|
||||||
|
"printing_settings_section",
|
||||||
"language",
|
"language",
|
||||||
"body_text",
|
"body_text",
|
||||||
"column_break_22",
|
"column_break_22",
|
||||||
"letter_head",
|
"letter_head",
|
||||||
"closing_text",
|
"closing_text",
|
||||||
|
"accounting_details_section",
|
||||||
|
"income_account",
|
||||||
|
"column_break_48",
|
||||||
|
"cost_center",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -60,32 +71,17 @@
|
|||||||
"fieldname": "naming_series",
|
"fieldname": "naming_series",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Series",
|
"label": "Series",
|
||||||
"options": "DUNN-.MM.-.YY.-"
|
"options": "DUNN-.MM.-.YY.-",
|
||||||
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "sales_invoice",
|
"fetch_from": "customer.customer_name",
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Sales Invoice",
|
|
||||||
"options": "Sales Invoice",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "sales_invoice.customer_name",
|
|
||||||
"fieldname": "customer_name",
|
"fieldname": "customer_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Customer Name",
|
"label": "Customer Name",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fetch_from": "sales_invoice.outstanding_amount",
|
|
||||||
"fieldname": "outstanding_amount",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Outstanding Amount",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
@ -94,13 +90,8 @@
|
|||||||
"default": "Today",
|
"default": "Today",
|
||||||
"fieldname": "posting_date",
|
"fieldname": "posting_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Date"
|
"label": "Date",
|
||||||
},
|
"reqd": 1
|
||||||
{
|
|
||||||
"fieldname": "overdue_days",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"label": "Overdue Days",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_6",
|
"fieldname": "section_break_6",
|
||||||
@ -112,16 +103,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Dunning Type",
|
"label": "Dunning Type",
|
||||||
"options": "Dunning Type",
|
"options": "Dunning Type"
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "interest_amount",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Interest Amount",
|
|
||||||
"precision": "2",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_8",
|
"fieldname": "column_break_8",
|
||||||
@ -134,6 +116,7 @@
|
|||||||
"fieldname": "dunning_fee",
|
"fieldname": "dunning_fee",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Dunning Fee",
|
"label": "Dunning Fee",
|
||||||
|
"options": "currency",
|
||||||
"precision": "2"
|
"precision": "2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -144,36 +127,24 @@
|
|||||||
"fieldname": "column_break_17",
|
"fieldname": "column_break_17",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "printing_setting_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Printing Setting"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "language",
|
"fieldname": "language",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Print Language",
|
"label": "Print Language",
|
||||||
"options": "Language"
|
"options": "Language",
|
||||||
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "letter_head",
|
"fieldname": "letter_head",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Letter Head",
|
"label": "Letter Head",
|
||||||
"options": "Letter Head"
|
"options": "Letter Head",
|
||||||
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_22",
|
"fieldname": "column_break_22",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fetch_from": "sales_invoice.currency",
|
|
||||||
"fieldname": "currency",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Currency",
|
|
||||||
"options": "Currency",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -183,14 +154,6 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"default": "{customer_name}",
|
|
||||||
"fieldname": "title",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Title"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "body_text",
|
"fieldname": "body_text",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
@ -201,13 +164,6 @@
|
|||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"label": "Closing Text"
|
"label": "Closing Text"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fetch_from": "sales_invoice.due_date",
|
|
||||||
"fieldname": "due_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"label": "Due Date",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "posting_time",
|
"fieldname": "posting_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
@ -222,26 +178,24 @@
|
|||||||
"label": "Rate of Interest (%) Yearly"
|
"label": "Rate of Interest (%) Yearly"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"collapsible": 1,
|
||||||
"fieldname": "address_and_contact_section",
|
"fieldname": "address_and_contact_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Address and Contact"
|
"label": "Address and Contact"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.address_display",
|
|
||||||
"fieldname": "address_display",
|
"fieldname": "address_display",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Address",
|
"label": "Address",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.contact_display",
|
|
||||||
"fieldname": "contact_display",
|
"fieldname": "contact_display",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Contact",
|
"label": "Contact",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.contact_mobile",
|
|
||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
@ -249,18 +203,12 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_18",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "sales_invoice.company_address_display",
|
|
||||||
"fieldname": "company_address_display",
|
"fieldname": "company_address_display",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Company Address",
|
"label": "Company Address Display",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.contact_email",
|
|
||||||
"fieldname": "contact_email",
|
"fieldname": "contact_email",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Contact Email",
|
"label": "Contact Email",
|
||||||
@ -268,18 +216,18 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.customer",
|
|
||||||
"fieldname": "customer",
|
"fieldname": "customer",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Customer",
|
"label": "Customer",
|
||||||
"options": "Customer",
|
"options": "Customer",
|
||||||
"read_only": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "grand_total",
|
"fieldname": "grand_total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Grand Total",
|
"label": "Grand Total",
|
||||||
|
"options": "currency",
|
||||||
"precision": "2",
|
"precision": "2",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -290,33 +238,150 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "Draft\nResolved\nUnresolved\nCancelled"
|
"options": "Draft\nResolved\nUnresolved\nCancelled",
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "dunning_amount",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Dunning Amount",
|
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "For dunning fee and interest",
|
||||||
|
"fetch_from": "dunning_type.income_account",
|
||||||
"fieldname": "income_account",
|
"fieldname": "income_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Income Account",
|
"label": "Income Account",
|
||||||
"options": "Account"
|
"options": "Account",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "overdue_payments",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Overdue Payments",
|
||||||
|
"options": "Overdue Payment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_28",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "total_interest",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Interest",
|
||||||
|
"options": "currency",
|
||||||
|
"precision": "2",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_outstanding",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Outstanding",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Customer Address",
|
||||||
|
"options": "Address",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "contact_person",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Contact Person",
|
||||||
|
"options": "Contact",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "dunning_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Dunning Amount",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "accounting_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "dunning_type.cost_center",
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "printing_settings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Printing Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_32",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_33",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "spacer",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Spacer",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"report_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_16",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company Address",
|
||||||
|
"options": "Address",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_9",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Currency",
|
||||||
|
"options": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_11",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.conversion_rate",
|
|
||||||
"fieldname": "conversion_rate",
|
"fieldname": "conversion_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 1,
|
"label": "Conversion Rate"
|
||||||
"label": "Conversion Rate",
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "base_dunning_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Dunning Amount (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_48",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-03 16:24:01.677026",
|
"modified": "2023-06-15 15:46:53.865712",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Dunning",
|
"name": "Dunning",
|
||||||
|
@ -1,131 +1,150 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
"""
|
||||||
|
# Accounting
|
||||||
|
|
||||||
|
1. Payment of outstanding invoices with dunning amount
|
||||||
|
|
||||||
|
- Debit full amount to bank
|
||||||
|
- Credit invoiced amount to receivables
|
||||||
|
- Credit dunning amount to interest and similar revenue
|
||||||
|
|
||||||
|
-> Resolves dunning automatically
|
||||||
|
"""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import cint, flt, getdate
|
from frappe import _
|
||||||
|
from frappe.contacts.doctype.address.address import get_address_display
|
||||||
|
from frappe.utils import getdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|
||||||
get_accounting_dimensions,
|
|
||||||
)
|
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
|
|
||||||
|
|
||||||
class Dunning(AccountsController):
|
class Dunning(AccountsController):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_overdue_days()
|
self.validate_same_currency()
|
||||||
self.validate_amount()
|
self.validate_overdue_payments()
|
||||||
if not self.income_account:
|
self.validate_totals()
|
||||||
self.income_account = frappe.get_cached_value("Company", self.company, "default_income_account")
|
self.set_party_details()
|
||||||
|
self.set_dunning_level()
|
||||||
|
|
||||||
def validate_overdue_days(self):
|
def validate_same_currency(self):
|
||||||
self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0
|
"""
|
||||||
|
Throw an error if invoice currency differs from dunning currency.
|
||||||
|
"""
|
||||||
|
for row in self.overdue_payments:
|
||||||
|
invoice_currency = frappe.get_value("Sales Invoice", row.sales_invoice, "currency")
|
||||||
|
if invoice_currency != self.currency:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"The currency of invoice {} ({}) is different from the currency of this dunning ({})."
|
||||||
|
).format(row.sales_invoice, invoice_currency, self.currency)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_amount(self):
|
def validate_overdue_payments(self):
|
||||||
amounts = calculate_interest_and_amount(
|
daily_interest = self.rate_of_interest / 100 / 365
|
||||||
self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days
|
|
||||||
|
for row in self.overdue_payments:
|
||||||
|
row.overdue_days = (getdate(self.posting_date) - getdate(row.due_date)).days or 0
|
||||||
|
row.interest = row.outstanding * daily_interest * row.overdue_days
|
||||||
|
|
||||||
|
def validate_totals(self):
|
||||||
|
self.total_outstanding = sum(row.outstanding for row in self.overdue_payments)
|
||||||
|
self.total_interest = sum(row.interest for row in self.overdue_payments)
|
||||||
|
self.dunning_amount = self.total_interest + self.dunning_fee
|
||||||
|
self.base_dunning_amount = self.dunning_amount * self.conversion_rate
|
||||||
|
self.grand_total = self.total_outstanding + self.dunning_amount
|
||||||
|
|
||||||
|
def set_party_details(self):
|
||||||
|
from erpnext.accounts.party import _get_party_details
|
||||||
|
|
||||||
|
party_details = _get_party_details(
|
||||||
|
self.customer,
|
||||||
|
ignore_permissions=self.flags.ignore_permissions,
|
||||||
|
doctype=self.doctype,
|
||||||
|
company=self.company,
|
||||||
|
posting_date=self.get("posting_date"),
|
||||||
|
fetch_payment_terms_template=False,
|
||||||
|
party_address=self.customer_address,
|
||||||
|
company_address=self.get("company_address"),
|
||||||
)
|
)
|
||||||
if self.interest_amount != amounts.get("interest_amount"):
|
for field in [
|
||||||
self.interest_amount = flt(amounts.get("interest_amount"), self.precision("interest_amount"))
|
"customer_address",
|
||||||
if self.dunning_amount != amounts.get("dunning_amount"):
|
"address_display",
|
||||||
self.dunning_amount = flt(amounts.get("dunning_amount"), self.precision("dunning_amount"))
|
"company_address",
|
||||||
if self.grand_total != amounts.get("grand_total"):
|
"contact_person",
|
||||||
self.grand_total = flt(amounts.get("grand_total"), self.precision("grand_total"))
|
"contact_display",
|
||||||
|
"contact_mobile",
|
||||||
|
]:
|
||||||
|
self.set(field, party_details.get(field))
|
||||||
|
|
||||||
def on_submit(self):
|
self.set("company_address_display", get_address_display(self.company_address))
|
||||||
self.make_gl_entries()
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def set_dunning_level(self):
|
||||||
if self.dunning_amount:
|
for row in self.overdue_payments:
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
past_dunnings = frappe.get_all(
|
||||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
"Overdue Payment",
|
||||||
|
filters={
|
||||||
def make_gl_entries(self):
|
"payment_schedule": row.payment_schedule,
|
||||||
if not self.dunning_amount:
|
"parent": ("!=", row.parent),
|
||||||
return
|
"docstatus": 1,
|
||||||
gl_entries = []
|
|
||||||
invoice_fields = [
|
|
||||||
"project",
|
|
||||||
"cost_center",
|
|
||||||
"debit_to",
|
|
||||||
"party_account_currency",
|
|
||||||
"conversion_rate",
|
|
||||||
"cost_center",
|
|
||||||
]
|
|
||||||
inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1)
|
|
||||||
|
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
|
||||||
invoice_fields.extend(accounting_dimensions)
|
|
||||||
|
|
||||||
dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate)
|
|
||||||
default_cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
|
|
||||||
|
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": inv.debit_to,
|
|
||||||
"party_type": "Customer",
|
|
||||||
"party": self.customer,
|
|
||||||
"due_date": self.due_date,
|
|
||||||
"against": self.income_account,
|
|
||||||
"debit": dunning_in_company_currency,
|
|
||||||
"debit_in_account_currency": self.dunning_amount,
|
|
||||||
"against_voucher": self.name,
|
|
||||||
"against_voucher_type": "Dunning",
|
|
||||||
"cost_center": inv.cost_center or default_cost_center,
|
|
||||||
"project": inv.project,
|
|
||||||
},
|
},
|
||||||
inv.party_account_currency,
|
|
||||||
item=inv,
|
|
||||||
)
|
)
|
||||||
)
|
row.dunning_level = len(past_dunnings) + 1
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": self.income_account,
|
|
||||||
"against": self.customer,
|
|
||||||
"credit": dunning_in_company_currency,
|
|
||||||
"cost_center": inv.cost_center or default_cost_center,
|
|
||||||
"credit_in_account_currency": self.dunning_amount,
|
|
||||||
"project": inv.project,
|
|
||||||
},
|
|
||||||
item=inv,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
make_gl_entries(
|
|
||||||
gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_dunning(doc, state):
|
def resolve_dunning(doc, state):
|
||||||
|
"""
|
||||||
|
Check if all payments have been made and resolve dunning, if yes. Called
|
||||||
|
when a Payment Entry is submitted.
|
||||||
|
"""
|
||||||
for reference in doc.references:
|
for reference in doc.references:
|
||||||
if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0:
|
# Consider partial and full payments:
|
||||||
dunnings = frappe.get_list(
|
# Submitting full payment: outstanding_amount will be 0
|
||||||
"Dunning",
|
# Submitting 1st partial payment: outstanding_amount will be the pending installment
|
||||||
filters={"sales_invoice": reference.reference_name, "status": ("!=", "Resolved")},
|
# Cancelling full payment: outstanding_amount will revert to total amount
|
||||||
ignore_permissions=True,
|
# Cancelling last partial payment: outstanding_amount will revert to pending amount
|
||||||
)
|
submit_condition = reference.outstanding_amount < reference.total_amount
|
||||||
|
cancel_condition = reference.outstanding_amount <= reference.total_amount
|
||||||
|
|
||||||
|
if reference.reference_doctype == "Sales Invoice" and (
|
||||||
|
submit_condition if doc.docstatus == 1 else cancel_condition
|
||||||
|
):
|
||||||
|
state = "Resolved" if doc.docstatus == 2 else "Unresolved"
|
||||||
|
dunnings = get_linked_dunnings_as_per_state(reference.reference_name, state)
|
||||||
|
|
||||||
for dunning in dunnings:
|
for dunning in dunnings:
|
||||||
frappe.db.set_value("Dunning", dunning.name, "status", "Resolved")
|
resolve = True
|
||||||
|
dunning = frappe.get_doc("Dunning", dunning.get("name"))
|
||||||
|
for overdue_payment in dunning.overdue_payments:
|
||||||
|
outstanding_inv = frappe.get_value(
|
||||||
|
"Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount"
|
||||||
|
)
|
||||||
|
outstanding_ps = frappe.get_value(
|
||||||
|
"Payment Schedule", overdue_payment.payment_schedule, "outstanding"
|
||||||
|
)
|
||||||
|
resolve = False if (outstanding_ps > 0 and outstanding_inv > 0) else True
|
||||||
|
|
||||||
|
dunning.status = "Resolved" if resolve else "Unresolved"
|
||||||
|
dunning.save()
|
||||||
|
|
||||||
|
|
||||||
def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
|
def get_linked_dunnings_as_per_state(sales_invoice, state):
|
||||||
interest_amount = 0
|
dunning = frappe.qb.DocType("Dunning")
|
||||||
grand_total = flt(outstanding_amount) + flt(dunning_fee)
|
overdue_payment = frappe.qb.DocType("Overdue Payment")
|
||||||
if rate_of_interest:
|
|
||||||
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
|
return (
|
||||||
interest_amount = (interest_per_year * cint(overdue_days)) / 365
|
frappe.qb.from_(dunning)
|
||||||
grand_total += flt(interest_amount)
|
.join(overdue_payment)
|
||||||
dunning_amount = flt(interest_amount) + flt(dunning_fee)
|
.on(overdue_payment.parent == dunning.name)
|
||||||
return {
|
.select(dunning.name)
|
||||||
"interest_amount": interest_amount,
|
.where(
|
||||||
"grand_total": grand_total,
|
(dunning.status == state)
|
||||||
"dunning_amount": dunning_amount,
|
& (dunning.docstatus != 2)
|
||||||
}
|
& (overdue_payment.sales_invoice == sales_invoice)
|
||||||
|
)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
from frappe import _
|
|
||||||
|
|
||||||
|
|
||||||
def get_data():
|
|
||||||
return {
|
|
||||||
"fieldname": "dunning",
|
|
||||||
"non_standard_fieldnames": {
|
|
||||||
"Journal Entry": "reference_name",
|
|
||||||
"Payment Entry": "reference_name",
|
|
||||||
},
|
|
||||||
"transactions": [{"label": _("Payment"), "items": ["Payment Entry", "Journal Entry"]}],
|
|
||||||
}
|
|
@ -1,162 +1,197 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_days, nowdate, today
|
from frappe.utils import add_days, nowdate, today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.dunning.dunning import calculate_interest_and_amount
|
from erpnext import get_default_cost_center
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
|
||||||
unlink_payment_on_cancel_of_invoice,
|
unlink_payment_on_cancel_of_invoice,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||||
|
create_dunning as create_dunning_from_sales_invoice,
|
||||||
|
)
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
|
||||||
create_sales_invoice_against_cost_center,
|
create_sales_invoice_against_cost_center,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test_dependencies = ["Company", "Cost Center"]
|
||||||
|
|
||||||
class TestDunning(unittest.TestCase):
|
|
||||||
|
class TestDunning(FrappeTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(cls):
|
||||||
create_dunning_type()
|
super().setUpClass()
|
||||||
create_dunning_type_with_zero_interest_rate()
|
create_dunning_type("First Notice", fee=0.0, interest=0.0, is_default=1)
|
||||||
|
create_dunning_type("Second Notice", fee=10.0, interest=10.0, is_default=0)
|
||||||
unlink_payment_on_cancel_of_invoice()
|
unlink_payment_on_cancel_of_invoice()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(self):
|
def tearDownClass(cls):
|
||||||
unlink_payment_on_cancel_of_invoice(0)
|
unlink_payment_on_cancel_of_invoice(0)
|
||||||
|
super().tearDownClass()
|
||||||
|
|
||||||
def test_dunning(self):
|
def test_dunning_without_fees(self):
|
||||||
dunning = create_dunning()
|
dunning = create_dunning(overdue_days=20)
|
||||||
amounts = calculate_interest_and_amount(
|
|
||||||
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
|
|
||||||
)
|
|
||||||
self.assertEqual(round(amounts.get("interest_amount"), 2), 0.44)
|
|
||||||
self.assertEqual(round(amounts.get("dunning_amount"), 2), 20.44)
|
|
||||||
self.assertEqual(round(amounts.get("grand_total"), 2), 120.44)
|
|
||||||
|
|
||||||
def test_dunning_with_zero_interest_rate(self):
|
self.assertEqual(round(dunning.total_outstanding, 2), 100.00)
|
||||||
dunning = create_dunning_with_zero_interest_rate()
|
self.assertEqual(round(dunning.total_interest, 2), 0.00)
|
||||||
amounts = calculate_interest_and_amount(
|
self.assertEqual(round(dunning.dunning_fee, 2), 0.00)
|
||||||
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
|
self.assertEqual(round(dunning.dunning_amount, 2), 0.00)
|
||||||
)
|
self.assertEqual(round(dunning.grand_total, 2), 100.00)
|
||||||
self.assertEqual(round(amounts.get("interest_amount"), 2), 0)
|
|
||||||
self.assertEqual(round(amounts.get("dunning_amount"), 2), 20)
|
|
||||||
self.assertEqual(round(amounts.get("grand_total"), 2), 120)
|
|
||||||
|
|
||||||
def test_gl_entries(self):
|
def test_dunning_with_fees_and_interest(self):
|
||||||
dunning = create_dunning()
|
dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice - _TC")
|
||||||
dunning.submit()
|
|
||||||
gl_entries = frappe.db.sql(
|
|
||||||
"""select account, debit, credit
|
|
||||||
from `tabGL Entry` where voucher_type='Dunning' and voucher_no=%s
|
|
||||||
order by account asc""",
|
|
||||||
dunning.name,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
self.assertTrue(gl_entries)
|
|
||||||
expected_values = dict(
|
|
||||||
(d[0], d) for d in [["Debtors - _TC", 20.44, 0.0], ["Sales - _TC", 0.0, 20.44]]
|
|
||||||
)
|
|
||||||
for gle in gl_entries:
|
|
||||||
self.assertEqual(expected_values[gle.account][0], gle.account)
|
|
||||||
self.assertEqual(expected_values[gle.account][1], gle.debit)
|
|
||||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
|
||||||
|
|
||||||
def test_payment_entry(self):
|
self.assertEqual(round(dunning.total_outstanding, 2), 100.00)
|
||||||
dunning = create_dunning()
|
self.assertEqual(round(dunning.total_interest, 2), 0.41)
|
||||||
|
self.assertEqual(round(dunning.dunning_fee, 2), 10.00)
|
||||||
|
self.assertEqual(round(dunning.dunning_amount, 2), 10.41)
|
||||||
|
self.assertEqual(round(dunning.grand_total, 2), 110.41)
|
||||||
|
|
||||||
|
def test_dunning_with_payment_entry(self):
|
||||||
|
dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice - _TC")
|
||||||
dunning.submit()
|
dunning.submit()
|
||||||
pe = get_payment_entry("Dunning", dunning.name)
|
pe = get_payment_entry("Dunning", dunning.name)
|
||||||
pe.reference_no = "1"
|
pe.reference_no = "1"
|
||||||
pe.reference_date = nowdate()
|
pe.reference_date = nowdate()
|
||||||
pe.paid_from_account_currency = dunning.currency
|
|
||||||
pe.paid_to_account_currency = dunning.currency
|
|
||||||
pe.source_exchange_rate = 1
|
|
||||||
pe.target_exchange_rate = 1
|
|
||||||
pe.insert()
|
pe.insert()
|
||||||
pe.submit()
|
pe.submit()
|
||||||
si_doc = frappe.get_doc("Sales Invoice", dunning.sales_invoice)
|
|
||||||
self.assertEqual(si_doc.outstanding_amount, 0)
|
for overdue_payment in dunning.overdue_payments:
|
||||||
|
outstanding_amount = frappe.get_value(
|
||||||
|
"Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount"
|
||||||
|
)
|
||||||
|
self.assertEqual(outstanding_amount, 0)
|
||||||
|
|
||||||
|
dunning.reload()
|
||||||
|
self.assertEqual(dunning.status, "Resolved")
|
||||||
|
|
||||||
|
def test_dunning_and_payment_against_partially_due_invoice(self):
|
||||||
|
"""
|
||||||
|
Create SI with first installment overdue. Check impact of Dunning and Payment Entry.
|
||||||
|
"""
|
||||||
|
create_payment_terms_template_for_dunning()
|
||||||
|
sales_invoice = create_sales_invoice_against_cost_center(
|
||||||
|
posting_date=add_days(today(), -1 * 6),
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
sales_invoice.payment_terms_template = "_Test 50-50 for Dunning"
|
||||||
|
sales_invoice.submit()
|
||||||
|
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
|
||||||
|
|
||||||
|
self.assertEqual(len(dunning.overdue_payments), 1)
|
||||||
|
self.assertEqual(dunning.overdue_payments[0].payment_term, "_Test Payment Term 1 for Dunning")
|
||||||
|
|
||||||
|
dunning.submit()
|
||||||
|
pe = get_payment_entry("Dunning", dunning.name)
|
||||||
|
pe.reference_no, pe.reference_date = "2", nowdate()
|
||||||
|
pe.insert()
|
||||||
|
pe.submit()
|
||||||
|
sales_invoice.load_from_db()
|
||||||
|
dunning.load_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(sales_invoice.status, "Partly Paid")
|
||||||
|
self.assertEqual(sales_invoice.payment_schedule[0].outstanding, 0)
|
||||||
|
self.assertEqual(dunning.status, "Resolved")
|
||||||
|
|
||||||
|
# Test impact on cancellation of PE
|
||||||
|
pe.cancel()
|
||||||
|
sales_invoice.reload()
|
||||||
|
dunning.reload()
|
||||||
|
|
||||||
|
self.assertEqual(sales_invoice.status, "Overdue")
|
||||||
|
self.assertEqual(dunning.status, "Unresolved")
|
||||||
|
|
||||||
|
|
||||||
def create_dunning():
|
def create_dunning(overdue_days, dunning_type_name=None):
|
||||||
posting_date = add_days(today(), -20)
|
posting_date = add_days(today(), -1 * overdue_days)
|
||||||
due_date = add_days(today(), -15)
|
|
||||||
sales_invoice = create_sales_invoice_against_cost_center(
|
sales_invoice = create_sales_invoice_against_cost_center(
|
||||||
posting_date=posting_date, due_date=due_date, status="Overdue"
|
posting_date=posting_date, qty=1, rate=100
|
||||||
)
|
)
|
||||||
dunning_type = frappe.get_doc("Dunning Type", "First Notice")
|
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
|
||||||
dunning = frappe.new_doc("Dunning")
|
|
||||||
dunning.sales_invoice = sales_invoice.name
|
if dunning_type_name:
|
||||||
dunning.customer_name = sales_invoice.customer_name
|
dunning_type = frappe.get_doc("Dunning Type", dunning_type_name)
|
||||||
dunning.outstanding_amount = sales_invoice.outstanding_amount
|
dunning.dunning_type = dunning_type.name
|
||||||
dunning.debit_to = sales_invoice.debit_to
|
dunning.rate_of_interest = dunning_type.rate_of_interest
|
||||||
dunning.currency = sales_invoice.currency
|
dunning.dunning_fee = dunning_type.dunning_fee
|
||||||
dunning.company = sales_invoice.company
|
dunning.income_account = dunning_type.income_account
|
||||||
dunning.posting_date = nowdate()
|
dunning.cost_center = dunning_type.cost_center
|
||||||
dunning.due_date = sales_invoice.due_date
|
|
||||||
dunning.dunning_type = "First Notice"
|
return dunning.save()
|
||||||
dunning.rate_of_interest = dunning_type.rate_of_interest
|
|
||||||
dunning.dunning_fee = dunning_type.dunning_fee
|
|
||||||
dunning.save()
|
|
||||||
return dunning
|
|
||||||
|
|
||||||
|
|
||||||
def create_dunning_with_zero_interest_rate():
|
def create_dunning_type(title, fee, interest, is_default):
|
||||||
posting_date = add_days(today(), -20)
|
company = "_Test Company"
|
||||||
due_date = add_days(today(), -15)
|
if frappe.db.exists("Dunning Type", f"{title} - _TC"):
|
||||||
sales_invoice = create_sales_invoice_against_cost_center(
|
return
|
||||||
posting_date=posting_date, due_date=due_date, status="Overdue"
|
|
||||||
)
|
|
||||||
dunning_type = frappe.get_doc("Dunning Type", "First Notice with 0% Rate of Interest")
|
|
||||||
dunning = frappe.new_doc("Dunning")
|
|
||||||
dunning.sales_invoice = sales_invoice.name
|
|
||||||
dunning.customer_name = sales_invoice.customer_name
|
|
||||||
dunning.outstanding_amount = sales_invoice.outstanding_amount
|
|
||||||
dunning.debit_to = sales_invoice.debit_to
|
|
||||||
dunning.currency = sales_invoice.currency
|
|
||||||
dunning.company = sales_invoice.company
|
|
||||||
dunning.posting_date = nowdate()
|
|
||||||
dunning.due_date = sales_invoice.due_date
|
|
||||||
dunning.dunning_type = "First Notice with 0% Rate of Interest"
|
|
||||||
dunning.rate_of_interest = dunning_type.rate_of_interest
|
|
||||||
dunning.dunning_fee = dunning_type.dunning_fee
|
|
||||||
dunning.save()
|
|
||||||
return dunning
|
|
||||||
|
|
||||||
|
|
||||||
def create_dunning_type():
|
|
||||||
dunning_type = frappe.new_doc("Dunning Type")
|
dunning_type = frappe.new_doc("Dunning Type")
|
||||||
dunning_type.dunning_type = "First Notice"
|
dunning_type.dunning_type = title
|
||||||
dunning_type.start_day = 10
|
dunning_type.company = company
|
||||||
dunning_type.end_day = 20
|
dunning_type.is_default = is_default
|
||||||
dunning_type.dunning_fee = 20
|
dunning_type.dunning_fee = fee
|
||||||
dunning_type.rate_of_interest = 8
|
dunning_type.rate_of_interest = interest
|
||||||
|
dunning_type.income_account = get_income_account(company)
|
||||||
|
dunning_type.cost_center = get_default_cost_center(company)
|
||||||
dunning_type.append(
|
dunning_type.append(
|
||||||
"dunning_letter_text",
|
"dunning_letter_text",
|
||||||
{
|
{
|
||||||
"language": "en",
|
"language": "en",
|
||||||
"body_text": "We have still not received payment for our invoice ",
|
"body_text": "We have still not received payment for our invoice",
|
||||||
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees.",
|
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees.",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
dunning_type.save()
|
dunning_type.insert()
|
||||||
|
|
||||||
|
|
||||||
def create_dunning_type_with_zero_interest_rate():
|
def get_income_account(company):
|
||||||
dunning_type = frappe.new_doc("Dunning Type")
|
return (
|
||||||
dunning_type.dunning_type = "First Notice with 0% Rate of Interest"
|
frappe.get_value("Company", company, "default_income_account")
|
||||||
dunning_type.start_day = 10
|
or frappe.get_all(
|
||||||
dunning_type.end_day = 20
|
"Account",
|
||||||
dunning_type.dunning_fee = 20
|
filters={"is_group": 0, "company": company},
|
||||||
dunning_type.rate_of_interest = 0
|
or_filters={
|
||||||
dunning_type.append(
|
"report_type": "Profit and Loss",
|
||||||
"dunning_letter_text",
|
"account_type": ("in", ("Income Account", "Temporary")),
|
||||||
{
|
},
|
||||||
"language": "en",
|
limit=1,
|
||||||
"body_text": "We have still not received payment for our invoice ",
|
pluck="name",
|
||||||
"closing_text": "We kindly request that you pay the outstanding amount immediately, and late fees.",
|
)[0]
|
||||||
},
|
|
||||||
)
|
)
|
||||||
dunning_type.save()
|
|
||||||
|
|
||||||
|
def create_payment_terms_template_for_dunning():
|
||||||
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_term
|
||||||
|
|
||||||
|
create_payment_term("_Test Payment Term 1 for Dunning")
|
||||||
|
create_payment_term("_Test Payment Term 2 for Dunning")
|
||||||
|
|
||||||
|
if not frappe.db.exists("Payment Terms Template", "_Test 50-50 for Dunning"):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Payment Terms Template",
|
||||||
|
"template_name": "_Test 50-50 for Dunning",
|
||||||
|
"allocate_payment_based_on_payment_terms": 1,
|
||||||
|
"terms": [
|
||||||
|
{
|
||||||
|
"doctype": "Payment Terms Template Detail",
|
||||||
|
"payment_term": "_Test Payment Term 1 for Dunning",
|
||||||
|
"invoice_portion": 50.00,
|
||||||
|
"credit_days_based_on": "Day(s) after invoice date",
|
||||||
|
"credit_days": 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Payment Terms Template Detail",
|
||||||
|
"payment_term": "_Test Payment Term 2 for Dunning",
|
||||||
|
"invoice_portion": 50.00,
|
||||||
|
"credit_days_based_on": "Day(s) after invoice date",
|
||||||
|
"credit_days": 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
@ -1,8 +1,24 @@
|
|||||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Dunning Type', {
|
frappe.ui.form.on("Dunning Type", {
|
||||||
// refresh: function(frm) {
|
setup: function (frm) {
|
||||||
|
frm.set_query("income_account", () => {
|
||||||
// }
|
return {
|
||||||
|
filters: {
|
||||||
|
root_type: "Income",
|
||||||
|
is_group: 0,
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
frm.set_query("cost_center", () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_group: 0,
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:dunning_type",
|
"beta": 1,
|
||||||
"creation": "2019-12-04 04:59:08.003664",
|
"creation": "2019-12-04 04:59:08.003664",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"dunning_type",
|
"dunning_type",
|
||||||
"overdue_interval_section",
|
"is_default",
|
||||||
"start_day",
|
"column_break_3",
|
||||||
"column_break_4",
|
"company",
|
||||||
"end_day",
|
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"dunning_fee",
|
"dunning_fee",
|
||||||
"column_break_8",
|
"column_break_8",
|
||||||
"rate_of_interest",
|
"rate_of_interest",
|
||||||
"text_block_section",
|
"text_block_section",
|
||||||
"dunning_letter_text"
|
"dunning_letter_text",
|
||||||
|
"section_break_9",
|
||||||
|
"income_account",
|
||||||
|
"column_break_13",
|
||||||
|
"cost_center"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -45,10 +48,6 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"options": "Dunning Letter Text"
|
"options": "Dunning Letter Text"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_4",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_6",
|
"fieldname": "section_break_6",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
@ -57,33 +56,62 @@
|
|||||||
"fieldname": "column_break_8",
|
"fieldname": "column_break_8",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "overdue_interval_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Overdue Interval"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "start_day",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"label": "Start Day"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "end_day",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"label": "End Day"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "rate_of_interest",
|
"fieldname": "rate_of_interest",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate of Interest (%) Yearly"
|
"label": "Rate of Interest (%) Yearly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_default",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_9",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "income_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Income Account",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_13",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [
|
||||||
"modified": "2020-07-15 17:14:17.835074",
|
{
|
||||||
|
"link_doctype": "Dunning",
|
||||||
|
"link_fieldname": "dunning_type"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2021-11-13 00:25:35.659283",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Dunning Type",
|
"name": "Dunning Type",
|
||||||
|
"naming_rule": "By script",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
# import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
class DunningType(Document):
|
class DunningType(Document):
|
||||||
pass
|
def autoname(self):
|
||||||
|
company_abbr = frappe.get_value("Company", self.company, "abbr")
|
||||||
|
self.name = f"{self.dunning_type} - {company_abbr}"
|
||||||
|
36
erpnext/accounts/doctype/dunning_type/test_records.json
Normal file
36
erpnext/accounts/doctype/dunning_type/test_records.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"doctype": "Dunning Type",
|
||||||
|
"dunning_type": "_Test First Notice",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"is_default": 1,
|
||||||
|
"dunning_fee": 0.0,
|
||||||
|
"rate_of_interest": 0.0,
|
||||||
|
"dunning_letter_text": [
|
||||||
|
{
|
||||||
|
"language": "en",
|
||||||
|
"body_text": "We have still not received payment for our invoice",
|
||||||
|
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Dunning Type",
|
||||||
|
"dunning_type": "_Test Second Notice",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"is_default": 0,
|
||||||
|
"dunning_fee": 10.0,
|
||||||
|
"rate_of_interest": 10.0,
|
||||||
|
"dunning_letter_text": [
|
||||||
|
{
|
||||||
|
"language": "en",
|
||||||
|
"body_text": "We have still not received payment for our invoice",
|
||||||
|
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC"
|
||||||
|
}
|
||||||
|
]
|
@ -8,17 +8,6 @@ frappe.ui.form.on('Fiscal Year', {
|
|||||||
frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1));
|
frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refresh: function (frm) {
|
|
||||||
if (!frm.doc.__islocal && (frm.doc.name != frappe.sys_defaults.fiscal_year)) {
|
|
||||||
frm.add_custom_button(__("Set as Default"), () => frm.events.set_as_default(frm));
|
|
||||||
frm.set_intro(__("To set this Fiscal Year as Default, click on 'Set as Default'"));
|
|
||||||
} else {
|
|
||||||
frm.set_intro("");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set_as_default: function(frm) {
|
|
||||||
return frm.call('set_as_default');
|
|
||||||
},
|
|
||||||
year_start_date: function(frm) {
|
year_start_date: function(frm) {
|
||||||
if (!frm.doc.is_short_year) {
|
if (!frm.doc.is_short_year) {
|
||||||
let year_end_date =
|
let year_end_date =
|
||||||
|
@ -4,28 +4,12 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from frappe import _, msgprint
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import add_days, add_years, cstr, getdate
|
from frappe.utils import add_days, add_years, cstr, getdate
|
||||||
|
|
||||||
|
|
||||||
class FiscalYear(Document):
|
class FiscalYear(Document):
|
||||||
@frappe.whitelist()
|
|
||||||
def set_as_default(self):
|
|
||||||
frappe.db.set_single_value("Global Defaults", "current_fiscal_year", self.name)
|
|
||||||
global_defaults = frappe.get_doc("Global Defaults")
|
|
||||||
global_defaults.check_permission("write")
|
|
||||||
global_defaults.on_update()
|
|
||||||
|
|
||||||
# clear cache
|
|
||||||
frappe.clear_cache()
|
|
||||||
|
|
||||||
msgprint(
|
|
||||||
_(
|
|
||||||
"{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect."
|
|
||||||
).format(self.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.validate_overlap()
|
self.validate_overlap()
|
||||||
@ -68,13 +52,6 @@ class FiscalYear(Document):
|
|||||||
frappe.cache().delete_value("fiscal_years")
|
frappe.cache().delete_value("fiscal_years")
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
global_defaults = frappe.get_doc("Global Defaults")
|
|
||||||
if global_defaults.current_fiscal_year == self.name:
|
|
||||||
frappe.throw(
|
|
||||||
_(
|
|
||||||
"You cannot delete Fiscal Year {0}. Fiscal Year {0} is set as default in Global Settings"
|
|
||||||
).format(self.name)
|
|
||||||
)
|
|
||||||
frappe.cache().delete_value("fiscal_years")
|
frappe.cache().delete_value("fiscal_years")
|
||||||
|
|
||||||
def validate_overlap(self):
|
def validate_overlap(self):
|
||||||
|
@ -264,11 +264,11 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(jvd.party_type && jvd.party) {
|
if(jvd.party_type && jvd.party) {
|
||||||
var party_field = "";
|
let party_field = "";
|
||||||
if(jvd.reference_type.indexOf("Sales")===0) {
|
if(jvd.reference_type.indexOf("Sales")===0) {
|
||||||
var party_field = "customer";
|
party_field = "customer";
|
||||||
} else if (jvd.reference_type.indexOf("Purchase")===0) {
|
} else if (jvd.reference_type.indexOf("Purchase")===0) {
|
||||||
var party_field = "supplier";
|
party_field = "supplier";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (party_field) {
|
if (party_field) {
|
||||||
@ -368,7 +368,7 @@ cur_frm.cscript.update_totals = function(doc) {
|
|||||||
td += flt(accounts[i].debit, precision("debit", accounts[i]));
|
td += flt(accounts[i].debit, precision("debit", accounts[i]));
|
||||||
tc += flt(accounts[i].credit, precision("credit", accounts[i]));
|
tc += flt(accounts[i].credit, precision("credit", accounts[i]));
|
||||||
}
|
}
|
||||||
var doc = locals[doc.doctype][doc.name];
|
doc = locals[doc.doctype][doc.name];
|
||||||
doc.total_debit = td;
|
doc.total_debit = td;
|
||||||
doc.total_credit = tc;
|
doc.total_credit = tc;
|
||||||
doc.difference = flt((td - tc), precision("difference"));
|
doc.difference = flt((td - tc), precision("difference"));
|
||||||
|
@ -408,6 +408,15 @@ class JournalEntry(AccountsController):
|
|||||||
d.idx, d.account
|
d.idx, d.account
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif (
|
||||||
|
d.party_type
|
||||||
|
and frappe.db.get_value("Party Type", d.party_type, "account_type") != account_type
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row {0}: Account {1} and Party Type {2} have different account types").format(
|
||||||
|
d.idx, d.account, d.party_type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def check_credit_limit(self):
|
def check_credit_limit(self):
|
||||||
customers = list(
|
customers = list(
|
||||||
|
170
erpnext/accounts/doctype/overdue_payment/overdue_payment.json
Normal file
170
erpnext/accounts/doctype/overdue_payment/overdue_payment.json
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-09-15 18:34:27.172906",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"sales_invoice",
|
||||||
|
"payment_schedule",
|
||||||
|
"dunning_level",
|
||||||
|
"payment_term",
|
||||||
|
"section_break_15",
|
||||||
|
"description",
|
||||||
|
"section_break_4",
|
||||||
|
"due_date",
|
||||||
|
"overdue_days",
|
||||||
|
"mode_of_payment",
|
||||||
|
"column_break_5",
|
||||||
|
"invoice_portion",
|
||||||
|
"section_break_16",
|
||||||
|
"payment_amount",
|
||||||
|
"outstanding",
|
||||||
|
"paid_amount",
|
||||||
|
"discounted_amount",
|
||||||
|
"interest"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "payment_term",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment Term",
|
||||||
|
"options": "Payment Term",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "section_break_15",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fetch_from": "payment_term.description",
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Description",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_4",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "due_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Due Date",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "mode_of_payment",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Mode of Payment",
|
||||||
|
"options": "Mode of Payment",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_5",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "invoice_portion",
|
||||||
|
"fieldtype": "Percent",
|
||||||
|
"label": "Invoice Portion",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "payment_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Payment Amount",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "payment_amount",
|
||||||
|
"fieldname": "outstanding",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Outstanding",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "paid_amount",
|
||||||
|
"fieldname": "paid_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Amount",
|
||||||
|
"options": "currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "discounted_amount",
|
||||||
|
"fieldname": "discounted_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Discounted Amount",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sales_invoice",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Sales Invoice",
|
||||||
|
"options": "Sales Invoice",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_schedule",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Payment Schedule",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "overdue_days",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Overdue Days",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "dunning_level",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Dunning Level",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_16",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "interest",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Interest",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-09-23 13:48:27.898830",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Overdue Payment",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class OverduePayment(Document):
|
||||||
|
pass
|
@ -1,10 +1,12 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
|
||||||
frappe.provide("erpnext.accounts.dimensions");
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
cur_frm.cscript.tax_table = "Advance Taxes and Charges";
|
cur_frm.cscript.tax_table = "Advance Taxes and Charges";
|
||||||
|
|
||||||
|
erpnext.accounts.taxes.setup_tax_validations("Payment Entry");
|
||||||
|
erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges");
|
||||||
|
|
||||||
frappe.ui.form.on('Payment Entry', {
|
frappe.ui.form.on('Payment Entry', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', "Repost Payment Ledger"];
|
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', "Repost Payment Ledger"];
|
||||||
@ -106,12 +108,11 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("reference_doctype", "references", function() {
|
frm.set_query("reference_doctype", "references", function() {
|
||||||
|
let doctypes = ["Journal Entry"];
|
||||||
if (frm.doc.party_type == "Customer") {
|
if (frm.doc.party_type == "Customer") {
|
||||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
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"];
|
doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
||||||
} else {
|
|
||||||
var doctypes = ["Journal Entry"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -122,13 +123,10 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frm.set_query('payment_term', 'references', function(frm, cdt, cdn) {
|
frm.set_query('payment_term', 'references', function(frm, cdt, cdn) {
|
||||||
const child = locals[cdt][cdn];
|
const child = locals[cdt][cdn];
|
||||||
if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) {
|
if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) {
|
||||||
let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name});
|
|
||||||
|
|
||||||
payment_term_list = payment_term_list.map(pt => pt.payment_term);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
query: "erpnext.controllers.queries.get_payment_terms_for_references",
|
||||||
filters: {
|
filters: {
|
||||||
'name': ['in', payment_term_list]
|
'reference': child.reference_name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,6 +163,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
company: function(frm) {
|
company: function(frm) {
|
||||||
|
frm.trigger('party');
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
frm.events.set_dynamic_labels(frm);
|
frm.events.set_dynamic_labels(frm);
|
||||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
@ -287,6 +286,13 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mode_of_payment: function(frm) {
|
||||||
|
erpnext.accounts.pos.get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){
|
||||||
|
let payment_account_field = frm.doc.payment_type == "Receive" ? "paid_to" : "paid_from";
|
||||||
|
frm.set_value(payment_account_field, account);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
party_type: function(frm) {
|
party_type: function(frm) {
|
||||||
|
|
||||||
let party_types = Object.keys(frappe.boot.party_account_types);
|
let party_types = Object.keys(frappe.boot.party_account_types);
|
||||||
@ -319,10 +325,6 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
company: function(frm){
|
|
||||||
frm.trigger('party');
|
|
||||||
},
|
|
||||||
|
|
||||||
party: function(frm) {
|
party: function(frm) {
|
||||||
if (frm.doc.contact_email || frm.doc.contact_person) {
|
if (frm.doc.contact_email || frm.doc.contact_person) {
|
||||||
frm.set_value("contact_email", "");
|
frm.set_value("contact_email", "");
|
||||||
@ -1106,7 +1108,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
if (tax.charge_type === 'On Net Total') {
|
if (tax.charge_type === 'On Net Total') {
|
||||||
tax.charge_type = 'On Paid Amount';
|
tax.charge_type = 'On Paid Amount';
|
||||||
}
|
}
|
||||||
me.frm.add_child("taxes", tax);
|
frm.add_child("taxes", tax);
|
||||||
}
|
}
|
||||||
frm.events.apply_taxes(frm);
|
frm.events.apply_taxes(frm);
|
||||||
frm.events.set_unallocated_amount(frm);
|
frm.events.set_unallocated_amount(frm);
|
||||||
@ -1222,7 +1224,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
|
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
|
||||||
} else {
|
} else {
|
||||||
tax.grand_total_fraction_for_current_item =
|
tax.grand_total_fraction_for_current_item =
|
||||||
me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item +
|
frm.doc["taxes"][i-1].grand_total_fraction_for_current_item +
|
||||||
tax.tax_fraction_for_current_item;
|
tax.tax_fraction_for_current_item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1269,7 +1271,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
|
$.each(frm.doc["taxes"] || [], function(i, tax) {
|
||||||
let current_tax_amount = frm.events.get_current_tax_amount(frm, tax);
|
let current_tax_amount = frm.events.get_current_tax_amount(frm, tax);
|
||||||
|
|
||||||
// Adjust divisional loss to the last item
|
// Adjust divisional loss to the last item
|
||||||
|
@ -207,6 +207,20 @@ class PaymentEntry(AccountsController):
|
|||||||
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
|
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||||
frappe.throw(fail_message.format(d.idx))
|
frappe.throw(fail_message.format(d.idx))
|
||||||
|
|
||||||
|
def term_based_allocation_enabled_for_reference(
|
||||||
|
self, reference_doctype: str, reference_name: str
|
||||||
|
) -> bool:
|
||||||
|
if (
|
||||||
|
reference_doctype
|
||||||
|
and reference_doctype in ["Sales Invoice", "Sales Order", "Purchase Order", "Purchase Invoice"]
|
||||||
|
and reference_name
|
||||||
|
):
|
||||||
|
if template := frappe.db.get_value(reference_doctype, reference_name, "payment_terms_template"):
|
||||||
|
return frappe.db.get_value(
|
||||||
|
"Payment Terms Template", template, "allocate_payment_based_on_payment_terms"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
def validate_allocated_amount_with_latest_data(self):
|
def validate_allocated_amount_with_latest_data(self):
|
||||||
latest_references = get_outstanding_reference_documents(
|
latest_references = get_outstanding_reference_documents(
|
||||||
{
|
{
|
||||||
@ -226,10 +240,25 @@ class PaymentEntry(AccountsController):
|
|||||||
latest_lookup = {}
|
latest_lookup = {}
|
||||||
for d in latest_references:
|
for d in latest_references:
|
||||||
d = frappe._dict(d)
|
d = frappe._dict(d)
|
||||||
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
|
latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d
|
||||||
|
|
||||||
for d in self.get("references"):
|
for idx, d in enumerate(self.get("references"), start=1):
|
||||||
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
|
latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()
|
||||||
|
|
||||||
|
# If term based allocation is enabled, throw
|
||||||
|
if (
|
||||||
|
d.payment_term is None or d.payment_term == ""
|
||||||
|
) and self.term_based_allocation_enabled_for_reference(
|
||||||
|
d.reference_doctype, d.reference_name
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
|
||||||
|
).format(frappe.bold(d.reference_name), frappe.bold(idx))
|
||||||
|
)
|
||||||
|
|
||||||
|
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
|
||||||
|
latest = latest.get(d.payment_term) or latest.get(None)
|
||||||
|
|
||||||
# The reference has already been fully paid
|
# The reference has already been fully paid
|
||||||
if not latest:
|
if not latest:
|
||||||
@ -251,6 +280,18 @@ class PaymentEntry(AccountsController):
|
|||||||
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
|
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
|
||||||
frappe.throw(fail_message.format(d.idx))
|
frappe.throw(fail_message.format(d.idx))
|
||||||
|
|
||||||
|
if d.payment_term and (
|
||||||
|
(flt(d.allocated_amount)) > 0
|
||||||
|
and flt(d.allocated_amount) > flt(latest.payment_term_outstanding)
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
|
||||||
|
).format(
|
||||||
|
d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Check for negative outstanding invoices as well
|
# Check for negative outstanding invoices as well
|
||||||
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
||||||
frappe.throw(fail_message.format(d.idx))
|
frappe.throw(fail_message.format(d.idx))
|
||||||
@ -473,7 +514,7 @@ class PaymentEntry(AccountsController):
|
|||||||
_(
|
_(
|
||||||
"References {0} of type {1} had no outstanding amount left before submitting the Payment Entry. Now they have a negative outstanding amount."
|
"References {0} of type {1} had no outstanding amount left before submitting the Payment Entry. Now they have a negative outstanding amount."
|
||||||
).format(
|
).format(
|
||||||
frappe.bold(comma_and((d.reference_name for d in references))),
|
frappe.bold(comma_and([d.reference_name for d in references])),
|
||||||
_(reference_doctype),
|
_(reference_doctype),
|
||||||
)
|
)
|
||||||
+ "<br><br>"
|
+ "<br><br>"
|
||||||
@ -1500,7 +1541,9 @@ def get_outstanding_reference_documents(args, validate=False):
|
|||||||
accounting_dimensions=accounting_dimensions_filter,
|
accounting_dimensions=accounting_dimensions_filter,
|
||||||
)
|
)
|
||||||
|
|
||||||
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
|
outstanding_invoices = split_invoices_based_on_payment_terms(
|
||||||
|
outstanding_invoices, args.get("company")
|
||||||
|
)
|
||||||
|
|
||||||
for d in outstanding_invoices:
|
for d in outstanding_invoices:
|
||||||
d["exchange_rate"] = 1
|
d["exchange_rate"] = 1
|
||||||
@ -1560,8 +1603,27 @@ def get_outstanding_reference_documents(args, validate=False):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def split_invoices_based_on_payment_terms(outstanding_invoices):
|
def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
||||||
invoice_ref_based_on_payment_terms = {}
|
invoice_ref_based_on_payment_terms = {}
|
||||||
|
|
||||||
|
company_currency = (
|
||||||
|
frappe.db.get_value("Company", company, "default_currency") if company else None
|
||||||
|
)
|
||||||
|
exc_rates = frappe._dict()
|
||||||
|
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
|
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
||||||
|
for x in frappe.db.get_all(
|
||||||
|
doctype,
|
||||||
|
filters={"name": ["in", invoices]},
|
||||||
|
fields=["name", "currency", "conversion_rate", "party_account_currency"],
|
||||||
|
):
|
||||||
|
exc_rates[x.name] = frappe._dict(
|
||||||
|
conversion_rate=x.conversion_rate,
|
||||||
|
currency=x.currency,
|
||||||
|
party_account_currency=x.party_account_currency,
|
||||||
|
company_currency=company_currency,
|
||||||
|
)
|
||||||
|
|
||||||
for idx, d in enumerate(outstanding_invoices):
|
for idx, d in enumerate(outstanding_invoices):
|
||||||
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
payment_term_template = frappe.db.get_value(
|
payment_term_template = frappe.db.get_value(
|
||||||
@ -1578,6 +1640,14 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
|
|||||||
|
|
||||||
for payment_term in payment_schedule:
|
for payment_term in payment_schedule:
|
||||||
if payment_term.outstanding > 0.1:
|
if payment_term.outstanding > 0.1:
|
||||||
|
doc_details = exc_rates.get(payment_term.parent, None)
|
||||||
|
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
||||||
|
doc_details.party_account_currency != doc_details.company_currency
|
||||||
|
)
|
||||||
|
payment_term_outstanding = flt(payment_term.outstanding)
|
||||||
|
if not is_multi_currency_acc:
|
||||||
|
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
||||||
|
|
||||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
||||||
invoice_ref_based_on_payment_terms[idx].append(
|
invoice_ref_based_on_payment_terms[idx].append(
|
||||||
frappe._dict(
|
frappe._dict(
|
||||||
@ -1589,6 +1659,10 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
|
|||||||
"posting_date": d.posting_date,
|
"posting_date": d.posting_date,
|
||||||
"invoice_amount": flt(d.invoice_amount),
|
"invoice_amount": flt(d.invoice_amount),
|
||||||
"outstanding_amount": flt(d.outstanding_amount),
|
"outstanding_amount": flt(d.outstanding_amount),
|
||||||
|
"payment_term_outstanding": payment_term_outstanding,
|
||||||
|
"allocated_amount": payment_term_outstanding
|
||||||
|
if payment_term_outstanding
|
||||||
|
else d.outstanding_amount,
|
||||||
"payment_amount": payment_term.payment_amount,
|
"payment_amount": payment_term.payment_amount,
|
||||||
"payment_term": payment_term.payment_term,
|
"payment_term": payment_term.payment_term,
|
||||||
"account": d.account,
|
"account": d.account,
|
||||||
@ -1664,7 +1738,7 @@ def get_orders_to_be_billed(
|
|||||||
{party_type} = %s
|
{party_type} = %s
|
||||||
and docstatus = 1
|
and docstatus = 1
|
||||||
and company = %s
|
and company = %s
|
||||||
and ifnull(status, "") != "Closed"
|
and status != "Closed"
|
||||||
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
|
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
|
||||||
and abs(100 - per_billed) > 0.01
|
and abs(100 - per_billed) > 0.01
|
||||||
{condition}
|
{condition}
|
||||||
@ -2010,28 +2084,27 @@ def get_payment_entry(
|
|||||||
pe.append("references", reference)
|
pe.append("references", reference)
|
||||||
else:
|
else:
|
||||||
if dt == "Dunning":
|
if dt == "Dunning":
|
||||||
|
for overdue_payment in doc.overdue_payments:
|
||||||
|
pe.append(
|
||||||
|
"references",
|
||||||
|
{
|
||||||
|
"reference_doctype": "Sales Invoice",
|
||||||
|
"reference_name": overdue_payment.sales_invoice,
|
||||||
|
"payment_term": overdue_payment.payment_term,
|
||||||
|
"due_date": overdue_payment.due_date,
|
||||||
|
"total_amount": overdue_payment.outstanding,
|
||||||
|
"outstanding_amount": overdue_payment.outstanding,
|
||||||
|
"allocated_amount": overdue_payment.outstanding,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
pe.append(
|
pe.append(
|
||||||
"references",
|
"deductions",
|
||||||
{
|
{
|
||||||
"reference_doctype": "Sales Invoice",
|
"account": doc.income_account,
|
||||||
"reference_name": doc.get("sales_invoice"),
|
"cost_center": doc.cost_center,
|
||||||
"bill_no": doc.get("bill_no"),
|
"amount": -1 * doc.dunning_amount,
|
||||||
"due_date": doc.get("due_date"),
|
"description": _("Interest and/or dunning fee"),
|
||||||
"total_amount": doc.get("outstanding_amount"),
|
|
||||||
"outstanding_amount": doc.get("outstanding_amount"),
|
|
||||||
"allocated_amount": doc.get("outstanding_amount"),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
pe.append(
|
|
||||||
"references",
|
|
||||||
{
|
|
||||||
"reference_doctype": dt,
|
|
||||||
"reference_name": dn,
|
|
||||||
"bill_no": doc.get("bill_no"),
|
|
||||||
"due_date": doc.get("due_date"),
|
|
||||||
"total_amount": doc.get("dunning_amount"),
|
|
||||||
"outstanding_amount": doc.get("dunning_amount"),
|
|
||||||
"allocated_amount": doc.get("dunning_amount"),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -2125,8 +2198,10 @@ def set_party_account_currency(dt, party_account, doc):
|
|||||||
|
|
||||||
def set_payment_type(dt, doc):
|
def set_payment_type(dt, doc):
|
||||||
if (
|
if (
|
||||||
dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)
|
(dt == "Sales Order" or (dt == "Sales Invoice" and doc.outstanding_amount > 0))
|
||||||
) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
|
or (dt == "Purchase Invoice" and doc.outstanding_amount < 0)
|
||||||
|
or dt == "Dunning"
|
||||||
|
):
|
||||||
payment_type = "Receive"
|
payment_type = "Receive"
|
||||||
else:
|
else:
|
||||||
payment_type = "Pay"
|
payment_type = "Pay"
|
||||||
@ -2371,6 +2446,7 @@ def get_reference_as_per_payment_terms(
|
|||||||
"due_date": doc.get("due_date"),
|
"due_date": doc.get("due_date"),
|
||||||
"total_amount": grand_total,
|
"total_amount": grand_total,
|
||||||
"outstanding_amount": outstanding_amount,
|
"outstanding_amount": outstanding_amount,
|
||||||
|
"payment_term_outstanding": payment_term_outstanding,
|
||||||
"payment_term": payment_term.payment_term,
|
"payment_term": payment_term.payment_term,
|
||||||
"allocated_amount": payment_term_outstanding,
|
"allocated_amount": payment_term_outstanding,
|
||||||
}
|
}
|
||||||
|
@ -1061,6 +1061,101 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
}
|
}
|
||||||
self.assertDictEqual(ref_details, expected_response)
|
self.assertDictEqual(ref_details, expected_response)
|
||||||
|
|
||||||
|
@change_settings(
|
||||||
|
"Accounts Settings",
|
||||||
|
{
|
||||||
|
"unlink_payment_on_cancellation_of_invoice": 1,
|
||||||
|
"delete_linked_ledger_entries": 1,
|
||||||
|
"allow_multi_currency_invoices_against_single_party_account": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def test_overallocation_validation_on_payment_terms(self):
|
||||||
|
"""
|
||||||
|
Validate Allocation on Payment Entry based on Payment Schedule. Upon overallocation, validation error must be thrown.
|
||||||
|
|
||||||
|
"""
|
||||||
|
customer = create_customer()
|
||||||
|
create_payment_terms_template()
|
||||||
|
|
||||||
|
# Validate allocation on base/company currency
|
||||||
|
si1 = create_sales_invoice(do_not_save=1, qty=1, rate=200)
|
||||||
|
si1.payment_terms_template = "Test Receivable Template"
|
||||||
|
si1.save().submit()
|
||||||
|
|
||||||
|
si1.reload()
|
||||||
|
pe = get_payment_entry(si1.doctype, si1.name).save()
|
||||||
|
# Allocated amount should be according to the payment schedule
|
||||||
|
for idx, schedule in enumerate(si1.payment_schedule):
|
||||||
|
with self.subTest(idx=idx):
|
||||||
|
self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount))
|
||||||
|
pe.save()
|
||||||
|
|
||||||
|
# Overallocation validation should trigger
|
||||||
|
pe.paid_amount = 400
|
||||||
|
pe.references[0].allocated_amount = 200
|
||||||
|
pe.references[1].allocated_amount = 200
|
||||||
|
self.assertRaises(frappe.ValidationError, pe.save)
|
||||||
|
pe.delete()
|
||||||
|
si1.cancel()
|
||||||
|
si1.delete()
|
||||||
|
|
||||||
|
# Validate allocation on foreign currency
|
||||||
|
si2 = create_sales_invoice(
|
||||||
|
customer="_Test Customer USD",
|
||||||
|
debit_to="_Test Receivable USD - _TC",
|
||||||
|
currency="USD",
|
||||||
|
conversion_rate=80,
|
||||||
|
do_not_save=1,
|
||||||
|
)
|
||||||
|
si2.payment_terms_template = "Test Receivable Template"
|
||||||
|
si2.save().submit()
|
||||||
|
|
||||||
|
si2.reload()
|
||||||
|
pe = get_payment_entry(si2.doctype, si2.name).save()
|
||||||
|
# Allocated amount should be according to the payment schedule
|
||||||
|
for idx, schedule in enumerate(si2.payment_schedule):
|
||||||
|
with self.subTest(idx=idx):
|
||||||
|
self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount))
|
||||||
|
pe.save()
|
||||||
|
|
||||||
|
# Overallocation validation should trigger
|
||||||
|
pe.paid_amount = 200
|
||||||
|
pe.references[0].allocated_amount = 100
|
||||||
|
pe.references[1].allocated_amount = 100
|
||||||
|
self.assertRaises(frappe.ValidationError, pe.save)
|
||||||
|
pe.delete()
|
||||||
|
si2.cancel()
|
||||||
|
si2.delete()
|
||||||
|
|
||||||
|
# Validate allocation in base/company currency on a foreign currency document
|
||||||
|
# when invoice is made is foreign currency, but posted to base/company currency debtors account
|
||||||
|
si3 = create_sales_invoice(
|
||||||
|
customer=customer,
|
||||||
|
currency="USD",
|
||||||
|
conversion_rate=80,
|
||||||
|
do_not_save=1,
|
||||||
|
)
|
||||||
|
si3.payment_terms_template = "Test Receivable Template"
|
||||||
|
si3.save().submit()
|
||||||
|
|
||||||
|
si3.reload()
|
||||||
|
pe = get_payment_entry(si3.doctype, si3.name).save()
|
||||||
|
# Allocated amount should be equal to payment term outstanding
|
||||||
|
self.assertEqual(len(pe.references), 2)
|
||||||
|
for idx, ref in enumerate(pe.references):
|
||||||
|
with self.subTest(idx=idx):
|
||||||
|
self.assertEqual(ref.payment_term_outstanding, ref.allocated_amount)
|
||||||
|
pe.save()
|
||||||
|
|
||||||
|
# Overallocation validation should trigger
|
||||||
|
pe.paid_amount = 16000
|
||||||
|
pe.references[0].allocated_amount = 8000
|
||||||
|
pe.references[1].allocated_amount = 8000
|
||||||
|
self.assertRaises(frappe.ValidationError, pe.save)
|
||||||
|
pe.delete()
|
||||||
|
si3.cancel()
|
||||||
|
si3.delete()
|
||||||
|
|
||||||
|
|
||||||
def create_payment_entry(**args):
|
def create_payment_entry(**args):
|
||||||
payment_entry = frappe.new_doc("Payment Entry")
|
payment_entry = frappe.new_doc("Payment Entry")
|
||||||
@ -1150,3 +1245,17 @@ def create_payment_terms_template_with_discount(
|
|||||||
def create_payment_term(name):
|
def create_payment_term(name):
|
||||||
if not frappe.db.exists("Payment Term", name):
|
if not frappe.db.exists("Payment Term", name):
|
||||||
frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert()
|
frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert()
|
||||||
|
|
||||||
|
|
||||||
|
def create_customer(name="_Test Customer 2 USD", currency="USD"):
|
||||||
|
customer = None
|
||||||
|
if frappe.db.exists("Customer", name):
|
||||||
|
customer = name
|
||||||
|
else:
|
||||||
|
customer = frappe.new_doc("Customer")
|
||||||
|
customer.customer_name = name
|
||||||
|
customer.default_currency = currency
|
||||||
|
customer.type = "Individual"
|
||||||
|
customer.save()
|
||||||
|
customer = customer.name
|
||||||
|
return customer
|
||||||
|
@ -124,7 +124,7 @@ frappe.ui.form.on('Payment Order', {
|
|||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: "erpnext.accounts.doctype.payment_order.payment_order.make_payment_records",
|
method: "erpnext.accounts.doctype.payment_order.payment_order.make_payment_records",
|
||||||
args: {
|
args: {
|
||||||
"name": me.frm.doc.name,
|
"name": frm.doc.name,
|
||||||
"supplier": args.supplier,
|
"supplier": args.supplier,
|
||||||
"mode_of_payment": args.mode_of_payment
|
"mode_of_payment": args.mode_of_payment
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,7 @@ frappe.ui.form.on('Payment Term', {
|
|||||||
if (frm.doc.discount) {
|
if (frm.doc.discount) {
|
||||||
let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]);
|
let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]);
|
||||||
if (frm.doc.discount_type == 'Amount') {
|
if (frm.doc.discount_type == 'Amount') {
|
||||||
description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)]);
|
description = __("{0} will be given as discount.", [frm.doc.discount]);
|
||||||
}
|
}
|
||||||
frm.set_df_property("discount", "description", description);
|
frm.set_df_property("discount", "description", description);
|
||||||
}
|
}
|
||||||
|
@ -126,21 +126,22 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
def make_gl_entries(self, get_opening_entries=False):
|
def make_gl_entries(self, get_opening_entries=False):
|
||||||
gl_entries = self.get_gl_entries()
|
gl_entries = self.get_gl_entries()
|
||||||
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
||||||
if gl_entries:
|
if len(gl_entries) > 5000:
|
||||||
if len(gl_entries) > 5000:
|
frappe.enqueue(
|
||||||
frappe.enqueue(
|
process_gl_entries,
|
||||||
process_gl_entries,
|
gl_entries=gl_entries,
|
||||||
gl_entries=gl_entries,
|
closing_entries=closing_entries,
|
||||||
closing_entries=closing_entries,
|
voucher_name=self.name,
|
||||||
voucher_name=self.name,
|
company=self.company,
|
||||||
queue="long",
|
closing_date=self.posting_date,
|
||||||
)
|
queue="long",
|
||||||
frappe.msgprint(
|
)
|
||||||
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
frappe.msgprint(
|
||||||
alert=True,
|
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
||||||
)
|
alert=True,
|
||||||
else:
|
)
|
||||||
process_gl_entries(gl_entries, closing_entries, voucher_name=self.name)
|
else:
|
||||||
|
process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
|
||||||
|
|
||||||
def get_grouped_gl_entries(self, get_opening_entries=False):
|
def get_grouped_gl_entries(self, get_opening_entries=False):
|
||||||
closing_entries = []
|
closing_entries = []
|
||||||
@ -321,24 +322,22 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
return query.run(as_dict=1)
|
return query.run(as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
def process_gl_entries(gl_entries, closing_entries, voucher_name=None):
|
def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
||||||
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
||||||
make_closing_entries,
|
make_closing_entries,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
|
||||||
try:
|
try:
|
||||||
make_gl_entries(gl_entries, merge_entries=False)
|
if gl_entries:
|
||||||
make_closing_entries(gl_entries + closing_entries, voucher_name=voucher_name)
|
make_gl_entries(gl_entries, merge_entries=False)
|
||||||
frappe.db.set_value(
|
|
||||||
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
|
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
||||||
)
|
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
frappe.log_error(e)
|
frappe.log_error(e)
|
||||||
frappe.db.set_value(
|
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
|
||||||
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def make_reverse_gl_entries(voucher_type, voucher_no):
|
def make_reverse_gl_entries(voucher_type, voucher_no):
|
||||||
|
@ -49,6 +49,24 @@ class TestPOSClosingEntry(unittest.TestCase):
|
|||||||
self.assertEqual(pcv_doc.total_quantity, 2)
|
self.assertEqual(pcv_doc.total_quantity, 2)
|
||||||
self.assertEqual(pcv_doc.net_total, 6700)
|
self.assertEqual(pcv_doc.net_total, 6700)
|
||||||
|
|
||||||
|
def test_pos_closing_without_item_code(self):
|
||||||
|
"""
|
||||||
|
Test if POS Closing Entry is created without item code
|
||||||
|
"""
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||||
|
|
||||||
|
pos_inv = create_pos_invoice(
|
||||||
|
rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1
|
||||||
|
)
|
||||||
|
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
||||||
|
pos_inv.submit()
|
||||||
|
|
||||||
|
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
||||||
|
pcv_doc.submit()
|
||||||
|
|
||||||
|
self.assertTrue(pcv_doc.name)
|
||||||
|
|
||||||
def test_cancelling_of_pos_closing_entry(self):
|
def test_cancelling_of_pos_closing_entry(self):
|
||||||
test_user, pos_profile = init_user_and_profile()
|
test_user, pos_profile = init_user_and_profile()
|
||||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
{% include 'erpnext/selling/sales_common.js' %};
|
|
||||||
frappe.provide("erpnext.accounts");
|
frappe.provide("erpnext.accounts");
|
||||||
|
erpnext.sales_common.setup_selling_controller();
|
||||||
|
|
||||||
|
erpnext.accounts.pos.setup("POS Invoice");
|
||||||
erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnext.selling.SellingController {
|
erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnext.selling.SellingController {
|
||||||
settings = {};
|
settings = {};
|
||||||
|
|
||||||
|
@ -986,19 +986,34 @@ def create_pos_invoice(**args):
|
|||||||
msg = f"Serial No {args.serial_no} not available for Item {args.item}"
|
msg = f"Serial No {args.serial_no} not available for Item {args.item}"
|
||||||
frappe.throw(_(msg))
|
frappe.throw(_(msg))
|
||||||
|
|
||||||
pos_inv.append(
|
pos_invoice_item = {
|
||||||
"items",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
{
|
"qty": args.qty or 1,
|
||||||
"item_code": args.item or args.item_code or "_Test Item",
|
"rate": args.rate if args.get("rate") is not None else 100,
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"income_account": args.income_account or "Sales - _TC",
|
||||||
"qty": args.qty or 1,
|
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||||
"rate": args.rate if args.get("rate") is not None else 100,
|
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||||
"income_account": args.income_account or "Sales - _TC",
|
"serial_and_batch_bundle": bundle_id,
|
||||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
}
|
||||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
# append in pos invoice items without item_code by checking flag without_item_code
|
||||||
"serial_and_batch_bundle": bundle_id,
|
if args.without_item_code:
|
||||||
},
|
pos_inv.append(
|
||||||
)
|
"items",
|
||||||
|
{
|
||||||
|
**pos_invoice_item,
|
||||||
|
"item_name": args.item_name or "_Test Item",
|
||||||
|
"description": args.item_name or "_Test Item",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
pos_inv.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
**pos_invoice_item,
|
||||||
|
"item_code": args.item or args.item_code or "_Test Item",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if not args.do_not_save:
|
if not args.do_not_save:
|
||||||
pos_inv.insert()
|
pos_inv.insert()
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
|
||||||
|
|
||||||
frappe.ui.form.on('POS Profile', {
|
frappe.ui.form.on('POS Profile', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.set_query("selling_price_list", function() {
|
frm.set_query("selling_price_list", function() {
|
||||||
|
@ -140,7 +140,7 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency):
|
|||||||
def get_ar_filters(doc, entry):
|
def get_ar_filters(doc, entry):
|
||||||
return {
|
return {
|
||||||
"report_date": doc.posting_date if doc.posting_date else None,
|
"report_date": doc.posting_date if doc.posting_date else None,
|
||||||
"customer_name": entry.customer,
|
"customer": entry.customer,
|
||||||
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
||||||
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
||||||
"sales_person": doc.sales_person if doc.sales_person else None,
|
"sales_person": doc.sales_person if doc.sales_person else None,
|
||||||
|
@ -10,16 +10,12 @@
|
|||||||
|
|
||||||
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
|
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{% if (filters.customer_name) %}
|
{{ filters.customer }}
|
||||||
{{ filters.customer_name }}
|
|
||||||
{% else %}
|
|
||||||
{{ filters.customer ~ filters.supplier }}
|
|
||||||
{% endif %}
|
|
||||||
</h4>
|
</h4>
|
||||||
<h6 class="text-center">
|
<h6 class="text-center">
|
||||||
{% if (filters.tax_id) %}
|
{% if (filters.tax_id) %}
|
||||||
{{ _("Tax Id: ") }}{{ filters.tax_id }}
|
{{ _("Tax Id: ") }}{{ filters.tax_id }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h6>
|
</h6>
|
||||||
<h5 class="text-center">
|
<h5 class="text-center">
|
||||||
{{ _(filters.ageing_based_on) }}
|
{{ _(filters.ageing_based_on) }}
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.provide("erpnext.accounts");
|
frappe.provide("erpnext.accounts");
|
||||||
{% include 'erpnext/public/js/controllers/buying.js' %};
|
|
||||||
|
erpnext.accounts.payment_triggers.setup("Purchase Invoice");
|
||||||
|
erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
|
||||||
|
erpnext.accounts.taxes.setup_tax_validations("Purchase Invoice");
|
||||||
|
erpnext.buying.setup_buying_controller();
|
||||||
|
|
||||||
erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.BuyingController {
|
erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.BuyingController {
|
||||||
setup(doc) {
|
setup(doc) {
|
||||||
@ -97,12 +101,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
cur_frm.add_custom_button(__('Return / Debit Note'),
|
cur_frm.add_custom_button(__('Return / Debit Note'),
|
||||||
this.make_debit_note, __('Create'));
|
this.make_debit_note, __('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!doc.auto_repeat) {
|
|
||||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
|
||||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
|
||||||
}, __('Create'))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.outstanding_amount > 0 && !cint(doc.is_return) && !doc.on_hold) {
|
if (doc.outstanding_amount > 0 && !cint(doc.is_return) && !doc.on_hold) {
|
||||||
@ -506,7 +504,8 @@ frappe.ui.form.on("Purchase Invoice", {
|
|||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Purchase Invoice': 'Return / Debit Note',
|
'Purchase Invoice': 'Return / Debit Note',
|
||||||
'Payment Entry': 'Payment'
|
'Payment Entry': 'Payment',
|
||||||
|
'Landed Cost Voucher': function () { frm.trigger('create_landed_cost_voucher') },
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.set_query("additional_discount_account", function() {
|
frm.set_query("additional_discount_account", function() {
|
||||||
@ -544,6 +543,26 @@ frappe.ui.form.on("Purchase Invoice", {
|
|||||||
frm.events.add_custom_buttons(frm);
|
frm.events.add_custom_buttons(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mode_of_payment: function(frm) {
|
||||||
|
erpnext.accounts.pos.get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account) {
|
||||||
|
frm.set_value("cash_bank_account", account);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
create_landed_cost_voucher: function (frm) {
|
||||||
|
let lcv = frappe.model.get_new_doc('Landed Cost Voucher');
|
||||||
|
lcv.company = frm.doc.company;
|
||||||
|
|
||||||
|
let lcv_receipt = frappe.model.get_new_doc('Landed Cost Purchase Invoice');
|
||||||
|
lcv_receipt.receipt_document_type = 'Purchase Invoice';
|
||||||
|
lcv_receipt.receipt_document = frm.doc.name;
|
||||||
|
lcv_receipt.supplier = frm.doc.supplier;
|
||||||
|
lcv_receipt.grand_total = frm.doc.grand_total;
|
||||||
|
lcv.purchase_receipts = [lcv_receipt];
|
||||||
|
|
||||||
|
frappe.set_route("Form", lcv.doctype, lcv.name);
|
||||||
|
},
|
||||||
|
|
||||||
add_custom_buttons: function(frm) {
|
add_custom_buttons: function(frm) {
|
||||||
if (frm.doc.docstatus == 1 && frm.doc.per_received < 100) {
|
if (frm.doc.docstatus == 1 && frm.doc.per_received < 100) {
|
||||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||||
|
@ -443,7 +443,8 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Batch No",
|
"label": "Batch No",
|
||||||
"options": "Batch",
|
"options": "Batch",
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "col_br_wh",
|
"fieldname": "col_br_wh",
|
||||||
@ -890,7 +891,8 @@
|
|||||||
"label": "Serial and Batch Bundle",
|
"label": "Serial and Batch Bundle",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"print_hide": 1
|
"print_hide": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:parent.update_stock == 1",
|
"depends_on": "eval:parent.update_stock == 1",
|
||||||
@ -905,7 +907,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-04 17:22:21.501152",
|
"modified": "2023-07-26 12:54:53.178156",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
@ -1,30 +1,31 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
|
erpnext.accounts.taxes.setup_tax_validations("Purchase Taxes and Charges Template");
|
||||||
|
erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
|
||||||
|
|
||||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
frappe.ui.form.on("Purchase Taxes and Charges", {
|
||||||
|
add_deduct_tax(doc, cdt, cdn) {
|
||||||
|
let d = locals[cdt][cdn];
|
||||||
|
|
||||||
frappe.ui.form.on("Purchase Taxes and Charges", "add_deduct_tax", function(doc, cdt, cdn) {
|
if(!d.category && d.add_deduct_tax) {
|
||||||
var d = locals[cdt][cdn];
|
frappe.msgprint(__("Please select Category first"));
|
||||||
|
d.add_deduct_tax = '';
|
||||||
|
}
|
||||||
|
else if(d.category != 'Total' && d.add_deduct_tax == 'Deduct') {
|
||||||
|
frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Valuation and Total'"));
|
||||||
|
d.add_deduct_tax = '';
|
||||||
|
}
|
||||||
|
refresh_field('add_deduct_tax', d.name, 'taxes');
|
||||||
|
},
|
||||||
|
|
||||||
if(!d.category && d.add_deduct_tax) {
|
category(doc, cdt, cdn) {
|
||||||
frappe.msgprint(__("Please select Category first"));
|
let d = locals[cdt][cdn];
|
||||||
d.add_deduct_tax = '';
|
|
||||||
|
if(d.category != 'Total' && d.add_deduct_tax == 'Deduct') {
|
||||||
|
frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Valuation and Total'"));
|
||||||
|
d.add_deduct_tax = '';
|
||||||
|
}
|
||||||
|
refresh_field('add_deduct_tax', d.name, 'taxes');
|
||||||
}
|
}
|
||||||
else if(d.category != 'Total' && d.add_deduct_tax == 'Deduct') {
|
|
||||||
frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Valuation and Total'"));
|
|
||||||
d.add_deduct_tax = '';
|
|
||||||
}
|
|
||||||
refresh_field('add_deduct_tax', d.name, 'taxes');
|
|
||||||
});
|
|
||||||
|
|
||||||
frappe.ui.form.on("Purchase Taxes and Charges", "category", function(doc, cdt, cdn) {
|
|
||||||
var d = locals[cdt][cdn];
|
|
||||||
|
|
||||||
if (d.category != 'Total' && d.add_deduct_tax == 'Deduct') {
|
|
||||||
frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Vaulation and Total'"));
|
|
||||||
d.add_deduct_tax = '';
|
|
||||||
}
|
|
||||||
refresh_field('add_deduct_tax', d.name, 'taxes');
|
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,23 @@
|
|||||||
{% include "erpnext/regional/italy/sales_invoice.js" %}
|
frappe.ui.form.on("Sales Invoice", {
|
||||||
|
refresh: (frm) => {
|
||||||
erpnext.setup_e_invoice_button('Sales Invoice')
|
if(frm.doc.docstatus == 1) {
|
||||||
|
frm.add_custom_button(__('Generate E-Invoice'), () => {
|
||||||
|
frm.call({
|
||||||
|
method: "erpnext.regional.italy.utils.generate_single_invoice",
|
||||||
|
args: {
|
||||||
|
docname: frm.doc.name
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
frm.reload_doc();
|
||||||
|
if(r.message) {
|
||||||
|
open_url_post(frappe.request.url, {
|
||||||
|
cmd: 'frappe.core.doctype.file.file.download_file',
|
||||||
|
file_url: r.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -1,10 +1,13 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
{% include 'erpnext/selling/sales_common.js' %};
|
|
||||||
frappe.provide("erpnext.accounts");
|
frappe.provide("erpnext.accounts");
|
||||||
|
|
||||||
|
erpnext.accounts.taxes.setup_tax_validations("Sales Invoice");
|
||||||
|
erpnext.accounts.payment_triggers.setup("Sales Invoice");
|
||||||
|
erpnext.accounts.pos.setup("Sales Invoice");
|
||||||
|
erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges");
|
||||||
|
erpnext.sales_common.setup_selling_controller();
|
||||||
erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends erpnext.selling.SellingController {
|
erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends erpnext.selling.SellingController {
|
||||||
setup(doc) {
|
setup(doc) {
|
||||||
this.setup_posting_date_time_check();
|
this.setup_posting_date_time_check();
|
||||||
@ -142,9 +145,15 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
cur_frm.events.create_invoice_discounting(cur_frm);
|
cur_frm.events.create_invoice_discounting(cur_frm);
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
|
|
||||||
if (doc.due_date < frappe.datetime.get_today()) {
|
const payment_is_overdue = doc.payment_schedule.map(
|
||||||
cur_frm.add_custom_button(__('Dunning'), function() {
|
row => Date.parse(row.due_date) < Date.now()
|
||||||
cur_frm.events.create_dunning(cur_frm);
|
).reduce(
|
||||||
|
(prev, current) => prev || current
|
||||||
|
);
|
||||||
|
|
||||||
|
if (payment_is_overdue) {
|
||||||
|
this.frm.add_custom_button(__('Dunning'), () => {
|
||||||
|
this.frm.events.create_dunning(this.frm);
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,12 +163,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
cur_frm.cscript.make_maintenance_schedule();
|
cur_frm.cscript.make_maintenance_schedule();
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!doc.auto_repeat) {
|
|
||||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
|
||||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
|
||||||
}, __('Create'))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show buttons only when pos view is active
|
// Show buttons only when pos view is active
|
||||||
@ -711,7 +714,7 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
|
|
||||||
frm.set_query('pos_profile', function(doc) {
|
frm.set_query('pos_profile', function(doc) {
|
||||||
if(!doc.company) {
|
if(!doc.company) {
|
||||||
frappe.throw(_('Please set Company'));
|
frappe.throw(__('Please set Company'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -767,7 +770,6 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
|
|
||||||
update_stock: function(frm, dt, dn) {
|
update_stock: function(frm, dt, dn) {
|
||||||
frm.events.hide_fields(frm);
|
frm.events.hide_fields(frm);
|
||||||
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock);
|
|
||||||
frm.trigger('reset_posting_time');
|
frm.trigger('reset_posting_time');
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -858,7 +860,7 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
kwargs = Object();
|
kwargs = Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!kwargs.hasOwnProperty("project") && frm.doc.project) {
|
if (!Object.prototype.hasOwnProperty.call(kwargs, "project") && frm.doc.project) {
|
||||||
kwargs.project = frm.doc.project;
|
kwargs.project = frm.doc.project;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -891,6 +893,8 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
frm.events.append_time_log(frm, timesheet, 1.0);
|
frm.events.append_time_log(frm, timesheet, 1.0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
frm.refresh_field("timesheets");
|
||||||
|
frm.trigger("calculate_timesheet_totals");
|
||||||
},
|
},
|
||||||
|
|
||||||
async get_exchange_rate(frm, from_currency, to_currency) {
|
async get_exchange_rate(frm, from_currency, to_currency) {
|
||||||
@ -930,9 +934,6 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate);
|
row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate);
|
||||||
row.timesheet_detail = time_log.name;
|
row.timesheet_detail = time_log.name;
|
||||||
row.project_name = time_log.project_name;
|
row.project_name = time_log.project_name;
|
||||||
|
|
||||||
frm.refresh_field("timesheets");
|
|
||||||
frm.trigger("calculate_timesheet_totals");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
calculate_timesheet_totals: function(frm) {
|
calculate_timesheet_totals: function(frm) {
|
||||||
|
@ -113,7 +113,6 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
if cint(self.update_stock):
|
if cint(self.update_stock):
|
||||||
self.validate_dropship_item()
|
self.validate_dropship_item()
|
||||||
self.validate_item_code()
|
|
||||||
self.validate_warehouse()
|
self.validate_warehouse()
|
||||||
self.update_current_stock()
|
self.update_current_stock()
|
||||||
self.validate_delivery_note()
|
self.validate_delivery_note()
|
||||||
@ -854,11 +853,6 @@ class SalesInvoice(SellingController):
|
|||||||
):
|
):
|
||||||
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
|
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
|
||||||
|
|
||||||
def validate_item_code(self):
|
|
||||||
for d in self.get("items"):
|
|
||||||
if not d.item_code and self.is_opening == "No":
|
|
||||||
msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True)
|
|
||||||
|
|
||||||
def validate_warehouse(self):
|
def validate_warehouse(self):
|
||||||
super(SalesInvoice, self).validate_warehouse()
|
super(SalesInvoice, self).validate_warehouse()
|
||||||
|
|
||||||
@ -2516,55 +2510,49 @@ def get_mode_of_payment_info(mode_of_payment, company):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_dunning(source_name, target_doc=None):
|
def create_dunning(source_name, target_doc=None, ignore_permissions=False):
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
from erpnext.accounts.doctype.dunning.dunning import (
|
def postprocess_dunning(source, target):
|
||||||
calculate_interest_and_amount,
|
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text
|
||||||
get_dunning_letter_text,
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_missing_values(source, target):
|
dunning_type = frappe.db.exists("Dunning Type", {"is_default": 1, "company": source.company})
|
||||||
target.sales_invoice = source_name
|
if dunning_type:
|
||||||
target.outstanding_amount = source.outstanding_amount
|
dunning_type = frappe.get_doc("Dunning Type", dunning_type)
|
||||||
overdue_days = (getdate(target.posting_date) - getdate(source.due_date)).days
|
|
||||||
target.overdue_days = overdue_days
|
|
||||||
if frappe.db.exists(
|
|
||||||
"Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]}
|
|
||||||
):
|
|
||||||
dunning_type = frappe.get_doc(
|
|
||||||
"Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]}
|
|
||||||
)
|
|
||||||
target.dunning_type = dunning_type.name
|
target.dunning_type = dunning_type.name
|
||||||
target.rate_of_interest = dunning_type.rate_of_interest
|
target.rate_of_interest = dunning_type.rate_of_interest
|
||||||
target.dunning_fee = dunning_type.dunning_fee
|
target.dunning_fee = dunning_type.dunning_fee
|
||||||
letter_text = get_dunning_letter_text(dunning_type=dunning_type.name, doc=target.as_dict())
|
target.income_account = dunning_type.income_account
|
||||||
|
target.cost_center = dunning_type.cost_center
|
||||||
|
letter_text = get_dunning_letter_text(
|
||||||
|
dunning_type=dunning_type.name, doc=target.as_dict(), language=source.language
|
||||||
|
)
|
||||||
|
|
||||||
if letter_text:
|
if letter_text:
|
||||||
target.body_text = letter_text.get("body_text")
|
target.body_text = letter_text.get("body_text")
|
||||||
target.closing_text = letter_text.get("closing_text")
|
target.closing_text = letter_text.get("closing_text")
|
||||||
target.language = letter_text.get("language")
|
target.language = letter_text.get("language")
|
||||||
amounts = calculate_interest_and_amount(
|
|
||||||
target.outstanding_amount,
|
|
||||||
target.rate_of_interest,
|
|
||||||
target.dunning_fee,
|
|
||||||
target.overdue_days,
|
|
||||||
)
|
|
||||||
target.interest_amount = amounts.get("interest_amount")
|
|
||||||
target.dunning_amount = amounts.get("dunning_amount")
|
|
||||||
target.grand_total = amounts.get("grand_total")
|
|
||||||
|
|
||||||
doclist = get_mapped_doc(
|
target.validate()
|
||||||
"Sales Invoice",
|
|
||||||
source_name,
|
return get_mapped_doc(
|
||||||
{
|
from_doctype="Sales Invoice",
|
||||||
|
from_docname=source_name,
|
||||||
|
target_doc=target_doc,
|
||||||
|
table_maps={
|
||||||
"Sales Invoice": {
|
"Sales Invoice": {
|
||||||
"doctype": "Dunning",
|
"doctype": "Dunning",
|
||||||
}
|
"field_map": {"customer_address": "customer_address", "parent": "sales_invoice"},
|
||||||
|
},
|
||||||
|
"Payment Schedule": {
|
||||||
|
"doctype": "Overdue Payment",
|
||||||
|
"field_map": {"name": "payment_schedule", "parent": "sales_invoice"},
|
||||||
|
"condition": lambda doc: doc.outstanding > 0 and getdate(doc.due_date) < getdate(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
target_doc,
|
postprocess=postprocess_dunning,
|
||||||
set_missing_values,
|
ignore_permissions=ignore_permissions,
|
||||||
)
|
)
|
||||||
return doclist
|
|
||||||
|
|
||||||
|
|
||||||
def check_if_return_invoice_linked_with_payment_entry(self):
|
def check_if_return_invoice_linked_with_payment_entry(self):
|
||||||
|
@ -1900,16 +1900,22 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
si = self.create_si_to_test_tax_breakup()
|
si = self.create_si_to_test_tax_breakup()
|
||||||
|
|
||||||
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
|
itemised_tax_data = get_itemised_tax_breakup_data(si)
|
||||||
|
|
||||||
expected_itemised_tax = {
|
expected_itemised_tax = [
|
||||||
"_Test Item": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0}},
|
{
|
||||||
"_Test Item 2": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0}},
|
"item": "_Test Item",
|
||||||
}
|
"taxable_amount": 10000.0,
|
||||||
expected_itemised_taxable_amount = {"_Test Item": 10000.0, "_Test Item 2": 5000.0}
|
"Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "_Test Item 2",
|
||||||
|
"taxable_amount": 5000.0,
|
||||||
|
"Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
self.assertEqual(itemised_tax, expected_itemised_tax)
|
self.assertEqual(itemised_tax_data, expected_itemised_tax)
|
||||||
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
|
|
||||||
|
|
||||||
frappe.flags.country = None
|
frappe.flags.country = None
|
||||||
|
|
||||||
|
@ -604,7 +604,8 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Batch No",
|
"label": "Batch No",
|
||||||
"options": "Batch",
|
"options": "Batch",
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "col_break5",
|
"fieldname": "col_break5",
|
||||||
@ -894,13 +895,14 @@
|
|||||||
"label": "Serial and Batch Bundle",
|
"label": "Serial and Batch Bundle",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"print_hide": 1
|
"print_hide": 1,
|
||||||
|
"search_index": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-12 13:42:24.303113",
|
"modified": "2023-07-26 12:53:22.404057",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
cur_frm.cscript.tax_table = "Sales Taxes and Charges";
|
erpnext.accounts.taxes.setup_tax_validations("Sales Taxes and Charges Template");
|
||||||
|
erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges");
|
||||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
|
@ -13,14 +13,11 @@ import erpnext
|
|||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
|
||||||
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
||||||
from erpnext.accounts.utils import create_payment_ledger_entry
|
from erpnext.accounts.utils import create_payment_ledger_entry
|
||||||
|
|
||||||
|
|
||||||
class ClosedAccountingPeriod(frappe.ValidationError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def make_gl_entries(
|
def make_gl_entries(
|
||||||
gl_map,
|
gl_map,
|
||||||
cancel=False,
|
cancel=False,
|
||||||
@ -166,7 +163,8 @@ def process_gl_map(gl_map, merge_entries=True, precision=None):
|
|||||||
if not gl_map:
|
if not gl_map:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
|
if gl_map[0].voucher_type != "Period Closing Voucher":
|
||||||
|
gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
|
||||||
|
|
||||||
if merge_entries:
|
if merge_entries:
|
||||||
gl_map = merge_similar_entries(gl_map, precision)
|
gl_map = merge_similar_entries(gl_map, precision)
|
||||||
|
@ -33,6 +33,7 @@ import erpnext
|
|||||||
from erpnext import get_company_currency
|
from erpnext import get_company_currency
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
|
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
|
||||||
|
from erpnext.utilities.regional import temporary_flag
|
||||||
|
|
||||||
PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
|
PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
|
||||||
SALES_TRANSACTION_TYPES = {
|
SALES_TRANSACTION_TYPES = {
|
||||||
@ -261,9 +262,8 @@ def set_address_details(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if doctype in TRANSACTION_TYPES:
|
if doctype in TRANSACTION_TYPES:
|
||||||
# required to set correct region
|
with temporary_flag("company", company):
|
||||||
frappe.flags.company = company
|
get_regional_address_details(party_details, doctype, company)
|
||||||
get_regional_address_details(party_details, doctype, company)
|
|
||||||
|
|
||||||
return party_address, shipping_address
|
return party_address, shipping_address
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"absolute_value": 0,
|
||||||
"align_labels_right": 0,
|
"align_labels_right": 0,
|
||||||
"creation": "2019-12-11 04:37:14.012805",
|
"creation": "2019-12-11 04:37:14.012805",
|
||||||
"css": ".print-format th {\n background-color: transparent !important;\n border-bottom: 1px solid !important;\n border-top: none !important;\n}\n.print-format .ql-editor {\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.print-format table {\n margin-bottom: 0px;\n }\n.print-format .table-data tr:last-child { \n border-bottom: 1px solid !important;\n}\n\n.print-format .table-inner tr:last-child {\n border-bottom:none !important;\n}\n.print-format .table-inner {\n margin: 0px 0px;\n}\n\n.print-format .table-data ul li { \n color:#787878 !important;\n}\n\n.no-top-border {\n border-top:none !important;\n}\n\n.table-inner td {\n padding-left: 0px !important; \n padding-top: 1px !important;\n padding-bottom: 1px !important;\n color:#787878 !important;\n}\n\n.total {\n background-color: lightgrey !important;\n padding-top: 4px !important;\n padding-bottom: 4px !important;\n}\n",
|
"css": ".print-format th {\n background-color: transparent !important;\n border-bottom: 1px solid !important;\n border-top: none !important;\n}\n.print-format .ql-editor {\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.print-format table {\n margin-bottom: 0px;\n }\n.print-format .table-data tr:last-child { \n border-bottom: 1px solid !important;\n}\n\n.print-format .table-inner tr:last-child {\n border-bottom:none !important;\n}\n.print-format .table-inner {\n margin: 0px 0px;\n}\n\n.print-format .table-data ul li { \n color:#787878 !important;\n}\n\n.no-top-border {\n border-top:none !important;\n}\n\n.table-inner td {\n padding-left: 0px !important; \n padding-top: 1px !important;\n padding-bottom: 1px !important;\n color:#787878 !important;\n}\n\n.total {\n background-color: lightgrey !important;\n padding-top: 4px !important;\n padding-bottom: 4px !important;\n}\n",
|
||||||
@ -9,10 +10,10 @@
|
|||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Print Format",
|
"doctype": "Print Format",
|
||||||
"font": "Arial",
|
"font": "Arial",
|
||||||
"format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<b>{{doc.customer_name}}</b> <br />\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<div style=\\\"text-align:left;\\\">\\n<div style=\\\"font-size:24px; text-transform:uppercase;\\\">{{_(doc.dunning_type)}}</div>\\n<div style=\\\"font-size:16px;padding-bottom:5px;\\\">{{ doc.name }}</div>\\n</div>\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldname\": \"sales_invoice\", \"print_hide\": 0, \"label\": \"Sales Invoice\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"label\": \"Due Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<table class=\\\"table table-borderless table-data\\\">\\n <tbody>\\n <tr>\\n <th>{{_(\\\"Description\\\")}}</th>\\n\\t <th style=\\\"text-align: right;\\\">{{_(\\\"Amount\\\")}}</th>\\n </tr>\\n <tr>\\n <td>\\n {{_(\\\"Outstanding Amount\\\")}}\\n </td>\\n <td style=\\\"text-align: right;\\\">\\n {{doc.get_formatted(\\\"outstanding_amount\\\")}}\\n </td>\\n </tr>\\n {%if doc.rate_of_interest > 0%}\\n <tr>\\n <td>\\n {{_(\\\"Interest \\\")}} {{doc.rate_of_interest}}% p.a. ({{doc.overdue_days}} {{_(\\\"days\\\")}})\\n </td>\\n <td style=\\\"text-align: right;\\\">\\n {{doc.get_formatted(\\\"interest_amount\\\")}}\\n </td>\\n </tr>\\n {% endif %}\\n {%if doc.dunning_fee > 0%}\\n <tr>\\n <td>\\n {{_(\\\"Dunning Fee\\\")}}\\n </td>\\n <td style=\\\"text-align: right;\\\">\\n {{doc.get_formatted(\\\"dunning_fee\\\")}}\\n </td>\\n </tr>\\n {% endif %}\\n </tbody>\\n</table>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n<div class=\\\"row total\\\" style =\\\"margin-right: 0px;\\\">\\n\\t\\t<div class=\\\"col-xs-5\\\">\\n\\t\\t\\t<b>{{_(\\\"Grand Total\\\")}}</b></div>\\n\\t\\t<div class=\\\"col-xs-7 text-right\\\" style=\\\"padding-right: 4px;\\\">\\n\\t\\t\\t<b>{{doc.get_formatted(\\\"grand_total\\\")}}</b>\\n\\t\\t</div>\\n</div>\\n\\n\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]",
|
"format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<b>{{doc.customer_name}}</b> <br />\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<div style=\\\"text-align:left;\\\">\\n<div style=\\\"font-size:24px; text-transform:uppercase;\\\">{{_(doc.dunning_type)}}</div>\\n<div style=\\\"font-size:16px;padding-bottom:5px;\\\">{{ doc.name }}</div>\\n</div>\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"overdue_payments\", \"print_hide\": 0, \"label\": \"Overdue Payments\", \"visible_columns\": [{\"fieldname\": \"sales_invoice\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"dunning_level\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"due_date\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"overdue_days\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"invoice_portion\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"outstanding\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"interest\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total_outstanding\", \"print_hide\": 0, \"label\": \"Total Outstanding\"}, {\"fieldname\": \"dunning_fee\", \"print_hide\": 0, \"label\": \"Dunning Fee\"}, {\"fieldname\": \"total_interest\", \"print_hide\": 0, \"label\": \"Total Interest\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"line_breaks": 0,
|
"line_breaks": 0,
|
||||||
"modified": "2020-07-14 18:25:44.348207",
|
"modified": "2021-09-30 10:22:02.603871",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Dunning Letter",
|
"name": "Dunning Letter",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports["Account Balance"] = {
|
frappe.query_reports["Account Balance"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 1,
|
||||||
"apply_user_permissions": 1,
|
"columns": [],
|
||||||
"creation": "2016-04-08 14:49:58.133098",
|
"creation": "2016-04-08 14:49:58.133098",
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
"idx": 2,
|
"idx": 2,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"modified": "2017-02-24 20:08:26.084484",
|
"letterhead": null,
|
||||||
|
"modified": "2023-07-26 21:05:33.554778",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Asset Depreciation Ledger",
|
"name": "Asset Depreciation Ledger",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
"ref_doctype": "Asset",
|
"ref_doctype": "Asset",
|
||||||
"report_name": "Asset Depreciation Ledger",
|
"report_name": "Asset Depreciation Ledger",
|
||||||
"report_type": "Script Report",
|
"report_type": "Script Report",
|
||||||
|
@ -15,14 +15,14 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
|
|||||||
"fieldname":"from_date",
|
"fieldname":"from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"to_date",
|
"fieldname":"to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 1,
|
||||||
"apply_user_permissions": 1,
|
"columns": [],
|
||||||
"creation": "2016-04-08 14:56:37.235981",
|
"creation": "2016-04-08 14:56:37.235981",
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
"idx": 2,
|
"idx": 2,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"modified": "2017-02-24 20:08:18.660476",
|
"letterhead": null,
|
||||||
|
"modified": "2023-07-26 21:04:54.751077",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Asset Depreciations and Balances",
|
"name": "Asset Depreciations and Balances",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
"ref_doctype": "Asset",
|
"ref_doctype": "Asset",
|
||||||
"report_name": "Asset Depreciations and Balances",
|
"report_name": "Asset Depreciations and Balances",
|
||||||
"report_type": "Script Report",
|
"report_type": "Script Report",
|
||||||
|
@ -7,7 +7,7 @@ frappe.query_reports["Bank Clearance Summary"] = {
|
|||||||
"fieldname":"from_date",
|
"fieldname":"from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
"width": "80"
|
"width": "80"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports['Billed Items To Be Received'] = {
|
frappe.query_reports['Billed Items To Be Received'] = {
|
||||||
'filters': [
|
'filters': [
|
||||||
@ -17,7 +17,7 @@ frappe.query_reports['Billed Items To Be Received'] = {
|
|||||||
'fieldname': 'posting_date',
|
'fieldname': 'posting_date',
|
||||||
'fieldtype': 'Date',
|
'fieldtype': 'Date',
|
||||||
'reqd': 1,
|
'reqd': 1,
|
||||||
'default': get_today()
|
'default': frappe.datetime.get_today()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'label': __('Purchase Invoice'),
|
'label': __('Purchase Invoice'),
|
||||||
|
@ -8,7 +8,7 @@ frappe.query_reports["Budget Variance Report"] = {
|
|||||||
label: __("From Fiscal Year"),
|
label: __("From Fiscal Year"),
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Fiscal Year",
|
options: "Fiscal Year",
|
||||||
default: frappe.sys_defaults.fiscal_year,
|
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
reqd: 1
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -16,7 +16,7 @@ frappe.query_reports["Budget Variance Report"] = {
|
|||||||
label: __("To Fiscal Year"),
|
label: __("To Fiscal Year"),
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Fiscal Year",
|
options: "Fiscal Year",
|
||||||
default: frappe.sys_defaults.fiscal_year,
|
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
reqd: 1
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||||
frappe.query_reports["Consolidated Financial Statement"] = {
|
frappe.query_reports["Consolidated Financial Statement"] = {
|
||||||
@ -49,7 +49,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"label": __("Start Year"),
|
"label": __("Start Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
on_change: () => {
|
on_change: () => {
|
||||||
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) {
|
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) {
|
||||||
@ -65,7 +65,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"label": __("End Year"),
|
"label": __("End Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
on_change: () => {
|
on_change: () => {
|
||||||
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) {
|
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) {
|
||||||
@ -139,7 +139,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
onload: function() {
|
onload: function() {
|
||||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
|
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||||
|
|
||||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||||
|
@ -720,7 +720,7 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
|||||||
additional_conditions = []
|
additional_conditions = []
|
||||||
|
|
||||||
if ignore_closing_entries:
|
if ignore_closing_entries:
|
||||||
additional_conditions.append("ifnull(gl.voucher_type, '')!='Period Closing Voucher'")
|
additional_conditions.append("gl.voucher_type != 'Period Closing Voucher'")
|
||||||
|
|
||||||
if from_date:
|
if from_date:
|
||||||
additional_conditions.append("gl.posting_date >= %(from_date)s")
|
additional_conditions.append("gl.posting_date >= %(from_date)s")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports["Customer Ledger Summary"] = {
|
frappe.query_reports["Customer Ledger Summary"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
function get_filters() {
|
function get_filters() {
|
||||||
let filters = [
|
let filters = [
|
||||||
@ -48,7 +48,7 @@ function get_filters() {
|
|||||||
"label": __("Start Year"),
|
"label": __("Start Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -56,7 +56,7 @@ function get_filters() {
|
|||||||
"label": __("End Year"),
|
"label": __("End Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -100,7 +100,7 @@ frappe.query_reports["Deferred Revenue and Expense"] = {
|
|||||||
return default_formatter(value, row, column, data);
|
return default_formatter(value, row, column, data);
|
||||||
},
|
},
|
||||||
onload: function(report){
|
onload: function(report){
|
||||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year");
|
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||||
|
|
||||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||||
|
@ -4,9 +4,10 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, qb
|
from frappe import _, qb
|
||||||
from frappe.query_builder import Column, functions
|
from frappe.query_builder import Column, functions
|
||||||
from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, rounded
|
from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, getdate, rounded
|
||||||
|
|
||||||
from erpnext.accounts.report.financial_statements import get_period_list
|
from erpnext.accounts.report.financial_statements import get_period_list
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
|
|
||||||
class Deferred_Item(object):
|
class Deferred_Item(object):
|
||||||
@ -226,7 +227,7 @@ class Deferred_Revenue_and_Expense_Report(object):
|
|||||||
|
|
||||||
# If no filters are provided, get user defaults
|
# If no filters are provided, get user defaults
|
||||||
if not filters:
|
if not filters:
|
||||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date=getdate()))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
|
@ -2,6 +2,7 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import create_account
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
@ -10,15 +11,15 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
|||||||
from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
|
from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
|
||||||
Deferred_Revenue_and_Expense_Report,
|
Deferred_Revenue_and_Expense_Report,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
|
|
||||||
class TestDeferredRevenueAndExpense(unittest.TestCase):
|
class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(self):
|
||||||
clear_accounts_and_items()
|
|
||||||
create_company()
|
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
|
|
||||||
def clear_old_entries(self):
|
def clear_old_entries(self):
|
||||||
@ -50,55 +51,58 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
if deferred_invoices:
|
if deferred_invoices:
|
||||||
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
|
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
|
||||||
|
|
||||||
def test_deferred_revenue(self):
|
def setup_deferred_accounts_and_items(self):
|
||||||
self.clear_old_entries()
|
# created deferred expense accounts, if not found
|
||||||
|
self.deferred_revenue_account = create_account(
|
||||||
|
account_name="Deferred Revenue",
|
||||||
|
parent_account="Current Liabilities - " + self.company_abbr,
|
||||||
|
company=self.company,
|
||||||
|
)
|
||||||
|
|
||||||
# created deferred expense accounts, if not found
|
# created deferred expense accounts, if not found
|
||||||
deferred_revenue_account = create_account(
|
self.deferred_expense_account = create_account(
|
||||||
account_name="Deferred Revenue",
|
account_name="Deferred Expense",
|
||||||
parent_account="Current Liabilities - _CD",
|
parent_account="Current Assets - " + self.company_abbr,
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
)
|
)
|
||||||
|
|
||||||
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
def setUp(self):
|
||||||
acc_settings.book_deferred_entries_based_on = "Months"
|
self.create_company()
|
||||||
acc_settings.save()
|
self.create_customer("_Test Customer")
|
||||||
|
self.create_supplier("_Test Furniture Supplier")
|
||||||
|
self.setup_deferred_accounts_and_items()
|
||||||
|
self.clear_old_entries()
|
||||||
|
|
||||||
customer = frappe.new_doc("Customer")
|
def tearDown(self):
|
||||||
customer.customer_name = "_Test Customer DR"
|
frappe.db.rollback()
|
||||||
customer.type = "Individual"
|
|
||||||
customer.insert()
|
|
||||||
|
|
||||||
item = create_item(
|
@change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"})
|
||||||
"_Test Internet Subscription",
|
def test_deferred_revenue(self):
|
||||||
is_stock_item=0,
|
self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company)
|
||||||
warehouse="All Warehouses - _CD",
|
item = frappe.get_doc("Item", self.item)
|
||||||
company="_Test Company DR",
|
|
||||||
)
|
|
||||||
item.enable_deferred_revenue = 1
|
item.enable_deferred_revenue = 1
|
||||||
item.deferred_revenue_account = deferred_revenue_account
|
item.deferred_revenue_account = self.deferred_revenue_account
|
||||||
item.no_of_months = 3
|
item.no_of_months = 3
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
item=item.name,
|
item=self.item,
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
customer="_Test Customer DR",
|
customer=self.customer,
|
||||||
debit_to="Debtors - _CD",
|
debit_to=self.debit_to,
|
||||||
posting_date="2021-05-01",
|
posting_date="2021-05-01",
|
||||||
parent_cost_center="Main - _CD",
|
parent_cost_center=self.cost_center,
|
||||||
cost_center="Main - _CD",
|
cost_center=self.cost_center,
|
||||||
do_not_save=True,
|
do_not_save=True,
|
||||||
rate=300,
|
rate=300,
|
||||||
price_list_rate=300,
|
price_list_rate=300,
|
||||||
)
|
)
|
||||||
|
|
||||||
si.items[0].income_account = "Sales - _CD"
|
si.items[0].income_account = self.income_account
|
||||||
si.items[0].enable_deferred_revenue = 1
|
si.items[0].enable_deferred_revenue = 1
|
||||||
si.items[0].service_start_date = "2021-05-01"
|
si.items[0].service_start_date = "2021-05-01"
|
||||||
si.items[0].service_end_date = "2021-08-01"
|
si.items[0].service_end_date = "2021-08-01"
|
||||||
si.items[0].deferred_revenue_account = deferred_revenue_account
|
si.items[0].deferred_revenue_account = self.deferred_revenue_account
|
||||||
si.items[0].income_account = "Sales - _CD"
|
|
||||||
si.save()
|
si.save()
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
@ -109,17 +113,17 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
start_date="2021-05-01",
|
start_date="2021-05-01",
|
||||||
end_date="2021-08-01",
|
end_date="2021-08-01",
|
||||||
type="Income",
|
type="Income",
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pda.insert()
|
pda.insert()
|
||||||
pda.submit()
|
pda.submit()
|
||||||
|
|
||||||
# execute report
|
# execute report
|
||||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": self.company,
|
||||||
"filter_based_on": "Date Range",
|
"filter_based_on": "Date Range",
|
||||||
"period_start_date": "2021-05-01",
|
"period_start_date": "2021-05-01",
|
||||||
"period_end_date": "2021-08-01",
|
"period_end_date": "2021-08-01",
|
||||||
@ -141,57 +145,36 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(report.period_total, expected)
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"})
|
||||||
def test_deferred_expense(self):
|
def test_deferred_expense(self):
|
||||||
self.clear_old_entries()
|
self.create_item("_Test Office Desk", 0, self.warehouse, self.company)
|
||||||
|
item = frappe.get_doc("Item", self.item)
|
||||||
# created deferred expense accounts, if not found
|
|
||||||
deferred_expense_account = create_account(
|
|
||||||
account_name="Deferred Expense",
|
|
||||||
parent_account="Current Assets - _CD",
|
|
||||||
company="_Test Company DR",
|
|
||||||
)
|
|
||||||
|
|
||||||
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
|
||||||
acc_settings.book_deferred_entries_based_on = "Months"
|
|
||||||
acc_settings.save()
|
|
||||||
|
|
||||||
supplier = create_supplier(
|
|
||||||
supplier_name="_Test Furniture Supplier", supplier_group="Local", supplier_type="Company"
|
|
||||||
)
|
|
||||||
supplier.save()
|
|
||||||
|
|
||||||
item = create_item(
|
|
||||||
"_Test Office Desk",
|
|
||||||
is_stock_item=0,
|
|
||||||
warehouse="All Warehouses - _CD",
|
|
||||||
company="_Test Company DR",
|
|
||||||
)
|
|
||||||
item.enable_deferred_expense = 1
|
item.enable_deferred_expense = 1
|
||||||
item.deferred_expense_account = deferred_expense_account
|
item.deferred_expense_account = self.deferred_expense_account
|
||||||
item.no_of_months_exp = 3
|
item.no_of_months_exp = 3
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
pi = make_purchase_invoice(
|
pi = make_purchase_invoice(
|
||||||
item=item.name,
|
item=self.item,
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
supplier="_Test Furniture Supplier",
|
supplier=self.supplier,
|
||||||
is_return=False,
|
is_return=False,
|
||||||
update_stock=False,
|
update_stock=False,
|
||||||
posting_date=frappe.utils.datetime.date(2021, 5, 1),
|
posting_date=frappe.utils.datetime.date(2021, 5, 1),
|
||||||
parent_cost_center="Main - _CD",
|
parent_cost_center=self.cost_center,
|
||||||
cost_center="Main - _CD",
|
cost_center=self.cost_center,
|
||||||
do_not_save=True,
|
do_not_save=True,
|
||||||
rate=300,
|
rate=300,
|
||||||
price_list_rate=300,
|
price_list_rate=300,
|
||||||
warehouse="All Warehouses - _CD",
|
warehouse=self.warehouse,
|
||||||
qty=1,
|
qty=1,
|
||||||
)
|
)
|
||||||
pi.set_posting_time = True
|
pi.set_posting_time = True
|
||||||
pi.items[0].enable_deferred_expense = 1
|
pi.items[0].enable_deferred_expense = 1
|
||||||
pi.items[0].service_start_date = "2021-05-01"
|
pi.items[0].service_start_date = "2021-05-01"
|
||||||
pi.items[0].service_end_date = "2021-08-01"
|
pi.items[0].service_end_date = "2021-08-01"
|
||||||
pi.items[0].deferred_expense_account = deferred_expense_account
|
pi.items[0].deferred_expense_account = self.deferred_expense_account
|
||||||
pi.items[0].expense_account = "Office Maintenance Expenses - _CD"
|
pi.items[0].expense_account = self.expense_account
|
||||||
pi.save()
|
pi.save()
|
||||||
pi.submit()
|
pi.submit()
|
||||||
|
|
||||||
@ -202,17 +185,17 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
start_date="2021-05-01",
|
start_date="2021-05-01",
|
||||||
end_date="2021-08-01",
|
end_date="2021-08-01",
|
||||||
type="Expense",
|
type="Expense",
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pda.insert()
|
pda.insert()
|
||||||
pda.submit()
|
pda.submit()
|
||||||
|
|
||||||
# execute report
|
# execute report
|
||||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": self.company,
|
||||||
"filter_based_on": "Date Range",
|
"filter_based_on": "Date Range",
|
||||||
"period_start_date": "2021-05-01",
|
"period_start_date": "2021-05-01",
|
||||||
"period_end_date": "2021-08-01",
|
"period_end_date": "2021-08-01",
|
||||||
@ -234,52 +217,31 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(report.period_total, expected)
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"})
|
||||||
def test_zero_months(self):
|
def test_zero_months(self):
|
||||||
self.clear_old_entries()
|
self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company)
|
||||||
# created deferred expense accounts, if not found
|
item = frappe.get_doc("Item", self.item)
|
||||||
deferred_revenue_account = create_account(
|
|
||||||
account_name="Deferred Revenue",
|
|
||||||
parent_account="Current Liabilities - _CD",
|
|
||||||
company="_Test Company DR",
|
|
||||||
)
|
|
||||||
|
|
||||||
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
|
||||||
acc_settings.book_deferred_entries_based_on = "Months"
|
|
||||||
acc_settings.save()
|
|
||||||
|
|
||||||
customer = frappe.new_doc("Customer")
|
|
||||||
customer.customer_name = "_Test Customer DR"
|
|
||||||
customer.type = "Individual"
|
|
||||||
customer.insert()
|
|
||||||
|
|
||||||
item = create_item(
|
|
||||||
"_Test Internet Subscription",
|
|
||||||
is_stock_item=0,
|
|
||||||
warehouse="All Warehouses - _CD",
|
|
||||||
company="_Test Company DR",
|
|
||||||
)
|
|
||||||
item.enable_deferred_revenue = 1
|
item.enable_deferred_revenue = 1
|
||||||
item.deferred_revenue_account = deferred_revenue_account
|
item.deferred_revenue_account = self.deferred_revenue_account
|
||||||
item.no_of_months = 0
|
item.no_of_months = 0
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
item=item.name,
|
item=item.name,
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
customer="_Test Customer DR",
|
customer=self.customer,
|
||||||
debit_to="Debtors - _CD",
|
debit_to=self.debit_to,
|
||||||
posting_date="2021-05-01",
|
posting_date="2021-05-01",
|
||||||
parent_cost_center="Main - _CD",
|
parent_cost_center=self.cost_center,
|
||||||
cost_center="Main - _CD",
|
cost_center=self.cost_center,
|
||||||
do_not_save=True,
|
do_not_save=True,
|
||||||
rate=300,
|
rate=300,
|
||||||
price_list_rate=300,
|
price_list_rate=300,
|
||||||
)
|
)
|
||||||
|
|
||||||
si.items[0].enable_deferred_revenue = 1
|
si.items[0].enable_deferred_revenue = 1
|
||||||
si.items[0].income_account = "Sales - _CD"
|
si.items[0].income_account = self.income_account
|
||||||
si.items[0].deferred_revenue_account = deferred_revenue_account
|
si.items[0].deferred_revenue_account = self.deferred_revenue_account
|
||||||
si.items[0].income_account = "Sales - _CD"
|
|
||||||
si.save()
|
si.save()
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
@ -290,17 +252,17 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
start_date="2021-05-01",
|
start_date="2021-05-01",
|
||||||
end_date="2021-08-01",
|
end_date="2021-08-01",
|
||||||
type="Income",
|
type="Income",
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pda.insert()
|
pda.insert()
|
||||||
pda.submit()
|
pda.submit()
|
||||||
|
|
||||||
# execute report
|
# execute report
|
||||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": self.company,
|
||||||
"filter_based_on": "Date Range",
|
"filter_based_on": "Date Range",
|
||||||
"period_start_date": "2021-05-01",
|
"period_start_date": "2021-05-01",
|
||||||
"period_end_date": "2021-08-01",
|
"period_end_date": "2021-08-01",
|
||||||
@ -321,30 +283,3 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
{"key": "aug_2021", "total": 0, "actual": 0},
|
{"key": "aug_2021", "total": 0, "actual": 0},
|
||||||
]
|
]
|
||||||
self.assertEqual(report.period_total, expected)
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
|
|
||||||
def create_company():
|
|
||||||
company = frappe.db.exists("Company", "_Test Company DR")
|
|
||||||
if not company:
|
|
||||||
company = frappe.new_doc("Company")
|
|
||||||
company.company_name = "_Test Company DR"
|
|
||||||
company.default_currency = "INR"
|
|
||||||
company.chart_of_accounts = "Standard"
|
|
||||||
company.insert()
|
|
||||||
|
|
||||||
|
|
||||||
def clear_accounts_and_items():
|
|
||||||
item = qb.DocType("Item")
|
|
||||||
account = qb.DocType("Account")
|
|
||||||
customer = qb.DocType("Customer")
|
|
||||||
supplier = qb.DocType("Supplier")
|
|
||||||
|
|
||||||
qb.from_(account).delete().where(
|
|
||||||
(account.account_name == "Deferred Revenue")
|
|
||||||
| (account.account_name == "Deferred Expense") & (account.company == "_Test Company DR")
|
|
||||||
).run()
|
|
||||||
qb.from_(item).delete().where(
|
|
||||||
(item.item_code == "_Test Internet Subscription") | (item.item_code == "_Test Office Rent")
|
|
||||||
).run()
|
|
||||||
qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run()
|
|
||||||
qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run()
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||||
frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
|
frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
|
||||||
@ -18,7 +18,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"label": __("Fiscal Year"),
|
"label": __("Fiscal Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"on_change": function(query_report) {
|
"on_change": function(query_report) {
|
||||||
var fiscal_year = query_report.get_values().fiscal_year;
|
var fiscal_year = query_report.get_values().fiscal_year;
|
||||||
@ -38,14 +38,14 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "to_date",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -416,6 +416,7 @@ def set_gl_entries_by_account(
|
|||||||
filters,
|
filters,
|
||||||
gl_entries_by_account,
|
gl_entries_by_account,
|
||||||
ignore_closing_entries=False,
|
ignore_closing_entries=False,
|
||||||
|
ignore_opening_entries=False,
|
||||||
):
|
):
|
||||||
"""Returns a dict like { "account": [gl entries], ... }"""
|
"""Returns a dict like { "account": [gl entries], ... }"""
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
@ -426,14 +427,19 @@ def set_gl_entries_by_account(
|
|||||||
pluck="name",
|
pluck="name",
|
||||||
)
|
)
|
||||||
|
|
||||||
ignore_opening_entries = False
|
|
||||||
if accounts_list:
|
if accounts_list:
|
||||||
# For balance sheet
|
# For balance sheet
|
||||||
if not from_date:
|
ignore_closing_balances = frappe.db.get_single_value(
|
||||||
from_date = filters["period_start_date"]
|
"Accounts Settings", "ignore_account_closing_balance"
|
||||||
|
)
|
||||||
|
if not from_date and not ignore_closing_balances:
|
||||||
last_period_closing_voucher = frappe.db.get_all(
|
last_period_closing_voucher = frappe.db.get_all(
|
||||||
"Period Closing Voucher",
|
"Period Closing Voucher",
|
||||||
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", from_date)},
|
filters={
|
||||||
|
"docstatus": 1,
|
||||||
|
"company": filters.company,
|
||||||
|
"posting_date": ("<", filters["period_start_date"]),
|
||||||
|
},
|
||||||
fields=["posting_date", "name"],
|
fields=["posting_date", "name"],
|
||||||
order_by="posting_date desc",
|
order_by="posting_date desc",
|
||||||
limit=1,
|
limit=1,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports["Gross and Net Profit Report"] = {
|
frappe.query_reports["Gross and Net Profit Report"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
@ -12,14 +12,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
erpnext.financial_statements);
|
erpnext.financial_statements);
|
||||||
|
|
||||||
frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
|
frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
|
||||||
{
|
|
||||||
"fieldname": "project",
|
|
||||||
"label": __("Project"),
|
|
||||||
"fieldtype": "MultiSelectList",
|
|
||||||
get_data: function(txt) {
|
|
||||||
return frappe.db.get_link_options('Project', txt);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "accumulated_values",
|
"fieldname": "accumulated_values",
|
||||||
"label": __("Accumulated Values"),
|
"label": __("Accumulated Values"),
|
||||||
|
@ -15,14 +15,14 @@ frappe.query_reports["Gross Profit"] = {
|
|||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "to_date",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports["Inactive Sales Items"] = {
|
frappe.query_reports["Inactive Sales Items"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
|
@ -309,7 +309,8 @@ def get_conditions(filters):
|
|||||||
|
|
||||||
def get_items(filters, additional_query_columns):
|
def get_items(filters, additional_query_columns):
|
||||||
conditions = get_conditions(filters)
|
conditions = get_conditions(filters)
|
||||||
|
if additional_query_columns:
|
||||||
|
additional_query_columns = "," + ",".join(additional_query_columns)
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
|
@ -381,7 +381,8 @@ def get_group_by_conditions(filters, doctype):
|
|||||||
|
|
||||||
def get_items(filters, additional_query_columns, additional_conditions=None):
|
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||||
conditions = get_conditions(filters, additional_conditions)
|
conditions = get_conditions(filters, additional_conditions)
|
||||||
|
if additional_query_columns:
|
||||||
|
additional_query_columns = "," + ",".join(additional_query_columns)
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
function get_filters() {
|
function get_filters() {
|
||||||
let filters = [
|
let filters = [
|
||||||
|
@ -15,7 +15,7 @@ frappe.query_reports["Payment Period Based On Invoice Date"] = {
|
|||||||
fieldname: "from_date",
|
fieldname: "from_date",
|
||||||
label: __("From Date"),
|
label: __("From Date"),
|
||||||
fieldtype: "Date",
|
fieldtype: "Date",
|
||||||
default: frappe.defaults.get_user_default("year_start_date"),
|
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname:"to_date",
|
fieldname:"to_date",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports["POS Register"] = {
|
frappe.query_reports["POS Register"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
|
@ -50,20 +50,20 @@ def get_pos_entries(filters, group_by_field):
|
|||||||
order_by = "p.posting_date"
|
order_by = "p.posting_date"
|
||||||
select_mop_field, from_sales_invoice_payment, group_by_mop_condition = "", "", ""
|
select_mop_field, from_sales_invoice_payment, group_by_mop_condition = "", "", ""
|
||||||
if group_by_field == "mode_of_payment":
|
if group_by_field == "mode_of_payment":
|
||||||
select_mop_field = ", sip.mode_of_payment"
|
select_mop_field = ", sip.mode_of_payment, sip.base_amount - IF(sip.type='Cash', p.change_amount, 0) as paid_amount"
|
||||||
from_sales_invoice_payment = ", `tabSales Invoice Payment` sip"
|
from_sales_invoice_payment = ", `tabSales Invoice Payment` sip"
|
||||||
group_by_mop_condition = "sip.parent = p.name AND ifnull(sip.base_amount, 0) != 0 AND"
|
group_by_mop_condition = "sip.parent = p.name AND ifnull(sip.base_amount - IF(sip.type='Cash', p.change_amount, 0), 0) != 0 AND"
|
||||||
order_by += ", sip.mode_of_payment"
|
order_by += ", sip.mode_of_payment"
|
||||||
|
|
||||||
elif group_by_field:
|
elif group_by_field:
|
||||||
order_by += ", p.{}".format(group_by_field)
|
order_by += ", p.{}".format(group_by_field)
|
||||||
|
select_mop_field = ", p.base_paid_amount - p.change_amount as paid_amount "
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
p.posting_date, p.name as pos_invoice, p.pos_profile,
|
p.posting_date, p.name as pos_invoice, p.pos_profile,
|
||||||
p.owner, p.base_grand_total as grand_total, p.base_paid_amount - p.change_amount as paid_amount,
|
p.owner, p.customer, p.is_return, p.base_grand_total as grand_total {select_mop_field}
|
||||||
p.customer, p.is_return {select_mop_field}
|
|
||||||
FROM
|
FROM
|
||||||
`tabPOS Invoice` p {from_sales_invoice_payment}
|
`tabPOS Invoice` p {from_sales_invoice_payment}
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -9,16 +9,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
erpnext.utils.add_dimensions('Profit and Loss Statement', 10);
|
erpnext.utils.add_dimensions('Profit and Loss Statement', 10);
|
||||||
|
|
||||||
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
|
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
|
||||||
{
|
|
||||||
"fieldname": "project",
|
|
||||||
"label": __("Project"),
|
|
||||||
"fieldtype": "MultiSelectList",
|
|
||||||
get_data: function(txt) {
|
|
||||||
return frappe.db.get_link_options('Project', txt, {
|
|
||||||
company: frappe.query_report.get_filter_value("company")
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "include_default_book_entries",
|
"fieldname": "include_default_book_entries",
|
||||||
"label": __("Include Default Book Entries"),
|
"label": __("Include Default Book Entries"),
|
||||||
|
@ -16,16 +16,37 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"fieldname": "based_on",
|
"fieldname": "based_on",
|
||||||
"label": __("Based On"),
|
"label": __("Based On"),
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"options": ["Cost Center", "Project"],
|
"options": ["Cost Center", "Project", "Accounting Dimension"],
|
||||||
"default": "Cost Center",
|
"default": "Cost Center",
|
||||||
"reqd": 1
|
"reqd": 1,
|
||||||
|
"on_change": function(query_report){
|
||||||
|
let based_on = query_report.get_values().based_on;
|
||||||
|
if(based_on!='Accounting Dimension'){
|
||||||
|
frappe.query_report.set_filter_value({
|
||||||
|
accounting_dimension: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "accounting_dimension",
|
||||||
|
"label": __("Accounting Dimension"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Accounting Dimension",
|
||||||
|
"get_query": () =>{
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"disabled": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "fiscal_year",
|
"fieldname": "fiscal_year",
|
||||||
"label": __("Fiscal Year"),
|
"label": __("Fiscal Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"on_change": function(query_report) {
|
"on_change": function(query_report) {
|
||||||
var fiscal_year = query_report.get_values().fiscal_year;
|
var fiscal_year = query_report.get_values().fiscal_year;
|
||||||
@ -45,13 +66,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "to_date",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "show_zero_values",
|
"fieldname": "show_zero_values",
|
||||||
|
@ -6,6 +6,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cstr, flt
|
from frappe.utils import cstr, flt
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
|
||||||
from erpnext.accounts.report.financial_statements import (
|
from erpnext.accounts.report.financial_statements import (
|
||||||
filter_accounts,
|
filter_accounts,
|
||||||
filter_out_zero_value_rows,
|
filter_out_zero_value_rows,
|
||||||
@ -16,10 +17,12 @@ value_fields = ("income", "expense", "gross_profit_loss")
|
|||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
if not filters.get("based_on"):
|
if filters.get("based_on") == "Accounting Dimension" and not filters.get("accounting_dimension"):
|
||||||
filters["based_on"] = "Cost Center"
|
frappe.throw(_("Select Accounting Dimension."))
|
||||||
|
|
||||||
based_on = filters.based_on.replace(" ", "_").lower()
|
based_on = (
|
||||||
|
filters.based_on if filters.based_on != "Accounting Dimension" else filters.accounting_dimension
|
||||||
|
)
|
||||||
validate_filters(filters)
|
validate_filters(filters)
|
||||||
accounts = get_accounts_data(based_on, filters.get("company"))
|
accounts = get_accounts_data(based_on, filters.get("company"))
|
||||||
data = get_data(accounts, filters, based_on)
|
data = get_data(accounts, filters, based_on)
|
||||||
@ -28,14 +31,14 @@ def execute(filters=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_accounts_data(based_on, company):
|
def get_accounts_data(based_on, company):
|
||||||
if based_on == "cost_center":
|
if based_on == "Cost Center":
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt
|
"""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt
|
||||||
from `tabCost Center` where company=%s order by name""",
|
from `tabCost Center` where company=%s order by name""",
|
||||||
company,
|
company,
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
elif based_on == "project":
|
elif based_on == "Project":
|
||||||
return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name")
|
return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name")
|
||||||
else:
|
else:
|
||||||
filters = {}
|
filters = {}
|
||||||
@ -56,11 +59,17 @@ def get_data(accounts, filters, based_on):
|
|||||||
|
|
||||||
gl_entries_by_account = {}
|
gl_entries_by_account = {}
|
||||||
|
|
||||||
|
accounting_dimensions = get_dimensions(with_cost_center_and_project=True)[0]
|
||||||
|
fieldname = ""
|
||||||
|
for dimension in accounting_dimensions:
|
||||||
|
if dimension["document_type"] == based_on:
|
||||||
|
fieldname = dimension["fieldname"]
|
||||||
|
|
||||||
set_gl_entries_by_account(
|
set_gl_entries_by_account(
|
||||||
filters.get("company"),
|
filters.get("company"),
|
||||||
filters.get("from_date"),
|
filters.get("from_date"),
|
||||||
filters.get("to_date"),
|
filters.get("to_date"),
|
||||||
based_on,
|
fieldname,
|
||||||
gl_entries_by_account,
|
gl_entries_by_account,
|
||||||
ignore_closing_entries=not flt(filters.get("with_period_closing_entry")),
|
ignore_closing_entries=not flt(filters.get("with_period_closing_entry")),
|
||||||
)
|
)
|
||||||
@ -199,7 +208,7 @@ def set_gl_entries_by_account(
|
|||||||
additional_conditions = []
|
additional_conditions = []
|
||||||
|
|
||||||
if ignore_closing_entries:
|
if ignore_closing_entries:
|
||||||
additional_conditions.append("and ifnull(voucher_type, '')!='Period Closing Voucher'")
|
additional_conditions.append("and voucher_type !='Period Closing Voucher'")
|
||||||
|
|
||||||
if from_date:
|
if from_date:
|
||||||
additional_conditions.append("and posting_date >= %(from_date)s")
|
additional_conditions.append("and posting_date >= %(from_date)s")
|
||||||
|
@ -52,6 +52,12 @@ frappe.query_reports["Purchase Register"] = {
|
|||||||
"label": __("Item Group"),
|
"label": __("Item Group"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Item Group"
|
"options": "Item Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "include_payments",
|
||||||
|
"label": __("Show Ledger View"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,22 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.utils import flt
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
|
from frappe.utils import flt, getdate
|
||||||
|
from pypika import Order
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.party import get_party_account
|
||||||
get_accounting_dimensions,
|
from erpnext.accounts.report.utils import (
|
||||||
get_dimension_with_children,
|
get_advance_taxes_and_charges,
|
||||||
|
get_conditions,
|
||||||
|
get_journal_entries,
|
||||||
|
get_opening_row,
|
||||||
|
get_party_details,
|
||||||
|
get_payment_entries,
|
||||||
|
get_query_columns,
|
||||||
|
get_taxes_query,
|
||||||
|
get_values_for_columns,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@ -21,9 +30,15 @@ def _execute(filters=None, additional_table_columns=None):
|
|||||||
if not filters:
|
if not filters:
|
||||||
filters = {}
|
filters = {}
|
||||||
|
|
||||||
|
include_payments = filters.get("include_payments")
|
||||||
|
if filters.get("include_payments") and not filters.get("supplier"):
|
||||||
|
frappe.throw(_("Please select a supplier for fetching payments."))
|
||||||
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
||||||
|
if filters.get("include_payments"):
|
||||||
|
invoice_list += get_payments(filters)
|
||||||
|
|
||||||
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
||||||
invoice_list, additional_table_columns
|
invoice_list, additional_table_columns, include_payments
|
||||||
)
|
)
|
||||||
|
|
||||||
if not invoice_list:
|
if not invoice_list:
|
||||||
@ -33,14 +48,28 @@ def _execute(filters=None, additional_table_columns=None):
|
|||||||
invoice_expense_map = get_invoice_expense_map(invoice_list)
|
invoice_expense_map = get_invoice_expense_map(invoice_list)
|
||||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||||
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(
|
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(
|
||||||
invoice_list, invoice_expense_map, expense_accounts
|
invoice_list, invoice_expense_map, expense_accounts, include_payments
|
||||||
)
|
)
|
||||||
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
||||||
suppliers = list(set(d.supplier for d in invoice_list))
|
suppliers = list(set(d.supplier for d in invoice_list))
|
||||||
supplier_details = get_supplier_details(suppliers)
|
supplier_details = get_party_details("Supplier", suppliers)
|
||||||
|
|
||||||
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
||||||
|
|
||||||
|
res = []
|
||||||
|
if include_payments:
|
||||||
|
opening_row = get_opening_row(
|
||||||
|
"Supplier", filters.supplier, getdate(filters.from_date), filters.company
|
||||||
|
)[0]
|
||||||
|
res.append(
|
||||||
|
{
|
||||||
|
"payable_account": opening_row.account,
|
||||||
|
"debit": flt(opening_row.debit),
|
||||||
|
"credit": flt(opening_row.credit),
|
||||||
|
"balance": flt(opening_row.balance),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for inv in invoice_list:
|
for inv in invoice_list:
|
||||||
# invoice details
|
# invoice details
|
||||||
@ -48,24 +77,23 @@ def _execute(filters=None, additional_table_columns=None):
|
|||||||
purchase_receipt = list(set(invoice_po_pr_map.get(inv.name, {}).get("purchase_receipt", [])))
|
purchase_receipt = list(set(invoice_po_pr_map.get(inv.name, {}).get("purchase_receipt", [])))
|
||||||
project = list(set(invoice_po_pr_map.get(inv.name, {}).get("project", [])))
|
project = list(set(invoice_po_pr_map.get(inv.name, {}).get("project", [])))
|
||||||
|
|
||||||
row = [
|
row = {
|
||||||
inv.name,
|
"voucher_type": inv.doctype,
|
||||||
inv.posting_date,
|
"voucher_no": inv.name,
|
||||||
inv.supplier,
|
"posting_date": inv.posting_date,
|
||||||
inv.supplier_name,
|
"supplier_id": inv.supplier,
|
||||||
*get_values_for_columns(additional_table_columns, inv).values(),
|
"supplier_name": inv.supplier_name,
|
||||||
supplier_details.get(inv.supplier), # supplier_group
|
**get_values_for_columns(additional_table_columns, inv),
|
||||||
inv.tax_id,
|
"supplier_group": supplier_details.get(inv.supplier).get("supplier_group"),
|
||||||
inv.credit_to,
|
"tax_id": supplier_details.get(inv.supplier).get("tax_id"),
|
||||||
inv.mode_of_payment,
|
"payable_account": inv.credit_to,
|
||||||
", ".join(project),
|
"mode_of_payment": inv.mode_of_payment,
|
||||||
inv.bill_no,
|
"project": ", ".join(project) if inv.doctype == "Purchase Invoice" else inv.project,
|
||||||
inv.bill_date,
|
"remarks": inv.remarks,
|
||||||
inv.remarks,
|
"purchase_order": ", ".join(purchase_order),
|
||||||
", ".join(purchase_order),
|
"purchase_receipt": ", ".join(purchase_receipt),
|
||||||
", ".join(purchase_receipt),
|
"currency": company_currency,
|
||||||
company_currency,
|
}
|
||||||
]
|
|
||||||
|
|
||||||
# map expense values
|
# map expense values
|
||||||
base_net_total = 0
|
base_net_total = 0
|
||||||
@ -75,14 +103,16 @@ def _execute(filters=None, additional_table_columns=None):
|
|||||||
else:
|
else:
|
||||||
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
||||||
base_net_total += expense_amount
|
base_net_total += expense_amount
|
||||||
row.append(expense_amount)
|
row.update({frappe.scrub(expense_acc): expense_amount})
|
||||||
|
|
||||||
# Add amount in unrealized account
|
# Add amount in unrealized account
|
||||||
for account in unrealized_profit_loss_accounts:
|
for account in unrealized_profit_loss_accounts:
|
||||||
row.append(flt(internal_invoice_map.get((inv.name, account))))
|
row.update(
|
||||||
|
{frappe.scrub(account + "_unrealized"): flt(internal_invoice_map.get((inv.name, account)))}
|
||||||
|
)
|
||||||
|
|
||||||
# net total
|
# net total
|
||||||
row.append(base_net_total or inv.base_net_total)
|
row.update({"net_total": base_net_total or inv.base_net_total})
|
||||||
|
|
||||||
# tax account
|
# tax account
|
||||||
total_tax = 0
|
total_tax = 0
|
||||||
@ -90,45 +120,190 @@ def _execute(filters=None, additional_table_columns=None):
|
|||||||
if tax_acc not in expense_accounts:
|
if tax_acc not in expense_accounts:
|
||||||
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc))
|
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc))
|
||||||
total_tax += tax_amount
|
total_tax += tax_amount
|
||||||
row.append(tax_amount)
|
row.update({frappe.scrub(tax_acc): tax_amount})
|
||||||
|
|
||||||
# total tax, grand total, rounded total & outstanding amount
|
# total tax, grand total, rounded total & outstanding amount
|
||||||
row += [total_tax, inv.base_grand_total, flt(inv.base_grand_total, 0), inv.outstanding_amount]
|
row.update(
|
||||||
|
{
|
||||||
|
"total_tax": total_tax,
|
||||||
|
"grand_total": inv.base_grand_total,
|
||||||
|
"rounded_total": inv.base_rounded_total,
|
||||||
|
"outstanding_amount": inv.outstanding_amount,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if inv.doctype == "Purchase Invoice":
|
||||||
|
row.update({"debit": inv.base_grand_total, "credit": 0.0})
|
||||||
|
else:
|
||||||
|
row.update({"debit": 0.0, "credit": inv.base_grand_total})
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
return columns, data
|
res += sorted(data, key=lambda x: x["posting_date"])
|
||||||
|
|
||||||
|
if include_payments:
|
||||||
|
running_balance = flt(opening_row.balance)
|
||||||
|
for row in range(1, len(res)):
|
||||||
|
running_balance += res[row]["debit"] - res[row]["credit"]
|
||||||
|
res[row].update({"balance": running_balance})
|
||||||
|
|
||||||
|
return columns, res, None, None, None, include_payments
|
||||||
|
|
||||||
|
|
||||||
def get_columns(invoice_list, additional_table_columns):
|
def get_columns(invoice_list, additional_table_columns, include_payments=False):
|
||||||
"""return columns based on filters"""
|
"""return columns based on filters"""
|
||||||
columns = [
|
columns = [
|
||||||
_("Invoice") + ":Link/Purchase Invoice:120",
|
{
|
||||||
_("Posting Date") + ":Date:80",
|
"label": _("Voucher Type"),
|
||||||
_("Supplier Id") + "::120",
|
"fieldname": "voucher_type",
|
||||||
_("Supplier Name") + "::120",
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Voucher"),
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"options": "voucher_type",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
|
||||||
|
{
|
||||||
|
"label": _("Supplier"),
|
||||||
|
"fieldname": "supplier_id",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Supplier",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 120},
|
||||||
]
|
]
|
||||||
|
|
||||||
if additional_table_columns:
|
if additional_table_columns and not include_payments:
|
||||||
columns += additional_table_columns
|
columns += additional_table_columns
|
||||||
|
|
||||||
columns += [
|
if not include_payments:
|
||||||
_("Supplier Group") + ":Link/Supplier Group:120",
|
columns += [
|
||||||
_("Tax Id") + "::80",
|
{
|
||||||
_("Payable Account") + ":Link/Account:120",
|
"label": _("Supplier Group"),
|
||||||
_("Mode of Payment") + ":Link/Mode of Payment:80",
|
"fieldname": "supplier_group",
|
||||||
_("Project") + ":Link/Project:80",
|
"fieldtype": "Link",
|
||||||
_("Bill No") + "::120",
|
"options": "Supplier Group",
|
||||||
_("Bill Date") + ":Date:80",
|
"width": 120,
|
||||||
_("Remarks") + "::150",
|
},
|
||||||
_("Purchase Order") + ":Link/Purchase Order:100",
|
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 80},
|
||||||
_("Purchase Receipt") + ":Link/Purchase Receipt:100",
|
{
|
||||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
"label": _("Payable Account"),
|
||||||
]
|
"fieldname": "payable_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Account",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Mode Of Payment"),
|
||||||
|
"fieldname": "mode_of_payment",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Project"),
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Project",
|
||||||
|
"width": 80,
|
||||||
|
},
|
||||||
|
{"label": _("Bill No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 120},
|
||||||
|
{"label": _("Bill Date"), "fieldname": "bill_date", "fieldtype": "Date", "width": 80},
|
||||||
|
{
|
||||||
|
"label": _("Purchase Order"),
|
||||||
|
"fieldname": "purchase_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Purchase Order",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Purchase Receipt"),
|
||||||
|
"fieldname": "purchase_receipt",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Purchase Receipt",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
columns += [
|
||||||
|
{
|
||||||
|
"fieldname": "payable_account",
|
||||||
|
"label": _("Payable Account"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Account",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 120},
|
||||||
|
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 120},
|
||||||
|
{"fieldname": "balance", "label": _("Balance"), "fieldtype": "Currency", "width": 120},
|
||||||
|
]
|
||||||
|
|
||||||
|
account_columns, accounts = get_account_columns(invoice_list, include_payments)
|
||||||
|
|
||||||
|
columns = (
|
||||||
|
columns
|
||||||
|
+ account_columns[0]
|
||||||
|
+ account_columns[1]
|
||||||
|
+ [
|
||||||
|
{
|
||||||
|
"label": _("Net Total"),
|
||||||
|
"fieldname": "net_total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
+ account_columns[2]
|
||||||
|
+ [
|
||||||
|
{
|
||||||
|
"label": _("Total Tax"),
|
||||||
|
"fieldname": "total_tax",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not include_payments:
|
||||||
|
columns += [
|
||||||
|
{
|
||||||
|
"label": _("Grand Total"),
|
||||||
|
"fieldname": "grand_total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Rounded Total"),
|
||||||
|
"fieldname": "rounded_total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Outstanding Amount"),
|
||||||
|
"fieldname": "outstanding_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
columns += [{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 120}]
|
||||||
|
return columns, accounts[0], accounts[2], accounts[1]
|
||||||
|
|
||||||
|
|
||||||
|
def get_account_columns(invoice_list, include_payments):
|
||||||
expense_accounts = []
|
expense_accounts = []
|
||||||
tax_accounts = []
|
tax_accounts = []
|
||||||
unrealized_profit_loss_accounts = []
|
unrealized_profit_loss_accounts = []
|
||||||
|
|
||||||
|
expense_columns = []
|
||||||
|
tax_columns = []
|
||||||
|
unrealized_profit_loss_account_columns = []
|
||||||
|
|
||||||
if invoice_list:
|
if invoice_list:
|
||||||
expense_accounts = frappe.db.sql_list(
|
expense_accounts = frappe.db.sql_list(
|
||||||
"""select distinct expense_account
|
"""select distinct expense_account
|
||||||
@ -139,15 +314,18 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
tuple([inv.name for inv in invoice_list]),
|
tuple([inv.name for inv in invoice_list]),
|
||||||
)
|
)
|
||||||
|
|
||||||
tax_accounts = frappe.db.sql_list(
|
purchase_taxes_query = get_taxes_query(
|
||||||
"""select distinct account_head
|
invoice_list, "Purchase Taxes and Charges", "Purchase Invoice"
|
||||||
from `tabPurchase Taxes and Charges` where parenttype = 'Purchase Invoice'
|
|
||||||
and docstatus = 1 and (account_head is not null and account_head != '')
|
|
||||||
and category in ('Total', 'Valuation and Total')
|
|
||||||
and parent in (%s) order by account_head"""
|
|
||||||
% ", ".join(["%s"] * len(invoice_list)),
|
|
||||||
tuple(inv.name for inv in invoice_list),
|
|
||||||
)
|
)
|
||||||
|
purchase_tax_accounts = purchase_taxes_query.run(as_dict=True, pluck="account_head")
|
||||||
|
tax_accounts = purchase_tax_accounts
|
||||||
|
|
||||||
|
if include_payments:
|
||||||
|
advance_taxes_query = get_taxes_query(
|
||||||
|
invoice_list, "Advance Taxes and Charges", "Payment Entry"
|
||||||
|
)
|
||||||
|
advance_tax_accounts = advance_taxes_query.run(as_dict=True, pluck="account_head")
|
||||||
|
tax_accounts = set(tax_accounts + advance_tax_accounts)
|
||||||
|
|
||||||
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
||||||
"""SELECT distinct unrealized_profit_loss_account
|
"""SELECT distinct unrealized_profit_loss_account
|
||||||
@ -158,107 +336,102 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
tuple(inv.name for inv in invoice_list),
|
tuple(inv.name for inv in invoice_list),
|
||||||
)
|
)
|
||||||
|
|
||||||
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
for account in expense_accounts:
|
||||||
unrealized_profit_loss_account_columns = [
|
expense_columns.append(
|
||||||
(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts
|
{
|
||||||
]
|
"label": account,
|
||||||
tax_columns = [
|
"fieldname": frappe.scrub(account),
|
||||||
(account + ":Currency/currency:120")
|
"fieldtype": "Currency",
|
||||||
for account in tax_accounts
|
"options": "currency",
|
||||||
if account not in expense_accounts
|
"width": 120,
|
||||||
]
|
}
|
||||||
|
)
|
||||||
|
|
||||||
columns = (
|
for account in tax_accounts:
|
||||||
columns
|
if account not in expense_accounts:
|
||||||
+ expense_columns
|
tax_columns.append(
|
||||||
+ unrealized_profit_loss_account_columns
|
{
|
||||||
+ [_("Net Total") + ":Currency/currency:120"]
|
"label": account,
|
||||||
+ tax_columns
|
"fieldname": frappe.scrub(account),
|
||||||
+ [
|
"fieldtype": "Currency",
|
||||||
_("Total Tax") + ":Currency/currency:120",
|
"options": "currency",
|
||||||
_("Grand Total") + ":Currency/currency:120",
|
"width": 120,
|
||||||
_("Rounded Total") + ":Currency/currency:120",
|
}
|
||||||
_("Outstanding Amount") + ":Currency/currency:120",
|
)
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
|
for account in unrealized_profit_loss_accounts:
|
||||||
|
unrealized_profit_loss_account_columns.append(
|
||||||
|
{
|
||||||
|
"label": account,
|
||||||
|
"fieldname": frappe.scrub(account),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
columns = [expense_columns, unrealized_profit_loss_account_columns, tax_columns]
|
||||||
|
accounts = [expense_accounts, unrealized_profit_loss_accounts, tax_accounts]
|
||||||
|
|
||||||
def get_conditions(filters):
|
return columns, accounts
|
||||||
conditions = ""
|
|
||||||
|
|
||||||
if filters.get("company"):
|
|
||||||
conditions += " and company=%(company)s"
|
|
||||||
if filters.get("supplier"):
|
|
||||||
conditions += " and supplier = %(supplier)s"
|
|
||||||
|
|
||||||
if filters.get("from_date"):
|
|
||||||
conditions += " and posting_date>=%(from_date)s"
|
|
||||||
if filters.get("to_date"):
|
|
||||||
conditions += " and posting_date<=%(to_date)s"
|
|
||||||
|
|
||||||
if filters.get("mode_of_payment"):
|
|
||||||
conditions += " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"
|
|
||||||
|
|
||||||
if filters.get("cost_center"):
|
|
||||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
|
||||||
where parent=`tabPurchase Invoice`.name
|
|
||||||
and ifnull(`tabPurchase Invoice Item`.cost_center, '') = %(cost_center)s)"""
|
|
||||||
|
|
||||||
if filters.get("warehouse"):
|
|
||||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
|
||||||
where parent=`tabPurchase Invoice`.name
|
|
||||||
and ifnull(`tabPurchase Invoice Item`.warehouse, '') = %(warehouse)s)"""
|
|
||||||
|
|
||||||
if filters.get("item_group"):
|
|
||||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
|
||||||
where parent=`tabPurchase Invoice`.name
|
|
||||||
and ifnull(`tabPurchase Invoice Item`.item_group, '') = %(item_group)s)"""
|
|
||||||
|
|
||||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
|
||||||
|
|
||||||
if accounting_dimensions:
|
|
||||||
common_condition = """
|
|
||||||
and exists(select name from `tabPurchase Invoice Item`
|
|
||||||
where parent=`tabPurchase Invoice`.name
|
|
||||||
"""
|
|
||||||
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 += (
|
|
||||||
common_condition
|
|
||||||
+ "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
conditions += (
|
|
||||||
common_condition
|
|
||||||
+ "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
|
||||||
)
|
|
||||||
|
|
||||||
return conditions
|
|
||||||
|
|
||||||
|
|
||||||
def get_invoices(filters, additional_query_columns):
|
def get_invoices(filters, additional_query_columns):
|
||||||
conditions = get_conditions(filters)
|
pi = frappe.qb.DocType("Purchase Invoice")
|
||||||
return frappe.db.sql(
|
invoice_item = frappe.qb.DocType("Purchase Invoice Item")
|
||||||
"""
|
query = (
|
||||||
select
|
frappe.qb.from_(pi)
|
||||||
name, posting_date, credit_to, supplier, supplier_name, tax_id, bill_no, bill_date,
|
.inner_join(invoice_item)
|
||||||
remarks, base_net_total, base_grand_total, outstanding_amount,
|
.on(pi.name == invoice_item.parent)
|
||||||
mode_of_payment {0}
|
.select(
|
||||||
from `tabPurchase Invoice`
|
ConstantColumn("Purchase Invoice").as_("doctype"),
|
||||||
where docstatus = 1 {1}
|
pi.name,
|
||||||
order by posting_date desc, name desc""".format(
|
pi.posting_date,
|
||||||
additional_query_columns, conditions
|
pi.credit_to,
|
||||||
),
|
pi.supplier,
|
||||||
filters,
|
pi.supplier_name,
|
||||||
as_dict=1,
|
pi.tax_id,
|
||||||
|
pi.bill_no,
|
||||||
|
pi.bill_date,
|
||||||
|
pi.remarks,
|
||||||
|
pi.base_net_total,
|
||||||
|
pi.base_grand_total,
|
||||||
|
pi.outstanding_amount,
|
||||||
|
pi.mode_of_payment,
|
||||||
|
)
|
||||||
|
.where((pi.docstatus == 1))
|
||||||
|
.orderby(pi.posting_date, pi.name, order=Order.desc)
|
||||||
)
|
)
|
||||||
|
if additional_query_columns:
|
||||||
|
for col in additional_query_columns:
|
||||||
|
query = query.select(col)
|
||||||
|
if filters.get("supplier"):
|
||||||
|
query = query.where(pi.supplier == filters.supplier)
|
||||||
|
query = get_conditions(
|
||||||
|
filters, query, doctype="Purchase Invoice", child_doctype="Purchase Invoice Item"
|
||||||
|
)
|
||||||
|
if filters.get("include_payments"):
|
||||||
|
party_account = get_party_account(
|
||||||
|
"Supplier", filters.get("supplier"), filters.get("company"), include_advance=True
|
||||||
|
)
|
||||||
|
query = query.where(pi.credit_to.isin(party_account))
|
||||||
|
invoices = query.run(as_dict=True)
|
||||||
|
return invoices
|
||||||
|
|
||||||
|
|
||||||
|
def get_payments(filters):
|
||||||
|
args = frappe._dict(
|
||||||
|
account="credit_to",
|
||||||
|
account_fieldname="paid_to",
|
||||||
|
party="supplier",
|
||||||
|
party_name="supplier_name",
|
||||||
|
party_account=get_party_account(
|
||||||
|
"Supplier", filters.supplier, filters.company, include_advance=True
|
||||||
|
),
|
||||||
|
)
|
||||||
|
payment_entries = get_payment_entries(filters, args)
|
||||||
|
journal_entries = get_journal_entries(filters, args)
|
||||||
|
return payment_entries + journal_entries
|
||||||
|
|
||||||
|
|
||||||
def get_invoice_expense_map(invoice_list):
|
def get_invoice_expense_map(invoice_list):
|
||||||
@ -300,7 +473,9 @@ def get_internal_invoice_map(invoice_list):
|
|||||||
return internal_invoice_map
|
return internal_invoice_map
|
||||||
|
|
||||||
|
|
||||||
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
def get_invoice_tax_map(
|
||||||
|
invoice_list, invoice_expense_map, expense_accounts, include_payments=False
|
||||||
|
):
|
||||||
tax_details = frappe.db.sql(
|
tax_details = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
|
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
|
||||||
@ -315,6 +490,9 @@ def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
|||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if include_payments:
|
||||||
|
tax_details += get_advance_taxes_and_charges(invoice_list)
|
||||||
|
|
||||||
invoice_tax_map = {}
|
invoice_tax_map = {}
|
||||||
for d in tax_details:
|
for d in tax_details:
|
||||||
if d.account_head in expense_accounts:
|
if d.account_head in expense_accounts:
|
||||||
@ -382,17 +560,3 @@ def get_account_details(invoice_list):
|
|||||||
account_map[acc.name] = acc.parent_account
|
account_map[acc.name] = acc.parent_account
|
||||||
|
|
||||||
return account_map
|
return account_map
|
||||||
|
|
||||||
|
|
||||||
def get_supplier_details(suppliers):
|
|
||||||
supplier_details = {}
|
|
||||||
for supp in frappe.db.sql(
|
|
||||||
"""select name, supplier_group from `tabSupplier`
|
|
||||||
where name in (%s)"""
|
|
||||||
% ", ".join(["%s"] * len(suppliers)),
|
|
||||||
tuple(suppliers),
|
|
||||||
as_dict=1,
|
|
||||||
):
|
|
||||||
supplier_details.setdefault(supp.name, supp.supplier_group)
|
|
||||||
|
|
||||||
return supplier_details
|
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# MIT License. See license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
from frappe.utils import add_months, getdate, today
|
||||||
|
|
||||||
|
from erpnext.accounts.report.purchase_register.purchase_register import execute
|
||||||
|
|
||||||
|
|
||||||
|
class TestPurchaseRegister(FrappeTestCase):
|
||||||
|
def test_purchase_register(self):
|
||||||
|
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
|
||||||
|
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")
|
||||||
|
|
||||||
|
filters = frappe._dict(
|
||||||
|
company="_Test Company 6", from_date=add_months(today(), -1), to_date=today()
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
report_results = execute(filters)
|
||||||
|
first_row = frappe._dict(report_results[1][0])
|
||||||
|
self.assertEqual(first_row.voucher_type, "Purchase Invoice")
|
||||||
|
self.assertEqual(first_row.voucher_no, pi.name)
|
||||||
|
self.assertEqual(first_row.payable_account, "Creditors - _TC6")
|
||||||
|
self.assertEqual(first_row.net_total, 1000)
|
||||||
|
self.assertEqual(first_row.total_tax, 100)
|
||||||
|
self.assertEqual(first_row.grand_total, 1100)
|
||||||
|
|
||||||
|
def test_purchase_register_ledger_view(self):
|
||||||
|
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
|
||||||
|
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")
|
||||||
|
|
||||||
|
filters = frappe._dict(
|
||||||
|
company="_Test Company 6",
|
||||||
|
from_date=add_months(today(), -1),
|
||||||
|
to_date=today(),
|
||||||
|
include_payments=True,
|
||||||
|
supplier="_Test Supplier",
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
pe = make_payment_entry()
|
||||||
|
|
||||||
|
report_results = execute(filters)
|
||||||
|
first_row = frappe._dict(report_results[1][2])
|
||||||
|
self.assertEqual(first_row.voucher_type, "Payment Entry")
|
||||||
|
self.assertEqual(first_row.voucher_no, pe.name)
|
||||||
|
self.assertEqual(first_row.payable_account, "Creditors - _TC6")
|
||||||
|
self.assertEqual(first_row.debit, 0)
|
||||||
|
self.assertEqual(first_row.credit, 600)
|
||||||
|
self.assertEqual(first_row.balance, 500)
|
||||||
|
|
||||||
|
|
||||||
|
def make_purchase_invoice():
|
||||||
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
|
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
|
||||||
|
gst_acc = create_account(
|
||||||
|
account_name="GST",
|
||||||
|
account_type="Tax",
|
||||||
|
parent_account="Duties and Taxes - _TC6",
|
||||||
|
company="_Test Company 6",
|
||||||
|
account_currency="INR",
|
||||||
|
)
|
||||||
|
create_warehouse(warehouse_name="_Test Warehouse - _TC6", company="_Test Company 6")
|
||||||
|
create_cost_center(cost_center_name="_Test Cost Center", company="_Test Company 6")
|
||||||
|
pi = create_purchase_invoice_with_taxes()
|
||||||
|
pi.submit()
|
||||||
|
return pi
|
||||||
|
|
||||||
|
|
||||||
|
def create_purchase_invoice_with_taxes():
|
||||||
|
return frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Purchase Invoice",
|
||||||
|
"posting_date": today(),
|
||||||
|
"supplier": "_Test Supplier",
|
||||||
|
"company": "_Test Company 6",
|
||||||
|
"cost_center": "_Test Cost Center - _TC6",
|
||||||
|
"taxes_and_charges": "",
|
||||||
|
"currency": "INR",
|
||||||
|
"credit_to": "Creditors - _TC6",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"doctype": "Purchase Invoice Item",
|
||||||
|
"cost_center": "_Test Cost Center - _TC6",
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 1000,
|
||||||
|
"expense_account": "Stock Received But Not Billed - _TC6",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"taxes": [
|
||||||
|
{
|
||||||
|
"account_head": "GST - _TC6",
|
||||||
|
"cost_center": "_Test Cost Center - _TC6",
|
||||||
|
"add_deduct_tax": "Add",
|
||||||
|
"category": "Valuation and Total",
|
||||||
|
"charge_type": "Actual",
|
||||||
|
"description": "Shipping Charges",
|
||||||
|
"doctype": "Purchase Taxes and Charges",
|
||||||
|
"parentfield": "taxes",
|
||||||
|
"rate": 100,
|
||||||
|
"tax_amount": 100.0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_payment_entry():
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||||
|
|
||||||
|
return create_payment_entry(
|
||||||
|
company="_Test Company 6",
|
||||||
|
party_type="Supplier",
|
||||||
|
party="_Test Supplier",
|
||||||
|
payment_type="Pay",
|
||||||
|
paid_from="Cash - _TC6",
|
||||||
|
paid_to="Creditors - _TC6",
|
||||||
|
paid_amount=600,
|
||||||
|
save=1,
|
||||||
|
submit=1,
|
||||||
|
)
|
@ -29,7 +29,6 @@ frappe.query_reports["Sales Payment Summary"] = {
|
|||||||
"label": __("Owner"),
|
"label": __("Owner"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "User",
|
"options": "User",
|
||||||
"defaults": user
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"is_pos",
|
"fieldname":"is_pos",
|
||||||
|
@ -64,6 +64,12 @@ frappe.query_reports["Sales Register"] = {
|
|||||||
"label": __("Item Group"),
|
"label": __("Item Group"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Item Group"
|
"options": "Item Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "include_payments",
|
||||||
|
"label": __("Show Ledger View"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,22 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.utils import flt
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
|
from frappe.utils import flt, getdate
|
||||||
|
from pypika import Order
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.party import get_party_account
|
||||||
get_accounting_dimensions,
|
from erpnext.accounts.report.utils import (
|
||||||
get_dimension_with_children,
|
get_advance_taxes_and_charges,
|
||||||
|
get_conditions,
|
||||||
|
get_journal_entries,
|
||||||
|
get_opening_row,
|
||||||
|
get_party_details,
|
||||||
|
get_payment_entries,
|
||||||
|
get_query_columns,
|
||||||
|
get_taxes_query,
|
||||||
|
get_values_for_columns,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@ -22,9 +31,15 @@ def _execute(filters, additional_table_columns=None):
|
|||||||
if not filters:
|
if not filters:
|
||||||
filters = frappe._dict({})
|
filters = frappe._dict({})
|
||||||
|
|
||||||
|
include_payments = filters.get("include_payments")
|
||||||
|
if filters.get("include_payments") and not filters.get("customer"):
|
||||||
|
frappe.throw(_("Please select a customer for fetching payments."))
|
||||||
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
||||||
|
if filters.get("include_payments"):
|
||||||
|
invoice_list += get_payments(filters)
|
||||||
|
|
||||||
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
||||||
invoice_list, additional_table_columns
|
invoice_list, additional_table_columns, include_payments
|
||||||
)
|
)
|
||||||
|
|
||||||
if not invoice_list:
|
if not invoice_list:
|
||||||
@ -34,13 +49,29 @@ def _execute(filters, additional_table_columns=None):
|
|||||||
invoice_income_map = get_invoice_income_map(invoice_list)
|
invoice_income_map = get_invoice_income_map(invoice_list)
|
||||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||||
invoice_income_map, invoice_tax_map = get_invoice_tax_map(
|
invoice_income_map, invoice_tax_map = get_invoice_tax_map(
|
||||||
invoice_list, invoice_income_map, income_accounts
|
invoice_list, invoice_income_map, income_accounts, include_payments
|
||||||
)
|
)
|
||||||
# Cost Center & Warehouse Map
|
# Cost Center & Warehouse Map
|
||||||
invoice_cc_wh_map = get_invoice_cc_wh_map(invoice_list)
|
invoice_cc_wh_map = get_invoice_cc_wh_map(invoice_list)
|
||||||
invoice_so_dn_map = get_invoice_so_dn_map(invoice_list)
|
invoice_so_dn_map = get_invoice_so_dn_map(invoice_list)
|
||||||
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
||||||
mode_of_payments = get_mode_of_payments([inv.name for inv in invoice_list])
|
mode_of_payments = get_mode_of_payments([inv.name for inv in invoice_list])
|
||||||
|
customers = list(set(d.customer for d in invoice_list))
|
||||||
|
customer_details = get_party_details("Customer", customers)
|
||||||
|
|
||||||
|
res = []
|
||||||
|
if include_payments:
|
||||||
|
opening_row = get_opening_row(
|
||||||
|
"Customer", filters.customer, getdate(filters.from_date), filters.company
|
||||||
|
)[0]
|
||||||
|
res.append(
|
||||||
|
{
|
||||||
|
"receivable_account": opening_row.account,
|
||||||
|
"debit": flt(opening_row.debit),
|
||||||
|
"credit": flt(opening_row.credit),
|
||||||
|
"balance": flt(opening_row.balance),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for inv in invoice_list:
|
for inv in invoice_list:
|
||||||
@ -51,14 +82,15 @@ def _execute(filters, additional_table_columns=None):
|
|||||||
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
|
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
|
||||||
|
|
||||||
row = {
|
row = {
|
||||||
"invoice": inv.name,
|
"voucher_type": inv.doctype,
|
||||||
|
"voucher_no": inv.name,
|
||||||
"posting_date": inv.posting_date,
|
"posting_date": inv.posting_date,
|
||||||
"customer": inv.customer,
|
"customer": inv.customer,
|
||||||
"customer_name": inv.customer_name,
|
"customer_name": inv.customer_name,
|
||||||
**get_values_for_columns(additional_table_columns, inv),
|
**get_values_for_columns(additional_table_columns, inv),
|
||||||
"customer_group": inv.get("customer_group"),
|
"customer_group": customer_details.get(inv.customer).get("customer_group"),
|
||||||
"territory": inv.get("territory"),
|
"territory": customer_details.get(inv.customer).get("territory"),
|
||||||
"tax_id": inv.get("tax_id"),
|
"tax_id": customer_details.get(inv.customer).get("tax_id"),
|
||||||
"receivable_account": inv.debit_to,
|
"receivable_account": inv.debit_to,
|
||||||
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
||||||
"project": inv.project,
|
"project": inv.project,
|
||||||
@ -116,19 +148,36 @@ def _execute(filters, additional_table_columns=None):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if inv.doctype == "Sales Invoice":
|
||||||
|
row.update({"debit": inv.base_grand_total, "credit": 0.0})
|
||||||
|
else:
|
||||||
|
row.update({"debit": 0.0, "credit": inv.base_grand_total})
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
return columns, data
|
res += sorted(data, key=lambda x: x["posting_date"])
|
||||||
|
|
||||||
|
if include_payments:
|
||||||
|
running_balance = flt(opening_row.balance)
|
||||||
|
for row in range(1, len(res)):
|
||||||
|
running_balance += res[row]["debit"] - res[row]["credit"]
|
||||||
|
res[row].update({"balance": running_balance})
|
||||||
|
|
||||||
|
return columns, res, None, None, None, include_payments
|
||||||
|
|
||||||
|
|
||||||
def get_columns(invoice_list, additional_table_columns):
|
def get_columns(invoice_list, additional_table_columns, include_payments=False):
|
||||||
"""return columns based on filters"""
|
"""return columns based on filters"""
|
||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
"label": _("Invoice"),
|
"label": _("Voucher Type"),
|
||||||
"fieldname": "invoice",
|
"fieldname": "voucher_type",
|
||||||
"fieldtype": "Link",
|
"width": 120,
|
||||||
"options": "Sales Invoice",
|
},
|
||||||
|
{
|
||||||
|
"label": _("Voucher"),
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"options": "voucher_type",
|
||||||
"width": 120,
|
"width": 120,
|
||||||
},
|
},
|
||||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
|
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
|
||||||
@ -142,83 +191,156 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
{"label": _("Customer Name"), "fieldname": "customer_name", "fieldtype": "Data", "width": 120},
|
{"label": _("Customer Name"), "fieldname": "customer_name", "fieldtype": "Data", "width": 120},
|
||||||
]
|
]
|
||||||
|
|
||||||
if additional_table_columns:
|
if additional_table_columns and not include_payments:
|
||||||
columns += additional_table_columns
|
columns += additional_table_columns
|
||||||
|
|
||||||
columns += [
|
if not include_payments:
|
||||||
|
columns += [
|
||||||
|
{
|
||||||
|
"label": _("Customer Group"),
|
||||||
|
"fieldname": "customer_group",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Customer Group",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Territory"),
|
||||||
|
"fieldname": "territory",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Territory",
|
||||||
|
"width": 80,
|
||||||
|
},
|
||||||
|
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 80},
|
||||||
|
{
|
||||||
|
"label": _("Receivable Account"),
|
||||||
|
"fieldname": "receivable_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Account",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Mode Of Payment"),
|
||||||
|
"fieldname": "mode_of_payment",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Project"),
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Project",
|
||||||
|
"width": 80,
|
||||||
|
},
|
||||||
|
{"label": _("Owner"), "fieldname": "owner", "fieldtype": "Data", "width": 100},
|
||||||
|
{
|
||||||
|
"label": _("Sales Order"),
|
||||||
|
"fieldname": "sales_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Sales Order",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Delivery Note"),
|
||||||
|
"fieldname": "delivery_note",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Delivery Note",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Cost Center"),
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Cost Center",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Warehouse"),
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Warehouse",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
columns += [
|
||||||
|
{
|
||||||
|
"fieldname": "receivable_account",
|
||||||
|
"label": _("Receivable Account"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Account",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 120},
|
||||||
|
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 120},
|
||||||
|
{"fieldname": "balance", "label": _("Balance"), "fieldtype": "Currency", "width": 120},
|
||||||
|
]
|
||||||
|
|
||||||
|
account_columns, accounts = get_account_columns(invoice_list, include_payments)
|
||||||
|
|
||||||
|
net_total_column = [
|
||||||
{
|
{
|
||||||
"label": _("Customer Group"),
|
"label": _("Net Total"),
|
||||||
"fieldname": "customer_group",
|
"fieldname": "net_total",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Currency",
|
||||||
"options": "Customer Group",
|
"options": "currency",
|
||||||
"width": 120,
|
"width": 120,
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"label": _("Territory"),
|
|
||||||
"fieldname": "territory",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Territory",
|
|
||||||
"width": 80,
|
|
||||||
},
|
|
||||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 120},
|
|
||||||
{
|
|
||||||
"label": _("Receivable Account"),
|
|
||||||
"fieldname": "receivable_account",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Account",
|
|
||||||
"width": 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Mode Of Payment"),
|
|
||||||
"fieldname": "mode_of_payment",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"width": 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Project"),
|
|
||||||
"fieldname": "project",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Project",
|
|
||||||
"width": 80,
|
|
||||||
},
|
|
||||||
{"label": _("Owner"), "fieldname": "owner", "fieldtype": "Data", "width": 150},
|
|
||||||
{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 150},
|
|
||||||
{
|
|
||||||
"label": _("Sales Order"),
|
|
||||||
"fieldname": "sales_order",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Sales Order",
|
|
||||||
"width": 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Delivery Note"),
|
|
||||||
"fieldname": "delivery_note",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Delivery Note",
|
|
||||||
"width": 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Cost Center"),
|
|
||||||
"fieldname": "cost_center",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Cost Center",
|
|
||||||
"width": 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Warehouse"),
|
|
||||||
"fieldname": "warehouse",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Warehouse",
|
|
||||||
"width": 100,
|
|
||||||
},
|
|
||||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
total_columns = [
|
||||||
|
{
|
||||||
|
"label": _("Tax Total"),
|
||||||
|
"fieldname": "tax_total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
if not include_payments:
|
||||||
|
total_columns += [
|
||||||
|
{
|
||||||
|
"label": _("Grand Total"),
|
||||||
|
"fieldname": "grand_total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Rounded Total"),
|
||||||
|
"fieldname": "rounded_total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Outstanding Amount"),
|
||||||
|
"fieldname": "outstanding_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
columns = (
|
||||||
|
columns
|
||||||
|
+ account_columns[0]
|
||||||
|
+ account_columns[2]
|
||||||
|
+ net_total_column
|
||||||
|
+ account_columns[1]
|
||||||
|
+ total_columns
|
||||||
|
)
|
||||||
|
columns += [{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 150}]
|
||||||
|
return columns, accounts[0], accounts[1], accounts[2]
|
||||||
|
|
||||||
|
|
||||||
|
def get_account_columns(invoice_list, include_payments):
|
||||||
income_accounts = []
|
income_accounts = []
|
||||||
tax_accounts = []
|
tax_accounts = []
|
||||||
|
unrealized_profit_loss_accounts = []
|
||||||
|
|
||||||
income_columns = []
|
income_columns = []
|
||||||
tax_columns = []
|
tax_columns = []
|
||||||
unrealized_profit_loss_accounts = []
|
|
||||||
unrealized_profit_loss_account_columns = []
|
unrealized_profit_loss_account_columns = []
|
||||||
|
|
||||||
if invoice_list:
|
if invoice_list:
|
||||||
@ -230,14 +352,16 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
tuple(inv.name for inv in invoice_list),
|
tuple(inv.name for inv in invoice_list),
|
||||||
)
|
)
|
||||||
|
|
||||||
tax_accounts = frappe.db.sql_list(
|
sales_taxes_query = get_taxes_query(invoice_list, "Sales Taxes and Charges", "Sales Invoice")
|
||||||
"""select distinct account_head
|
sales_tax_accounts = sales_taxes_query.run(as_dict=True, pluck="account_head")
|
||||||
from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice'
|
tax_accounts = sales_tax_accounts
|
||||||
and docstatus = 1 and base_tax_amount_after_discount_amount != 0
|
|
||||||
and parent in (%s) order by account_head"""
|
if include_payments:
|
||||||
% ", ".join(["%s"] * len(invoice_list)),
|
advance_taxes_query = get_taxes_query(
|
||||||
tuple(inv.name for inv in invoice_list),
|
invoice_list, "Advance Taxes and Charges", "Payment Entry"
|
||||||
)
|
)
|
||||||
|
advance_tax_accounts = advance_taxes_query.run(as_dict=True, pluck="account_head")
|
||||||
|
tax_accounts = set(tax_accounts + advance_tax_accounts)
|
||||||
|
|
||||||
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
||||||
"""SELECT distinct unrealized_profit_loss_account
|
"""SELECT distinct unrealized_profit_loss_account
|
||||||
@ -283,133 +407,71 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
net_total_column = [
|
columns = [income_columns, unrealized_profit_loss_account_columns, tax_columns]
|
||||||
{
|
accounts = [income_accounts, unrealized_profit_loss_accounts, tax_accounts]
|
||||||
"label": _("Net Total"),
|
|
||||||
"fieldname": "net_total",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"options": "currency",
|
|
||||||
"width": 120,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
total_columns = [
|
return columns, accounts
|
||||||
{
|
|
||||||
"label": _("Tax Total"),
|
|
||||||
"fieldname": "tax_total",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"options": "currency",
|
|
||||||
"width": 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Grand Total"),
|
|
||||||
"fieldname": "grand_total",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"options": "currency",
|
|
||||||
"width": 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Rounded Total"),
|
|
||||||
"fieldname": "rounded_total",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"options": "currency",
|
|
||||||
"width": 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Outstanding Amount"),
|
|
||||||
"fieldname": "outstanding_amount",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"options": "currency",
|
|
||||||
"width": 120,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
columns = (
|
|
||||||
columns
|
|
||||||
+ income_columns
|
|
||||||
+ unrealized_profit_loss_account_columns
|
|
||||||
+ net_total_column
|
|
||||||
+ tax_columns
|
|
||||||
+ total_columns
|
|
||||||
)
|
|
||||||
|
|
||||||
return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
|
|
||||||
|
|
||||||
|
|
||||||
def get_conditions(filters):
|
|
||||||
conditions = ""
|
|
||||||
|
|
||||||
accounting_dimensions = get_accounting_dimensions(as_list=False) or []
|
|
||||||
accounting_dimensions_list = [d.fieldname for d in accounting_dimensions]
|
|
||||||
|
|
||||||
if filters.get("company"):
|
|
||||||
conditions += " and company=%(company)s"
|
|
||||||
|
|
||||||
if filters.get("customer") and "customer" not in accounting_dimensions_list:
|
|
||||||
conditions += " and customer = %(customer)s"
|
|
||||||
|
|
||||||
if filters.get("from_date"):
|
|
||||||
conditions += " and posting_date >= %(from_date)s"
|
|
||||||
if filters.get("to_date"):
|
|
||||||
conditions += " and posting_date <= %(to_date)s"
|
|
||||||
|
|
||||||
if filters.get("owner"):
|
|
||||||
conditions += " and owner = %(owner)s"
|
|
||||||
|
|
||||||
def get_sales_invoice_item_field_condition(field, table="Sales Invoice Item") -> str:
|
|
||||||
if not filters.get(field) or field in accounting_dimensions_list:
|
|
||||||
return ""
|
|
||||||
return f""" and exists(select name from `tab{table}`
|
|
||||||
where parent=`tabSales Invoice`.name
|
|
||||||
and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
|
|
||||||
|
|
||||||
conditions += get_sales_invoice_item_field_condition("mode_of_payment", "Sales Invoice Payment")
|
|
||||||
conditions += get_sales_invoice_item_field_condition("cost_center")
|
|
||||||
conditions += get_sales_invoice_item_field_condition("warehouse")
|
|
||||||
conditions += get_sales_invoice_item_field_condition("brand")
|
|
||||||
conditions += get_sales_invoice_item_field_condition("item_group")
|
|
||||||
|
|
||||||
if accounting_dimensions:
|
|
||||||
common_condition = """
|
|
||||||
and exists(select name from `tabSales Invoice Item`
|
|
||||||
where parent=`tabSales Invoice`.name
|
|
||||||
"""
|
|
||||||
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 += (
|
|
||||||
common_condition
|
|
||||||
+ "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
conditions += (
|
|
||||||
common_condition
|
|
||||||
+ "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
|
||||||
)
|
|
||||||
|
|
||||||
return conditions
|
|
||||||
|
|
||||||
|
|
||||||
def get_invoices(filters, additional_query_columns):
|
def get_invoices(filters, additional_query_columns):
|
||||||
conditions = get_conditions(filters)
|
si = frappe.qb.DocType("Sales Invoice")
|
||||||
return frappe.db.sql(
|
invoice_item = frappe.qb.DocType("Sales Invoice Item")
|
||||||
"""
|
invoice_payment = frappe.qb.DocType("Sales Invoice Payment")
|
||||||
select name, posting_date, debit_to, project, customer,
|
query = (
|
||||||
customer_name, owner, remarks, territory, tax_id, customer_group,
|
frappe.qb.from_(si)
|
||||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
|
.inner_join(invoice_item)
|
||||||
is_internal_customer, represents_company, company {0}
|
.on(si.name == invoice_item.parent)
|
||||||
from `tabSales Invoice`
|
.left_join(invoice_payment)
|
||||||
where docstatus = 1 {1}
|
.on(si.name == invoice_payment.parent)
|
||||||
order by posting_date desc, name desc""".format(
|
.select(
|
||||||
additional_query_columns, conditions
|
ConstantColumn("Sales Invoice").as_("doctype"),
|
||||||
),
|
si.name,
|
||||||
filters,
|
si.posting_date,
|
||||||
as_dict=1,
|
si.debit_to,
|
||||||
|
si.project,
|
||||||
|
si.customer,
|
||||||
|
si.customer_name,
|
||||||
|
si.owner,
|
||||||
|
si.remarks,
|
||||||
|
si.territory,
|
||||||
|
si.tax_id,
|
||||||
|
si.customer_group,
|
||||||
|
si.base_net_total,
|
||||||
|
si.base_grand_total,
|
||||||
|
si.base_rounded_total,
|
||||||
|
si.outstanding_amount,
|
||||||
|
si.is_internal_customer,
|
||||||
|
si.represents_company,
|
||||||
|
si.company,
|
||||||
|
)
|
||||||
|
.where((si.docstatus == 1))
|
||||||
|
.orderby(si.posting_date, si.name, order=Order.desc)
|
||||||
)
|
)
|
||||||
|
if additional_query_columns:
|
||||||
|
for col in additional_query_columns:
|
||||||
|
query = query.select(col)
|
||||||
|
if filters.get("customer"):
|
||||||
|
query = query.where(si.customer == filters.customer)
|
||||||
|
query = get_conditions(
|
||||||
|
filters, query, doctype="Sales Invoice", child_doctype="Sales Invoice Item"
|
||||||
|
)
|
||||||
|
invoices = query.run(as_dict=True)
|
||||||
|
return invoices
|
||||||
|
|
||||||
|
|
||||||
|
def get_payments(filters):
|
||||||
|
args = frappe._dict(
|
||||||
|
account="debit_to",
|
||||||
|
account_fieldname="paid_from",
|
||||||
|
party="customer",
|
||||||
|
party_name="customer_name",
|
||||||
|
party_account=get_party_account(
|
||||||
|
"Customer", filters.customer, filters.company, include_advance=True
|
||||||
|
),
|
||||||
|
)
|
||||||
|
payment_entries = get_payment_entries(filters, args)
|
||||||
|
journal_entries = get_journal_entries(filters, args)
|
||||||
|
return payment_entries + journal_entries
|
||||||
|
|
||||||
|
|
||||||
def get_invoice_income_map(invoice_list):
|
def get_invoice_income_map(invoice_list):
|
||||||
@ -447,7 +509,7 @@ def get_internal_invoice_map(invoice_list):
|
|||||||
return internal_invoice_map
|
return internal_invoice_map
|
||||||
|
|
||||||
|
|
||||||
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts, include_payments=False):
|
||||||
tax_details = frappe.db.sql(
|
tax_details = frappe.db.sql(
|
||||||
"""select parent, account_head,
|
"""select parent, account_head,
|
||||||
sum(base_tax_amount_after_discount_amount) as tax_amount
|
sum(base_tax_amount_after_discount_amount) as tax_amount
|
||||||
@ -457,6 +519,9 @@ def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
|||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if include_payments:
|
||||||
|
tax_details += get_advance_taxes_and_charges(invoice_list)
|
||||||
|
|
||||||
invoice_tax_map = {}
|
invoice_tax_map = {}
|
||||||
for d in tax_details:
|
for d in tax_details:
|
||||||
if d.account_head in income_accounts:
|
if d.account_head in income_accounts:
|
||||||
@ -475,7 +540,7 @@ def get_invoice_so_dn_map(invoice_list):
|
|||||||
si_items = frappe.db.sql(
|
si_items = frappe.db.sql(
|
||||||
"""select parent, sales_order, delivery_note, so_detail
|
"""select parent, sales_order, delivery_note, so_detail
|
||||||
from `tabSales Invoice Item` where parent in (%s)
|
from `tabSales Invoice Item` where parent in (%s)
|
||||||
and (ifnull(sales_order, '') != '' or ifnull(delivery_note, '') != '')"""
|
and (sales_order != '' or delivery_note != '')"""
|
||||||
% ", ".join(["%s"] * len(invoice_list)),
|
% ", ".join(["%s"] * len(invoice_list)),
|
||||||
tuple(inv.name for inv in invoice_list),
|
tuple(inv.name for inv in invoice_list),
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
@ -510,7 +575,7 @@ def get_invoice_cc_wh_map(invoice_list):
|
|||||||
si_items = frappe.db.sql(
|
si_items = frappe.db.sql(
|
||||||
"""select parent, cost_center, warehouse
|
"""select parent, cost_center, warehouse
|
||||||
from `tabSales Invoice Item` where parent in (%s)
|
from `tabSales Invoice Item` where parent in (%s)
|
||||||
and (ifnull(cost_center, '') != '' or ifnull(warehouse, '') != '')"""
|
and (cost_center != '' or warehouse != '')"""
|
||||||
% ", ".join(["%s"] * len(invoice_list)),
|
% ", ".join(["%s"] * len(invoice_list)),
|
||||||
tuple(inv.name for inv in invoice_list),
|
tuple(inv.name for inv in invoice_list),
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// -*- coding: utf-8 -*-
|
// -*- coding: utf-8 -*-
|
||||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports["Share Balance"] = {
|
frappe.query_reports["Share Balance"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// -*- coding: utf-8 -*-
|
// -*- coding: utf-8 -*-
|
||||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports["Share Ledger"] = {
|
frappe.query_reports["Share Ledger"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports["Supplier Ledger Summary"] = {
|
frappe.query_reports["Supplier Ledger Summary"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
|
frappe.query_reports["Tax Withholding Details"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname":"company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"default": frappe.defaults.get_default('company')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"party_type",
|
||||||
|
"label": __("Party Type"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": ["Supplier", "Customer"],
|
||||||
|
"reqd": 1,
|
||||||
|
"default": "Supplier",
|
||||||
|
"on_change": function(){
|
||||||
|
frappe.query_report.set_filter_value("party", "");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"party",
|
||||||
|
"label": __("Party"),
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"get_options": function() {
|
||||||
|
var party_type = frappe.query_report.get_filter_value('party_type');
|
||||||
|
var party = frappe.query_report.get_filter_value('party');
|
||||||
|
if(party && !party_type) {
|
||||||
|
frappe.throw(__("Please select Party Type first"));
|
||||||
|
}
|
||||||
|
return party_type;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"from_date",
|
||||||
|
"label": __("From Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||||
|
"reqd": 1,
|
||||||
|
"width": "60px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"to_date",
|
||||||
|
"label": __("To Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"default": frappe.datetime.get_today(),
|
||||||
|
"reqd": 1,
|
||||||
|
"width": "60px"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -12,11 +12,11 @@
|
|||||||
"modified": "2021-09-20 12:05:50.387572",
|
"modified": "2021-09-20 12:05:50.387572",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "TDS Payable Monthly",
|
"name": "Tax Withholding Details",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"prepared_report": 0,
|
"prepared_report": 0,
|
||||||
"ref_doctype": "Purchase Invoice",
|
"ref_doctype": "Purchase Invoice",
|
||||||
"report_name": "TDS Payable Monthly",
|
"report_name": "Tax Withholding Details",
|
||||||
"report_type": "Script Report",
|
"report_type": "Script Report",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
@ -33,77 +33,94 @@ def validate_filters(filters):
|
|||||||
def get_result(
|
def get_result(
|
||||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map
|
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map
|
||||||
):
|
):
|
||||||
supplier_map = get_supplier_pan_map()
|
party_map = get_party_pan_map(filters.get("party_type"))
|
||||||
tax_rate_map = get_tax_rate_map(filters)
|
tax_rate_map = get_tax_rate_map(filters)
|
||||||
gle_map = get_gle_map(tds_docs)
|
gle_map = get_gle_map(tds_docs)
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
for name, details in gle_map.items():
|
for name, details in gle_map.items():
|
||||||
tds_deducted, total_amount_credited = 0, 0
|
tax_amount, total_amount = 0, 0
|
||||||
tax_withholding_category = tax_category_map.get(name)
|
tax_withholding_category = tax_category_map.get(name)
|
||||||
rate = tax_rate_map.get(tax_withholding_category)
|
rate = tax_rate_map.get(tax_withholding_category)
|
||||||
|
|
||||||
for entry in details:
|
for entry in details:
|
||||||
supplier = entry.party or entry.against
|
party = entry.party or entry.against
|
||||||
posting_date = entry.posting_date
|
posting_date = entry.posting_date
|
||||||
voucher_type = entry.voucher_type
|
voucher_type = entry.voucher_type
|
||||||
|
|
||||||
if voucher_type == "Journal Entry":
|
if voucher_type == "Journal Entry":
|
||||||
suppliers = journal_entry_party_map.get(name)
|
party_list = journal_entry_party_map.get(name)
|
||||||
if suppliers:
|
if party_list:
|
||||||
supplier = suppliers[0]
|
party = party_list[0]
|
||||||
|
|
||||||
if not tax_withholding_category:
|
if not tax_withholding_category:
|
||||||
tax_withholding_category = supplier_map.get(supplier, {}).get("tax_withholding_category")
|
tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
|
||||||
rate = tax_rate_map.get(tax_withholding_category)
|
rate = tax_rate_map.get(tax_withholding_category)
|
||||||
|
|
||||||
if entry.account in tds_accounts:
|
if entry.account in tds_accounts:
|
||||||
tds_deducted += entry.credit - entry.debit
|
tax_amount += entry.credit - entry.debit
|
||||||
|
|
||||||
if invoice_net_total_map.get(name):
|
if invoice_net_total_map.get(name):
|
||||||
total_amount_credited = invoice_net_total_map.get(name)
|
total_amount = invoice_net_total_map.get(name)
|
||||||
else:
|
else:
|
||||||
total_amount_credited += entry.credit
|
total_amount += entry.credit
|
||||||
|
|
||||||
|
if tax_amount:
|
||||||
|
if party_map.get(party, {}).get("party_type") == "Supplier":
|
||||||
|
party_name = "supplier_name"
|
||||||
|
party_type = "supplier_type"
|
||||||
|
table_name = "Supplier"
|
||||||
|
else:
|
||||||
|
party_name = "customer_name"
|
||||||
|
party_type = "customer_type"
|
||||||
|
table_name = "Customer"
|
||||||
|
|
||||||
if tds_deducted:
|
|
||||||
row = {
|
row = {
|
||||||
"pan"
|
"pan"
|
||||||
if frappe.db.has_column("Supplier", "pan")
|
if frappe.db.has_column(table_name, "pan")
|
||||||
else "tax_id": supplier_map.get(supplier, {}).get("pan"),
|
else "tax_id": party_map.get(party, {}).get("pan"),
|
||||||
"supplier": supplier_map.get(supplier, {}).get("name"),
|
"party": party_map.get(party, {}).get("name"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if filters.naming_series == "Naming Series":
|
if filters.naming_series == "Naming Series":
|
||||||
row.update({"supplier_name": supplier_map.get(supplier, {}).get("supplier_name")})
|
row.update({"party_name": party_map.get(party, {}).get(party_name)})
|
||||||
|
|
||||||
row.update(
|
row.update(
|
||||||
{
|
{
|
||||||
"section_code": tax_withholding_category,
|
"section_code": tax_withholding_category,
|
||||||
"entity_type": supplier_map.get(supplier, {}).get("supplier_type"),
|
"entity_type": party_map.get(party, {}).get(party_type),
|
||||||
"tds_rate": rate,
|
"rate": rate,
|
||||||
"total_amount_credited": total_amount_credited,
|
"total_amount": total_amount,
|
||||||
"tds_deducted": tds_deducted,
|
"tax_amount": tax_amount,
|
||||||
"transaction_date": posting_date,
|
"transaction_date": posting_date,
|
||||||
"transaction_type": voucher_type,
|
"transaction_type": voucher_type,
|
||||||
"ref_no": name,
|
"ref_no": name,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
out.append(row)
|
out.append(row)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def get_supplier_pan_map():
|
def get_party_pan_map(party_type):
|
||||||
supplier_map = frappe._dict()
|
party_map = frappe._dict()
|
||||||
suppliers = frappe.db.get_all(
|
|
||||||
"Supplier", fields=["name", "pan", "supplier_type", "supplier_name", "tax_withholding_category"]
|
|
||||||
)
|
|
||||||
|
|
||||||
for d in suppliers:
|
fields = ["name", "tax_withholding_category"]
|
||||||
supplier_map[d.name] = d
|
if party_type == "Supplier":
|
||||||
|
fields += ["supplier_type", "supplier_name"]
|
||||||
|
else:
|
||||||
|
fields += ["customer_type", "customer_name"]
|
||||||
|
|
||||||
return supplier_map
|
if frappe.db.has_column(party_type, "pan"):
|
||||||
|
fields.append("pan")
|
||||||
|
|
||||||
|
party_details = frappe.db.get_all(party_type, fields=fields)
|
||||||
|
|
||||||
|
for party in party_details:
|
||||||
|
party.party_type = party_type
|
||||||
|
party_map[party.name] = party
|
||||||
|
|
||||||
|
return party_map
|
||||||
|
|
||||||
|
|
||||||
def get_gle_map(documents):
|
def get_gle_map(documents):
|
||||||
@ -131,17 +148,17 @@ def get_columns(filters):
|
|||||||
columns = [
|
columns = [
|
||||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90},
|
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90},
|
||||||
{
|
{
|
||||||
"label": _("Supplier"),
|
"label": _(filters.get("party_type")),
|
||||||
"options": "Supplier",
|
"fieldname": "party",
|
||||||
"fieldname": "supplier",
|
"fieldtype": "Dynamic Link",
|
||||||
"fieldtype": "Link",
|
"options": "party_type",
|
||||||
"width": 180,
|
"width": 180,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
if filters.naming_series == "Naming Series":
|
if filters.naming_series == "Naming Series":
|
||||||
columns.append(
|
columns.append(
|
||||||
{"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 180}
|
{"label": _("Party Name"), "fieldname": "party_name", "fieldtype": "Data", "width": 180}
|
||||||
)
|
)
|
||||||
|
|
||||||
columns.extend(
|
columns.extend(
|
||||||
@ -153,17 +170,22 @@ def get_columns(filters):
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"width": 180,
|
"width": 180,
|
||||||
},
|
},
|
||||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180},
|
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 120},
|
||||||
{"label": _("TDS Rate %"), "fieldname": "tds_rate", "fieldtype": "Percent", "width": 90},
|
|
||||||
{
|
{
|
||||||
"label": _("Total Amount Credited"),
|
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||||
"fieldname": "total_amount_credited",
|
"fieldname": "rate",
|
||||||
|
"fieldtype": "Percent",
|
||||||
|
"width": 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Total Amount"),
|
||||||
|
"fieldname": "total_amount",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"width": 90,
|
"width": 90,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Amount of TDS Deducted"),
|
"label": _("TDS Amount") if filters.get("party_type") == "Supplier" else _("TCS Amount"),
|
||||||
"fieldname": "tds_deducted",
|
"fieldname": "tax_amount",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"width": 90,
|
"width": 90,
|
||||||
},
|
},
|
||||||
@ -173,13 +195,13 @@ def get_columns(filters):
|
|||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"width": 90,
|
"width": 90,
|
||||||
},
|
},
|
||||||
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 90},
|
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100},
|
||||||
{
|
{
|
||||||
"label": _("Reference No."),
|
"label": _("Reference No."),
|
||||||
"fieldname": "ref_no",
|
"fieldname": "ref_no",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"options": "transaction_type",
|
"options": "transaction_type",
|
||||||
"width": 90,
|
"width": 180,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -190,6 +212,7 @@ def get_columns(filters):
|
|||||||
def get_tds_docs(filters):
|
def get_tds_docs(filters):
|
||||||
tds_documents = []
|
tds_documents = []
|
||||||
purchase_invoices = []
|
purchase_invoices = []
|
||||||
|
sales_invoices = []
|
||||||
payment_entries = []
|
payment_entries = []
|
||||||
journal_entries = []
|
journal_entries = []
|
||||||
tax_category_map = frappe._dict()
|
tax_category_map = frappe._dict()
|
||||||
@ -209,10 +232,13 @@ def get_tds_docs(filters):
|
|||||||
"against": ("not in", bank_accounts),
|
"against": ("not in", bank_accounts),
|
||||||
}
|
}
|
||||||
|
|
||||||
if filters.get("supplier"):
|
party = frappe.get_all(filters.get("party_type"), pluck="name")
|
||||||
|
query_filters.update({"against": ("in", party)})
|
||||||
|
|
||||||
|
if filters.get("party"):
|
||||||
del query_filters["account"]
|
del query_filters["account"]
|
||||||
del query_filters["against"]
|
del query_filters["against"]
|
||||||
or_filters = {"against": filters.get("supplier"), "party": filters.get("supplier")}
|
or_filters = {"against": filters.get("party"), "party": filters.get("party")}
|
||||||
|
|
||||||
tds_docs = frappe.get_all(
|
tds_docs = frappe.get_all(
|
||||||
"GL Entry",
|
"GL Entry",
|
||||||
@ -224,6 +250,8 @@ def get_tds_docs(filters):
|
|||||||
for d in tds_docs:
|
for d in tds_docs:
|
||||||
if d.voucher_type == "Purchase Invoice":
|
if d.voucher_type == "Purchase Invoice":
|
||||||
purchase_invoices.append(d.voucher_no)
|
purchase_invoices.append(d.voucher_no)
|
||||||
|
if d.voucher_type == "Sales Invoice":
|
||||||
|
sales_invoices.append(d.voucher_no)
|
||||||
elif d.voucher_type == "Payment Entry":
|
elif d.voucher_type == "Payment Entry":
|
||||||
payment_entries.append(d.voucher_no)
|
payment_entries.append(d.voucher_no)
|
||||||
elif d.voucher_type == "Journal Entry":
|
elif d.voucher_type == "Journal Entry":
|
||||||
@ -234,6 +262,9 @@ def get_tds_docs(filters):
|
|||||||
if purchase_invoices:
|
if purchase_invoices:
|
||||||
get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, invoice_net_total_map)
|
get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, invoice_net_total_map)
|
||||||
|
|
||||||
|
if sales_invoices:
|
||||||
|
get_doc_info(sales_invoices, "Sales Invoice", tax_category_map, invoice_net_total_map)
|
||||||
|
|
||||||
if payment_entries:
|
if payment_entries:
|
||||||
get_doc_info(payment_entries, "Payment Entry", tax_category_map)
|
get_doc_info(payment_entries, "Payment Entry", tax_category_map)
|
||||||
|
|
||||||
@ -267,6 +298,8 @@ def get_journal_entry_party_map(journal_entries):
|
|||||||
def get_doc_info(vouchers, doctype, tax_category_map, invoice_net_total_map=None):
|
def get_doc_info(vouchers, doctype, tax_category_map, invoice_net_total_map=None):
|
||||||
if doctype == "Purchase Invoice":
|
if doctype == "Purchase Invoice":
|
||||||
fields = ["name", "tax_withholding_category", "base_tax_withholding_net_total"]
|
fields = ["name", "tax_withholding_category", "base_tax_withholding_net_total"]
|
||||||
|
if doctype == "Sales Invoice":
|
||||||
|
fields = ["name", "base_net_total"]
|
||||||
else:
|
else:
|
||||||
fields = ["name", "tax_withholding_category"]
|
fields = ["name", "tax_withholding_category"]
|
||||||
|
|
||||||
@ -276,6 +309,8 @@ def get_doc_info(vouchers, doctype, tax_category_map, invoice_net_total_map=None
|
|||||||
tax_category_map.update({entry.name: entry.tax_withholding_category})
|
tax_category_map.update({entry.name: entry.tax_withholding_category})
|
||||||
if doctype == "Purchase Invoice":
|
if doctype == "Purchase Invoice":
|
||||||
invoice_net_total_map.update({entry.name: entry.base_tax_withholding_net_total})
|
invoice_net_total_map.update({entry.name: entry.base_tax_withholding_net_total})
|
||||||
|
if doctype == "Sales Invoice":
|
||||||
|
invoice_net_total_map.update({entry.name: entry.base_net_total})
|
||||||
|
|
||||||
|
|
||||||
def get_tax_rate_map(filters):
|
def get_tax_rate_map(filters):
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports["TDS Computation Summary"] = {
|
frappe.query_reports["TDS Computation Summary"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
frappe.query_reports["TDS Payable Monthly"] = {
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"fieldname":"company",
|
|
||||||
"label": __("Company"),
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Company",
|
|
||||||
"default": frappe.defaults.get_default('company')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname":"supplier",
|
|
||||||
"label": __("Supplier"),
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Supplier",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname":"from_date",
|
|
||||||
"label": __("From Date"),
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
|
||||||
"reqd": 1,
|
|
||||||
"width": "60px"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname":"to_date",
|
|
||||||
"label": __("To Date"),
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"default": frappe.datetime.get_today(),
|
|
||||||
"reqd": 1,
|
|
||||||
"width": "60px"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user